pick-me-too 1.0.0 → 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 ADDED
@@ -0,0 +1,4 @@
1
+ # Changelog
2
+
3
+ ## v1.1.0 *2022-4-16*
4
+ - factored `pickMeToo` out of `pickMe`
package/README.md CHANGED
@@ -43,7 +43,7 @@ const consonants = pickMe(
43
43
  ],
44
44
  rng,
45
45
  )
46
- // our language as simple CV syllables
46
+ // our language has simple CV syllables
47
47
  const syllable = () => `${consonants()}${vowels()}`
48
48
  const morphemeLength = pickMe(
49
49
  [
@@ -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
  {
2
2
  "name": "pick-me-too",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Generate a function that returns random items from a list with specified frequencies.",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -12,7 +12,10 @@
12
12
  "frequency",
13
13
  "random",
14
14
  "list",
15
- "pick"
15
+ "pick",
16
+ "mock",
17
+ "mocking",
18
+ "testing"
16
19
  ],
17
20
  "author": {
18
21
  "name": "David F. Houghton",
@@ -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 {() => number} [rando=() => Math.random()] - random number generator
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?: () => number): () => T {
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(rando!())]!
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 {() => number} a generator of random numbers
166
+ * @returns {Rng} a generator of random numbers
134
167
  */
135
- export function rando(seed: number): () => 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)