pick-me-too 1.1.1 → 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 CHANGED
@@ -1,6 +1,8 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.2.0 *2022-4-17*
4
+ - some efficiency improvements in edge cases
3
5
  ## v1.1.1 *2022-4-16*
4
6
  - fixed some project configuration
5
- - ## v1.1.0 *2022-4-16*
7
+ ## v1.1.0 *2022-4-16*
6
8
  - factored `pickMeToo` out of `pickMe`
package/lib/index.js CHANGED
@@ -74,7 +74,10 @@ exports.pickMe = pickMe;
74
74
  * @returns {(rng: Rng) => () => T} - a function from random number generators to pickers of things in the frequency list
75
75
  */
76
76
  function pickMeToo(frequencies) {
77
- frequencies = sanitize(normalize(frequencies));
77
+ const simpleton = simplePicker(frequencies);
78
+ if (simpleton)
79
+ return simpleton;
80
+ frequencies = sanitize(frequencies);
78
81
  // short-circuit edge cases
79
82
  if (frequencies.length === 0)
80
83
  throw new Error(`nothing to select in ${frequencies}`);
@@ -82,15 +85,7 @@ function pickMeToo(frequencies) {
82
85
  const [t] = frequencies[0];
83
86
  return (_rng) => () => t;
84
87
  }
85
- // throw an error in case of duplicates
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
- }
88
+ dupChecker(frequencies, (item) => item[0]);
94
89
  let total = 0, acc = 0;
95
90
  for (const [, n] of frequencies)
96
91
  total += n;
@@ -108,14 +103,39 @@ function pickMeToo(frequencies) {
108
103
  return (rng) => () => items[f(rng())];
109
104
  }
110
105
  exports.pickMeToo = pickMeToo;
111
- // take in an array of ambiguous type and return a list of pairs, T and frequency
112
- function normalize(frequencies) {
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) {
113
119
  if (frequencies.length === 0)
114
- return frequencies;
115
- if (frequencies.every((s) => Array.isArray(s) && s.length === 2 && typeof s[1] === 'number'))
116
- return frequencies;
117
- const ar = frequencies.map((t) => [t, 1]);
118
- 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
+ }
119
139
  }
120
140
  // remove impossible frequencies
121
141
  function sanitize(frequencies) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pick-me-too",
3
- "version": "1.1.1",
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",
@@ -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
  })
package/src/index.ts CHANGED
@@ -71,21 +71,18 @@ export function pickMe<T>(frequencies: [T, number][] | T[], rando?: Rng): () =>
71
71
  * @returns {(rng: Rng) => () => T} - a function from random number generators to pickers of things in the frequency list
72
72
  */
73
73
  export function pickMeToo<T>(frequencies: [T, number][] | T[]): (rng: Rng) => () => T {
74
- frequencies = sanitize(normalize(frequencies))
74
+ const simpleton = simplePicker(frequencies)
75
+ if (simpleton) return simpleton
76
+
77
+ frequencies = sanitize(frequencies as any as [T, number][])
78
+
75
79
  // short-circuit edge cases
76
80
  if (frequencies.length === 0) throw new Error(`nothing to select in ${frequencies}`)
77
81
  if (frequencies.length === 1) {
78
82
  const [t] = frequencies[0]
79
83
  return (_rng: Rng) => () => t
80
84
  }
81
- // throw an error in case of duplicates
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
- }
85
+ dupChecker(frequencies, (item: [T, number] | T) => (item as [T, number])[0])
89
86
  let total = 0,
90
87
  acc = 0
91
88
  for (const [, n] of frequencies) total += n
@@ -111,13 +108,37 @@ export function pickMeToo<T>(frequencies: [T, number][] | T[]): (rng: Rng) => ()
111
108
  */
112
109
  export type Rng = () => number
113
110
 
114
- // take in an array of ambiguous type and return a list of pairs, T and frequency
115
- function normalize<T>(frequencies: [T, number][] | T[]): [T, number][] {
116
- if (frequencies.length === 0) return frequencies as any as [T, number][]
117
- if ((frequencies as any).every((s: any) => Array.isArray(s) && s.length === 2 && typeof s[1] === 'number'))
118
- return frequencies as any as [T, number][]
119
- const ar = frequencies.map((t) => [t, 1])
120
- return ar as any as [T, number][]
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
+ }
120
+ }
121
+
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
+ }
121
142
  }
122
143
 
123
144
  // remove impossible frequencies