pick-me-too 1.1.1 → 1.3.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 +7 -1
- package/README.md +5 -1
- package/lib/index.d.ts +14 -0
- package/lib/index.js +56 -19
- package/package.json +1 -1
- package/src/__tests__/documentation.test.ts +3 -2
- package/src/__tests__/functionality.test.ts +13 -0
- package/src/index.ts +55 -18
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v1.3.0 *2022-7-23*
|
|
4
|
+
- made `rando` reseedable
|
|
5
|
+
## v1.2.1 *2022-4-30*
|
|
6
|
+
- fixed typo in README
|
|
7
|
+
## v1.2.0 *2022-4-17*
|
|
8
|
+
- some efficiency improvements in edge cases
|
|
3
9
|
## v1.1.1 *2022-4-16*
|
|
4
10
|
- fixed some project configuration
|
|
5
|
-
|
|
11
|
+
## v1.1.0 *2022-4-16*
|
|
6
12
|
- factored `pickMeToo` out of `pickMe`
|
package/README.md
CHANGED
|
@@ -180,7 +180,7 @@ Generates a random number sequence generator.
|
|
|
180
180
|
|
|
181
181
|
This is a demonstration generator useful for testing and documentation. Any zero-argument function that returns
|
|
182
182
|
numbers will suffice for `pickMe`. If it returns 0 or less, the first item
|
|
183
|
-
given to `pickMe` will be picked. If it is 1 or
|
|
183
|
+
given to `pickMe` will be picked. If it is 1 or more, the last item will be.
|
|
184
184
|
|
|
185
185
|
The implementation of `rando` is taken from [this Stack Overflow comment](https://stackoverflow.com/a/47593316/15060051).
|
|
186
186
|
There are many others if you don't find this suitable.
|
|
@@ -194,6 +194,10 @@ const rng = rando(1)
|
|
|
194
194
|
const ar: number[] = []
|
|
195
195
|
for (let i = 0; i < 3; i++) ar.push(rng())
|
|
196
196
|
// => [ 0.6270739405881613, 0.002735721180215478, 0.5274470399599522 ]
|
|
197
|
+
|
|
198
|
+
// reseed the generator
|
|
199
|
+
(rng as any)(Math.random())
|
|
200
|
+
// => ??? the random number sequence is still deterministic, but at a completely different place
|
|
197
201
|
```
|
|
198
202
|
|
|
199
203
|
## Acknowledgements
|
package/lib/index.d.ts
CHANGED
|
@@ -82,6 +82,10 @@ export declare type Rng = () => number;
|
|
|
82
82
|
* The implementation of `rando` is taken from https://stackoverflow.com/a/47593316/15060051.
|
|
83
83
|
* It is the Mulberry32 algorithm.
|
|
84
84
|
*
|
|
85
|
+
* Note, `rando` can be re-seeded by passing an optional number argument as the fresh seed (see
|
|
86
|
+
* example below). This can be useful if you are sharing a random number generator among
|
|
87
|
+
* several objects and you need to change random number sequences.
|
|
88
|
+
*
|
|
85
89
|
* @example
|
|
86
90
|
* ```ts
|
|
87
91
|
* import { rando } from 'pick-me'
|
|
@@ -91,6 +95,16 @@ export declare type Rng = () => number;
|
|
|
91
95
|
* const ar: number[] = []
|
|
92
96
|
* for (let i = 0; i < 3; i++) ar.push(rng())
|
|
93
97
|
* // => [ 0.6270739405881613, 0.002735721180215478, 0.5274470399599522 ]
|
|
98
|
+
*
|
|
99
|
+
* // the same seed will produce the same random number sequence
|
|
100
|
+
* rando(1)()
|
|
101
|
+
* // => 0.6270739405881613
|
|
102
|
+
* rando(1)()
|
|
103
|
+
* // => 0.6270739405881613
|
|
104
|
+
*
|
|
105
|
+
* // but you can provide a fresh seed to change the sequence
|
|
106
|
+
* (rando(1) as any)(2)
|
|
107
|
+
* // => 0.7342509443406016
|
|
94
108
|
* ```
|
|
95
109
|
*
|
|
96
110
|
* @param {number} seed - the seed for a random number sequence
|
package/lib/index.js
CHANGED
|
@@ -47,7 +47,8 @@ exports.rando = exports.pickMeToo = exports.pickMe = void 0;
|
|
|
47
47
|
* @returns {() => T} a picker of T's
|
|
48
48
|
*/
|
|
49
49
|
function pickMe(frequencies, rando) {
|
|
50
|
-
|
|
50
|
+
const rng = rando !== null && rando !== void 0 ? rando : (() => Math.random());
|
|
51
|
+
return pickMeToo(frequencies)(rng);
|
|
51
52
|
}
|
|
52
53
|
exports.pickMe = pickMe;
|
|
53
54
|
/**
|
|
@@ -74,7 +75,10 @@ exports.pickMe = pickMe;
|
|
|
74
75
|
* @returns {(rng: Rng) => () => T} - a function from random number generators to pickers of things in the frequency list
|
|
75
76
|
*/
|
|
76
77
|
function pickMeToo(frequencies) {
|
|
77
|
-
|
|
78
|
+
const simpleton = simplePicker(frequencies);
|
|
79
|
+
if (simpleton)
|
|
80
|
+
return simpleton;
|
|
81
|
+
frequencies = sanitize(frequencies);
|
|
78
82
|
// short-circuit edge cases
|
|
79
83
|
if (frequencies.length === 0)
|
|
80
84
|
throw new Error(`nothing to select in ${frequencies}`);
|
|
@@ -82,15 +86,7 @@ function pickMeToo(frequencies) {
|
|
|
82
86
|
const [t] = frequencies[0];
|
|
83
87
|
return (_rng) => () => t;
|
|
84
88
|
}
|
|
85
|
-
|
|
86
|
-
// it is difficult to do this efficiently because we allow T to be anything
|
|
87
|
-
for (let i = 0, lim = frequencies.length - 1; i < lim; i++) {
|
|
88
|
-
const t = frequencies[i][0];
|
|
89
|
-
for (let j = i + 1; j < frequencies.length; j++) {
|
|
90
|
-
if (t === frequencies[j][0])
|
|
91
|
-
throw new Error(`${t} appears more than once among frequencies`);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
89
|
+
dupChecker(frequencies, (item) => item[0]);
|
|
94
90
|
let total = 0, acc = 0;
|
|
95
91
|
for (const [, n] of frequencies)
|
|
96
92
|
total += n;
|
|
@@ -108,14 +104,39 @@ function pickMeToo(frequencies) {
|
|
|
108
104
|
return (rng) => () => items[f(rng())];
|
|
109
105
|
}
|
|
110
106
|
exports.pickMeToo = pickMeToo;
|
|
111
|
-
//
|
|
112
|
-
|
|
107
|
+
// throw an error in case of duplicates
|
|
108
|
+
// it is difficult to do this efficiently because we allow T to be anything, so we can't for instance use a Set
|
|
109
|
+
function dupChecker(frequencies, toT) {
|
|
110
|
+
for (let i = 0, lim = frequencies.length - 1; i < lim; i++) {
|
|
111
|
+
const t = toT(frequencies[i]);
|
|
112
|
+
for (let j = i + 1; j < frequencies.length; j++) {
|
|
113
|
+
if (t === toT(frequencies[j]))
|
|
114
|
+
throw new Error(`${t} appears more than once among frequencies`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// if all frequencies are the same, we can make a simpler picker
|
|
119
|
+
function simplePicker(frequencies) {
|
|
113
120
|
if (frequencies.length === 0)
|
|
114
|
-
return
|
|
115
|
-
if (frequencies.every((s) => Array.isArray(s) && s.length === 2 && typeof s[1] === 'number'))
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
121
|
+
return;
|
|
122
|
+
if (frequencies.every((s) => Array.isArray(s) && s.length === 2 && typeof s[1] === 'number')) {
|
|
123
|
+
const castFrequencies = frequencies;
|
|
124
|
+
const [, n1] = castFrequencies[0];
|
|
125
|
+
if (n1 > 0 && castFrequencies.every(([, n2]) => n2 === n1)) {
|
|
126
|
+
const items = castFrequencies.map(([t]) => t);
|
|
127
|
+
dupChecker(items, (item) => item);
|
|
128
|
+
return (rng) => () => items[Math.floor(items.length * rng())];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
const items = [...frequencies];
|
|
133
|
+
if (items.length === 1) {
|
|
134
|
+
const t = items[0];
|
|
135
|
+
return (_rng) => () => t;
|
|
136
|
+
}
|
|
137
|
+
dupChecker(items, (item) => item);
|
|
138
|
+
return (rng) => () => items[Math.floor(items.length * rng())];
|
|
139
|
+
}
|
|
119
140
|
}
|
|
120
141
|
// remove impossible frequencies
|
|
121
142
|
function sanitize(frequencies) {
|
|
@@ -143,6 +164,10 @@ function portion(thresholds, start, end) {
|
|
|
143
164
|
* The implementation of `rando` is taken from https://stackoverflow.com/a/47593316/15060051.
|
|
144
165
|
* It is the Mulberry32 algorithm.
|
|
145
166
|
*
|
|
167
|
+
* Note, `rando` can be re-seeded by passing an optional number argument as the fresh seed (see
|
|
168
|
+
* example below). This can be useful if you are sharing a random number generator among
|
|
169
|
+
* several objects and you need to change random number sequences.
|
|
170
|
+
*
|
|
146
171
|
* @example
|
|
147
172
|
* ```ts
|
|
148
173
|
* import { rando } from 'pick-me'
|
|
@@ -152,13 +177,25 @@ function portion(thresholds, start, end) {
|
|
|
152
177
|
* const ar: number[] = []
|
|
153
178
|
* for (let i = 0; i < 3; i++) ar.push(rng())
|
|
154
179
|
* // => [ 0.6270739405881613, 0.002735721180215478, 0.5274470399599522 ]
|
|
180
|
+
*
|
|
181
|
+
* // the same seed will produce the same random number sequence
|
|
182
|
+
* rando(1)()
|
|
183
|
+
* // => 0.6270739405881613
|
|
184
|
+
* rando(1)()
|
|
185
|
+
* // => 0.6270739405881613
|
|
186
|
+
*
|
|
187
|
+
* // but you can provide a fresh seed to change the sequence
|
|
188
|
+
* (rando(1) as any)(2)
|
|
189
|
+
* // => 0.7342509443406016
|
|
155
190
|
* ```
|
|
156
191
|
*
|
|
157
192
|
* @param {number} seed - the seed for a random number sequence
|
|
158
193
|
* @returns {Rng} a generator of random numbers
|
|
159
194
|
*/
|
|
160
195
|
function rando(seed) {
|
|
161
|
-
return () => {
|
|
196
|
+
return (reseed) => {
|
|
197
|
+
if (reseed !== undefined)
|
|
198
|
+
seed = reseed;
|
|
162
199
|
let t = (seed += 0x6d2b79f5);
|
|
163
200
|
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
164
201
|
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
package/package.json
CHANGED
|
@@ -87,12 +87,11 @@ describe('README', () => {
|
|
|
87
87
|
rng,
|
|
88
88
|
)
|
|
89
89
|
const clause = () => {
|
|
90
|
-
let s = []
|
|
90
|
+
let s: string[] = []
|
|
91
91
|
for (let i = 0, lim = clauseLength(); i < lim; i++) s.push(word())
|
|
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
|
})
|
|
@@ -128,5 +127,7 @@ describe('index.ts', () => {
|
|
|
128
127
|
test('rando', () => {
|
|
129
128
|
expect(ar).toEqual([0.6270739405881613, 0.002735721180215478, 0.5274470399599522])
|
|
130
129
|
})
|
|
130
|
+
test('rando(1)()', () => expect(rando(1)()).toEqual(0.6270739405881613))
|
|
131
|
+
test('(rando(1) as any)(2)', () => expect((rando(1) as any)(2)).toEqual(0.7342509443406016))
|
|
131
132
|
})
|
|
132
133
|
})
|
|
@@ -16,6 +16,19 @@ describe('rando', () => {
|
|
|
16
16
|
for (let i = 0; i < bins.length; i++) {
|
|
17
17
|
test('even distribution', () => expect(Math.round(bins[i] / 1000)).toEqual(1))
|
|
18
18
|
}
|
|
19
|
+
for (let i = 0; i < 10; i++) {
|
|
20
|
+
const seed = Math.random()
|
|
21
|
+
const rng1 = rando(seed),
|
|
22
|
+
rng2 = rando(seed)
|
|
23
|
+
const ar1: number[] = [],
|
|
24
|
+
ar2: number[] = []
|
|
25
|
+
for (let j = 0; j < 10; j++) {
|
|
26
|
+
ar1.push(rng1())
|
|
27
|
+
ar2.push(rng2())
|
|
28
|
+
}
|
|
29
|
+
test(`two randos with seed ${seed} produce the same sequence of random values`, () => expect(ar1).toEqual(ar2))
|
|
30
|
+
test(`randos can be reseeded`, () => expect(rng1()).not.toEqual((rng2 as any)(Math.random())))
|
|
31
|
+
}
|
|
19
32
|
})
|
|
20
33
|
|
|
21
34
|
describe('pickMe', () => {
|
package/src/index.ts
CHANGED
|
@@ -44,7 +44,8 @@
|
|
|
44
44
|
* @returns {() => T} a picker of T's
|
|
45
45
|
*/
|
|
46
46
|
export function pickMe<T>(frequencies: [T, number][] | T[], rando?: Rng): () => T {
|
|
47
|
-
|
|
47
|
+
const rng = rando ?? (() => Math.random())
|
|
48
|
+
return pickMeToo(frequencies)(rng)
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
/**
|
|
@@ -71,21 +72,18 @@ export function pickMe<T>(frequencies: [T, number][] | T[], rando?: Rng): () =>
|
|
|
71
72
|
* @returns {(rng: Rng) => () => T} - a function from random number generators to pickers of things in the frequency list
|
|
72
73
|
*/
|
|
73
74
|
export function pickMeToo<T>(frequencies: [T, number][] | T[]): (rng: Rng) => () => T {
|
|
74
|
-
|
|
75
|
+
const simpleton = simplePicker(frequencies)
|
|
76
|
+
if (simpleton) return simpleton
|
|
77
|
+
|
|
78
|
+
frequencies = sanitize(frequencies as any as [T, number][])
|
|
79
|
+
|
|
75
80
|
// short-circuit edge cases
|
|
76
81
|
if (frequencies.length === 0) throw new Error(`nothing to select in ${frequencies}`)
|
|
77
82
|
if (frequencies.length === 1) {
|
|
78
83
|
const [t] = frequencies[0]
|
|
79
84
|
return (_rng: Rng) => () => t
|
|
80
85
|
}
|
|
81
|
-
|
|
82
|
-
// it is difficult to do this efficiently because we allow T to be anything
|
|
83
|
-
for (let i = 0, lim = frequencies.length - 1; i < lim; i++) {
|
|
84
|
-
const t = frequencies[i][0]
|
|
85
|
-
for (let j = i + 1; j < frequencies.length; j++) {
|
|
86
|
-
if (t === frequencies[j][0]) throw new Error(`${t} appears more than once among frequencies`)
|
|
87
|
-
}
|
|
88
|
-
}
|
|
86
|
+
dupChecker(frequencies, (item: [T, number] | T) => (item as [T, number])[0])
|
|
89
87
|
let total = 0,
|
|
90
88
|
acc = 0
|
|
91
89
|
for (const [, n] of frequencies) total += n
|
|
@@ -111,13 +109,37 @@ export function pickMeToo<T>(frequencies: [T, number][] | T[]): (rng: Rng) => ()
|
|
|
111
109
|
*/
|
|
112
110
|
export type Rng = () => number
|
|
113
111
|
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
112
|
+
// throw an error in case of duplicates
|
|
113
|
+
// it is difficult to do this efficiently because we allow T to be anything, so we can't for instance use a Set
|
|
114
|
+
function dupChecker<T>(frequencies: [T, number][] | T[], toT: (item: [T, number] | T) => T) {
|
|
115
|
+
for (let i = 0, lim = frequencies.length - 1; i < lim; i++) {
|
|
116
|
+
const t = toT(frequencies[i])
|
|
117
|
+
for (let j = i + 1; j < frequencies.length; j++) {
|
|
118
|
+
if (t === toT(frequencies[j])) throw new Error(`${t} appears more than once among frequencies`)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// if all frequencies are the same, we can make a simpler picker
|
|
124
|
+
function simplePicker<T>(frequencies: [T, number][] | T[]): undefined | ((rng: Rng) => () => T) {
|
|
125
|
+
if (frequencies.length === 0) return
|
|
126
|
+
if ((frequencies as any).every((s: any) => Array.isArray(s) && s.length === 2 && typeof s[1] === 'number')) {
|
|
127
|
+
const castFrequencies: [T, number][] = frequencies as any as [T, number][]
|
|
128
|
+
const [, n1] = castFrequencies[0]
|
|
129
|
+
if (n1 > 0 && castFrequencies.every(([, n2]) => n2 === n1)) {
|
|
130
|
+
const items = castFrequencies.map(([t]) => t)
|
|
131
|
+
dupChecker(items, (item: [T, number] | T) => item as T)
|
|
132
|
+
return (rng: Rng) => () => items[Math.floor(items.length * rng())]
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
const items: T[] = [...frequencies] as any as T[]
|
|
136
|
+
if (items.length === 1) {
|
|
137
|
+
const t = items[0]
|
|
138
|
+
return (_rng: Rng) => () => t
|
|
139
|
+
}
|
|
140
|
+
dupChecker(items, (item: [T, number] | T) => item as T)
|
|
141
|
+
return (rng: Rng) => () => items[Math.floor(items.length * rng())]
|
|
142
|
+
}
|
|
121
143
|
}
|
|
122
144
|
|
|
123
145
|
// remove impossible frequencies
|
|
@@ -150,6 +172,10 @@ function portion(thresholds: number[], start: number, end: number): string {
|
|
|
150
172
|
*
|
|
151
173
|
* The implementation of `rando` is taken from https://stackoverflow.com/a/47593316/15060051.
|
|
152
174
|
* It is the Mulberry32 algorithm.
|
|
175
|
+
*
|
|
176
|
+
* Note, `rando` can be re-seeded by passing an optional number argument as the fresh seed (see
|
|
177
|
+
* example below). This can be useful if you are sharing a random number generator among
|
|
178
|
+
* several objects and you need to change random number sequences.
|
|
153
179
|
*
|
|
154
180
|
* @example
|
|
155
181
|
* ```ts
|
|
@@ -160,13 +186,24 @@ function portion(thresholds: number[], start: number, end: number): string {
|
|
|
160
186
|
* const ar: number[] = []
|
|
161
187
|
* for (let i = 0; i < 3; i++) ar.push(rng())
|
|
162
188
|
* // => [ 0.6270739405881613, 0.002735721180215478, 0.5274470399599522 ]
|
|
189
|
+
*
|
|
190
|
+
* // the same seed will produce the same random number sequence
|
|
191
|
+
* rando(1)()
|
|
192
|
+
* // => 0.6270739405881613
|
|
193
|
+
* rando(1)()
|
|
194
|
+
* // => 0.6270739405881613
|
|
195
|
+
*
|
|
196
|
+
* // but you can provide a fresh seed to change the sequence
|
|
197
|
+
* (rando(1) as any)(2)
|
|
198
|
+
* // => 0.7342509443406016
|
|
163
199
|
* ```
|
|
164
200
|
*
|
|
165
201
|
* @param {number} seed - the seed for a random number sequence
|
|
166
202
|
* @returns {Rng} a generator of random numbers
|
|
167
203
|
*/
|
|
168
204
|
export function rando(seed: number): Rng {
|
|
169
|
-
return () => {
|
|
205
|
+
return (reseed?: number) => {
|
|
206
|
+
if (reseed !== undefined) seed = reseed
|
|
170
207
|
let t = (seed += 0x6d2b79f5)
|
|
171
208
|
t = Math.imul(t ^ (t >>> 15), t | 1)
|
|
172
209
|
t ^= t + Math.imul(t ^ (t >>> 7), t | 61)
|