pick-me-too 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/README.md +24 -0
- package/package.json +1 -1
- package/src/__tests__/documentation.test.ts +10 -1
- package/src/index.ts +42 -9
package/CHANGELOG.md
ADDED
package/README.md
CHANGED
|
@@ -150,6 +150,30 @@ for (let i = 0; i < 10; i++) ar.push(fooBarBaz())
|
|
|
150
150
|
// => [ 'baz', 'foo', 'baz', 'baz', 'baz', 'bar', 'baz', 'baz', 'bar', 'baz' ]
|
|
151
151
|
```
|
|
152
152
|
|
|
153
|
+
### `function pickMeToo<T>(frequencies: [T, number][] | T[]): (rng: () => number) => () => T`
|
|
154
|
+
|
|
155
|
+
Generates a function from random number generators to generators of `T`.
|
|
156
|
+
|
|
157
|
+
This is like `pickMe` -- in fact, `pickMe` delegates to this -- but it factors out the random
|
|
158
|
+
number generator. It is useful in a situation where you want to incur the compilation of the
|
|
159
|
+
picker only once but you want to plug in different random number sequences on different
|
|
160
|
+
occasions.
|
|
161
|
+
|
|
162
|
+
@example
|
|
163
|
+
```ts
|
|
164
|
+
import { pickMeToo } from 'pick-me-too'
|
|
165
|
+
|
|
166
|
+
const fooBarBaz = pickMeToo([["foo", 1], ["bar", 1000], ["baz", 1]])
|
|
167
|
+
|
|
168
|
+
// minimum probablility generator just returns "foo"
|
|
169
|
+
fooBarBaz(() => 0)()
|
|
170
|
+
// => "foo"
|
|
171
|
+
|
|
172
|
+
// maximum probability generator just returns "baz"
|
|
173
|
+
fooBarBaz(() => 1)()
|
|
174
|
+
// => "baz"
|
|
175
|
+
```
|
|
176
|
+
|
|
153
177
|
### `function rando(seed: number): () => number`
|
|
154
178
|
|
|
155
179
|
Generates a random number sequence generator.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** tests for all the code shown in documentation */
|
|
2
2
|
|
|
3
|
-
import { rando, pickMe } from '..'
|
|
3
|
+
import { rando, pickMe, pickMeToo } from '..'
|
|
4
4
|
|
|
5
5
|
describe('README', () => {
|
|
6
6
|
describe('Synopsis', () => {
|
|
@@ -112,6 +112,15 @@ describe('index.ts', () => {
|
|
|
112
112
|
for (let i = 0; i < 10; i++) ar.push(fooBarBaz())
|
|
113
113
|
test('pickMe', () => expect(ar).toEqual(['baz', 'foo', 'baz', 'baz', 'baz', 'bar', 'baz', 'baz', 'bar', 'baz']))
|
|
114
114
|
})
|
|
115
|
+
describe('pickMeToo', () => {
|
|
116
|
+
const fooBarBaz = pickMeToo([
|
|
117
|
+
['foo', 1],
|
|
118
|
+
['bar', 1000],
|
|
119
|
+
['baz', 1],
|
|
120
|
+
])
|
|
121
|
+
test('low probability', () => expect(fooBarBaz(() => 0)()).toEqual('foo'))
|
|
122
|
+
test('high probability', () => expect(fooBarBaz(() => 1)()).toEqual('baz'))
|
|
123
|
+
})
|
|
115
124
|
describe('rando', () => {
|
|
116
125
|
const rng = rando(1)
|
|
117
126
|
const ar: number[] = []
|
package/src/index.ts
CHANGED
|
@@ -19,10 +19,10 @@
|
|
|
19
19
|
* a non-positive frequency will be culled from the list. `pickMe` will throw an error if the list is empty.
|
|
20
20
|
* Likewise, it will throw an error if any item appears more than once in the list. `[]` is
|
|
21
21
|
* not good. `[[1, 1], [1, 2]]` is no better.
|
|
22
|
-
*
|
|
22
|
+
*
|
|
23
23
|
* Note, the item returned by `pickMe` is the item you provided, not a copy. If you are picking from a list of
|
|
24
24
|
* objects and you modify one of them, it will remain modified.
|
|
25
|
-
*
|
|
25
|
+
*
|
|
26
26
|
* Finally, `pickMe` compiles an efficient picker from the parameters provided. As with other compiled things, such
|
|
27
27
|
* as regular expressions, you want to do this one time in an initialization phase and then reuse it in loops.
|
|
28
28
|
* The compilation is not terribly expensive, but skipping it altogether is cheaper.
|
|
@@ -40,16 +40,43 @@
|
|
|
40
40
|
* ```
|
|
41
41
|
*
|
|
42
42
|
* @param {[T, number][] | T[]} frequencies - phrases to match
|
|
43
|
-
* @param {
|
|
43
|
+
* @param {Rng} [rando=() => Math.random()] - random number generator
|
|
44
44
|
* @returns {() => T} a picker of T's
|
|
45
45
|
*/
|
|
46
|
-
export function pickMe<T>(frequencies: [T, number][] | T[], rando?:
|
|
46
|
+
export function pickMe<T>(frequencies: [T, number][] | T[], rando?: Rng): () => T {
|
|
47
|
+
return pickMeToo(frequencies)(rando ?? (() => Math.random()))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Generates pickers of things. This separates the compilation step from frequencies so you
|
|
52
|
+
* can plug different random number generators in.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* import { pickMeToo } from 'pick-me'
|
|
57
|
+
*
|
|
58
|
+
* const fooBarBaz = pickMeToo([["foo", 1], ["bar", 1000], ["baz", 1]])
|
|
59
|
+
*
|
|
60
|
+
* // minimum probablility generator just returns "foo"
|
|
61
|
+
* fooBarBaz(() => 0)()
|
|
62
|
+
* // => "foo"
|
|
63
|
+
*
|
|
64
|
+
* // maximum probability generator just returns "baz"
|
|
65
|
+
* fooBarBaz(() => 1)()
|
|
66
|
+
* // => "baz"
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* @template T - the sort of thing to pick
|
|
70
|
+
* @param {[T, number][] | T[]} frequencies - things to pick according to their frequencies
|
|
71
|
+
* @returns {(rng: Rng) => () => T} - a function from random number generators to pickers of things in the frequency list
|
|
72
|
+
*/
|
|
73
|
+
export function pickMeToo<T>(frequencies: [T, number][] | T[]): (rng: Rng) => () => T {
|
|
47
74
|
frequencies = sanitize(normalize(frequencies))
|
|
48
75
|
// short-circuit edge cases
|
|
49
76
|
if (frequencies.length === 0) throw new Error(`nothing to select in ${frequencies}`)
|
|
50
77
|
if (frequencies.length === 1) {
|
|
51
78
|
const [t] = frequencies[0]
|
|
52
|
-
return () => t
|
|
79
|
+
return (_rng: Rng) => () => t
|
|
53
80
|
}
|
|
54
81
|
// throw an error in case of duplicates
|
|
55
82
|
// it is difficult to do this efficiently because we allow T to be anything
|
|
@@ -59,7 +86,6 @@ export function pickMe<T>(frequencies: [T, number][] | T[], rando?: () => number
|
|
|
59
86
|
if (t === frequencies[j][0]) throw new Error(`${t} appears more than once among frequencies`)
|
|
60
87
|
}
|
|
61
88
|
}
|
|
62
|
-
rando ??= () => Math.random()
|
|
63
89
|
let total = 0,
|
|
64
90
|
acc = 0
|
|
65
91
|
for (const [, n] of frequencies) total += n
|
|
@@ -75,9 +101,16 @@ export function pickMe<T>(frequencies: [T, number][] | T[], rando?: () => number
|
|
|
75
101
|
thresholds.push(n)
|
|
76
102
|
}
|
|
77
103
|
const f = thresholder(thresholds)
|
|
78
|
-
return () => items[f(
|
|
104
|
+
return (rng: Rng) => () => items[f(rng())]!
|
|
79
105
|
}
|
|
80
106
|
|
|
107
|
+
/**
|
|
108
|
+
* The type of random number generators. Ideally this will return number from a continuous distribution
|
|
109
|
+
* in the interval [0, 1], but for `pickMe` and number less than 0 is the same as 0 and any number
|
|
110
|
+
* greater than 1 is the same as 1.
|
|
111
|
+
*/
|
|
112
|
+
export type Rng = () => number
|
|
113
|
+
|
|
81
114
|
// take in an array of ambiguous type and return a list of pairs, T and frequency
|
|
82
115
|
function normalize<T>(frequencies: [T, number][] | T[]): [T, number][] {
|
|
83
116
|
if (frequencies.length === 0) return frequencies as any as [T, number][]
|
|
@@ -130,9 +163,9 @@ function portion(thresholds: number[], start: number, end: number): string {
|
|
|
130
163
|
* ```
|
|
131
164
|
*
|
|
132
165
|
* @param {number} seed - the seed for a random number sequence
|
|
133
|
-
* @returns {
|
|
166
|
+
* @returns {Rng} a generator of random numbers
|
|
134
167
|
*/
|
|
135
|
-
export function rando(seed: number):
|
|
168
|
+
export function rando(seed: number): Rng {
|
|
136
169
|
return () => {
|
|
137
170
|
let t = (seed += 0x6d2b79f5)
|
|
138
171
|
t = Math.imul(t ^ (t >>> 15), t | 1)
|