@ix-xs/node-comfort 1.2.0 → 2.0.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 +190 -0
- package/README.md +136 -549
- package/index.js +80 -35
- package/package.json +160 -33
- package/src/Arr.js +887 -419
- package/src/Async.js +585 -0
- package/src/Cache.js +329 -0
- package/src/Checker.js +971 -539
- package/src/Cli.js +1034 -0
- package/src/Color.js +415 -0
- package/src/Crypto.js +707 -0
- package/src/Emitter.js +289 -0
- package/src/Env.js +555 -0
- package/src/FS.js +1483 -894
- package/src/Func.js +670 -353
- package/src/Http.js +538 -0
- package/src/Id.js +384 -201
- package/src/Logger.js +739 -378
- package/src/Num.js +844 -361
- package/src/Obj.js +855 -372
- package/src/SQLite.js +1308 -1005
- package/src/Schema.js +1586 -0
- package/src/Str.js +983 -436
- package/src/Sys.js +511 -0
- package/src/Time.js +1260 -422
- package/src/Utils.js +321 -325
- package/src/errors.js +292 -0
- package/types/index.d.ts +693 -526
- package/types/src/Arr.d.ts +455 -145
- package/types/src/Arr.d.ts.map +1 -1
- package/types/src/Async.d.ts +321 -0
- package/types/src/Async.d.ts.map +1 -0
- package/types/src/Cache.d.ts +195 -0
- package/types/src/Cache.d.ts.map +1 -0
- package/types/src/Checker.d.ts +501 -235
- package/types/src/Checker.d.ts.map +1 -1
- package/types/src/Cli.d.ts +582 -0
- package/types/src/Cli.d.ts.map +1 -0
- package/types/src/Color.d.ts +294 -0
- package/types/src/Color.d.ts.map +1 -0
- package/types/src/Crypto.d.ts +534 -0
- package/types/src/Crypto.d.ts.map +1 -0
- package/types/src/Emitter.d.ts +170 -0
- package/types/src/Emitter.d.ts.map +1 -0
- package/types/src/Env.d.ts +382 -0
- package/types/src/Env.d.ts.map +1 -0
- package/types/src/FS.d.ts +686 -238
- package/types/src/FS.d.ts.map +1 -1
- package/types/src/Func.d.ts +380 -135
- package/types/src/Func.d.ts.map +1 -1
- package/types/src/Http.d.ts +374 -0
- package/types/src/Http.d.ts.map +1 -0
- package/types/src/Id.d.ts +149 -68
- package/types/src/Id.d.ts.map +1 -1
- package/types/src/Logger.d.ts +363 -46
- package/types/src/Logger.d.ts.map +1 -1
- package/types/src/Num.d.ts +473 -137
- package/types/src/Num.d.ts.map +1 -1
- package/types/src/Obj.d.ts +390 -104
- package/types/src/Obj.d.ts.map +1 -1
- package/types/src/SQLite.d.ts +646 -309
- package/types/src/SQLite.d.ts.map +1 -1
- package/types/src/Schema.d.ts +940 -0
- package/types/src/Schema.d.ts.map +1 -0
- package/types/src/Str.d.ts +569 -166
- package/types/src/Str.d.ts.map +1 -1
- package/types/src/Sys.d.ts +269 -0
- package/types/src/Sys.d.ts.map +1 -0
- package/types/src/Time.d.ts +663 -204
- package/types/src/Time.d.ts.map +1 -1
- package/types/src/Utils.d.ts +154 -102
- package/types/src/Utils.d.ts.map +1 -1
- package/types/src/errors.d.ts +292 -0
- package/types/src/errors.d.ts.map +1 -0
- package/types/index.d.ts.map +0 -1
package/src/Arr.js
CHANGED
|
@@ -1,433 +1,901 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Array helpers. None of them mutate what you pass in, and item types carry
|
|
5
|
+
* through (`chunk(numbers, 2)` is a `number[][]`). Many take an iteratee:
|
|
6
|
+
* a property name, which your editor completes, or a function.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* arr.chunk([1, 2, 3, 4, 5], 2); // [[1, 2], [3, 4], [5]]
|
|
10
|
+
* arr.groupBy(users, "role"); // { admin: [...], user: [...] }
|
|
11
|
+
* arr.sortBy(users, ["lastName", "age"]);
|
|
12
|
+
*/
|
|
13
|
+
|
|
1
14
|
/**
|
|
2
|
-
*
|
|
15
|
+
* How to get a value out of an item: a property name, or a function
|
|
16
|
+
* `(item, index) => value`.
|
|
3
17
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
18
|
+
* @template T
|
|
19
|
+
* @typedef {(T extends object ? keyof T & (string | number) : never) | ((item: T, index: number) => unknown)} Iteratee
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A sort key for `sortBy()`: an iteratee, or `[iteratee, "asc" | "desc"]`
|
|
24
|
+
* to give that key its own direction.
|
|
6
25
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
26
|
+
* @template T
|
|
27
|
+
* @typedef {Iteratee<T> | [Iteratee<T>, "asc" | "desc"]} SortKey
|
|
9
28
|
*/
|
|
10
29
|
|
|
11
30
|
/**
|
|
12
|
-
*
|
|
13
|
-
* @
|
|
14
|
-
* @
|
|
15
|
-
* @
|
|
31
|
+
* Options for `sortBy()`.
|
|
32
|
+
* @typedef {object} SortByOptions
|
|
33
|
+
* @property {"asc"|"desc"} [order] Direction for keys that don't set their own. Defaults to `"asc"`.
|
|
34
|
+
* @property {boolean} [natural] Sort text like a person would: ignore case, respect accents, `"item2"` before `"item10"`.
|
|
35
|
+
* @property {string} [locale] Language used by `natural`. Defaults to the system's.
|
|
16
36
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A page returned by `paginate()`.
|
|
40
|
+
* @template T
|
|
41
|
+
* @typedef {object} Page
|
|
42
|
+
* @property {T[]} items The items on this page.
|
|
43
|
+
* @property {number} page The page number, starting at 1.
|
|
44
|
+
* @property {number} perPage
|
|
45
|
+
* @property {number} total Total number of items.
|
|
46
|
+
* @property {number} pages Number of pages, at least 1.
|
|
47
|
+
* @property {boolean} hasPrev
|
|
48
|
+
* @property {boolean} hasNext
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {unknown} iteratee
|
|
53
|
+
* @returns {(item: any, index: number) => any}
|
|
54
|
+
*/
|
|
55
|
+
const _fn = (iteratee) => {
|
|
56
|
+
if (typeof iteratee === "function") return /** @type {(item: any, index: number) => any} */ (iteratee);
|
|
57
|
+
if (typeof iteratee === "string" || typeof iteratee === "number" || typeof iteratee === "symbol") {
|
|
58
|
+
return (item) => (item == null ? undefined : item[iteratee]);
|
|
59
|
+
}
|
|
20
60
|
return (item) => item;
|
|
21
61
|
};
|
|
22
62
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
maxBy(array, iteratee) {
|
|
382
|
-
const fn = _iteratee(iteratee);
|
|
383
|
-
let best;
|
|
384
|
-
let bestVal = -Infinity;
|
|
385
|
-
for (const item of array) {
|
|
386
|
-
const val = fn(item);
|
|
387
|
-
if (val > bestVal) {
|
|
388
|
-
bestVal = val;
|
|
389
|
-
best = item;
|
|
63
|
+
/**
|
|
64
|
+
* @param {unknown} key
|
|
65
|
+
* @returns {PropertyKey}
|
|
66
|
+
*/
|
|
67
|
+
const _key = (key) => (typeof key === "symbol" || typeof key === "number" ? key : String(key));
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Splits an array into groups of `size` items. The last group can be shorter.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* arr.chunk([1, 2, 3, 4, 5], 2); // [[1, 2], [3, 4], [5]]
|
|
74
|
+
* arr.chunk(emails, 100).forEach(sendBatch);
|
|
75
|
+
*
|
|
76
|
+
* @template T
|
|
77
|
+
* @param {readonly T[]} array
|
|
78
|
+
* @param {number} size
|
|
79
|
+
* @returns {T[][]}
|
|
80
|
+
*/
|
|
81
|
+
function chunk(array, size) {
|
|
82
|
+
const n = Math.floor(size);
|
|
83
|
+
if (!(n >= 1)) return [];
|
|
84
|
+
/** @type {T[][]} */
|
|
85
|
+
const out = [];
|
|
86
|
+
for (let i = 0; i < array.length; i += n) out.push(array.slice(i, i + n));
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Sliding windows of `size` items, moving by `step`. Good for moving
|
|
92
|
+
* averages and comparing neighbours.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* arr.windows([1, 2, 3, 4], 2); // [[1, 2], [2, 3], [3, 4]]
|
|
96
|
+
* arr.windows([1, 2, 3, 4, 5], 3, 2); // [[1, 2, 3], [3, 4, 5]]
|
|
97
|
+
*
|
|
98
|
+
* @template T
|
|
99
|
+
* @param {readonly T[]} array
|
|
100
|
+
* @param {number} size
|
|
101
|
+
* @param {number} [step=1]
|
|
102
|
+
* @returns {T[][]}
|
|
103
|
+
*/
|
|
104
|
+
function windows(array, size, step = 1) {
|
|
105
|
+
const n = Math.floor(size);
|
|
106
|
+
const s = Math.max(1, Math.floor(step));
|
|
107
|
+
/** @type {T[][]} */
|
|
108
|
+
const out = [];
|
|
109
|
+
if (!(n >= 1)) return out;
|
|
110
|
+
for (let i = 0; i + n <= array.length; i += s) out.push(array.slice(i, i + n));
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The first item, or the first `n` items.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* arr.first([1, 2, 3]); // 1
|
|
119
|
+
* arr.first([1, 2, 3], 2); // [1, 2]
|
|
120
|
+
*
|
|
121
|
+
* @template T
|
|
122
|
+
* @overload
|
|
123
|
+
* @param {readonly T[]} array
|
|
124
|
+
* @returns {T | undefined}
|
|
125
|
+
*/
|
|
126
|
+
/**
|
|
127
|
+
* The first `n` items.
|
|
128
|
+
*
|
|
129
|
+
* @template T
|
|
130
|
+
* @overload
|
|
131
|
+
* @param {readonly T[]} array
|
|
132
|
+
* @param {number} n
|
|
133
|
+
* @returns {T[]}
|
|
134
|
+
*/
|
|
135
|
+
/**
|
|
136
|
+
* @template T
|
|
137
|
+
* @param {readonly T[]} array
|
|
138
|
+
* @param {number} [n]
|
|
139
|
+
* @returns {T | T[] | undefined}
|
|
140
|
+
*/
|
|
141
|
+
function first(array, n) {
|
|
142
|
+
if (n === undefined) return array[0];
|
|
143
|
+
return array.slice(0, Math.max(0, n));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The last item, or the last `n` items.
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* arr.last([1, 2, 3]); // 3
|
|
151
|
+
* arr.last([1, 2, 3], 2); // [2, 3]
|
|
152
|
+
*
|
|
153
|
+
* @template T
|
|
154
|
+
* @overload
|
|
155
|
+
* @param {readonly T[]} array
|
|
156
|
+
* @returns {T | undefined}
|
|
157
|
+
*/
|
|
158
|
+
/**
|
|
159
|
+
* The last `n` items.
|
|
160
|
+
*
|
|
161
|
+
* @template T
|
|
162
|
+
* @overload
|
|
163
|
+
* @param {readonly T[]} array
|
|
164
|
+
* @param {number} n
|
|
165
|
+
* @returns {T[]}
|
|
166
|
+
*/
|
|
167
|
+
/**
|
|
168
|
+
* @template T
|
|
169
|
+
* @param {readonly T[]} array
|
|
170
|
+
* @param {number} [n]
|
|
171
|
+
* @returns {T | T[] | undefined}
|
|
172
|
+
*/
|
|
173
|
+
function last(array, n) {
|
|
174
|
+
if (n === undefined) return array[array.length - 1];
|
|
175
|
+
return n <= 0 ? [] : array.slice(-n);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* One page of an array, with what you need to draw pagination controls.
|
|
180
|
+
* Pages start at 1; an out-of-range page is brought back into range.
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* arr.paginate(products, 2, 20);
|
|
184
|
+
* // { items: [...], page: 2, perPage: 20, total: 95, pages: 5, hasPrev: true, hasNext: true }
|
|
185
|
+
*
|
|
186
|
+
* @template T
|
|
187
|
+
* @param {readonly T[]} array
|
|
188
|
+
* @param {number} [page=1]
|
|
189
|
+
* @param {number} [perPage=20]
|
|
190
|
+
* @returns {Page<T>}
|
|
191
|
+
*/
|
|
192
|
+
function paginate(array, page = 1, perPage = 20) {
|
|
193
|
+
const size = Math.max(1, Math.floor(perPage));
|
|
194
|
+
const pages = Math.max(1, Math.ceil(array.length / size));
|
|
195
|
+
const current = Math.min(Math.max(1, Math.floor(page) || 1), pages);
|
|
196
|
+
const start = (current - 1) * size;
|
|
197
|
+
return {
|
|
198
|
+
items: array.slice(start, start + size),
|
|
199
|
+
page: current,
|
|
200
|
+
perPage: size,
|
|
201
|
+
total: array.length,
|
|
202
|
+
pages,
|
|
203
|
+
hasPrev: current > 1,
|
|
204
|
+
hasNext: current < pages,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Removes duplicates. With an iteratee, items that give the same value are
|
|
210
|
+
* duplicates; the first one is kept.
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* arr.unique([1, 1, 2, 3]); // [1, 2, 3]
|
|
214
|
+
* arr.unique(users, "email"); // one user per email
|
|
215
|
+
* arr.unique(tags, (t) => t.toLowerCase());
|
|
216
|
+
*
|
|
217
|
+
* @template T
|
|
218
|
+
* @param {readonly T[]} array
|
|
219
|
+
* @param {Iteratee<T>} [iteratee]
|
|
220
|
+
* @returns {T[]}
|
|
221
|
+
*/
|
|
222
|
+
function unique(array, iteratee) {
|
|
223
|
+
if (iteratee === undefined) return [...new Set(array)];
|
|
224
|
+
const fn = _fn(iteratee);
|
|
225
|
+
const seen = new Set();
|
|
226
|
+
return array.filter((item, i) => {
|
|
227
|
+
const key = fn(item, i);
|
|
228
|
+
if (seen.has(key)) return false;
|
|
229
|
+
seen.add(key);
|
|
230
|
+
return true;
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Groups items by key.
|
|
236
|
+
*
|
|
237
|
+
* @example
|
|
238
|
+
* arr.groupBy(users, "role"); // { admin: [...], user: [...] }
|
|
239
|
+
* arr.groupBy([1, 2, 3, 4], (n) => (n % 2 ? "odd" : "even")); // { odd: [1, 3], even: [2, 4] }
|
|
240
|
+
*
|
|
241
|
+
* @template T
|
|
242
|
+
* @param {readonly T[]} array
|
|
243
|
+
* @param {Iteratee<T>} iteratee
|
|
244
|
+
* @returns {Record<string, T[]>}
|
|
245
|
+
*/
|
|
246
|
+
function groupBy(array, iteratee) {
|
|
247
|
+
const fn = _fn(iteratee);
|
|
248
|
+
/** @type {Record<PropertyKey, T[]>} */
|
|
249
|
+
const out = {};
|
|
250
|
+
array.forEach((item, i) => {
|
|
251
|
+
(out[_key(fn(item, i))] ??= []).push(item);
|
|
252
|
+
});
|
|
253
|
+
return out;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Indexes items by key. If two items share a key, the last one wins.
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* const byId = arr.keyBy(users, "id");
|
|
261
|
+
* byId[42]; // user 42
|
|
262
|
+
*
|
|
263
|
+
* @template T
|
|
264
|
+
* @param {readonly T[]} array
|
|
265
|
+
* @param {Iteratee<T>} iteratee
|
|
266
|
+
* @returns {Record<string, T>}
|
|
267
|
+
*/
|
|
268
|
+
function keyBy(array, iteratee) {
|
|
269
|
+
const fn = _fn(iteratee);
|
|
270
|
+
/** @type {Record<PropertyKey, T>} */
|
|
271
|
+
const out = {};
|
|
272
|
+
array.forEach((item, i) => {
|
|
273
|
+
out[_key(fn(item, i))] = item;
|
|
274
|
+
});
|
|
275
|
+
return out;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Builds a `Map` from an array. Unlike `keyBy()`, keys keep their type.
|
|
280
|
+
*
|
|
281
|
+
* @example
|
|
282
|
+
* arr.toMap(users, "id"); // Map<number, User>
|
|
283
|
+
* arr.toMap(users, "id", (u) => u.name); // Map<number, string>
|
|
284
|
+
*
|
|
285
|
+
* @template T
|
|
286
|
+
* @template [V=T]
|
|
287
|
+
* @param {readonly T[]} array
|
|
288
|
+
* @param {Iteratee<T>} key
|
|
289
|
+
* @param {(item: T, index: number) => V} [value] The value to store. Defaults to the item.
|
|
290
|
+
* @returns {Map<any, V>}
|
|
291
|
+
*/
|
|
292
|
+
function toMap(array, key, value) {
|
|
293
|
+
const keyFn = _fn(key);
|
|
294
|
+
/** @type {Map<any, V>} */
|
|
295
|
+
const map = new Map();
|
|
296
|
+
array.forEach((item, i) => map.set(keyFn(item, i), value ? value(item, i) : /** @type {V} */ (/** @type {unknown} */ (item))));
|
|
297
|
+
return map;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Counts items per key.
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* arr.countBy(["a", "b", "a"]); // { a: 2, b: 1 }
|
|
305
|
+
* arr.countBy(users, "country"); // { FR: 12, US: 7 }
|
|
306
|
+
*
|
|
307
|
+
* @template T
|
|
308
|
+
* @param {readonly T[]} array
|
|
309
|
+
* @param {Iteratee<T>} [iteratee] Defaults to the item itself.
|
|
310
|
+
* @returns {Record<string, number>}
|
|
311
|
+
*/
|
|
312
|
+
function countBy(array, iteratee) {
|
|
313
|
+
const fn = _fn(iteratee);
|
|
314
|
+
/** @type {Record<PropertyKey, number>} */
|
|
315
|
+
const out = {};
|
|
316
|
+
array.forEach((item, i) => {
|
|
317
|
+
const key = _key(fn(item, i));
|
|
318
|
+
out[key] = (out[key] ?? 0) + 1;
|
|
319
|
+
});
|
|
320
|
+
return out;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Counts the items that pass a test.
|
|
325
|
+
*
|
|
326
|
+
* @example
|
|
327
|
+
* arr.count(users, (u) => u.active);
|
|
328
|
+
*
|
|
329
|
+
* @template T
|
|
330
|
+
* @param {readonly T[]} array
|
|
331
|
+
* @param {(item: T, index: number) => unknown} predicate
|
|
332
|
+
* @returns {number}
|
|
333
|
+
*/
|
|
334
|
+
function count(array, predicate) {
|
|
335
|
+
let total = 0;
|
|
336
|
+
array.forEach((item, i) => {
|
|
337
|
+
if (predicate(item, i)) total++;
|
|
338
|
+
});
|
|
339
|
+
return total;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Splits items into those that pass a test and those that don't.
|
|
344
|
+
*
|
|
345
|
+
* @example
|
|
346
|
+
* const [adults, minors] = arr.partition(people, (p) => p.age >= 18);
|
|
347
|
+
*
|
|
348
|
+
* @template T
|
|
349
|
+
* @param {readonly T[]} array
|
|
350
|
+
* @param {(item: T, index: number) => unknown} predicate
|
|
351
|
+
* @returns {[T[], T[]]}
|
|
352
|
+
*/
|
|
353
|
+
function partition(array, predicate) {
|
|
354
|
+
/** @type {T[]} */
|
|
355
|
+
const pass = [];
|
|
356
|
+
/** @type {T[]} */
|
|
357
|
+
const fail = [];
|
|
358
|
+
array.forEach((item, i) => (predicate(item, i) ? pass : fail).push(item));
|
|
359
|
+
return [pass, fail];
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Takes one property from every item.
|
|
364
|
+
*
|
|
365
|
+
* @example
|
|
366
|
+
* arr.pluck(users, "email"); // ["ada@x.io", "bob@x.io"]
|
|
367
|
+
*
|
|
368
|
+
* @template T
|
|
369
|
+
* @template {keyof T} K
|
|
370
|
+
* @param {readonly T[]} array
|
|
371
|
+
* @param {K} key
|
|
372
|
+
* @returns {Array<T[K]>}
|
|
373
|
+
*/
|
|
374
|
+
function pluck(array, key) {
|
|
375
|
+
return array.map((item) => (item == null ? /** @type {T[K]} */ (/** @type {unknown} */ (undefined)) : item[key]));
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Sorts by one or more keys, each with its own direction if you like.
|
|
380
|
+
* The sort is stable and `null`/`undefined` always go last.
|
|
381
|
+
*
|
|
382
|
+
* @example
|
|
383
|
+
* arr.sortBy(users, "age"); // youngest first
|
|
384
|
+
* arr.sortBy(users, "age", "desc"); // oldest first
|
|
385
|
+
* arr.sortBy(users, [["age", "desc"], "lastName"]);
|
|
386
|
+
* arr.sortBy(files, "name", { natural: true }); // "file2" before "file10"
|
|
387
|
+
*
|
|
388
|
+
* @template T
|
|
389
|
+
* @param {readonly T[]} array
|
|
390
|
+
* @param {SortKey<T> | Array<SortKey<T>>} keys
|
|
391
|
+
* @param {"asc" | "desc" | SortByOptions} [order="asc"]
|
|
392
|
+
* @returns {T[]}
|
|
393
|
+
*/
|
|
394
|
+
function sortBy(array, keys, order = "asc") {
|
|
395
|
+
const options = typeof order === "string" ? { order } : order;
|
|
396
|
+
const defaultDir = options.order === "desc" ? -1 : 1;
|
|
397
|
+
const collator = options.natural ? new Intl.Collator(options.locale, { numeric: true, sensitivity: "base" }) : null;
|
|
398
|
+
const isTuple = (/** @type {unknown} */ k) => Array.isArray(k) && k.length === 2 && (k[1] === "asc" || k[1] === "desc");
|
|
399
|
+
const list = Array.isArray(keys) && !isTuple(keys) ? keys : [keys];
|
|
400
|
+
const comparators = list.map((k) => {
|
|
401
|
+
const [iteratee, dir] = isTuple(k) ? /** @type {[unknown, string]} */ (k) : [k, undefined];
|
|
402
|
+
return { fn: _fn(iteratee), dir: dir === undefined ? defaultDir : dir === "desc" ? -1 : 1 };
|
|
403
|
+
});
|
|
404
|
+
return array
|
|
405
|
+
.map((item, index) => ({ item, index }))
|
|
406
|
+
.sort((a, b) => {
|
|
407
|
+
for (const { fn, dir } of comparators) {
|
|
408
|
+
const va = fn(a.item, a.index);
|
|
409
|
+
const vb = fn(b.item, b.index);
|
|
410
|
+
const aNil = va === null || va === undefined;
|
|
411
|
+
const bNil = vb === null || vb === undefined;
|
|
412
|
+
if (aNil || bNil) {
|
|
413
|
+
if (aNil && bNil) continue;
|
|
414
|
+
return aNil ? 1 : -1;
|
|
415
|
+
}
|
|
416
|
+
let result = 0;
|
|
417
|
+
if (collator && typeof va === "string" && typeof vb === "string") result = collator.compare(va, vb);
|
|
418
|
+
else if (va < vb) result = -1;
|
|
419
|
+
else if (va > vb) result = 1;
|
|
420
|
+
if (result) return result * dir;
|
|
390
421
|
}
|
|
422
|
+
return a.index - b.index;
|
|
423
|
+
})
|
|
424
|
+
.map((entry) => entry.item);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* A shuffled copy (Fisher-Yates).
|
|
429
|
+
*
|
|
430
|
+
* @template T
|
|
431
|
+
* @param {readonly T[]} array
|
|
432
|
+
* @returns {T[]}
|
|
433
|
+
*/
|
|
434
|
+
function shuffle(array) {
|
|
435
|
+
const out = [...array];
|
|
436
|
+
for (let i = out.length - 1; i > 0; i--) {
|
|
437
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
438
|
+
[out[i], out[j]] = [out[j], out[i]];
|
|
439
|
+
}
|
|
440
|
+
return out;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* One random item, or `undefined` if the array is empty.
|
|
445
|
+
*
|
|
446
|
+
* @template T
|
|
447
|
+
* @param {readonly T[]} array
|
|
448
|
+
* @returns {T | undefined}
|
|
449
|
+
*/
|
|
450
|
+
function sample(array) {
|
|
451
|
+
return array.length ? array[Math.floor(Math.random() * array.length)] : undefined;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* `n` random items, never the same one twice.
|
|
456
|
+
*
|
|
457
|
+
* @example
|
|
458
|
+
* arr.sampleSize(players, 3);
|
|
459
|
+
*
|
|
460
|
+
* @template T
|
|
461
|
+
* @param {readonly T[]} array
|
|
462
|
+
* @param {number} n
|
|
463
|
+
* @returns {T[]}
|
|
464
|
+
*/
|
|
465
|
+
function sampleSize(array, n) {
|
|
466
|
+
return shuffle(array).slice(0, Math.max(0, n));
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Rotates items. Positive `n` moves items from the start to the end,
|
|
471
|
+
* negative `n` the other way.
|
|
472
|
+
*
|
|
473
|
+
* @example
|
|
474
|
+
* arr.rotate([1, 2, 3, 4], 1); // [2, 3, 4, 1]
|
|
475
|
+
* arr.rotate([1, 2, 3, 4], -1); // [4, 1, 2, 3]
|
|
476
|
+
*
|
|
477
|
+
* @template T
|
|
478
|
+
* @param {readonly T[]} array
|
|
479
|
+
* @param {number} [n=1]
|
|
480
|
+
* @returns {T[]}
|
|
481
|
+
*/
|
|
482
|
+
function rotate(array, n = 1) {
|
|
483
|
+
if (!array.length) return [];
|
|
484
|
+
const k = ((Math.trunc(n) % array.length) + array.length) % array.length;
|
|
485
|
+
return [...array.slice(k), ...array.slice(0, k)];
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Moves one item to another position. Negative indexes count from the end.
|
|
490
|
+
*
|
|
491
|
+
* @example
|
|
492
|
+
* arr.move(["a", "b", "c"], 0, 2); // ["b", "c", "a"]
|
|
493
|
+
*
|
|
494
|
+
* @template T
|
|
495
|
+
* @param {readonly T[]} array
|
|
496
|
+
* @param {number} from
|
|
497
|
+
* @param {number} to
|
|
498
|
+
* @returns {T[]}
|
|
499
|
+
*/
|
|
500
|
+
function move(array, from, to) {
|
|
501
|
+
const out = [...array];
|
|
502
|
+
const len = out.length;
|
|
503
|
+
const src = from < 0 ? len + from : from;
|
|
504
|
+
if (src < 0 || src >= len) return out;
|
|
505
|
+
const [item] = out.splice(src, 1);
|
|
506
|
+
out.splice(to < 0 ? len + to : to, 0, item);
|
|
507
|
+
return out;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Swaps two items.
|
|
512
|
+
*
|
|
513
|
+
* @example
|
|
514
|
+
* arr.swap(["a", "b", "c"], 0, 2); // ["c", "b", "a"]
|
|
515
|
+
*
|
|
516
|
+
* @template T
|
|
517
|
+
* @param {readonly T[]} array
|
|
518
|
+
* @param {number} i
|
|
519
|
+
* @param {number} j
|
|
520
|
+
* @returns {T[]}
|
|
521
|
+
*/
|
|
522
|
+
function swap(array, i, j) {
|
|
523
|
+
const out = [...array];
|
|
524
|
+
if (i in out && j in out) [out[i], out[j]] = [out[j], out[i]];
|
|
525
|
+
return out;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Items of `array` that appear in none of the others.
|
|
530
|
+
*
|
|
531
|
+
* @example
|
|
532
|
+
* arr.difference([1, 2, 3, 4], [2, 4]); // [1, 3]
|
|
533
|
+
*
|
|
534
|
+
* @template T
|
|
535
|
+
* @param {readonly T[]} array
|
|
536
|
+
* @param {...ReadonlyArray<T>} others
|
|
537
|
+
* @returns {T[]}
|
|
538
|
+
*/
|
|
539
|
+
function difference(array, ...others) {
|
|
540
|
+
const exclude = new Set(others.flat());
|
|
541
|
+
return array.filter((item) => !exclude.has(item));
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Items present in every array, without duplicates.
|
|
546
|
+
*
|
|
547
|
+
* @example
|
|
548
|
+
* arr.intersection([1, 2, 3], [2, 3, 4], [3, 2]); // [2, 3]
|
|
549
|
+
*
|
|
550
|
+
* @template T
|
|
551
|
+
* @param {...ReadonlyArray<T>} arrays
|
|
552
|
+
* @returns {T[]}
|
|
553
|
+
*/
|
|
554
|
+
function intersection(...arrays) {
|
|
555
|
+
if (!arrays.length) return [];
|
|
556
|
+
const [head, ...rest] = arrays;
|
|
557
|
+
const sets = rest.map((a) => new Set(a));
|
|
558
|
+
return [...new Set(head)].filter((item) => sets.every((set) => set.has(item)));
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Items present in any array, without duplicates.
|
|
563
|
+
*
|
|
564
|
+
* @example
|
|
565
|
+
* arr.union([1, 2], [2, 3], [3, 4]); // [1, 2, 3, 4]
|
|
566
|
+
*
|
|
567
|
+
* @template T
|
|
568
|
+
* @param {...ReadonlyArray<T>} arrays
|
|
569
|
+
* @returns {T[]}
|
|
570
|
+
*/
|
|
571
|
+
function union(...arrays) {
|
|
572
|
+
return [...new Set(arrays.flat())];
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* A copy without the given values. `NaN` works too.
|
|
577
|
+
*
|
|
578
|
+
* @example
|
|
579
|
+
* arr.without([1, 2, 3, 2], 2); // [1, 3]
|
|
580
|
+
*
|
|
581
|
+
* @template T
|
|
582
|
+
* @param {readonly T[]} array
|
|
583
|
+
* @param {...T} values
|
|
584
|
+
* @returns {T[]}
|
|
585
|
+
*/
|
|
586
|
+
function without(array, ...values) {
|
|
587
|
+
const exclude = new Set(values);
|
|
588
|
+
return array.filter((item) => !exclude.has(item));
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* A copy without the items that pass the test.
|
|
593
|
+
*
|
|
594
|
+
* @example
|
|
595
|
+
* arr.remove([1, 2, 3, 4], (n) => n % 2 === 0); // [1, 3]
|
|
596
|
+
*
|
|
597
|
+
* @template T
|
|
598
|
+
* @param {readonly T[]} array
|
|
599
|
+
* @param {(item: T, index: number) => unknown} predicate
|
|
600
|
+
* @returns {T[]}
|
|
601
|
+
*/
|
|
602
|
+
function remove(array, predicate) {
|
|
603
|
+
return array.filter((item, i) => !predicate(item, i));
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Removes falsy values (`false`, `null`, `undefined`, `0`, `""`, `NaN`).
|
|
608
|
+
*
|
|
609
|
+
* @example
|
|
610
|
+
* arr.compact([0, 1, false, 2, "", 3, null]); // [1, 2, 3]
|
|
611
|
+
*
|
|
612
|
+
* @template T
|
|
613
|
+
* @param {readonly T[]} array
|
|
614
|
+
* @returns {Array<Exclude<T, false | 0 | 0n | "" | null | undefined>>}
|
|
615
|
+
*/
|
|
616
|
+
function compact(array) {
|
|
617
|
+
return /** @type {any} */ (array.filter(Boolean));
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Adds the item if it's missing, removes it if it's there. Handy for
|
|
622
|
+
* multi-select state.
|
|
623
|
+
*
|
|
624
|
+
* @example
|
|
625
|
+
* arr.toggle(["a", "b"], "b"); // ["a"]
|
|
626
|
+
* arr.toggle(["a"], "b"); // ["a", "b"]
|
|
627
|
+
*
|
|
628
|
+
* @template T
|
|
629
|
+
* @param {readonly T[]} array
|
|
630
|
+
* @param {T} item
|
|
631
|
+
* @returns {T[]}
|
|
632
|
+
*/
|
|
633
|
+
function toggle(array, item) {
|
|
634
|
+
return array.includes(item) ? array.filter((x) => x !== item) : [...array, item];
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Replaces the item with the same identity, or appends it if there's none.
|
|
639
|
+
*
|
|
640
|
+
* @example
|
|
641
|
+
* arr.upsert(users, { id: 2, name: "Bob" }, "id");
|
|
642
|
+
*
|
|
643
|
+
* @template T
|
|
644
|
+
* @param {readonly T[]} array
|
|
645
|
+
* @param {T} item
|
|
646
|
+
* @param {Iteratee<T>} identity What makes two items "the same", like `"id"`.
|
|
647
|
+
* @returns {T[]}
|
|
648
|
+
*/
|
|
649
|
+
function upsert(array, item, identity) {
|
|
650
|
+
const fn = _fn(identity);
|
|
651
|
+
const id = fn(item, -1);
|
|
652
|
+
const index = array.findIndex((x, i) => fn(x, i) === id);
|
|
653
|
+
if (index === -1) return [...array, item];
|
|
654
|
+
const out = [...array];
|
|
655
|
+
out[index] = item;
|
|
656
|
+
return out;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Inserts items at an index. Negative indexes count from the end.
|
|
661
|
+
*
|
|
662
|
+
* @example
|
|
663
|
+
* arr.insert(["a", "d"], 1, "b", "c"); // ["a", "b", "c", "d"]
|
|
664
|
+
*
|
|
665
|
+
* @template T
|
|
666
|
+
* @param {readonly T[]} array
|
|
667
|
+
* @param {number} index
|
|
668
|
+
* @param {...T} items
|
|
669
|
+
* @returns {T[]}
|
|
670
|
+
*/
|
|
671
|
+
function insert(array, index, ...items) {
|
|
672
|
+
const out = [...array];
|
|
673
|
+
out.splice(index, 0, ...items);
|
|
674
|
+
return out;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* Flattens nested arrays, one level by default.
|
|
679
|
+
*
|
|
680
|
+
* @example
|
|
681
|
+
* arr.flatten([1, [2, [3, [4]]]]); // [1, 2, [3, [4]]]
|
|
682
|
+
* arr.flatten([1, [2, [3, [4]]]], Infinity); // [1, 2, 3, 4]
|
|
683
|
+
*
|
|
684
|
+
* @template T
|
|
685
|
+
* @template {number} [D=1]
|
|
686
|
+
* @param {readonly T[]} array
|
|
687
|
+
* @param {D} [depth]
|
|
688
|
+
* @returns {FlatArray<T[], D>[]}
|
|
689
|
+
*/
|
|
690
|
+
function flatten(array, depth) {
|
|
691
|
+
return /** @type {any} */ (array.flat(depth ?? 1));
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* Pairs items by position. Shorter arrays leave `undefined` holes.
|
|
696
|
+
*
|
|
697
|
+
* @example
|
|
698
|
+
* arr.zip(["a", "b"], [1, 2]); // [["a", 1], ["b", 2]]
|
|
699
|
+
*
|
|
700
|
+
* @template T
|
|
701
|
+
* @param {...ReadonlyArray<T>} arrays
|
|
702
|
+
* @returns {Array<Array<T | undefined>>}
|
|
703
|
+
*/
|
|
704
|
+
function zip(...arrays) {
|
|
705
|
+
const size = Math.max(0, ...arrays.map((a) => a.length));
|
|
706
|
+
return Array.from({ length: size }, (_, i) => arrays.map((a) => a[i]));
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* The opposite of `zip()`: rows become columns.
|
|
711
|
+
*
|
|
712
|
+
* @example
|
|
713
|
+
* arr.unzip([["a", 1], ["b", 2]]); // [["a", "b"], [1, 2]]
|
|
714
|
+
*
|
|
715
|
+
* @template T
|
|
716
|
+
* @param {ReadonlyArray<ReadonlyArray<T>>} tuples
|
|
717
|
+
* @returns {Array<Array<T | undefined>>}
|
|
718
|
+
*/
|
|
719
|
+
function unzip(tuples) {
|
|
720
|
+
return zip(...tuples);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* Takes items from each array in turn, then appends the rest.
|
|
725
|
+
*
|
|
726
|
+
* @example
|
|
727
|
+
* arr.interleave([1, 3, 5], [2, 4]); // [1, 2, 3, 4, 5]
|
|
728
|
+
*
|
|
729
|
+
* @template T
|
|
730
|
+
* @param {...ReadonlyArray<T>} arrays
|
|
731
|
+
* @returns {T[]}
|
|
732
|
+
*/
|
|
733
|
+
function interleave(...arrays) {
|
|
734
|
+
/** @type {T[]} */
|
|
735
|
+
const out = [];
|
|
736
|
+
const size = Math.max(0, ...arrays.map((a) => a.length));
|
|
737
|
+
for (let i = 0; i < size; i++) for (const a of arrays) if (i < a.length) out.push(a[i]);
|
|
738
|
+
return out;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Every combination of one item from each array.
|
|
743
|
+
*
|
|
744
|
+
* @example
|
|
745
|
+
* arr.cartesian(["S", "M"], ["red", "blue"]);
|
|
746
|
+
* // [["S", "red"], ["S", "blue"], ["M", "red"], ["M", "blue"]]
|
|
747
|
+
*
|
|
748
|
+
* @template T
|
|
749
|
+
* @param {...ReadonlyArray<T>} arrays
|
|
750
|
+
* @returns {T[][]}
|
|
751
|
+
*/
|
|
752
|
+
function cartesian(...arrays) {
|
|
753
|
+
if (!arrays.length) return [];
|
|
754
|
+
return arrays.reduce((acc, list) => acc.flatMap((combo) => list.map((item) => [...combo, item])), /** @type {T[][]} */ ([[]]));
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* An array of `n` items, from a value or a function of the index.
|
|
759
|
+
*
|
|
760
|
+
* @example
|
|
761
|
+
* arr.times(3, "x"); // ["x", "x", "x"]
|
|
762
|
+
* arr.times(3, (i) => i * 2); // [0, 2, 4]
|
|
763
|
+
*
|
|
764
|
+
* @template T
|
|
765
|
+
* @param {number} n
|
|
766
|
+
* @param {T | ((index: number) => T)} [value]
|
|
767
|
+
* @returns {T[]}
|
|
768
|
+
*/
|
|
769
|
+
function times(n, value) {
|
|
770
|
+
const size = Math.max(0, Math.floor(n));
|
|
771
|
+
const fn = typeof value === "function" ? /** @type {(index: number) => T} */ (value) : () => /** @type {T} */ (value);
|
|
772
|
+
return Array.from({ length: size }, (_, i) => fn(i));
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Adds up a number from each item. Non-numbers count as 0.
|
|
777
|
+
*
|
|
778
|
+
* @example
|
|
779
|
+
* arr.sumBy(cart, "price");
|
|
780
|
+
* arr.sumBy(cart, (item) => item.price * item.quantity);
|
|
781
|
+
*
|
|
782
|
+
* @template T
|
|
783
|
+
* @param {readonly T[]} array
|
|
784
|
+
* @param {Iteratee<T>} [iteratee]
|
|
785
|
+
* @returns {number}
|
|
786
|
+
*/
|
|
787
|
+
function sumBy(array, iteratee) {
|
|
788
|
+
const fn = _fn(iteratee);
|
|
789
|
+
return array.reduce((acc, item, i) => acc + (Number(fn(item, i)) || 0), 0);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* The average of a number from each item, or `0` for an empty array.
|
|
794
|
+
*
|
|
795
|
+
* @example
|
|
796
|
+
* arr.averageBy(reviews, "rating"); // 4.3
|
|
797
|
+
*
|
|
798
|
+
* @template T
|
|
799
|
+
* @param {readonly T[]} array
|
|
800
|
+
* @param {Iteratee<T>} [iteratee]
|
|
801
|
+
* @returns {number}
|
|
802
|
+
*/
|
|
803
|
+
function averageBy(array, iteratee) {
|
|
804
|
+
return array.length ? sumBy(array, iteratee) / array.length : 0;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* The item with the highest value.
|
|
809
|
+
*
|
|
810
|
+
* @example
|
|
811
|
+
* arr.maxBy(players, "score");
|
|
812
|
+
*
|
|
813
|
+
* @template T
|
|
814
|
+
* @param {readonly T[]} array
|
|
815
|
+
* @param {Iteratee<T>} [iteratee]
|
|
816
|
+
* @returns {T | undefined}
|
|
817
|
+
*/
|
|
818
|
+
function maxBy(array, iteratee) {
|
|
819
|
+
const fn = _fn(iteratee);
|
|
820
|
+
/** @type {T | undefined} */
|
|
821
|
+
let best;
|
|
822
|
+
/** @type {any} */
|
|
823
|
+
let bestValue;
|
|
824
|
+
array.forEach((item, i) => {
|
|
825
|
+
const v = fn(item, i);
|
|
826
|
+
if (v !== null && v !== undefined && (bestValue === undefined || v > bestValue)) {
|
|
827
|
+
bestValue = v;
|
|
828
|
+
best = item;
|
|
391
829
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
830
|
+
});
|
|
831
|
+
return best;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* The item with the lowest value.
|
|
836
|
+
*
|
|
837
|
+
* @example
|
|
838
|
+
* arr.minBy(products, "price");
|
|
839
|
+
*
|
|
840
|
+
* @template T
|
|
841
|
+
* @param {readonly T[]} array
|
|
842
|
+
* @param {Iteratee<T>} [iteratee]
|
|
843
|
+
* @returns {T | undefined}
|
|
844
|
+
*/
|
|
845
|
+
function minBy(array, iteratee) {
|
|
846
|
+
const fn = _fn(iteratee);
|
|
847
|
+
/** @type {T | undefined} */
|
|
848
|
+
let best;
|
|
849
|
+
/** @type {any} */
|
|
850
|
+
let bestValue;
|
|
851
|
+
array.forEach((item, i) => {
|
|
852
|
+
const v = fn(item, i);
|
|
853
|
+
if (v !== null && v !== undefined && (bestValue === undefined || v < bestValue)) {
|
|
854
|
+
bestValue = v;
|
|
855
|
+
best = item;
|
|
412
856
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
857
|
+
});
|
|
858
|
+
return best;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
module.exports = {
|
|
862
|
+
chunk,
|
|
863
|
+
windows,
|
|
864
|
+
first,
|
|
865
|
+
last,
|
|
866
|
+
paginate,
|
|
867
|
+
unique,
|
|
868
|
+
groupBy,
|
|
869
|
+
keyBy,
|
|
870
|
+
toMap,
|
|
871
|
+
countBy,
|
|
872
|
+
count,
|
|
873
|
+
partition,
|
|
874
|
+
pluck,
|
|
875
|
+
sortBy,
|
|
876
|
+
shuffle,
|
|
877
|
+
sample,
|
|
878
|
+
sampleSize,
|
|
879
|
+
rotate,
|
|
880
|
+
move,
|
|
881
|
+
swap,
|
|
882
|
+
difference,
|
|
883
|
+
intersection,
|
|
884
|
+
union,
|
|
885
|
+
without,
|
|
886
|
+
remove,
|
|
887
|
+
compact,
|
|
888
|
+
toggle,
|
|
889
|
+
upsert,
|
|
890
|
+
insert,
|
|
891
|
+
flatten,
|
|
892
|
+
zip,
|
|
893
|
+
unzip,
|
|
894
|
+
interleave,
|
|
895
|
+
cartesian,
|
|
896
|
+
times,
|
|
897
|
+
sumBy,
|
|
898
|
+
averageBy,
|
|
899
|
+
maxBy,
|
|
900
|
+
minBy,
|
|
433
901
|
};
|