puzlink 0.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.
Files changed (233) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +35 -0
  3. package/dist/data/answerLengths.d.ts +10 -0
  4. package/dist/data/answerLengths.d.ts.map +1 -0
  5. package/dist/data/answerLengths.js +63 -0
  6. package/dist/data/answerLengths.js.map +1 -0
  7. package/dist/data/categories/compass.d.ts +3 -0
  8. package/dist/data/categories/compass.d.ts.map +1 -0
  9. package/dist/data/categories/compass.js +11 -0
  10. package/dist/data/categories/compass.js.map +1 -0
  11. package/dist/data/categories/countryAlpha2.d.ts +3 -0
  12. package/dist/data/categories/countryAlpha2.d.ts.map +1 -0
  13. package/dist/data/categories/countryAlpha2.js +252 -0
  14. package/dist/data/categories/countryAlpha2.js.map +1 -0
  15. package/dist/data/categories/countryAlpha3.d.ts +3 -0
  16. package/dist/data/categories/countryAlpha3.d.ts.map +1 -0
  17. package/dist/data/categories/countryAlpha3.js +252 -0
  18. package/dist/data/categories/countryAlpha3.js.map +1 -0
  19. package/dist/data/categories/daysOfTheWeek.d.ts +3 -0
  20. package/dist/data/categories/daysOfTheWeek.d.ts.map +1 -0
  21. package/dist/data/categories/daysOfTheWeek.js +10 -0
  22. package/dist/data/categories/daysOfTheWeek.js.map +1 -0
  23. package/dist/data/categories/elementSymbols.d.ts +3 -0
  24. package/dist/data/categories/elementSymbols.d.ts.map +1 -0
  25. package/dist/data/categories/elementSymbols.js +121 -0
  26. package/dist/data/categories/elementSymbols.js.map +1 -0
  27. package/dist/data/categories/greekLetters.d.ts +3 -0
  28. package/dist/data/categories/greekLetters.d.ts.map +1 -0
  29. package/dist/data/categories/greekLetters.js +27 -0
  30. package/dist/data/categories/greekLetters.js.map +1 -0
  31. package/dist/data/categories/months.d.ts +3 -0
  32. package/dist/data/categories/months.d.ts.map +1 -0
  33. package/dist/data/categories/months.js +15 -0
  34. package/dist/data/categories/months.js.map +1 -0
  35. package/dist/data/categories/natoAlphabet.d.ts +3 -0
  36. package/dist/data/categories/natoAlphabet.d.ts.map +1 -0
  37. package/dist/data/categories/natoAlphabet.js +29 -0
  38. package/dist/data/categories/natoAlphabet.js.map +1 -0
  39. package/dist/data/categories/numbers.d.ts +3 -0
  40. package/dist/data/categories/numbers.d.ts.map +1 -0
  41. package/dist/data/categories/numbers.js +16 -0
  42. package/dist/data/categories/numbers.js.map +1 -0
  43. package/dist/data/categories/romanNumerals.d.ts +3 -0
  44. package/dist/data/categories/romanNumerals.d.ts.map +1 -0
  45. package/dist/data/categories/romanNumerals.js +134 -0
  46. package/dist/data/categories/romanNumerals.js.map +1 -0
  47. package/dist/data/categories/solfege.d.ts +3 -0
  48. package/dist/data/categories/solfege.d.ts.map +1 -0
  49. package/dist/data/categories/solfege.js +11 -0
  50. package/dist/data/categories/solfege.js.map +1 -0
  51. package/dist/data/categories/usStateAbbreviations.d.ts +3 -0
  52. package/dist/data/categories/usStateAbbreviations.d.ts.map +1 -0
  53. package/dist/data/categories/usStateAbbreviations.js +53 -0
  54. package/dist/data/categories/usStateAbbreviations.js.map +1 -0
  55. package/dist/data/categories.d.ts +10 -0
  56. package/dist/data/categories.d.ts.map +1 -0
  57. package/dist/data/categories.js +31 -0
  58. package/dist/data/categories.js.map +1 -0
  59. package/dist/data/knownLogProbs.d.ts +6 -0
  60. package/dist/data/knownLogProbs.d.ts.map +1 -0
  61. package/dist/data/knownLogProbs.js +2975 -0
  62. package/dist/data/knownLogProbs.js.map +1 -0
  63. package/dist/data/morse.d.ts +2 -0
  64. package/dist/data/morse.d.ts.map +1 -0
  65. package/dist/data/morse.js +29 -0
  66. package/dist/data/morse.js.map +1 -0
  67. package/dist/data/scrabble.d.ts +2 -0
  68. package/dist/data/scrabble.d.ts.map +1 -0
  69. package/dist/data/scrabble.js +29 -0
  70. package/dist/data/scrabble.js.map +1 -0
  71. package/dist/features/index.d.ts +32 -0
  72. package/dist/features/index.d.ts.map +1 -0
  73. package/dist/features/index.js +79 -0
  74. package/dist/features/index.js.map +1 -0
  75. package/dist/features/letterCount.d.ts +7 -0
  76. package/dist/features/letterCount.d.ts.map +1 -0
  77. package/dist/features/letterCount.js +121 -0
  78. package/dist/features/letterCount.js.map +1 -0
  79. package/dist/features/letterSequence.d.ts +7 -0
  80. package/dist/features/letterSequence.d.ts.map +1 -0
  81. package/dist/features/letterSequence.js +155 -0
  82. package/dist/features/letterSequence.js.map +1 -0
  83. package/dist/features/logProbCache.d.ts +16 -0
  84. package/dist/features/logProbCache.d.ts.map +1 -0
  85. package/dist/features/logProbCache.js +36 -0
  86. package/dist/features/logProbCache.js.map +1 -0
  87. package/dist/features/other.d.ts +4 -0
  88. package/dist/features/other.d.ts.map +1 -0
  89. package/dist/features/other.js +190 -0
  90. package/dist/features/other.js.map +1 -0
  91. package/dist/features/substring.d.ts +3 -0
  92. package/dist/features/substring.d.ts.map +1 -0
  93. package/dist/features/substring.js +146 -0
  94. package/dist/features/substring.js.map +1 -0
  95. package/dist/features/wordplay.d.ts +7 -0
  96. package/dist/features/wordplay.d.ts.map +1 -0
  97. package/dist/features/wordplay.js +387 -0
  98. package/dist/features/wordplay.js.map +1 -0
  99. package/dist/index.d.ts +4 -0
  100. package/dist/index.d.ts.map +1 -0
  101. package/dist/index.js +3 -0
  102. package/dist/index.js.map +1 -0
  103. package/dist/lib/affixDistribution.d.ts +26 -0
  104. package/dist/lib/affixDistribution.d.ts.map +1 -0
  105. package/dist/lib/affixDistribution.js +105 -0
  106. package/dist/lib/affixDistribution.js.map +1 -0
  107. package/dist/lib/counter.d.ts +23 -0
  108. package/dist/lib/counter.d.ts.map +1 -0
  109. package/dist/lib/counter.js +55 -0
  110. package/dist/lib/counter.js.map +1 -0
  111. package/dist/lib/distribution.d.ts +40 -0
  112. package/dist/lib/distribution.d.ts.map +1 -0
  113. package/dist/lib/distribution.js +176 -0
  114. package/dist/lib/distribution.js.map +1 -0
  115. package/dist/lib/lengthDistribution.d.ts +30 -0
  116. package/dist/lib/lengthDistribution.d.ts.map +1 -0
  117. package/dist/lib/lengthDistribution.js +137 -0
  118. package/dist/lib/lengthDistribution.js.map +1 -0
  119. package/dist/lib/letterBitset.d.ts +49 -0
  120. package/dist/lib/letterBitset.d.ts.map +1 -0
  121. package/dist/lib/letterBitset.js +101 -0
  122. package/dist/lib/letterBitset.js.map +1 -0
  123. package/dist/lib/letterDistribution.d.ts +60 -0
  124. package/dist/lib/letterDistribution.d.ts.map +1 -0
  125. package/dist/lib/letterDistribution.js +230 -0
  126. package/dist/lib/letterDistribution.js.map +1 -0
  127. package/dist/lib/letterIndices.d.ts +13 -0
  128. package/dist/lib/letterIndices.d.ts.map +1 -0
  129. package/dist/lib/letterIndices.js +41 -0
  130. package/dist/lib/letterIndices.js.map +1 -0
  131. package/dist/lib/logCounter.d.ts +23 -0
  132. package/dist/lib/logCounter.d.ts.map +1 -0
  133. package/dist/lib/logCounter.js +49 -0
  134. package/dist/lib/logCounter.js.map +1 -0
  135. package/dist/lib/logNum.d.ts +36 -0
  136. package/dist/lib/logNum.d.ts.map +1 -0
  137. package/dist/lib/logNum.js +193 -0
  138. package/dist/lib/logNum.js.map +1 -0
  139. package/dist/lib/memoize.d.ts +5 -0
  140. package/dist/lib/memoize.d.ts.map +1 -0
  141. package/dist/lib/memoize.js +104 -0
  142. package/dist/lib/memoize.js.map +1 -0
  143. package/dist/lib/util.d.ts +30 -0
  144. package/dist/lib/util.d.ts.map +1 -0
  145. package/dist/lib/util.js +111 -0
  146. package/dist/lib/util.js.map +1 -0
  147. package/dist/lib/wordlist.d.ts +66 -0
  148. package/dist/lib/wordlist.d.ts.map +1 -0
  149. package/dist/lib/wordlist.js +166 -0
  150. package/dist/lib/wordlist.js.map +1 -0
  151. package/dist/linkers/index.d.ts +34 -0
  152. package/dist/linkers/index.d.ts.map +1 -0
  153. package/dist/linkers/index.js +25 -0
  154. package/dist/linkers/index.js.map +1 -0
  155. package/dist/linkers/indexing.d.ts +5 -0
  156. package/dist/linkers/indexing.d.ts.map +1 -0
  157. package/dist/linkers/indexing.js +152 -0
  158. package/dist/linkers/indexing.js.map +1 -0
  159. package/dist/linkers/length.d.ts +5 -0
  160. package/dist/linkers/length.d.ts.map +1 -0
  161. package/dist/linkers/length.js +101 -0
  162. package/dist/linkers/length.js.map +1 -0
  163. package/dist/linkers/letterDistribution.d.ts +4 -0
  164. package/dist/linkers/letterDistribution.d.ts.map +1 -0
  165. package/dist/linkers/letterDistribution.js +46 -0
  166. package/dist/linkers/letterDistribution.js.map +1 -0
  167. package/dist/linkers/other.d.ts +5 -0
  168. package/dist/linkers/other.d.ts.map +1 -0
  169. package/dist/linkers/other.js +90 -0
  170. package/dist/linkers/other.js.map +1 -0
  171. package/dist/parse.d.ts +8 -0
  172. package/dist/parse.d.ts.map +1 -0
  173. package/dist/parse.js +23 -0
  174. package/dist/parse.js.map +1 -0
  175. package/dist/puzlink.d.ts +84 -0
  176. package/dist/puzlink.d.ts.map +1 -0
  177. package/dist/puzlink.js +59 -0
  178. package/dist/puzlink.js.map +1 -0
  179. package/package.json +57 -0
  180. package/src/data/answerLengths.ts +63 -0
  181. package/src/data/categories/README.md +3 -0
  182. package/src/data/categories/compass.ts +1 -0
  183. package/src/data/categories/countryAlpha2.ts +251 -0
  184. package/src/data/categories/countryAlpha3.ts +251 -0
  185. package/src/data/categories/daysOfTheWeek.ts +1 -0
  186. package/src/data/categories/elementSymbols.ts +120 -0
  187. package/src/data/categories/greekLetters.ts +26 -0
  188. package/src/data/categories/months.ts +14 -0
  189. package/src/data/categories/natoAlphabet.ts +28 -0
  190. package/src/data/categories/numbers.ts +15 -0
  191. package/src/data/categories/romanNumerals.ts +133 -0
  192. package/src/data/categories/solfege.ts +1 -0
  193. package/src/data/categories/txt/compass.txt +8 -0
  194. package/src/data/categories/txt/daysOfTheWeek.txt +7 -0
  195. package/src/data/categories/txt/elementSymbols.txt +118 -0
  196. package/src/data/categories/txt/greekLetters.txt +24 -0
  197. package/src/data/categories/txt/months.txt +12 -0
  198. package/src/data/categories/txt/natoAlphabet.txt +26 -0
  199. package/src/data/categories/txt/numbers.txt +13 -0
  200. package/src/data/categories/txt/solfege.txt +8 -0
  201. package/src/data/categories/txt/usStateAbbreviations.txt +50 -0
  202. package/src/data/categories/usStateAbbreviations.ts +52 -0
  203. package/src/data/categories.ts +42 -0
  204. package/src/data/knownLogProbs.ts +2992 -0
  205. package/src/data/morse.ts +28 -0
  206. package/src/data/scrabble.ts +28 -0
  207. package/src/features/index.ts +120 -0
  208. package/src/features/letterCount.ts +174 -0
  209. package/src/features/letterSequence.ts +222 -0
  210. package/src/features/logProbCache.ts +48 -0
  211. package/src/features/other.ts +214 -0
  212. package/src/features/substring.ts +173 -0
  213. package/src/features/wordplay.ts +428 -0
  214. package/src/index.ts +3 -0
  215. package/src/lib/affixDistribution.ts +70 -0
  216. package/src/lib/counter.ts +71 -0
  217. package/src/lib/distribution.ts +162 -0
  218. package/src/lib/lengthDistribution.ts +108 -0
  219. package/src/lib/letterBitset.ts +123 -0
  220. package/src/lib/letterDistribution.ts +236 -0
  221. package/src/lib/letterIndices.ts +51 -0
  222. package/src/lib/logCounter.ts +74 -0
  223. package/src/lib/logNum.ts +193 -0
  224. package/src/lib/memoize.ts +136 -0
  225. package/src/lib/testUtils.ts +1 -0
  226. package/src/lib/util.ts +150 -0
  227. package/src/lib/wordlist.ts +162 -0
  228. package/src/linkers/index.ts +56 -0
  229. package/src/linkers/indexing.ts +194 -0
  230. package/src/linkers/length.ts +122 -0
  231. package/src/linkers/other.ts +117 -0
  232. package/src/parse.ts +20 -0
  233. package/src/puzlink.ts +141 -0
@@ -0,0 +1,74 @@
1
+ import { LogNum } from "./logNum.js";
2
+
3
+ /** A map from items to log counts. */
4
+ export class LogCounter<T extends PropertyKey> {
5
+ private readonly counts: ReadonlyMap<T, LogNum>;
6
+
7
+ constructor(counts: ReadonlyMap<T, LogNum>, totalCache?: LogNum) {
8
+ this.counts = counts;
9
+ this.totalCache = totalCache;
10
+ }
11
+
12
+ static from(data: string): LogCounter<string>;
13
+ static from<T extends PropertyKey>(data: Iterable<T>): LogCounter<T>;
14
+ static from<T extends PropertyKey, U>(
15
+ data: Iterable<U>,
16
+ map: (item: U) => T,
17
+ ): LogCounter<T>;
18
+ static from(
19
+ data: string | Iterable<PropertyKey>,
20
+ map?: (item: PropertyKey) => PropertyKey,
21
+ ) {
22
+ const counts = new Map<PropertyKey, number>();
23
+ let total = 0;
24
+
25
+ for (const item_ of data) {
26
+ const item = map ? map(item_) : item_;
27
+ counts.set(item, (counts.get(item) ?? 0) + 1);
28
+ total++;
29
+ }
30
+
31
+ return new LogCounter(
32
+ new Map(
33
+ Array.from(counts).map(([item, count]) => [item, LogNum.from(count)]),
34
+ ),
35
+ LogNum.from(total),
36
+ );
37
+ }
38
+
39
+ /** The number of distinct items. */
40
+ get distinct(): LogNum {
41
+ return LogNum.from(this.counts.size);
42
+ }
43
+
44
+ private totalCache: LogNum | undefined;
45
+
46
+ /** The total number of all items. */
47
+ get total(): LogNum {
48
+ return (this.totalCache ??= LogNum.sum(Array.from(this.counts.values())));
49
+ }
50
+
51
+ /** The log count of the given item. */
52
+ get(item: T): LogNum {
53
+ return this.counts.get(item) ?? LogNum.from(0);
54
+ }
55
+
56
+ /** Returns an iterable of [item, log count] pairs. */
57
+ entries(): IterableIterator<[T, LogNum]> {
58
+ return this.counts.entries();
59
+ }
60
+
61
+ /** Returns a list of items that satisfy the given predicate. */
62
+ filterKeys(fn: (item: T, count: LogNum) => boolean): T[] {
63
+ return Array.from(this.counts.entries())
64
+ .filter(([item, count]) => fn(item, count))
65
+ .map(([item]) => item);
66
+ }
67
+
68
+ /** Returns an iterable of [item, log probability] pairs. */
69
+ *frequencies(): IterableIterator<[T, LogNum]> {
70
+ for (const [item, count] of this.counts) {
71
+ yield [item, count.div(this.total)];
72
+ }
73
+ }
74
+ }
@@ -0,0 +1,193 @@
1
+ import { zipfToLogProb } from "cromulence";
2
+ import { gammaln } from "simple-statistics";
3
+ import { memoize } from "./memoize.js";
4
+ import { interval } from "./util.js";
5
+
6
+ /**
7
+ * A numerically-stable-ish log(1 - exp(x)).
8
+ *
9
+ * Follows the algorithm in <https://cran.r-project.org/web/packages/Rmpfr/vignettes/log1mexp-note.pdf>.
10
+ */
11
+ function log1mExp(x: number): number {
12
+ return x > -Math.log(2) ? Math.log1p(-Math.exp(x)) : Math.log(-Math.expm1(x));
13
+ }
14
+
15
+ /**
16
+ * A numerically-stable-ish log(1 + exp(x)).
17
+ *
18
+ * Follows the algorithm in <https://cran.r-project.org/web/packages/Rmpfr/vignettes/log1mexp-note.pdf>.
19
+ */
20
+ function log1pExp(x: number): number {
21
+ return x <= 18 ? Math.log1p(Math.exp(x)) : x + Math.exp(-x);
22
+ }
23
+
24
+ /**
25
+ * A numerically-stable-ish log of a non-negative number. Operations represent
26
+ * operations on the numbers, not the log numbers.
27
+ *
28
+ * @example LogNum.from(2).add(LogNum.from(3)).toNum() // 5
29
+ */
30
+ export class LogNum {
31
+ private readonly data: number;
32
+
33
+ /** Construct from the actual log number. */
34
+ constructor(data: number) {
35
+ this.data = data;
36
+ }
37
+
38
+ static from(value: number): LogNum {
39
+ return new LogNum(Math.log(value));
40
+ }
41
+
42
+ static fromExp(value: number): LogNum {
43
+ return new LogNum(value);
44
+ }
45
+
46
+ static fromFraction(numerator: number, denominator: number): LogNum {
47
+ return new LogNum(Math.log(numerator) - Math.log(denominator));
48
+ }
49
+
50
+ @memoize(2)
51
+ static fromBinomial(n: number, k: number): LogNum {
52
+ if (n === 0 || k === 0 || k === n) {
53
+ return LogNum.from(1);
54
+ }
55
+
56
+ return LogNum.fromBinomial(n - 1, k - 1).add(LogNum.fromBinomial(n - 1, k));
57
+ }
58
+
59
+ static fromZipf(zipf: number): LogNum {
60
+ return new LogNum(zipfToLogProb(zipf));
61
+ }
62
+
63
+ static fromFactorial(n: number): LogNum {
64
+ return new LogNum(gammaln(n + 1));
65
+ }
66
+
67
+ toNum(): number {
68
+ return Math.exp(this.data);
69
+ }
70
+
71
+ toLog(): number {
72
+ return this.data;
73
+ }
74
+
75
+ exp(): LogNum {
76
+ return new LogNum(Math.exp(this.data));
77
+ }
78
+
79
+ log(): LogNum {
80
+ return LogNum.from(this.data);
81
+ }
82
+
83
+ pow(power: number): LogNum {
84
+ return new LogNum(this.data * power);
85
+ }
86
+
87
+ mul(other: LogNum): LogNum {
88
+ return new LogNum(this.data + other.data);
89
+ }
90
+
91
+ div(other: LogNum): LogNum {
92
+ return new LogNum(this.data - other.data);
93
+ }
94
+
95
+ add(other: LogNum): LogNum {
96
+ let [max, min] = [this.data, other.data];
97
+ if (max < min) {
98
+ [max, min] = [min, max];
99
+ }
100
+ if (min === -Infinity) {
101
+ return new LogNum(max);
102
+ }
103
+ return new LogNum(max + log1pExp(min - max));
104
+ }
105
+
106
+ absSub(other: LogNum): LogNum {
107
+ let [max, min] = [this.data, other.data];
108
+ if (max < min) {
109
+ [max, min] = [min, max];
110
+ }
111
+ return new LogNum(max + log1mExp(min - max));
112
+ }
113
+
114
+ sub(other: LogNum): LogNum {
115
+ if (this.lt(other)) {
116
+ throw new Error(
117
+ `log underflow: ${this.data.toString()} - ${other.data.toString()}`,
118
+ );
119
+ }
120
+ const [max, min] = [this.data, other.data];
121
+ return new LogNum(max + log1mExp(min - max));
122
+ }
123
+
124
+ gt(other: LogNum): boolean {
125
+ return this.data > other.data;
126
+ }
127
+
128
+ lt(other: LogNum): boolean {
129
+ return this.data < other.data;
130
+ }
131
+
132
+ static max(values: LogNum[]): LogNum {
133
+ return new LogNum(Math.max(...values.map((x) => x.data)));
134
+ }
135
+
136
+ static min(values: LogNum[]): LogNum {
137
+ return new LogNum(Math.min(...values.map((x) => x.data)));
138
+ }
139
+
140
+ static sum(values: LogNum[]): LogNum {
141
+ // Strip zeroes first:
142
+ values = values.filter((x) => x.data !== -Infinity);
143
+ const max = Math.max(...values.map((x) => x.data));
144
+ if (max === Infinity) {
145
+ return new LogNum(Infinity);
146
+ }
147
+ const expSum = values.reduce((acc, x) => acc + Math.exp(x.data - max), 0);
148
+ return new LogNum(max + Math.log(expSum));
149
+ }
150
+
151
+ static prod(values: LogNum[]): LogNum {
152
+ return new LogNum(values.reduce((acc, x) => acc + x.data, 0));
153
+ }
154
+
155
+ static binomialProb(
156
+ successes: number,
157
+ trials: number,
158
+ frequency: LogNum,
159
+ ): LogNum {
160
+ return LogNum.fromBinomial(trials, successes).mul(
161
+ frequency.pow(successes).mul(
162
+ LogNum.from(1)
163
+ .sub(frequency)
164
+ .pow(trials - successes),
165
+ ),
166
+ );
167
+ }
168
+
169
+ static binomialPValue(
170
+ successes: number,
171
+ trials: number,
172
+ frequency: LogNum,
173
+ ): LogNum {
174
+ const expected = trials * frequency.toNum();
175
+ const probs = [];
176
+
177
+ if (successes > expected) {
178
+ for (const i of interval(successes, trials)) {
179
+ probs.push(LogNum.binomialProb(i, trials, frequency));
180
+ }
181
+ } else {
182
+ for (const i of interval(0, successes)) {
183
+ probs.push(LogNum.binomialProb(i, trials, frequency));
184
+ }
185
+ }
186
+
187
+ return LogNum.sum(probs);
188
+ }
189
+
190
+ [Symbol.for("nodejs.util.inspect.custom")](): string {
191
+ return `LogNum(${this.data.toFixed(3)}: ${this.toNum().toString()})`;
192
+ }
193
+ }
@@ -0,0 +1,136 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+
3
+ type AnyFn = (...args: readonly any[]) => unknown;
4
+
5
+ type Cache<Args extends readonly any[], Return> = {
6
+ get: (key: Args) => Return | null;
7
+ set: (key: Args, value: Return) => void;
8
+ };
9
+
10
+ function memoizeWith<Fn extends AnyFn>(
11
+ fn: Fn,
12
+ makeCache: () => Cache<Parameters<Fn>, ReturnType<Fn>>,
13
+ ): Fn {
14
+ const cache = makeCache();
15
+
16
+ const memoized = function (
17
+ this: any,
18
+ ...args: Parameters<Fn>
19
+ ): ReturnType<Fn> {
20
+ const cached = cache.get(args);
21
+ if (cached !== null) {
22
+ return cached;
23
+ }
24
+ const result = fn.apply(this, args) as ReturnType<Fn>;
25
+ cache.set(args, result);
26
+ return result;
27
+ } as Fn;
28
+
29
+ for (const property of Reflect.ownKeys(fn)) {
30
+ if (
31
+ property === "length" ||
32
+ property === "prototype" ||
33
+ property === "argunments" ||
34
+ property === "prototype"
35
+ ) {
36
+ continue;
37
+ }
38
+ const fnDesc = Object.getOwnPropertyDescriptor(fn, property)!;
39
+ const mmDesc = Object.getOwnPropertyDescriptor(memoized, property);
40
+ if (
41
+ mmDesc === undefined ||
42
+ mmDesc.configurable ||
43
+ (mmDesc.writable === fnDesc.writable &&
44
+ mmDesc.enumerable === fnDesc.enumerable &&
45
+ mmDesc.configurable === fnDesc.configurable &&
46
+ (mmDesc.writable || mmDesc.value === fnDesc.value))
47
+ ) {
48
+ Object.defineProperty(memoized, property, fnDesc);
49
+ }
50
+ }
51
+
52
+ Object.setPrototypeOf(memoized, Object.getPrototypeOf(fn) as object);
53
+
54
+ return memoized;
55
+ }
56
+
57
+ function memoizeDecorator<Fn extends AnyFn>(
58
+ makeCache: () => Cache<Parameters<Fn>, ReturnType<Fn>>,
59
+ ) {
60
+ return (target: Fn): Fn => {
61
+ const instanceMap = new WeakMap<any, Fn>();
62
+ return function (this: any, ...args: Parameters<Fn>): ReturnType<Fn> {
63
+ let memoized = instanceMap.get(this);
64
+ if (memoized === undefined) {
65
+ memoized = memoizeWith(target, makeCache);
66
+ instanceMap.set(this, memoized);
67
+ }
68
+ return memoized.apply(this, args) as ReturnType<Fn>;
69
+ } as Fn;
70
+ };
71
+ }
72
+
73
+ function memoize1<Fn extends (Arg: any) => unknown>() {
74
+ return memoizeDecorator<Fn>(() => {
75
+ const cache = new Map<any, unknown>();
76
+ return {
77
+ get: ([key]) => {
78
+ return cache.get(key) ?? null;
79
+ },
80
+ set: ([key], value) => {
81
+ return cache.set(key, value);
82
+ },
83
+ } as Cache<Parameters<Fn>, ReturnType<Fn>>;
84
+ });
85
+ }
86
+
87
+ function memoize2<Fn extends (Arg1: any, Arg2: any) => unknown>() {
88
+ return memoizeDecorator<Fn>(() => {
89
+ const cache = new Map<any, Map<any, unknown>>();
90
+ return {
91
+ get: ([outerKey, innerKey]) => {
92
+ return cache.get(outerKey)?.get(innerKey) ?? null;
93
+ },
94
+ set: ([outerKey, innerKey], value) => {
95
+ let inner = cache.get(outerKey);
96
+ if (inner === undefined) {
97
+ inner = new Map();
98
+ cache.set(outerKey, inner);
99
+ }
100
+ inner.set(innerKey, value);
101
+ },
102
+ } as Cache<Parameters<Fn>, ReturnType<Fn>>;
103
+ });
104
+ }
105
+
106
+ function memoize3<Fn extends (Arg1: any, Arg2: any, Arg3: any) => unknown>() {
107
+ return memoizeDecorator<Fn>(() => {
108
+ const cache = new Map<any, Map<any, Map<any, unknown>>>();
109
+ return {
110
+ get: ([outerKey, middleKey, innerKey]) => {
111
+ return cache.get(outerKey)?.get(middleKey)?.get(innerKey) ?? null;
112
+ },
113
+ set: ([outerKey, middleKey, innerKey], value) => {
114
+ let middle = cache.get(outerKey);
115
+ if (middle === undefined) {
116
+ middle = new Map();
117
+ cache.set(outerKey, middle);
118
+ }
119
+ let inner = middle.get(middleKey);
120
+ if (inner === undefined) {
121
+ inner = new Map();
122
+ middle.set(middleKey, inner);
123
+ }
124
+ inner.set(innerKey, value);
125
+ },
126
+ } as Cache<Parameters<Fn>, ReturnType<Fn>>;
127
+ });
128
+ }
129
+
130
+ /** Memoize a class method that takes up to three arguments. */
131
+ export function memoize<const Fn extends AnyFn>(
132
+ levels?: 1 | 2 | 3,
133
+ ): (target: Fn) => Fn;
134
+ export function memoize(levels?: 1 | 2 | 3) {
135
+ return levels === 3 ? memoize3() : levels === 2 ? memoize2() : memoize1();
136
+ }
@@ -0,0 +1 @@
1
+ export const speedTest = process.env["SPEED_TEST"] === "true";
@@ -0,0 +1,150 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+
3
+ /** Capitalizes the letters of a slug at the given indices. */
4
+ export function capitalizeAt(slug: string, indices: number[]): string {
5
+ const capitalized = [];
6
+ for (let i = 0; i < slug.length; i++) {
7
+ capitalized.push(indices.includes(i) ? slug[i]!.toUpperCase() : slug[i]!);
8
+ }
9
+ return capitalized.join("");
10
+ }
11
+
12
+ /** Prints the index of a slug at the given indices. */
13
+ export function printIndexSlug(slug: string, indices: number[]): string {
14
+ indices = Array.from(new Set(indices)).sort((a, b) => a - b);
15
+ const isInterval =
16
+ indices.length > 2 &&
17
+ Math.max(...indices) - Math.min(...indices) === indices.length - 1;
18
+ const indexString = isInterval
19
+ ? `${Math.min(...indices).toString()}..${Math.max(...indices).toString()}`
20
+ : `${indices.slice(0, 5).join(", ")}${indices.length > 5 ? ", ..." : ""}`;
21
+ return `index(${slug}, ${indexString}) = ${capitalizeAt(slug, indices)}`;
22
+ }
23
+
24
+ /** Returns an array of numbers from start to end (inclusive). */
25
+ export function interval(start: number, end: number, step = 1): number[] {
26
+ const result = [];
27
+ for (let i = start; i <= end; i += step) {
28
+ result.push(i);
29
+ }
30
+ return result;
31
+ }
32
+
33
+ type Product<A extends Iterable<any>[]> = A extends [infer First, ...infer Rest]
34
+ ? First extends Iterable<infer F>
35
+ ? Rest extends Iterable<any>[]
36
+ ? [F, ...Product<Rest>]
37
+ : never
38
+ : never
39
+ : [];
40
+
41
+ /** Cartesian product of iterables. */
42
+ function* product<const Args extends Iterable<any>[]>(
43
+ ...args: Args
44
+ ): Generator<Product<Args>> {
45
+ if (args.length === 1) {
46
+ for (const arg of args[0]!) {
47
+ yield [arg] as Product<Args>;
48
+ }
49
+ return;
50
+ }
51
+ const [first, ...rest] = args;
52
+ for (const a of first!) {
53
+ for (const b of product(...rest)) {
54
+ yield [a, ...b] as Product<Args>;
55
+ }
56
+ }
57
+ }
58
+
59
+ type IterMap<A extends any[]> = A extends [infer First, ...infer Rest]
60
+ ? [Iterable<First>, ...IterMap<Rest>]
61
+ : [];
62
+
63
+ /** Map a function to a product of iterators. */
64
+ export function* mapProduct<const Args extends any[], R>(
65
+ fn: (...args: Args) => R,
66
+ ...args: IterMap<Args>
67
+ ): Generator<R> {
68
+ for (const arg of product(...args)) {
69
+ yield fn(...(arg as unknown as Args));
70
+ }
71
+ }
72
+
73
+ /** Returns the ordinal suffix of the given number. */
74
+ export function ordinal(n: number): string {
75
+ const suffix =
76
+ Math.abs(n) % 100 >= 11 && Math.abs(n) % 100 <= 14
77
+ ? "th"
78
+ : Math.abs(n) % 10 === 1
79
+ ? "st"
80
+ : Math.abs(n) % 10 === 2
81
+ ? "nd"
82
+ : Math.abs(n) % 10 === 3
83
+ ? "rd"
84
+ : "th";
85
+ return `${n.toString()}${suffix}`;
86
+ }
87
+
88
+ const aCharCode = "a".charCodeAt(0);
89
+
90
+ /** Caesar shift a slug. */
91
+ export function caesar(slug: string, n: number): string {
92
+ n %= 26;
93
+ return Array.from(slug, (c) => {
94
+ return String.fromCharCode(
95
+ ((c.charCodeAt(0) - aCharCode + n + 26) % 26) + aCharCode,
96
+ );
97
+ }).join("");
98
+ }
99
+
100
+ /** Returns an iterator over [index, item] pairs. */
101
+ export function* enumerate<T>(iter: Iterable<T>): Generator<[number, T]> {
102
+ let i = 0;
103
+ for (const item of iter) {
104
+ yield [i, item];
105
+ i += 1;
106
+ }
107
+ }
108
+
109
+ /** Returns an iterator over windows of the given size. */
110
+ export function windows<T>(iter: Iterable<T>, size: 2): Generator<[T, T]>;
111
+ export function windows<T>(iter: Iterable<T>, size: 3): Generator<[T, T, T]>;
112
+ export function windows<T>(iter: Iterable<T>, size: number): Generator<T[]>;
113
+ export function* windows(iter: Iterable<any>, size: number) {
114
+ const buffer = [];
115
+ for (const item of iter) {
116
+ if (buffer.length === size) {
117
+ buffer.shift();
118
+ }
119
+ buffer.push(item);
120
+ if (buffer.length === size) {
121
+ yield buffer.slice();
122
+ }
123
+ }
124
+ }
125
+
126
+ /**
127
+ * If the given array is a non-constant arithmetic sequence, returns the start,
128
+ * step, and last. Else, returns null.
129
+ */
130
+ export function getArithmeticSequenceInfo(terms: number[]): {
131
+ start: number;
132
+ step: number;
133
+ last: number;
134
+ } | null {
135
+ if (terms.length < 2) {
136
+ return null;
137
+ }
138
+ terms.sort((a, b) => a - b);
139
+ const start = terms[0]!;
140
+ const step = terms[1]! - start;
141
+ if (step === 0) {
142
+ return null;
143
+ }
144
+ for (let i = 2; i < terms.length; i++) {
145
+ if (terms[i]! - terms[i - 1]! !== step) {
146
+ return null;
147
+ }
148
+ }
149
+ return { start, step, last: terms.at(-1)! };
150
+ }