@stacksjs/arrays 0.74.32 → 0.74.33

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/dist/arr.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './contains';
2
2
  export * from './helpers';
3
3
  export * from './math';
4
+ export * from './transform';
package/dist/arr.js CHANGED
@@ -1 +1 @@
1
- export*from"./contains";export*from"./helpers";export*from"./math";
1
+ export*from"./contains";export*from"./helpers";export*from"./math";export*from"./transform";
@@ -0,0 +1,383 @@
1
+ /**
2
+ * Shaping an array: slicing it, grouping it, pairing it with another.
3
+ *
4
+ * `helpers.ts` holds the conversions and mutations, `math.ts` the statistics,
5
+ * `contains.ts` the membership questions. These are the operations that were
6
+ * reached for often enough that applications kept writing them inline, and
7
+ * that a request to add Remeda (stacksjs/stacks#412) was really asking for -
8
+ * answered by implementing them here rather than by taking a second utility
9
+ * library and a second idiom for operations the framework already half had.
10
+ *
11
+ * Every function takes the array first and returns a new one. Nothing mutates
12
+ * its input, and nothing throws on an empty array: an empty input produces an
13
+ * empty output, or `undefined` where a single element was asked for.
14
+ */
15
+ /**
16
+ * Split an array into consecutive groups of at most `size`.
17
+ *
18
+ * The last group is short when the length does not divide evenly, which is the
19
+ * behaviour that makes this useful for batching - a caller that wants only
20
+ * whole groups can drop it.
21
+ *
22
+ * @category Array
23
+ * @example
24
+ * ```ts
25
+ * chunk([1, 2, 3, 4, 5], 2) // [[1, 2], [3, 4], [5]]
26
+ * chunk([], 3) // []
27
+ * ```
28
+ */
29
+ export declare function chunk<T>(array: readonly T[], size: number): T[][];
30
+ /**
31
+ * Drop `null` and `undefined`, and say so in the type.
32
+ *
33
+ * Deliberately not "drop every falsy value", which is the other common reading:
34
+ * that one silently removes `0`, `''` and `false`, which are ordinary data far
35
+ * more often than they are mistakes, and it is `array.filter(Boolean)` when it
36
+ * really is wanted. Narrowing to `NonNullable<T>` is the part a hand-written
37
+ * filter does not give you.
38
+ *
39
+ * @category Array
40
+ * @example
41
+ * ```ts
42
+ * compact([1, null, 2, undefined, 3]) // [1, 2, 3]
43
+ * compact([0, '', false, null]) // [0, '', false]
44
+ * ```
45
+ */
46
+ export declare function compact<T>(array: readonly T[]): NonNullable<T>[];
47
+ /**
48
+ * The first element, or `undefined` when there is none.
49
+ *
50
+ * The counterpart to the existing `last`. `array[0]` is the same thing until
51
+ * `noUncheckedIndexedAccess` is on, at which point this is the version whose
52
+ * type admits that an empty array has no first element.
53
+ *
54
+ * @category Array
55
+ * @example
56
+ * ```ts
57
+ * first([1, 2, 3]) // 1
58
+ * first([]) // undefined
59
+ * ```
60
+ */
61
+ export declare function first<T>(array: readonly T[]): T | undefined;
62
+ /**
63
+ * Everything after the first element.
64
+ *
65
+ * @category Array
66
+ * @example
67
+ * ```ts
68
+ * tail([1, 2, 3]) // [2, 3]
69
+ * tail([1]) // []
70
+ * tail([]) // []
71
+ * ```
72
+ */
73
+ export declare function tail<T>(array: readonly T[]): T[];
74
+ /**
75
+ * The first `count` elements.
76
+ *
77
+ * A negative or zero `count` takes nothing, rather than counting from the end -
78
+ * `slice`'s negative-index behaviour is a trap here, because `take(xs, -2)`
79
+ * reads as "take two" and would silently return everything but the last two.
80
+ *
81
+ * @category Array
82
+ * @example
83
+ * ```ts
84
+ * take([1, 2, 3, 4], 2) // [1, 2]
85
+ * take([1, 2, 3, 4], 10) // [1, 2, 3, 4]
86
+ * take([1, 2, 3, 4], -2) // []
87
+ * ```
88
+ */
89
+ export declare function take<T>(array: readonly T[], count: number): T[];
90
+ /**
91
+ * The last `count` elements, in their original order.
92
+ *
93
+ * @category Array
94
+ * @example
95
+ * ```ts
96
+ * takeLast([1, 2, 3, 4], 2) // [3, 4]
97
+ * takeLast([1, 2], 0) // []
98
+ * ```
99
+ */
100
+ export declare function takeLast<T>(array: readonly T[], count: number): T[];
101
+ /**
102
+ * Elements from the start, stopping at the first that fails `predicate`.
103
+ *
104
+ * Stops rather than filters: a later element that would pass is not included,
105
+ * which is the difference between this and `filter` and the whole reason to
106
+ * reach for it.
107
+ *
108
+ * @category Array
109
+ * @example
110
+ * ```ts
111
+ * takeWhile([1, 2, 5, 1], n => n < 3) // [1, 2]
112
+ * ```
113
+ */
114
+ export declare function takeWhile<T>(array: readonly T[], predicate: (value: T, index: number) => boolean): T[];
115
+ /**
116
+ * Elements from the end, stopping at the last that fails `predicate`, returned
117
+ * in their original order.
118
+ *
119
+ * @category Array
120
+ * @example
121
+ * ```ts
122
+ * takeLastWhile([1, 5, 2, 1], n => n < 3) // [2, 1]
123
+ * ```
124
+ */
125
+ export declare function takeLastWhile<T>(array: readonly T[], predicate: (value: T, index: number) => boolean): T[];
126
+ /**
127
+ * Everything after the first `count` elements.
128
+ *
129
+ * @category Array
130
+ * @example
131
+ * ```ts
132
+ * drop([1, 2, 3, 4], 2) // [3, 4]
133
+ * drop([1, 2, 3, 4], -1) // [1, 2, 3, 4]
134
+ * ```
135
+ */
136
+ export declare function drop<T>(array: readonly T[], count: number): T[];
137
+ /**
138
+ * Everything except the last `count` elements.
139
+ *
140
+ * @category Array
141
+ * @example
142
+ * ```ts
143
+ * dropLast([1, 2, 3, 4], 2) // [1, 2]
144
+ * ```
145
+ */
146
+ export declare function dropLast<T>(array: readonly T[], count: number): T[];
147
+ /**
148
+ * Everything from the first element that fails `predicate` onwards.
149
+ *
150
+ * @category Array
151
+ * @example
152
+ * ```ts
153
+ * dropWhile([1, 2, 5, 1], n => n < 3) // [5, 1]
154
+ * ```
155
+ */
156
+ export declare function dropWhile<T>(array: readonly T[], predicate: (value: T, index: number) => boolean): T[];
157
+ /**
158
+ * Everything up to the last element that fails `predicate`.
159
+ *
160
+ * @category Array
161
+ * @example
162
+ * ```ts
163
+ * dropLastWhile([1, 5, 2, 1], n => n < 3) // [1, 5]
164
+ * ```
165
+ */
166
+ export declare function dropLastWhile<T>(array: readonly T[], predicate: (value: T, index: number) => boolean): T[];
167
+ /**
168
+ * Group elements under the key each one produces.
169
+ *
170
+ * Insertion order is preserved within every group, and a key of `undefined`
171
+ * drops the element rather than creating an `"undefined"` bucket - which is
172
+ * what lets this double as a filter-and-group in one pass.
173
+ *
174
+ * @category Array
175
+ * @example
176
+ * ```ts
177
+ * groupBy([1, 2, 3, 4], n => n % 2 ? 'odd' : 'even')
178
+ * // { odd: [1, 3], even: [2, 4] }
179
+ * ```
180
+ */
181
+ export declare function groupBy<T, K extends PropertyKey>(array: readonly T[], key: (value: T, index: number) => K | undefined): Partial<Record<K, T[]>>;
182
+ /**
183
+ * Index elements by the key each one produces, last one winning.
184
+ *
185
+ * Last-wins rather than first-wins because this is normally used to build a
186
+ * lookup from a list that is already in priority order, and because it matches
187
+ * what an assignment loop would do.
188
+ *
189
+ * @category Array
190
+ * @example
191
+ * ```ts
192
+ * keyBy([{ id: 'a', n: 1 }, { id: 'b', n: 2 }], user => user.id)
193
+ * // { a: { id: 'a', n: 1 }, b: { id: 'b', n: 2 } }
194
+ * ```
195
+ */
196
+ export declare function keyBy<T, K extends PropertyKey>(array: readonly T[], key: (value: T, index: number) => K | undefined): Partial<Record<K, T>>;
197
+ /**
198
+ * How many elements produce each key.
199
+ *
200
+ * @category Array
201
+ * @example
202
+ * ```ts
203
+ * countBy(['a', 'bb', 'c'], word => word.length) // { 1: 2, 2: 1 }
204
+ * ```
205
+ */
206
+ export declare function countBy<T, K extends PropertyKey>(array: readonly T[], key: (value: T, index: number) => K | undefined): Partial<Record<K, number>>;
207
+ /**
208
+ * Sort by one or more selectors, without mutating the input.
209
+ *
210
+ * `Array.prototype.sort` sorts in place and compares stringified values, so
211
+ * `[10, 9].sort()` is `[10, 9]`. This copies first and compares the selected
212
+ * values by their own type, falling through to the next selector on a tie -
213
+ * which is what makes "by role, then by name" a single call.
214
+ *
215
+ * @category Array
216
+ * @example
217
+ * ```ts
218
+ * sortBy(users, user => user.age)
219
+ * sortBy(users, { by: user => user.age, order: 'desc' }, user => user.name)
220
+ * ```
221
+ */
222
+ export declare function sortBy<T>(array: readonly T[], ...selectors: SortSelector<T>[]): T[];
223
+ /**
224
+ * Sum the number each element produces.
225
+ *
226
+ * `0` for an empty array, which is the identity for addition and lets a caller
227
+ * total a filtered list without checking whether anything survived.
228
+ *
229
+ * @category Array
230
+ * @example
231
+ * ```ts
232
+ * sumBy(orders, order => order.total) // 41.5
233
+ * ```
234
+ */
235
+ export declare function sumBy<T>(array: readonly T[], value: (item: T, index: number) => number): number;
236
+ /**
237
+ * The mean of the number each element produces, or `undefined` when empty.
238
+ *
239
+ * `undefined` rather than `NaN` or a throw: an empty list has no mean, and a
240
+ * caller that has to handle that is better served by a type that says so than
241
+ * by a number that poisons every later arithmetic.
242
+ *
243
+ * @category Array
244
+ * @example
245
+ * ```ts
246
+ * meanBy(orders, order => order.total) // 20.75
247
+ * meanBy([], order => order.total) // undefined
248
+ * ```
249
+ */
250
+ export declare function meanBy<T>(array: readonly T[], value: (item: T, index: number) => number): number | undefined;
251
+ /**
252
+ * The element with the largest selected value, or `undefined` when empty.
253
+ *
254
+ * Returns the *element*, not the value - which is the difference from `max`
255
+ * and the reason to reach for it. Ties go to the first, so the result is stable
256
+ * for an already-ordered input.
257
+ *
258
+ * @category Array
259
+ * @example
260
+ * ```ts
261
+ * maxBy(users, user => user.age) // the oldest user
262
+ * ```
263
+ */
264
+ export declare function maxBy<T>(array: readonly T[], value: (item: T, index: number) => number): T | undefined;
265
+ /**
266
+ * The element with the smallest selected value, or `undefined` when empty.
267
+ *
268
+ * @category Array
269
+ * @example
270
+ * ```ts
271
+ * minBy(users, user => user.age) // the youngest user
272
+ * ```
273
+ */
274
+ export declare function minBy<T>(array: readonly T[], value: (item: T, index: number) => number): T | undefined;
275
+ /**
276
+ * Pair up two arrays, stopping at the shorter one.
277
+ *
278
+ * @category Array
279
+ * @example
280
+ * ```ts
281
+ * zip([1, 2, 3], ['a', 'b']) // [[1, 'a'], [2, 'b']]
282
+ * ```
283
+ */
284
+ export declare function zip<A, B>(first: readonly A[], second: readonly B[]): Array<[A, B]>;
285
+ /**
286
+ * Combine two arrays element-wise, stopping at the shorter one.
287
+ *
288
+ * @category Array
289
+ * @example
290
+ * ```ts
291
+ * zipWith([1, 2], [10, 20], (a, b) => a + b) // [11, 22]
292
+ * ```
293
+ */
294
+ export declare function zipWith<A, B, R>(first: readonly A[], second: readonly B[], combine: (a: A, b: B, index: number) => R): R[];
295
+ /**
296
+ * The inverse of {@link zip}: pairs back into two arrays.
297
+ *
298
+ * @category Array
299
+ * @example
300
+ * ```ts
301
+ * unzip([[1, 'a'], [2, 'b']]) // [[1, 2], ['a', 'b']]
302
+ * ```
303
+ */
304
+ export declare function unzip<A, B>(pairs: ReadonlyArray<readonly [A, B]>): [A[], B[]];
305
+ /**
306
+ * Elements of `array` that are not in `other`, keeping duplicates.
307
+ *
308
+ * Membership is `Set` identity - `SameValueZero`, so `NaN` matches `NaN` and
309
+ * `0` matches `-0`, and two structurally equal objects do not match. A caller
310
+ * comparing objects wants a selector, not this.
311
+ *
312
+ * @category Array
313
+ * @example
314
+ * ```ts
315
+ * difference([1, 2, 2, 3], [2]) // [1, 3]
316
+ * ```
317
+ */
318
+ export declare function difference<T>(array: readonly T[], other: readonly T[]): T[];
319
+ /**
320
+ * Elements present in both, in the order of the first, without duplicates.
321
+ *
322
+ * @category Array
323
+ * @example
324
+ * ```ts
325
+ * intersection([1, 2, 2, 3], [2, 3, 4]) // [2, 3]
326
+ * ```
327
+ */
328
+ export declare function intersection<T>(array: readonly T[], other: readonly T[]): T[];
329
+ /**
330
+ * Every element across the given arrays, in first-seen order, without
331
+ * duplicates.
332
+ *
333
+ * @category Array
334
+ * @example
335
+ * ```ts
336
+ * union([1, 2], [2, 3], [3, 4]) // [1, 2, 3, 4]
337
+ * ```
338
+ */
339
+ export declare function union<T>(...arrays: ReadonlyArray<readonly T[]>): T[];
340
+ /**
341
+ * Cut an array in two at `index`.
342
+ *
343
+ * A negative index counts from the end, matching `slice`; out of range in
344
+ * either direction gives one empty half rather than throwing.
345
+ *
346
+ * @category Array
347
+ * @example
348
+ * ```ts
349
+ * splitAt([1, 2, 3, 4], 2) // [[1, 2], [3, 4]]
350
+ * splitAt([1, 2, 3, 4], -1) // [[1, 2, 3], [4]]
351
+ * ```
352
+ */
353
+ export declare function splitAt<T>(array: readonly T[], index: number): [T[], T[]];
354
+ /**
355
+ * Cut an array in two at the first element that satisfies `predicate`, which
356
+ * begins the second half.
357
+ *
358
+ * When nothing satisfies it, everything lands in the first half - so the two
359
+ * halves always concatenate back to the input.
360
+ *
361
+ * @category Array
362
+ * @example
363
+ * ```ts
364
+ * splitWhen([1, 2, 3, 1], n => n > 2) // [[1, 2], [3, 1]]
365
+ * splitWhen([1, 2], n => n > 9) // [[1, 2], []]
366
+ * ```
367
+ */
368
+ export declare function splitWhen<T>(array: readonly T[], predicate: (value: T, index: number) => boolean): [T[], T[]];
369
+ /**
370
+ * Build an array by calling `create` with each index.
371
+ *
372
+ * @category Array
373
+ * @example
374
+ * ```ts
375
+ * times(3, index => index * 2) // [0, 2, 4]
376
+ * ```
377
+ */
378
+ export declare function times<T>(count: number, create: (index: number) => T): T[];
379
+ /** How one `sortBy` selector orders: the value to compare, and the direction. */
380
+ export type SortSelector<T> = ((value: T) => number | string | bigint | boolean | Date) | {
381
+ by: (value: T) => number | string | bigint | boolean | Date
382
+ order?: 'asc' | 'desc'
383
+ }
@@ -0,0 +1 @@
1
+ export function chunk(array,size){if(!Number.isInteger(size)||size<1)throw TypeError(`chunk: size must be a positive integer, got ${size}`);const chunks=[];for(let index=0;index<array.length;index+=size)chunks.push(array.slice(index,index+size));return chunks}export function compact(array){return array.filter((value)=>value!==null&&value!==void 0)}export function first(array){return array[0]}export function tail(array){return array.slice(1)}export function take(array,count){return count<=0?[]:array.slice(0,count)}export function takeLast(array,count){return count<=0?[]:array.slice(Math.max(0,array.length-count))}export function takeWhile(array,predicate){const result=[];for(const[index,value]of array.entries()){if(!predicate(value,index))break;result.push(value)}return result}export function takeLastWhile(array,predicate){for(let index=array.length-1;index>=0;index--)if(!predicate(array[index],index))return array.slice(index+1);return array.slice()}export function drop(array,count){return count<=0?array.slice():array.slice(count)}export function dropLast(array,count){return count<=0?array.slice():array.slice(0,Math.max(0,array.length-count))}export function dropWhile(array,predicate){for(const[index,value]of array.entries())if(!predicate(value,index))return array.slice(index);return[]}export function dropLastWhile(array,predicate){for(let index=array.length-1;index>=0;index--)if(!predicate(array[index],index))return array.slice(0,index+1);return[]}export function groupBy(array,key){const groups={};for(const[index,value]of array.entries()){const group=key(value,index);if(group===void 0)continue;(groups[group]??=[]).push(value)}return groups}export function keyBy(array,key){const indexed={};for(const[index,value]of array.entries()){const at=key(value,index);if(at!==void 0)indexed[at]=value}return indexed}export function countBy(array,key){const counts={};for(const[index,value]of array.entries()){const at=key(value,index);if(at!==void 0)counts[at]=(counts[at]??0)+1}return counts}function compareSelected(a,b){if(a instanceof Date&&b instanceof Date)return a.getTime()-b.getTime();if(typeof a==="string"&&typeof b==="string")return a<b?-1:a>b?1:0;if(typeof a==="boolean"&&typeof b==="boolean")return Number(a)-Number(b);if(typeof a==="bigint"&&typeof b==="bigint")return a<b?-1:a>b?1:0;return Number(a)-Number(b)}export function sortBy(array,...selectors){const normalized=selectors.map((selector)=>typeof selector==="function"?{by:selector,order:"asc"}:{order:"asc",...selector});return array.slice().sort((left,right)=>{for(const{by,order}of normalized){const comparison=compareSelected(by(left),by(right));if(comparison!==0)return order==="desc"?-comparison:comparison}return 0})}export function sumBy(array,value){let total=0;for(const[index,item]of array.entries())total+=value(item,index);return total}export function meanBy(array,value){return array.length===0?void 0:sumBy(array,value)/array.length}export function maxBy(array,value){let best,bestValue=Number.NEGATIVE_INFINITY;for(const[index,item]of array.entries()){const candidate=value(item,index);if(candidate>bestValue){bestValue=candidate;best=item}}return best}export function minBy(array,value){let best,bestValue=Number.POSITIVE_INFINITY;for(const[index,item]of array.entries()){const candidate=value(item,index);if(candidate<bestValue){bestValue=candidate;best=item}}return best}export function zip(first,second){const length=Math.min(first.length,second.length),pairs=[];for(let index=0;index<length;index++)pairs.push([first[index],second[index]]);return pairs}export function zipWith(first,second,combine){const length=Math.min(first.length,second.length),combined=[];for(let index=0;index<length;index++)combined.push(combine(first[index],second[index],index));return combined}export function unzip(pairs){const first=[],second=[];for(const[a,b]of pairs){first.push(a);second.push(b)}return[first,second]}export function difference(array,other){const exclude=new Set(other);return array.filter((value)=>!exclude.has(value))}export function intersection(array,other){const include=new Set(other),seen=new Set;return array.filter((value)=>{if(!include.has(value)||seen.has(value))return!1;seen.add(value);return!0})}export function union(...arrays){return[...new Set(arrays.flat())]}export function splitAt(array,index){return[array.slice(0,index),array.slice(index)]}export function splitWhen(array,predicate){for(const[index,value]of array.entries())if(predicate(value,index))return[array.slice(0,index),array.slice(index)];return[array.slice(),[]]}export function times(count,create){if(!Number.isInteger(count)||count<0)throw TypeError(`times: count must be a non-negative integer, got ${count}`);const result=Array.from({length:count});for(let index=0;index<count;index++)result[index]=create(index);return result}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/arrays",
3
3
  "type": "module",
4
- "version": "0.74.32",
4
+ "version": "0.74.33",
5
5
  "description": "The Stacks array utilities.",
6
6
  "author": "Chris Breuer",
7
7
  "contributors": [