Skip to content

Commit

Permalink
add Random.choice
Browse files Browse the repository at this point in the history
  • Loading branch information
sukovanej committed Jul 21, 2024
1 parent 4f72730 commit 3d67d2f
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 3 deletions.
14 changes: 14 additions & 0 deletions .changeset/new-garlics-own.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"effect": minor
---

Add `Random.choice`.

```ts
import { Random } from "effect"

Effect.gen(function* () {
const randomItem = yield* Random.choice([1, 2, 3])
console.log(randomItem)
})
```
17 changes: 17 additions & 0 deletions packages/effect/src/Random.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/**
* @since 2.0.0
*/
import type * as Array from "./Array.js"
import type * as Chunk from "./Chunk.js"
import type * as Context from "./Context.js"
import type * as Effect from "./Effect.js"
Expand Down Expand Up @@ -103,6 +104,22 @@ export const nextIntBetween: (min: number, max: number) => Effect.Effect<number>
*/
export const shuffle: <A>(elements: Iterable<A>) => Effect.Effect<Chunk.Chunk<A>> = defaultServices.shuffle

/**
* Get a random element from a non-empty array.
*
* @example
* import { Random } from "effect"
*
* Effect.gen(function* () {
* const randomItem = yield* Random.choice([1, 2, 3])
* console.log(randomItem)
* })
*
* @since 2.0.0
* @category constructors
*/
export const choice: <A>(elements: Array.NonEmptyReadonlyArray<A>) => Effect.Effect<A> = internal.choice

/**
* Retreives the `Random` service from the context and uses it to run the
* specified workflow.
Expand Down
8 changes: 8 additions & 0 deletions packages/effect/src/internal/random.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type * as array from "../Array.js"
import * as Chunk from "../Chunk.js"
import * as Context from "../Context.js"
import type * as Effect from "../Effect.js"
Expand All @@ -6,6 +7,7 @@ import * as Hash from "../Hash.js"
import type * as Random from "../Random.js"
import * as PCGRandom from "../Utils.js"
import * as core from "./core.js"
import * as defaultServices from "./defaultServices.js"

/** @internal */
const RandomSymbolKey = "effect/Random"
Expand Down Expand Up @@ -79,6 +81,12 @@ const shuffleWith = <A>(
)
}

export const choice = <A>(elements: array.NonEmptyReadonlyArray<A>): Effect.Effect<A> =>
core.map(
defaultServices.randomWith((random) => random.nextIntBetween(0, elements.length)),
(i) => elements[i]
)

const swap = <A>(buffer: Array<A>, index1: number, index2: number): Array<A> => {
const tmp = buffer[index1]!
buffer[index1] = buffer[index2]!
Expand Down
12 changes: 9 additions & 3 deletions packages/effect/test/Random.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Array, Chunk, Data, Effect, Random } from "effect"
import * as it from "effect/test/utils/extend"
import { expect, it } from "effect/test/utils/extend"
import { assert, describe } from "vitest"

describe("Random", () => {
it.effect("shuffle", () =>
Effect.gen(function*($) {
Effect.gen(function*() {
const start = Array.range(0, 100)
const end = yield* $(Random.shuffle(start))
const end = yield* Random.shuffle(start)
assert.isTrue(Chunk.every(end, (n) => n !== undefined))
assert.deepStrictEqual(start.sort(), Array.fromIterable(end).sort())
}).pipe(Effect.repeatN(100)))
Expand All @@ -25,4 +25,10 @@ describe("Random", () => {
assert.strictEqual(n2, n3)
assert.notStrictEqual(n0, n2)
}))

it.live("choice", () =>
Effect.gen(function*() {
expect(yield* Random.choice([1])).toEqual(1)
expect(yield* Random.choice([1, 2, 3])).oneOf([1, 2, 3])
}))
})

0 comments on commit 3d67d2f

Please sign in to comment.