pick-me-too 1.0.2 → 1.2.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,8 @@
1
+ # Changelog
2
+
3
+ ## v1.2.0 *2022-4-17*
4
+ - some efficiency improvements in edge cases
5
+ ## v1.1.1 *2022-4-16*
6
+ - fixed some project configuration
7
+ ## v1.1.0 *2022-4-16*
8
+ - factored `pickMeToo` out of `pickMe`
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/lib/index.d.ts CHANGED
@@ -7,37 +7,73 @@
7
7
  * If the parameters are `[[1, 1], [2, 2]]`, you will get twice as many 2s as 1s. You will get the same
8
8
  * results with `[[1, 0.1], [2, 0.2]]`, `[[1, 10], [2, 20]]`, and `[[1, 0.13], [2, 0.26]]`.
9
9
  *
10
+ * The second, optional parameter is a random number sequence generator. Provide this if you want a reproducible
11
+ * sequence of selections from your frequency list. If this doesn't concern you, leave it off and take
12
+ * whatever `Math.random()` gives you.
13
+ *
10
14
  * If the frequency list is not a list of pairs but just a list of items, this is equivalent to a list of
11
- * pairs where all the frequencies are the same. Note, this will not work if the items are all pairs whose
12
- * second value is a number. If you want to pick among such pairs you'll need to wrap them each the first member
13
- * of a frequency pair with the second value being some common positive number.
15
+ * pairs where all the frequencies are the same. (Note, this will not work if the items are all pairs whose
16
+ * second value is a number. If you want to pick among such pairs you'll need to wrap them in new pairs.)
14
17
  *
15
18
  * An item with
16
19
  * a non-positive frequency will be culled from the list. `pickMe` will throw an error if the list is empty.
17
- * Likewise, it will throw an error if any item appears more than once in the list. `[[1, 1], [1, 2]]` is
18
- * not good. `[[1, 1], [1,2]]` is no better.
20
+ * Likewise, it will throw an error if any item appears more than once in the list. `[]` is
21
+ * not good. `[[1, 1], [1, 2]]` is no better.
19
22
  *
20
- * The second, optional parameter is a random number sequence generator. Provide this if you want a reproducible
21
- * sequence of selections from your frequency list. If this doesn't concern you, leave it off and take
22
- * whatever `Math.random()` gives you.
23
+ * Note, the item returned by `pickMe` is the item you provided, not a copy. If you are picking from a list of
24
+ * objects and you modify one of them, it will remain modified.
25
+ *
26
+ * Finally, `pickMe` compiles an efficient picker from the parameters provided. As with other compiled things, such
27
+ * as regular expressions, you want to do this one time in an initialization phase and then reuse it in loops.
28
+ * The compilation is not terribly expensive, but skipping it altogether is cheaper.
23
29
  *
24
30
  * @example
25
31
  * ```ts
26
- * import { rando, pickMe } from 'pick-me';
32
+ * import { rando, pickMe } from 'pick-me'
27
33
  *
28
34
  * // make a random number generator with a seed of 1
29
- * const rng = rando(1);
30
- * const fooBarBaz = pickMe([["foo", 1], ["bar", 2], ["baz", 3]], rng);
31
- * const ar: string[] = [];
35
+ * const rng = rando(1)
36
+ * const fooBarBaz = pickMe([["foo", 1], ["bar", 2], ["baz", 3]], rng)
37
+ * const ar: string[] = []
32
38
  * for (let i = 0; i < 10; i++) ar.push(fooBarBaz())
33
39
  * // => [ 'baz', 'foo', 'baz', 'baz', 'baz', 'bar', 'baz', 'baz', 'bar', 'baz' ]
34
40
  * ```
35
41
  *
36
42
  * @param {[T, number][] | T[]} frequencies - phrases to match
37
- * @param {() => number} [rando=() => Math.random()] - random number generator
43
+ * @param {Rng} [rando=() => Math.random()] - random number generator
38
44
  * @returns {() => T} a picker of T's
39
45
  */
40
- export declare function pickMe<T>(frequencies: [T, number][] | T[], rando?: () => number): () => T;
46
+ export declare function pickMe<T>(frequencies: [T, number][] | T[], rando?: Rng): () => T;
47
+ /**
48
+ * Generates pickers of things. This separates the compilation step from frequencies so you
49
+ * can plug different random number generators in.
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * import { pickMeToo } from 'pick-me'
54
+ *
55
+ * const fooBarBaz = pickMeToo([["foo", 1], ["bar", 1000], ["baz", 1]])
56
+ *
57
+ * // minimum probablility generator just returns "foo"
58
+ * fooBarBaz(() => 0)()
59
+ * // => "foo"
60
+ *
61
+ * // maximum probability generator just returns "baz"
62
+ * fooBarBaz(() => 1)()
63
+ * // => "baz"
64
+ * ```
65
+ *
66
+ * @template T - the sort of thing to pick
67
+ * @param {[T, number][] | T[]} frequencies - things to pick according to their frequencies
68
+ * @returns {(rng: Rng) => () => T} - a function from random number generators to pickers of things in the frequency list
69
+ */
70
+ export declare function pickMeToo<T>(frequencies: [T, number][] | T[]): (rng: Rng) => () => T;
71
+ /**
72
+ * The type of random number generators. Ideally this will return number from a continuous distribution
73
+ * in the interval [0, 1], but for `pickMe` and number less than 0 is the same as 0 and any number
74
+ * greater than 1 is the same as 1.
75
+ */
76
+ export declare type Rng = () => number;
41
77
  /**
42
78
  * Generates a random number sequence generator. This is a demonstration generator
43
79
  * useful for testing and documentation. Any zero-argument function that returns
@@ -48,16 +84,16 @@ export declare function pickMe<T>(frequencies: [T, number][] | T[], rando?: () =
48
84
  *
49
85
  * @example
50
86
  * ```ts
51
- * import { rando } from 'pick-me';
87
+ * import { rando } from 'pick-me'
52
88
  *
53
89
  * // make a random number generator with a seed of 1
54
- * const rng = rando(1);
55
- * const ar: number[] = [];
90
+ * const rng = rando(1)
91
+ * const ar: number[] = []
56
92
  * for (let i = 0; i < 3; i++) ar.push(rng())
57
93
  * // => [ 0.6270739405881613, 0.002735721180215478, 0.5274470399599522 ]
58
94
  * ```
59
95
  *
60
96
  * @param {number} seed - the seed for a random number sequence
61
- * @returns {() => number} a generator of random numbers
97
+ * @returns {Rng} a generator of random numbers
62
98
  */
63
- export declare function rando(seed: number): () => number;
99
+ export declare function rando(seed: number): Rng;
package/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.rando = exports.pickMe = void 0;
3
+ exports.rando = exports.pickMeToo = exports.pickMe = void 0;
4
4
  /**
5
5
  * Generates a zero-argument function that returns an item from the frequency list
6
6
  * parameter with a probability concordant with its specified frequency.
@@ -10,55 +10,82 @@ exports.rando = exports.pickMe = void 0;
10
10
  * If the parameters are `[[1, 1], [2, 2]]`, you will get twice as many 2s as 1s. You will get the same
11
11
  * results with `[[1, 0.1], [2, 0.2]]`, `[[1, 10], [2, 20]]`, and `[[1, 0.13], [2, 0.26]]`.
12
12
  *
13
+ * The second, optional parameter is a random number sequence generator. Provide this if you want a reproducible
14
+ * sequence of selections from your frequency list. If this doesn't concern you, leave it off and take
15
+ * whatever `Math.random()` gives you.
16
+ *
13
17
  * If the frequency list is not a list of pairs but just a list of items, this is equivalent to a list of
14
- * pairs where all the frequencies are the same. Note, this will not work if the items are all pairs whose
15
- * second value is a number. If you want to pick among such pairs you'll need to wrap them each the first member
16
- * of a frequency pair with the second value being some common positive number.
18
+ * pairs where all the frequencies are the same. (Note, this will not work if the items are all pairs whose
19
+ * second value is a number. If you want to pick among such pairs you'll need to wrap them in new pairs.)
17
20
  *
18
21
  * An item with
19
22
  * a non-positive frequency will be culled from the list. `pickMe` will throw an error if the list is empty.
20
- * Likewise, it will throw an error if any item appears more than once in the list. `[[1, 1], [1, 2]]` is
21
- * not good. `[[1, 1], [1,2]]` is no better.
23
+ * Likewise, it will throw an error if any item appears more than once in the list. `[]` is
24
+ * not good. `[[1, 1], [1, 2]]` is no better.
22
25
  *
23
- * The second, optional parameter is a random number sequence generator. Provide this if you want a reproducible
24
- * sequence of selections from your frequency list. If this doesn't concern you, leave it off and take
25
- * whatever `Math.random()` gives you.
26
+ * Note, the item returned by `pickMe` is the item you provided, not a copy. If you are picking from a list of
27
+ * objects and you modify one of them, it will remain modified.
28
+ *
29
+ * Finally, `pickMe` compiles an efficient picker from the parameters provided. As with other compiled things, such
30
+ * as regular expressions, you want to do this one time in an initialization phase and then reuse it in loops.
31
+ * The compilation is not terribly expensive, but skipping it altogether is cheaper.
26
32
  *
27
33
  * @example
28
34
  * ```ts
29
- * import { rando, pickMe } from 'pick-me';
35
+ * import { rando, pickMe } from 'pick-me'
30
36
  *
31
37
  * // make a random number generator with a seed of 1
32
- * const rng = rando(1);
33
- * const fooBarBaz = pickMe([["foo", 1], ["bar", 2], ["baz", 3]], rng);
34
- * const ar: string[] = [];
38
+ * const rng = rando(1)
39
+ * const fooBarBaz = pickMe([["foo", 1], ["bar", 2], ["baz", 3]], rng)
40
+ * const ar: string[] = []
35
41
  * for (let i = 0; i < 10; i++) ar.push(fooBarBaz())
36
42
  * // => [ 'baz', 'foo', 'baz', 'baz', 'baz', 'bar', 'baz', 'baz', 'bar', 'baz' ]
37
43
  * ```
38
44
  *
39
45
  * @param {[T, number][] | T[]} frequencies - phrases to match
40
- * @param {() => number} [rando=() => Math.random()] - random number generator
46
+ * @param {Rng} [rando=() => Math.random()] - random number generator
41
47
  * @returns {() => T} a picker of T's
42
48
  */
43
49
  function pickMe(frequencies, rando) {
44
- frequencies = sanitize(normalize(frequencies));
50
+ return pickMeToo(frequencies)(rando !== null && rando !== void 0 ? rando : (() => Math.random()));
51
+ }
52
+ exports.pickMe = pickMe;
53
+ /**
54
+ * Generates pickers of things. This separates the compilation step from frequencies so you
55
+ * can plug different random number generators in.
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * import { pickMeToo } from 'pick-me'
60
+ *
61
+ * const fooBarBaz = pickMeToo([["foo", 1], ["bar", 1000], ["baz", 1]])
62
+ *
63
+ * // minimum probablility generator just returns "foo"
64
+ * fooBarBaz(() => 0)()
65
+ * // => "foo"
66
+ *
67
+ * // maximum probability generator just returns "baz"
68
+ * fooBarBaz(() => 1)()
69
+ * // => "baz"
70
+ * ```
71
+ *
72
+ * @template T - the sort of thing to pick
73
+ * @param {[T, number][] | T[]} frequencies - things to pick according to their frequencies
74
+ * @returns {(rng: Rng) => () => T} - a function from random number generators to pickers of things in the frequency list
75
+ */
76
+ function pickMeToo(frequencies) {
77
+ const simpleton = simplePicker(frequencies);
78
+ if (simpleton)
79
+ return simpleton;
80
+ frequencies = sanitize(frequencies);
45
81
  // short-circuit edge cases
46
82
  if (frequencies.length === 0)
47
83
  throw new Error(`nothing to select in ${frequencies}`);
48
84
  if (frequencies.length === 1) {
49
85
  const [t] = frequencies[0];
50
- return () => t;
86
+ return (_rng) => () => t;
51
87
  }
52
- // throw an error in case of duplicates
53
- // it is difficult to do this efficiently because we allow T to be anything
54
- for (let i = 0, lim = frequencies.length - 1; i < lim; i++) {
55
- const t = frequencies[i][0];
56
- for (let j = i + 1; j < frequencies.length; j++) {
57
- if (t === frequencies[j][0])
58
- throw new Error(`${t} appears more than once among frequencies`);
59
- }
60
- }
61
- rando !== null && rando !== void 0 ? rando : (rando = () => Math.random());
88
+ dupChecker(frequencies, (item) => item[0]);
62
89
  let total = 0, acc = 0;
63
90
  for (const [, n] of frequencies)
64
91
  total += n;
@@ -73,17 +100,42 @@ function pickMe(frequencies, rando) {
73
100
  thresholds.push(n);
74
101
  }
75
102
  const f = thresholder(thresholds);
76
- return () => items[f(rando())];
103
+ return (rng) => () => items[f(rng())];
77
104
  }
78
- exports.pickMe = pickMe;
79
- // take in an array of ambiguous type and return a list of pairs, T and frequency
80
- function normalize(frequencies) {
105
+ exports.pickMeToo = pickMeToo;
106
+ // throw an error in case of duplicates
107
+ // it is difficult to do this efficiently because we allow T to be anything, so we can't for instance use a Set
108
+ function dupChecker(frequencies, toT) {
109
+ for (let i = 0, lim = frequencies.length - 1; i < lim; i++) {
110
+ const t = toT(frequencies[i]);
111
+ for (let j = i + 1; j < frequencies.length; j++) {
112
+ if (t === toT(frequencies[j]))
113
+ throw new Error(`${t} appears more than once among frequencies`);
114
+ }
115
+ }
116
+ }
117
+ // if all frequencies are the same, we can make a simpler picker
118
+ function simplePicker(frequencies) {
81
119
  if (frequencies.length === 0)
82
- return frequencies;
83
- if (frequencies.every((s) => Array.isArray(s) && s.length === 2 && typeof s[1] === 'number'))
84
- return frequencies;
85
- const ar = frequencies.map((t) => [t, 1]);
86
- return ar;
120
+ return;
121
+ if (frequencies.every((s) => Array.isArray(s) && s.length === 2 && typeof s[1] === 'number')) {
122
+ const castFrequencies = frequencies;
123
+ const [, n1] = castFrequencies[0];
124
+ if (n1 > 0 && castFrequencies.every(([, n2]) => n2 === n1)) {
125
+ const items = castFrequencies.map(([t]) => t);
126
+ dupChecker(items, (item) => item);
127
+ return (rng) => () => items[Math.floor(items.length * rng())];
128
+ }
129
+ }
130
+ else {
131
+ const items = [...frequencies];
132
+ if (items.length === 1) {
133
+ const t = items[0];
134
+ return (_rng) => () => t;
135
+ }
136
+ dupChecker(items, (item) => item);
137
+ return (rng) => () => items[Math.floor(items.length * rng())];
138
+ }
87
139
  }
88
140
  // remove impossible frequencies
89
141
  function sanitize(frequencies) {
@@ -103,8 +155,6 @@ function portion(thresholds, start, end) {
103
155
  const i = start + (n % 2 === 0 ? n / 2 - 1 : (n - 1) / 2);
104
156
  return `(n > ${thresholds[i]} ? ${portion(thresholds, i + 1, end)} : ${portion(thresholds, start, i + 1)})`;
105
157
  }
106
- // return a generator of a reproducible random number sequence
107
- // gotten from https://stackoverflow.com/a/47593316/15060051 -- this is Mulberry32
108
158
  /**
109
159
  * Generates a random number sequence generator. This is a demonstration generator
110
160
  * useful for testing and documentation. Any zero-argument function that returns
@@ -115,17 +165,17 @@ function portion(thresholds, start, end) {
115
165
  *
116
166
  * @example
117
167
  * ```ts
118
- * import { rando } from 'pick-me';
168
+ * import { rando } from 'pick-me'
119
169
  *
120
170
  * // make a random number generator with a seed of 1
121
- * const rng = rando(1);
122
- * const ar: number[] = [];
171
+ * const rng = rando(1)
172
+ * const ar: number[] = []
123
173
  * for (let i = 0; i < 3; i++) ar.push(rng())
124
174
  * // => [ 0.6270739405881613, 0.002735721180215478, 0.5274470399599522 ]
125
175
  * ```
126
176
  *
127
177
  * @param {number} seed - the seed for a random number sequence
128
- * @returns {() => number} a generator of random numbers
178
+ * @returns {Rng} a generator of random numbers
129
179
  */
130
180
  function rando(seed) {
131
181
  return () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pick-me-too",
3
- "version": "1.0.2",
3
+ "version": "1.2.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",
@@ -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', () => {
@@ -92,7 +92,6 @@ describe('README', () => {
92
92
  return s.join(' ')
93
93
  }
94
94
  const saying = clause()
95
- console.log(saying)
96
95
  test('polynesianish', () => expect(saying).toEqual("rekātū pe'ide kēpe 'ātēdapidapi kaha"))
97
96
  })
98
97
  })
@@ -112,6 +111,15 @@ describe('index.ts', () => {
112
111
  for (let i = 0; i < 10; i++) ar.push(fooBarBaz())
113
112
  test('pickMe', () => expect(ar).toEqual(['baz', 'foo', 'baz', 'baz', 'baz', 'bar', 'baz', 'baz', 'bar', 'baz']))
114
113
  })
114
+ describe('pickMeToo', () => {
115
+ const fooBarBaz = pickMeToo([
116
+ ['foo', 1],
117
+ ['bar', 1000],
118
+ ['baz', 1],
119
+ ])
120
+ test('low probability', () => expect(fooBarBaz(() => 0)()).toEqual('foo'))
121
+ test('high probability', () => expect(fooBarBaz(() => 1)()).toEqual('baz'))
122
+ })
115
123
  describe('rando', () => {
116
124
  const rng = rando(1)
117
125
  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,26 +40,49 @@
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 {
47
- frequencies = sanitize(normalize(frequencies))
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 {
74
+ const simpleton = simplePicker(frequencies)
75
+ if (simpleton) return simpleton
76
+
77
+ frequencies = sanitize(frequencies as any as [T, number][])
78
+
48
79
  // short-circuit edge cases
49
80
  if (frequencies.length === 0) throw new Error(`nothing to select in ${frequencies}`)
50
81
  if (frequencies.length === 1) {
51
82
  const [t] = frequencies[0]
52
- return () => t
83
+ return (_rng: Rng) => () => t
53
84
  }
54
- // throw an error in case of duplicates
55
- // it is difficult to do this efficiently because we allow T to be anything
56
- for (let i = 0, lim = frequencies.length - 1; i < lim; i++) {
57
- const t = frequencies[i][0]
58
- for (let j = i + 1; j < frequencies.length; j++) {
59
- if (t === frequencies[j][0]) throw new Error(`${t} appears more than once among frequencies`)
60
- }
61
- }
62
- rando ??= () => Math.random()
85
+ dupChecker(frequencies, (item: [T, number] | T) => (item as [T, number])[0])
63
86
  let total = 0,
64
87
  acc = 0
65
88
  for (const [, n] of frequencies) total += n
@@ -75,16 +98,47 @@ export function pickMe<T>(frequencies: [T, number][] | T[], rando?: () => number
75
98
  thresholds.push(n)
76
99
  }
77
100
  const f = thresholder(thresholds)
78
- return () => items[f(rando!())]!
101
+ return (rng: Rng) => () => items[f(rng())]!
102
+ }
103
+
104
+ /**
105
+ * The type of random number generators. Ideally this will return number from a continuous distribution
106
+ * in the interval [0, 1], but for `pickMe` and number less than 0 is the same as 0 and any number
107
+ * greater than 1 is the same as 1.
108
+ */
109
+ export type Rng = () => number
110
+
111
+ // throw an error in case of duplicates
112
+ // it is difficult to do this efficiently because we allow T to be anything, so we can't for instance use a Set
113
+ function dupChecker<T>(frequencies: [T, number][] | T[], toT: (item: [T, number] | T) => T) {
114
+ for (let i = 0, lim = frequencies.length - 1; i < lim; i++) {
115
+ const t = toT(frequencies[i])
116
+ for (let j = i + 1; j < frequencies.length; j++) {
117
+ if (t === toT(frequencies[j])) throw new Error(`${t} appears more than once among frequencies`)
118
+ }
119
+ }
79
120
  }
80
121
 
81
- // take in an array of ambiguous type and return a list of pairs, T and frequency
82
- function normalize<T>(frequencies: [T, number][] | T[]): [T, number][] {
83
- if (frequencies.length === 0) return frequencies as any as [T, number][]
84
- if ((frequencies as any).every((s: any) => Array.isArray(s) && s.length === 2 && typeof s[1] === 'number'))
85
- return frequencies as any as [T, number][]
86
- const ar = frequencies.map((t) => [t, 1])
87
- return ar as any as [T, number][]
122
+ // if all frequencies are the same, we can make a simpler picker
123
+ function simplePicker<T>(frequencies: [T, number][] | T[]): undefined | ((rng: Rng) => () => T) {
124
+ if (frequencies.length === 0) return
125
+ if ((frequencies as any).every((s: any) => Array.isArray(s) && s.length === 2 && typeof s[1] === 'number')) {
126
+ const castFrequencies: [T, number][] = frequencies as any as [T, number][]
127
+ const [, n1] = castFrequencies[0]
128
+ if (n1 > 0 && castFrequencies.every(([, n2]) => n2 === n1)) {
129
+ const items = castFrequencies.map(([t]) => t)
130
+ dupChecker(items, (item: [T, number] | T) => item as T)
131
+ return (rng: Rng) => () => items[Math.floor(items.length * rng())]
132
+ }
133
+ } else {
134
+ const items: T[] = [...frequencies] as any as T[]
135
+ if (items.length === 1) {
136
+ const t = items[0]
137
+ return (_rng: Rng) => () => t
138
+ }
139
+ dupChecker(items, (item: [T, number] | T) => item as T)
140
+ return (rng: Rng) => () => items[Math.floor(items.length * rng())]
141
+ }
88
142
  }
89
143
 
90
144
  // remove impossible frequencies
@@ -130,9 +184,9 @@ function portion(thresholds: number[], start: number, end: number): string {
130
184
  * ```
131
185
  *
132
186
  * @param {number} seed - the seed for a random number sequence
133
- * @returns {() => number} a generator of random numbers
187
+ * @returns {Rng} a generator of random numbers
134
188
  */
135
- export function rando(seed: number): () => number {
189
+ export function rando(seed: number): Rng {
136
190
  return () => {
137
191
  let t = (seed += 0x6d2b79f5)
138
192
  t = Math.imul(t ^ (t >>> 15), t | 1)