ts-data-forge 1.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.
Files changed (143) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +534 -0
  3. package/package.json +101 -0
  4. package/src/array/array-utils-creation.test.mts +443 -0
  5. package/src/array/array-utils-modification.test.mts +197 -0
  6. package/src/array/array-utils-overload-type-error.test.mts +149 -0
  7. package/src/array/array-utils-reducing-value.test.mts +425 -0
  8. package/src/array/array-utils-search.test.mts +169 -0
  9. package/src/array/array-utils-set-op.test.mts +335 -0
  10. package/src/array/array-utils-slice-clamped.test.mts +113 -0
  11. package/src/array/array-utils-slicing.test.mts +316 -0
  12. package/src/array/array-utils-transformation.test.mts +790 -0
  13. package/src/array/array-utils-validation.test.mts +492 -0
  14. package/src/array/array-utils.mts +4000 -0
  15. package/src/array/array.test.mts +146 -0
  16. package/src/array/index.mts +2 -0
  17. package/src/array/tuple-utils.mts +519 -0
  18. package/src/array/tuple-utils.test.mts +518 -0
  19. package/src/collections/imap-mapped.mts +801 -0
  20. package/src/collections/imap-mapped.test.mts +860 -0
  21. package/src/collections/imap.mts +651 -0
  22. package/src/collections/imap.test.mts +932 -0
  23. package/src/collections/index.mts +6 -0
  24. package/src/collections/iset-mapped.mts +889 -0
  25. package/src/collections/iset-mapped.test.mts +1187 -0
  26. package/src/collections/iset.mts +682 -0
  27. package/src/collections/iset.test.mts +1084 -0
  28. package/src/collections/queue.mts +390 -0
  29. package/src/collections/queue.test.mts +282 -0
  30. package/src/collections/stack.mts +423 -0
  31. package/src/collections/stack.test.mts +225 -0
  32. package/src/expect-type.mts +206 -0
  33. package/src/functional/index.mts +4 -0
  34. package/src/functional/match.mts +300 -0
  35. package/src/functional/match.test.mts +177 -0
  36. package/src/functional/optional.mts +733 -0
  37. package/src/functional/optional.test.mts +619 -0
  38. package/src/functional/pipe.mts +212 -0
  39. package/src/functional/pipe.test.mts +85 -0
  40. package/src/functional/result.mts +1134 -0
  41. package/src/functional/result.test.mts +777 -0
  42. package/src/globals.d.mts +38 -0
  43. package/src/guard/has-key.mts +119 -0
  44. package/src/guard/has-key.test.mts +219 -0
  45. package/src/guard/index.mts +7 -0
  46. package/src/guard/is-non-empty-string.mts +108 -0
  47. package/src/guard/is-non-empty-string.test.mts +91 -0
  48. package/src/guard/is-non-null-object.mts +106 -0
  49. package/src/guard/is-non-null-object.test.mts +90 -0
  50. package/src/guard/is-primitive.mts +165 -0
  51. package/src/guard/is-primitive.test.mts +102 -0
  52. package/src/guard/is-record.mts +153 -0
  53. package/src/guard/is-record.test.mts +112 -0
  54. package/src/guard/is-type.mts +450 -0
  55. package/src/guard/is-type.test.mts +496 -0
  56. package/src/guard/key-is-in.mts +163 -0
  57. package/src/guard/key-is-in.test.mts +19 -0
  58. package/src/index.mts +10 -0
  59. package/src/iterator/index.mts +1 -0
  60. package/src/iterator/range.mts +120 -0
  61. package/src/iterator/range.test.mts +33 -0
  62. package/src/json/index.mts +1 -0
  63. package/src/json/json.mts +711 -0
  64. package/src/json/json.test.mts +628 -0
  65. package/src/number/branded-types/finite-number.mts +354 -0
  66. package/src/number/branded-types/finite-number.test.mts +135 -0
  67. package/src/number/branded-types/index.mts +26 -0
  68. package/src/number/branded-types/int.mts +278 -0
  69. package/src/number/branded-types/int.test.mts +140 -0
  70. package/src/number/branded-types/int16.mts +192 -0
  71. package/src/number/branded-types/int16.test.mts +170 -0
  72. package/src/number/branded-types/int32.mts +193 -0
  73. package/src/number/branded-types/int32.test.mts +170 -0
  74. package/src/number/branded-types/non-negative-finite-number.mts +223 -0
  75. package/src/number/branded-types/non-negative-finite-number.test.mts +188 -0
  76. package/src/number/branded-types/non-negative-int16.mts +187 -0
  77. package/src/number/branded-types/non-negative-int16.test.mts +201 -0
  78. package/src/number/branded-types/non-negative-int32.mts +187 -0
  79. package/src/number/branded-types/non-negative-int32.test.mts +204 -0
  80. package/src/number/branded-types/non-zero-finite-number.mts +229 -0
  81. package/src/number/branded-types/non-zero-finite-number.test.mts +198 -0
  82. package/src/number/branded-types/non-zero-int.mts +167 -0
  83. package/src/number/branded-types/non-zero-int.test.mts +177 -0
  84. package/src/number/branded-types/non-zero-int16.mts +196 -0
  85. package/src/number/branded-types/non-zero-int16.test.mts +195 -0
  86. package/src/number/branded-types/non-zero-int32.mts +196 -0
  87. package/src/number/branded-types/non-zero-int32.test.mts +197 -0
  88. package/src/number/branded-types/non-zero-safe-int.mts +196 -0
  89. package/src/number/branded-types/non-zero-safe-int.test.mts +232 -0
  90. package/src/number/branded-types/non-zero-uint16.mts +189 -0
  91. package/src/number/branded-types/non-zero-uint16.test.mts +199 -0
  92. package/src/number/branded-types/non-zero-uint32.mts +189 -0
  93. package/src/number/branded-types/non-zero-uint32.test.mts +199 -0
  94. package/src/number/branded-types/positive-finite-number.mts +241 -0
  95. package/src/number/branded-types/positive-finite-number.test.mts +204 -0
  96. package/src/number/branded-types/positive-int.mts +304 -0
  97. package/src/number/branded-types/positive-int.test.mts +176 -0
  98. package/src/number/branded-types/positive-int16.mts +188 -0
  99. package/src/number/branded-types/positive-int16.test.mts +197 -0
  100. package/src/number/branded-types/positive-int32.mts +188 -0
  101. package/src/number/branded-types/positive-int32.test.mts +197 -0
  102. package/src/number/branded-types/positive-safe-int.mts +187 -0
  103. package/src/number/branded-types/positive-safe-int.test.mts +210 -0
  104. package/src/number/branded-types/positive-uint16.mts +188 -0
  105. package/src/number/branded-types/positive-uint16.test.mts +203 -0
  106. package/src/number/branded-types/positive-uint32.mts +188 -0
  107. package/src/number/branded-types/positive-uint32.test.mts +203 -0
  108. package/src/number/branded-types/safe-int.mts +291 -0
  109. package/src/number/branded-types/safe-int.test.mts +170 -0
  110. package/src/number/branded-types/safe-uint.mts +187 -0
  111. package/src/number/branded-types/safe-uint.test.mts +176 -0
  112. package/src/number/branded-types/uint.mts +179 -0
  113. package/src/number/branded-types/uint.test.mts +158 -0
  114. package/src/number/branded-types/uint16.mts +186 -0
  115. package/src/number/branded-types/uint16.test.mts +170 -0
  116. package/src/number/branded-types/uint32.mts +218 -0
  117. package/src/number/branded-types/uint32.test.mts +170 -0
  118. package/src/number/enum/index.mts +2 -0
  119. package/src/number/enum/int8.mts +344 -0
  120. package/src/number/enum/int8.test.mts +180 -0
  121. package/src/number/enum/uint8.mts +293 -0
  122. package/src/number/enum/uint8.test.mts +164 -0
  123. package/src/number/index.mts +4 -0
  124. package/src/number/num.mts +604 -0
  125. package/src/number/num.test.mts +242 -0
  126. package/src/number/refined-number-utils.mts +566 -0
  127. package/src/object/index.mts +1 -0
  128. package/src/object/object.mts +447 -0
  129. package/src/object/object.test.mts +124 -0
  130. package/src/others/cast-mutable.mts +113 -0
  131. package/src/others/cast-readonly.mts +192 -0
  132. package/src/others/cast-readonly.test.mts +89 -0
  133. package/src/others/if-then.mts +98 -0
  134. package/src/others/if-then.test.mts +75 -0
  135. package/src/others/index.mts +7 -0
  136. package/src/others/map-nullable.mts +172 -0
  137. package/src/others/map-nullable.test.mts +297 -0
  138. package/src/others/memoize-function.mts +196 -0
  139. package/src/others/memoize-function.test.mts +168 -0
  140. package/src/others/tuple.mts +160 -0
  141. package/src/others/tuple.test.mts +11 -0
  142. package/src/others/unknown-to-string.mts +215 -0
  143. package/src/others/unknown-to-string.test.mts +114 -0
@@ -0,0 +1,4000 @@
1
+ import { IMap } from '../collections/index.mjs';
2
+ import { expectType } from '../expect-type.mjs';
3
+ import { Optional, pipe, Result } from '../functional/index.mjs';
4
+ import { isString, isUndefined } from '../guard/index.mjs';
5
+ import { range as rangeIterator } from '../iterator/index.mjs';
6
+ import { asPositiveUint32, asUint32, Num, Uint32 } from '../number/index.mjs';
7
+ import { castMutable, tp, unknownToString } from '../others/index.mjs';
8
+
9
+ /**
10
+ * A comprehensive, immutable utility library for array manipulations in TypeScript.
11
+ * Provides a wide range of functions for array creation, validation, transformation,
12
+ * reduction, slicing, set operations, and more, with a focus on type safety and
13
+ * leveraging TypeScript's type inference capabilities.
14
+ * All functions operate on `readonly` arrays and return new `readonly` arrays,
15
+ * ensuring immutability.
16
+ */
17
+ export namespace Arr {
18
+ /**
19
+ * Returns the size (length) of an array as a type-safe branded integer.
20
+ *
21
+ * This function provides the array length with enhanced type safety through branded types:
22
+ * - For arrays known to be non-empty at compile time: returns `PositiveNumber & SizeType.Arr`
23
+ * - For general arrays that may be empty: returns `SizeType.Arr` (branded Uint32)
24
+ *
25
+ * The returned value is always a non-negative integer that can be safely used for array indexing
26
+ * and size comparisons. The branded type prevents common integer overflow issues and provides
27
+ * better type checking than plain numbers.
28
+ *
29
+ * @template Ar The exact type of the input array, used for precise return type inference.
30
+ * @param array The array to measure. Can be any readonly array type.
31
+ * @returns The length of the array as a branded type:
32
+ * - `IntersectBrand<PositiveNumber, SizeType.Arr>` for known non-empty arrays
33
+ * - `SizeType.Arr` for general arrays (branded Uint32, may be 0)
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * // Known non-empty arrays get positive branded type
38
+ * const tuple = [1, 2, 3] as const;
39
+ * const tupleSize = Arr.size(tuple);
40
+ * // Type: IntersectBrand<PositiveNumber, SizeType.Arr>
41
+ * // Value: 3 (branded, guaranteed positive)
42
+ *
43
+ * const nonEmpty: NonEmptyArray<string> = ['a', 'b'] as NonEmptyArray<string>;
44
+ * const nonEmptySize = Arr.size(nonEmpty);
45
+ * // Type: IntersectBrand<PositiveNumber, SizeType.Arr>
46
+ * // Guaranteed to be > 0
47
+ *
48
+ * // General arrays may be empty, get regular branded type
49
+ * const generalArray: number[] = [1, 2, 3];
50
+ * const generalSize = Arr.size(generalArray);
51
+ * // Type: SizeType.Arr (branded Uint32)
52
+ * // May be 0 or positive
53
+ *
54
+ * // Empty arrays
55
+ * const emptyArray = [] as const;
56
+ * const emptySize = Arr.size(emptyArray);
57
+ * // Type: SizeType.Arr
58
+ * // Value: 0 (branded)
59
+ *
60
+ * // Runtime arrays with unknown content
61
+ * const dynamicArray = Array.from({ length: Math.random() * 10 }, (_, i) => i);
62
+ * const dynamicSize = Arr.size(dynamicArray);
63
+ * // Type: SizeType.Arr (may be 0)
64
+ *
65
+ * // Using size for safe operations
66
+ * const data = [10, 20, 30];
67
+ * const dataSize = Arr.size(data);
68
+ *
69
+ * // Safe for array creation
70
+ * const indices = Arr.seq(dataSize); // Creates [0, 1, 2]
71
+ * const zeros = Arr.zeros(dataSize); // Creates [0, 0, 0]
72
+ *
73
+ * // Safe for bounds checking
74
+ * const isValidIndex = (index: number) => index >= 0 && index < dataSize;
75
+ *
76
+ * // Comparison with other sizes
77
+ * const otherArray = ['a', 'b'];
78
+ * const sizeDiff = Uint32.sub(Arr.size(data), Arr.size(otherArray)); // 1
79
+ *
80
+ * // Functional composition
81
+ * const arrays = [
82
+ * [1, 2],
83
+ * [3, 4, 5],
84
+ * [],
85
+ * [6]
86
+ * ];
87
+ * const sizes = arrays.map(Arr.size); // [2, 3, 0, 1] (all branded)
88
+ * const totalElements = sizes.reduce(Uint32.add, 0); // 6
89
+ *
90
+ * // Type guards work with size
91
+ * if (Arr.size(data) > 0) {
92
+ * // TypeScript knows data is non-empty here
93
+ * const firstElement = data[0]; // Safe access
94
+ * }
95
+ *
96
+ * // Type inference examples
97
+ * expectType<typeof tupleSize, IntersectBrand<PositiveNumber, SizeType.Arr>>('=');
98
+ * expectType<typeof generalSize, SizeType.Arr>('=');
99
+ * expectType<typeof emptySize, SizeType.Arr>('=');
100
+ * ```
101
+ *
102
+ * @see {@link length} - Alias for this function
103
+ * @see {@link isEmpty} for checking if size is 0
104
+ * @see {@link isNonEmpty} for checking if size > 0
105
+ */
106
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
107
+ export const size: SizeFnOverload = (<Ar extends readonly unknown[]>(
108
+ array: Ar,
109
+ ): SizeType.Arr => asUint32(array.length)) as SizeFnOverload;
110
+
111
+ type SizeFnOverload = {
112
+ <Ar extends NonEmptyArray<unknown>>(
113
+ array: Ar,
114
+ ): IntersectBrand<PositiveNumber, SizeType.Arr>;
115
+
116
+ <Ar extends readonly unknown[]>(array: Ar): SizeType.Arr;
117
+ };
118
+
119
+ export const length = size;
120
+
121
+ // type guard
122
+
123
+ /**
124
+ * Type guard that checks if a value is an array, excluding types that cannot be arrays.
125
+ * This function refines the type by filtering out non-array types from unions.
126
+ * @template E The input type that may or may not be an array.
127
+ * @param value The value to check.
128
+ * @returns `true` if the value is an array, `false` otherwise.
129
+ * @example
130
+ * ```ts
131
+ * function processValue(value: string | number[] | null) {
132
+ * if (Arr.isArray(value)) {
133
+ * // value is now typed as number[]
134
+ * console.log(value.length);
135
+ * }
136
+ * }
137
+ *
138
+ * Arr.isArray([1, 2, 3]); // true
139
+ * Arr.isArray("hello"); // false
140
+ * Arr.isArray(null); // false
141
+ * ```
142
+ */
143
+ export const isArray = <E,>(value: E): value is FilterArray<E> =>
144
+ Array.isArray(value);
145
+
146
+ type FilterArray<T> = T extends T
147
+ ? // eslint-disable-next-line @typescript-eslint/no-explicit-any
148
+ BoolOr<TypeEq<T, unknown>, TypeEq<T, any>> extends true
149
+ ? Cast<readonly unknown[], T>
150
+ : T extends readonly unknown[]
151
+ ? T
152
+ : never // Exclude non-array types
153
+ : never;
154
+
155
+ type Cast<A, B> = A extends B ? A : never;
156
+
157
+ // validation
158
+
159
+ /**
160
+ * Type guard that checks if an array is empty (has no elements).
161
+ *
162
+ * This function serves as both a runtime check and a TypeScript type guard,
163
+ * narrowing the array type to `readonly []` when the check passes. It's useful
164
+ * for conditional logic and type-safe handling of potentially empty arrays.
165
+ *
166
+ * @template E The type of elements in the array.
167
+ * @param array The array to check for emptiness.
168
+ * @returns `true` if the array has length 0, `false` otherwise.
169
+ * When `true`, TypeScript narrows the type to `readonly []`.
170
+ *
171
+ * @example
172
+ * ```typescript
173
+ * // Basic emptiness checking
174
+ * const emptyArray: number[] = [];
175
+ * const nonEmptyArray = [1, 2, 3];
176
+ *
177
+ * console.log(Arr.isEmpty(emptyArray)); // true
178
+ * console.log(Arr.isEmpty(nonEmptyArray)); // false
179
+ *
180
+ * // Type guard behavior
181
+ * function processArray(arr: readonly number[]) {
182
+ * if (Arr.isEmpty(arr)) {
183
+ * // arr is now typed as readonly []
184
+ * console.log('Array is empty');
185
+ * return 0;
186
+ * } else {
187
+ * // arr is now typed as NonEmptyArray<number>
188
+ * return arr[0]; // Safe access - TypeScript knows it's non-empty
189
+ * }
190
+ * }
191
+ *
192
+ * // Conditional processing
193
+ * const data = [10, 20, 30];
194
+ * if (!Arr.isEmpty(data)) {
195
+ * // Safe to access elements
196
+ * const firstElement = data[0]; // No undefined risk
197
+ * const lastElement = data[data.length - 1];
198
+ * }
199
+ *
200
+ * // Filtering empty arrays
201
+ * const arrayList: readonly number[][] = [[1, 2], [], [3], []];
202
+ * const nonEmptyArrays = arrayList.filter(arr => !Arr.isEmpty(arr));
203
+ * // nonEmptyArrays: [[1, 2], [3]]
204
+ *
205
+ * // Early returns
206
+ * function sumArray(numbers: readonly number[]): number {
207
+ * if (Arr.isEmpty(numbers)) {
208
+ * return 0; // Handle empty case early
209
+ * }
210
+ * return numbers.reduce((sum, n) => sum + n, 0);
211
+ * }
212
+ *
213
+ * // Type inference examples
214
+ * const testEmpty = [] as const;
215
+ * const testNonEmpty = [1, 2] as const;
216
+ *
217
+ * expectType<Parameters<typeof Arr.isEmpty>[0], readonly unknown[]>('=');
218
+ * expectType<ReturnType<typeof Arr.isEmpty>, boolean>('=');
219
+ * ```
220
+ *
221
+ * @see {@link isNonEmpty} for the opposite check (non-empty arrays)
222
+ * @see {@link size} for getting the exact length
223
+ * @see {@link isArrayOfLength} for checking specific lengths
224
+ */
225
+ export const isEmpty = <E,>(array: readonly E[]): array is readonly [] =>
226
+ array.length === 0;
227
+
228
+ /**
229
+ * Type guard that checks if an array is non-empty (has at least one element).
230
+ *
231
+ * This function serves as both a runtime check and a TypeScript type guard,
232
+ * narrowing the array type to `NonEmptyArray<E>` when the check passes. This enables
233
+ * safe access to array elements without undefined checks, as TypeScript knows the array
234
+ * has at least one element.
235
+ *
236
+ * @template E The type of elements in the array.
237
+ * @param array The array to check for non-emptiness.
238
+ * @returns `true` if the array has length > 0, `false` otherwise.
239
+ * When `true`, TypeScript narrows the type to `NonEmptyArray<E>`.
240
+ *
241
+ * @example
242
+ * ```typescript
243
+ * // Basic non-emptiness checking
244
+ * const emptyArray: number[] = [];
245
+ * const nonEmptyArray = [1, 2, 3];
246
+ *
247
+ * console.log(Arr.isNonEmpty(emptyArray)); // false
248
+ * console.log(Arr.isNonEmpty(nonEmptyArray)); // true
249
+ *
250
+ * // Type guard behavior enables safe element access
251
+ * function getFirstElement(arr: readonly number[]): number | undefined {
252
+ * if (Arr.isNonEmpty(arr)) {
253
+ * // arr is now typed as NonEmptyArray<number>
254
+ * return arr[0]; // Safe - no undefined, TypeScript knows this exists
255
+ * }
256
+ * return undefined;
257
+ * }
258
+ *
259
+ * // Safe operations on non-empty arrays
260
+ * function processData(data: readonly string[]) {
261
+ * if (Arr.isNonEmpty(data)) {
262
+ * // All of these are now safe without undefined checks
263
+ * const first = data[0];
264
+ * const last = data[data.length - 1];
265
+ * const middle = data[Math.floor(data.length / 2)];
266
+ *
267
+ * // Can safely use non-empty array methods
268
+ * const joined = data.join(', ');
269
+ * const reduced = data.reduce((acc, item) => acc + item.length, 0);
270
+ * }
271
+ * }
272
+ *
273
+ * // Filtering and working with arrays
274
+ * const possiblyEmptyArrays: readonly number[][] = [
275
+ * [1, 2, 3],
276
+ * [],
277
+ * [4, 5],
278
+ * []
279
+ * ];
280
+ *
281
+ * // Get only non-empty arrays with proper typing
282
+ * const definitelyNonEmpty = possiblyEmptyArrays.filter(Arr.isNonEmpty);
283
+ * // Type: NonEmptyArray<number>[]
284
+ *
285
+ * // Now safe to access elements
286
+ * const firstElements = definitelyNonEmpty.map(arr => arr[0]); // [1, 4]
287
+ *
288
+ * // Early validation
289
+ * function calculateAverage(numbers: readonly number[]): number {
290
+ * if (!Arr.isNonEmpty(numbers)) {
291
+ * throw new Error('Cannot calculate average of empty array');
292
+ * }
293
+ *
294
+ * // numbers is now NonEmptyArray<number>
295
+ * return numbers.reduce((sum, n) => sum + n, 0) / numbers.length;
296
+ * }
297
+ *
298
+ * // Functional composition
299
+ * const arrayGroups = [
300
+ * [1, 2],
301
+ * [],
302
+ * [3, 4, 5],
303
+ * []
304
+ * ];
305
+ *
306
+ * const nonEmptyGroups = arrayGroups
307
+ * .filter(Arr.isNonEmpty) // Filter to NonEmptyArray<number>[]
308
+ * .map(group => group[0]); // Safe access to first element: [1, 3]
309
+ *
310
+ * // Combined with other array operations
311
+ * function processArraySafely<T>(
312
+ * arr: readonly T[],
313
+ * processor: (item: T) => string
314
+ * ): string {
315
+ * if (Arr.isNonEmpty(arr)) {
316
+ * return arr.map(processor).join(' -> ');
317
+ * }
318
+ * return 'No items to process';
319
+ * }
320
+ *
321
+ * // Type inference examples
322
+ * const testArray = [1, 2, 3];
323
+ * const isNonEmptyResult = Arr.isNonEmpty(testArray);
324
+ *
325
+ * expectType<typeof isNonEmptyResult, boolean>('=');
326
+ * expectType<Parameters<typeof Arr.isNonEmpty>[0], readonly unknown[]>('=');
327
+ *
328
+ * // Type narrowing in conditional
329
+ * if (Arr.isNonEmpty(testArray)) {
330
+ * expectType<typeof testArray, NonEmptyArray<number>>('=');
331
+ * }
332
+ * ```
333
+ *
334
+ * @see {@link isEmpty} for the opposite check (empty arrays)
335
+ * @see {@link size} for getting the exact length
336
+ * @see {@link head} for safely getting the first element
337
+ * @see {@link last} for safely getting the last element
338
+ */
339
+ export const isNonEmpty = <E,>(
340
+ array: readonly E[],
341
+ ): array is NonEmptyArray<E> => array.length > 0;
342
+
343
+ /**
344
+ * Checks if an array has a specific length.
345
+ * @template E The type of elements in the array.
346
+ * @template N The expected length of the array (must be a number type).
347
+ * @param array The array to check.
348
+ * @param len The expected length.
349
+ * @returns `true` if the array has the specified length, `false` otherwise.
350
+ * @example
351
+ * ```ts
352
+ * const arr: readonly number[] = [1, 2, 3];
353
+ * if (Arr.isArrayOfLength(arr, 3)) {
354
+ * // arr is now typed as readonly [number, number, number]
355
+ * }
356
+ * Arr.isArrayOfLength([1, 2], 3); // false
357
+ * ```
358
+ */
359
+ export const isArrayOfLength = <E, N extends SizeType.ArgArrNonNegative>(
360
+ array: readonly E[],
361
+ len: N,
362
+ ): array is ArrayOfLength<N, E> => array.length === len;
363
+
364
+ /**
365
+ * Checks if an array has at least a specific length.
366
+ * @template E The type of elements in the array.
367
+ * @template N The minimum expected length of the array (must be a number type).
368
+ * @param array The array to check.
369
+ * @param len The minimum expected length.
370
+ * @returns `true` if the array has at least the specified length, `false` otherwise.
371
+ * @example
372
+ * ```ts
373
+ * const arr: readonly number[] = [1, 2, 3];
374
+ * if (Arr.isArrayAtLeastLength(arr, 2)) {
375
+ * // arr is now typed as readonly [number, number, ...number[]]
376
+ * }
377
+ * Arr.isArrayAtLeastLength([1], 2); // false
378
+ * ```
379
+ */
380
+ export const isArrayAtLeastLength = <E, N extends SizeType.ArgArrNonNegative>(
381
+ array: readonly E[],
382
+ len: N,
383
+ ): array is ArrayAtLeastLen<N, E> => array.length >= len;
384
+
385
+ /**
386
+ * Checks if an index is within the valid range of an array (i.e., `0 <= index < array.length`).
387
+ * @template E The type of elements in the array.
388
+ * @param array The input array.
389
+ * @param index The index to check.
390
+ * @returns `true` if the index is within the array bounds, `false` otherwise.
391
+ * @example
392
+ * ```ts
393
+ * Arr.indexIsInRange([10, 20], 0); // true
394
+ * Arr.indexIsInRange([10, 20], 1); // true
395
+ * Arr.indexIsInRange([10, 20], 2); // false
396
+ * Arr.indexIsInRange([10, 20], -1); // false
397
+ * Arr.indexIsInRange([], 0); // false
398
+ * ```
399
+ */
400
+ export const indexIsInRange = <E,>(
401
+ array: readonly E[],
402
+ index: SizeType.ArgArrNonNegative,
403
+ ): boolean => Num.isInRange(0, array.length)(index);
404
+
405
+ // array creation
406
+
407
+ /**
408
+ * Creates an array of zeros with the specified length.
409
+ *
410
+ * This function provides compile-time type safety with precise return types:
411
+ * - When `len` is a compile-time known `SmallUint` (0-100), returns a tuple with exact length
412
+ * - When `len` is a positive runtime value, returns a `NonEmptyArray<0>`
413
+ * - Otherwise, returns a `readonly 0[]` that may be empty
414
+ *
415
+ * @template N The type of the length parameter. When a `SmallUint` literal is provided,
416
+ * the return type will be a tuple of exactly that length filled with zeros.
417
+ * @param len The length of the array to create. Must be a non-negative integer.
418
+ * @returns An immutable array of zeros. The exact return type depends on the input:
419
+ * - `ArrayOfLength<N, 0>` when `N` is a `SmallUint` literal
420
+ * - `NonEmptyArray<0>` when `len` is a positive runtime value
421
+ * - `readonly 0[]` for general non-negative values
422
+ *
423
+ * @example
424
+ * ```typescript
425
+ * // Compile-time known lengths produce precise tuple types
426
+ * const exactLength = Arr.zeros(3); // readonly [0, 0, 0]
427
+ * const empty = Arr.zeros(0); // readonly []
428
+ *
429
+ * // Runtime positive values produce non-empty arrays
430
+ * const count = Math.floor(Math.random() * 5) + 1;
431
+ * const nonEmpty = Arr.zeros(count); // NonEmptyArray<0>
432
+ *
433
+ * // General runtime values may be empty
434
+ * const maybeEmpty = Arr.zeros(Math.floor(Math.random() * 5)); // readonly 0[]
435
+ *
436
+ * // Type inference examples
437
+ * expectType<typeof exactLength, readonly [0, 0, 0]>('=');
438
+ * expectType<typeof empty, readonly []>('=');
439
+ * expectType<typeof nonEmpty, NonEmptyArray<0>>('=');
440
+ * expectType<typeof maybeEmpty, readonly 0[]>('=');
441
+ * ```
442
+ */
443
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
444
+ export const zeros: ZerosFnOverload = ((
445
+ len: SizeType.ArgArrNonNegative,
446
+ ): readonly 0[] => Array.from<0>({ length: len }).fill(0)) as ZerosFnOverload;
447
+
448
+ type ZerosFnOverload = {
449
+ /**
450
+ * Create array of zeros with compile-time length.
451
+ */
452
+ <N extends SmallUint>(len: N): ArrayOfLength<N, 0>;
453
+
454
+ /**
455
+ * Create non-empty array of zeros.
456
+ */
457
+ (len: SizeType.ArgArrPositive): NonEmptyArray<0>;
458
+
459
+ /**
460
+ * Create array of zeros.
461
+ */
462
+ (len: SizeType.ArgArrNonNegative): readonly 0[];
463
+ };
464
+
465
+ /**
466
+ * Creates a sequence of consecutive integers from 0 to `len-1`.
467
+ *
468
+ * This function generates index sequences with precise compile-time typing:
469
+ * - When `len` is a compile-time known `SmallUint` (0-100), returns a tuple of consecutive integers
470
+ * - When `len` is a positive runtime value, returns a `NonEmptyArray<SizeType.Arr>`
471
+ * - Otherwise, returns a `readonly SizeType.Arr[]` that may be empty
472
+ *
473
+ * @template N The type of the length parameter. When a `SmallUint` literal is provided,
474
+ * the return type will be a tuple containing the sequence [0, 1, 2, ..., N-1].
475
+ * @param len The length of the sequence to create. Must be a non-negative integer.
476
+ * @returns An immutable array containing the sequence [0, 1, 2, ..., len-1].
477
+ * The exact return type depends on the input:
478
+ * - `Seq<N>` (precise tuple) when `N` is a `SmallUint` literal
479
+ * - `NonEmptyArray<SizeType.Arr>` when `len` is a positive runtime value
480
+ * - `readonly SizeType.Arr[]` for general non-negative values
481
+ *
482
+ * @example
483
+ * ```typescript
484
+ * // Compile-time known lengths produce precise tuple types
485
+ * const indices = Arr.seq(4); // readonly [0, 1, 2, 3]
486
+ * const empty = Arr.seq(0); // readonly []
487
+ * const single = Arr.seq(1); // readonly [0]
488
+ *
489
+ * // Runtime positive values produce non-empty arrays
490
+ * const count = Math.floor(Math.random() * 5) + 1;
491
+ * const nonEmpty = Arr.seq(count); // NonEmptyArray<SizeType.Arr>
492
+ *
493
+ * // General runtime values may be empty
494
+ * const maybeEmpty = Arr.seq(Math.floor(Math.random() * 5)); // readonly SizeType.Arr[]
495
+ *
496
+ * // Useful for generating array indices
497
+ * const data = ['a', 'b', 'c', 'd'];
498
+ * const indexSequence = Arr.seq(data.length); // [0, 1, 2, 3]
499
+ *
500
+ * // Type inference examples
501
+ * expectType<typeof indices, readonly [0, 1, 2, 3]>('=');
502
+ * expectType<typeof empty, readonly []>('=');
503
+ * expectType<typeof single, readonly [0]>('=');
504
+ * ```
505
+ */
506
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
507
+ export const seq: SeqFnOverload = ((
508
+ len: SizeType.ArgArrNonNegative,
509
+ ): readonly SizeType.Arr[] =>
510
+ Array.from({ length: len }, (_, i) => asUint32(i))) as SeqFnOverload;
511
+
512
+ type SeqFnOverload = {
513
+ <N extends SmallUint>(len: N): Seq<N>;
514
+
515
+ (len: SizeType.ArgArrPositive): NonEmptyArray<SizeType.Arr>;
516
+
517
+ (len: SizeType.ArgArrNonNegative): readonly SizeType.Arr[];
518
+ };
519
+
520
+ /**
521
+ * Creates a new array of the specified length, with each position filled with the provided initial value.
522
+ *
523
+ * This function provides compile-time type safety with precise return types and performs shallow copying
524
+ * of the initial value (the same reference is used for all positions):
525
+ * - When `len` is a compile-time known `SmallUint` (0-100), returns a tuple of exactly that length
526
+ * - When `len` is a positive runtime value, returns a `NonEmptyArray<V>`
527
+ * - Otherwise, returns a `readonly V[]` that may be empty
528
+ *
529
+ * @template V The type of the initial value. The `const` constraint preserves literal types.
530
+ * @template N The type of the length parameter when it's a `SmallUint` literal.
531
+ * @param len The length of the array to create. Must be a non-negative integer.
532
+ * @param init The value to fill each position with. The same reference is used for all positions.
533
+ * @returns An immutable array filled with the initial value. The exact return type depends on the length:
534
+ * - `ArrayOfLength<N, V>` when `len` is a `SmallUint` literal
535
+ * - `NonEmptyArray<V>` when `len` is a positive runtime value
536
+ * - `readonly V[]` for general non-negative values
537
+ *
538
+ * @example
539
+ * ```typescript
540
+ * // Compile-time known lengths produce precise tuple types
541
+ * const strings = Arr.create(3, 'hello'); // readonly ['hello', 'hello', 'hello']
542
+ * const numbers = Arr.create(2, 42); // readonly [42, 42]
543
+ * const empty = Arr.create(0, 'unused'); // readonly []
544
+ *
545
+ * // Object references are shared (shallow copy behavior)
546
+ * const obj = { id: 1, name: 'test' };
547
+ * const objects = Arr.create(3, obj); // readonly [obj, obj, obj]
548
+ * objects[0] === objects[1]; // true - same reference
549
+ * objects[0].id = 999; // Mutates the shared object
550
+ * console.log(objects[1].id); // 999 - all positions affected
551
+ *
552
+ * // Runtime positive values produce non-empty arrays
553
+ * const count = Math.floor(Math.random() * 5) + 1;
554
+ * const nonEmpty = Arr.create(count, 'item'); // NonEmptyArray<string>
555
+ *
556
+ * // Literal type preservation with const assertion
557
+ * const literals = Arr.create(2, 'success' as const); // readonly ['success', 'success']
558
+ *
559
+ * // Type inference examples
560
+ * expectType<typeof strings, readonly ['hello', 'hello', 'hello']>('=');
561
+ * expectType<typeof numbers, readonly [42, 42]>('=');
562
+ * expectType<typeof empty, readonly []>('=');
563
+ * ```
564
+ *
565
+ * @see {@link zeros} for creating arrays filled with zeros
566
+ * @see {@link seq} for creating sequences of consecutive integers
567
+ */
568
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
569
+ export const create: CreateFnOverload = (<const V,>(
570
+ len: SizeType.ArgArrNonNegative,
571
+ init: V,
572
+ ): readonly V[] =>
573
+ Array.from({ length: Math.max(0, len) }, () => init)) as CreateFnOverload;
574
+
575
+ type CreateFnOverload = {
576
+ <const V, N extends SmallUint>(len: N, init: V): ArrayOfLength<N, V>;
577
+
578
+ <const V>(len: SizeType.ArgArrPositive, init: V): NonEmptyArray<V>;
579
+
580
+ <const V>(len: SizeType.ArgArrNonNegative, init: V): readonly V[];
581
+ };
582
+
583
+ export const newArray = create;
584
+
585
+ /**
586
+ * Creates a shallow copy of an array, preserving the exact type signature.
587
+ *
588
+ * This function creates a new array with the same elements as the input, but with a new array reference.
589
+ * Object references within the array are preserved (shallow copy), and the readonly/mutable status
590
+ * of the array type is maintained.
591
+ *
592
+ * @template Ar The exact type of the input array, preserving tuple types, readonly status, and element types.
593
+ * @param array The array to copy. Can be any array type: mutable, readonly, tuple, or general array.
594
+ * @returns A new array that is a shallow copy of the input. The return type exactly matches the input type,
595
+ * preserving readonly status, tuple structure, and element types.
596
+ *
597
+ * @example
598
+ * ```typescript
599
+ * // Mutable arrays remain mutable
600
+ * const mutableOriginal = [1, 2, 3];
601
+ * const mutableCopy = Arr.copy(mutableOriginal); // number[]
602
+ * mutableCopy[0] = 999; // OK - still mutable
603
+ * mutableOriginal[0]; // 1 - original unchanged
604
+ *
605
+ * // Readonly arrays remain readonly
606
+ * const readonlyOriginal = [1, 2, 3] as const;
607
+ * const readonlyCopy = Arr.copy(readonlyOriginal); // readonly [1, 2, 3]
608
+ * // readonlyCopy[0] = 999; // Error - readonly array
609
+ *
610
+ * // Tuple types are preserved
611
+ * const tupleOriginal: [string, number, boolean] = ['hello', 42, true];
612
+ * const tupleCopy = Arr.copy(tupleOriginal); // [string, number, boolean]
613
+ *
614
+ * // Shallow copy behavior with objects
615
+ * const objectArray = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
616
+ * const objectCopy = Arr.copy(objectArray);
617
+ * objectCopy[0].name = 'Charlie'; // Mutates the shared object reference
618
+ * console.log(objectArray[0].name); // 'Charlie' - original affected
619
+ * objectCopy.push({ id: 3, name: 'Dave' }); // Array structure changes don't affect original
620
+ * console.log(objectArray.length); // 2 - original array length unchanged
621
+ *
622
+ * // Empty arrays
623
+ * const emptyArray: number[] = [];
624
+ * const emptyCopy = Arr.copy(emptyArray); // number[]
625
+ * const emptyTuple = [] as const;
626
+ * const emptyTupleCopy = Arr.copy(emptyTuple); // readonly []
627
+ *
628
+ * // Type inference examples
629
+ * expectType<typeof mutableCopy, number[]>('=');
630
+ * expectType<typeof readonlyCopy, readonly [1, 2, 3]>('=');
631
+ * expectType<typeof tupleCopy, [string, number, boolean]>('=');
632
+ * ```
633
+ *
634
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice | Array.prototype.slice}
635
+ * The underlying implementation uses `slice()` for efficient shallow copying
636
+ */
637
+ export const copy = <Ar extends readonly unknown[]>(array: Ar): Ar =>
638
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
639
+ array.slice() as unknown as Ar;
640
+
641
+ /**
642
+ * @internal
643
+ * Helper type for `range` function to represent a sequence of numbers up to N-1.
644
+ * `LEQ[N]` would be `0 | 1 | ... | N-1`.
645
+ */
646
+ type LT = Readonly<{
647
+ [N in SmallUint]: Index<N>;
648
+ }>;
649
+
650
+ /**
651
+ * @internal
652
+ * This type is used to avoid incorrect type calculation results for unions with `Seq`.
653
+ * It computes the type of an array generated by `Arr.range(S, E)`.
654
+ * If `S` or `E` is a union type, it falls back to a more general `readonly number[]` type
655
+ * to prevent overly complex or incorrect tuple/union types.
656
+ * Otherwise, it computes a precise tuple type like `readonly [S, S+1, ..., E-1]`.
657
+ * @template S The start of the range (inclusive), constrained to `SmallUint`.
658
+ * @template E The end of the range (exclusive), constrained to `SmallUint`.
659
+ */
660
+ type RangeList<S extends SmallUint, E extends SmallUint> =
661
+ BoolOr<IsUnion<S>, IsUnion<E>> extends true
662
+ ? readonly RelaxedExclude<LT[E], LT[Min<S>]>[] // Avoid incorrect type calculation for unions with Seq
663
+ : List.Skip<S, Seq<E>>;
664
+
665
+ expectType<RangeList<1, 5>, readonly [1, 2, 3, 4]>('=');
666
+ expectType<RangeList<1, 2>, readonly [1]>('=');
667
+ expectType<RangeList<1, 1>, readonly []>('=');
668
+ expectType<RangeList<1, 1 | 3>, readonly (1 | 2)[]>('=');
669
+ expectType<RangeList<1 | 3, 3 | 5>, readonly (1 | 2 | 3 | 4)[]>('=');
670
+ expectType<
671
+ RangeList<1 | 2 | 3, 5 | 6 | 7>,
672
+ readonly (1 | 2 | 3 | 4 | 5 | 6)[]
673
+ >('=');
674
+ expectType<RangeList<5, 1>, readonly []>('=');
675
+
676
+ /**
677
+ * Creates an array of numbers within a specified range with optional step increment.
678
+ *
679
+ * This function generates arithmetic sequences with advanced compile-time type inference:
680
+ * - When `start` and `end` are {@link SmallUint} literals and `step` is 1 (or omitted), returns a precise tuple type
681
+ * - When parameters are runtime values, returns appropriate array types based on sign constraints
682
+ * - Empty arrays are returned for invalid ranges (e.g., start ≥ end with positive step)
683
+ * - Never throws exceptions - invalid parameters result in empty arrays
684
+ *
685
+ * **SmallUint Constraint:** The {@link SmallUint} constraint (0-255) enables precise tuple type inference
686
+ * for compile-time known ranges. This allows TypeScript to compute exact tuple types like `readonly [1, 2, 3, 4]`
687
+ * instead of generic `readonly number[]`.
688
+ *
689
+ * **Type Inference Behavior:**
690
+ * - Literal {@link SmallUint} values with step=1 → precise tuple type (`RangeList<S, E>`)
691
+ * - Non-negative parameters → `readonly SafeUint[]`
692
+ * - Mixed signs or negative parameters → `readonly SafeInt[]`
693
+ * - Runtime values → lose precise typing but maintain safety
694
+ *
695
+ * @template S The type of the start value. When a {@link SmallUint} literal (0-255), enables precise tuple typing.
696
+ * @template E The type of the end value. When a {@link SmallUint} literal (0-255), enables precise tuple typing.
697
+ * @param start The start of the range (inclusive). Must be a safe integer. Supports:
698
+ * - **Literal {@link SmallUint}:** Enables precise tuple types (0-255)
699
+ * - **Runtime {@link SafeInt}:** Fallback to general array types
700
+ * - **Negative values:** Supported for countdown sequences
701
+ * @param end The end of the range (exclusive). Must be a safe integer. Supports:
702
+ * - **Literal {@link SmallUint}:** Enables precise tuple types (0-255)
703
+ * - **Runtime {@link SafeInt}:** Fallback to general array types
704
+ * - **Equal to start:** Results in empty array
705
+ * @param step The step increment (default: 1). Must be a non-zero safe integer.
706
+ * - **Positive step:** generates increasing sequence from start to end
707
+ * - **Negative step:** generates decreasing sequence from start to end
708
+ * - **Zero step:** Not allowed (branded type prevents this)
709
+ * @returns An immutable array containing the arithmetic sequence. Return type depends on parameters:
710
+ * - `RangeList<S, E>` (precise tuple like `readonly [1, 2, 3, 4]`) when `S` and `E` are {@link SmallUint} literals and step is 1
711
+ * - `readonly SafeUint[]` when all parameters are non-negative
712
+ * - `readonly SafeInt[]` for general integer ranges including negative values
713
+ *
714
+ * @example
715
+ * ```typescript
716
+ * // Compile-time known ranges with step=1 produce precise tuple types
717
+ * const range1to4 = Arr.range(1, 5); // readonly [1, 2, 3, 4]
718
+ * const range0to2 = Arr.range(0, 3); // readonly [0, 1, 2]
719
+ * const emptyRange = Arr.range(5, 5); // readonly []
720
+ * const reverseEmpty = Arr.range(5, 1); // readonly [] (invalid with positive step)
721
+ *
722
+ * // SmallUint constraint examples (0-255 for precise typing)
723
+ * const small = Arr.range(0, 10); // readonly [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
724
+ * const maxSmall = Arr.range(250, 255); // readonly [250, 251, 252, 253, 254]
725
+ * const beyondSmall = Arr.range(0, 300); // readonly SafeUint[] (loses precision)
726
+ *
727
+ * // Custom step increments
728
+ * const evens = Arr.range(0, 10, 2); // readonly SafeUint[] -> [0, 2, 4, 6, 8]
729
+ * const odds = Arr.range(1, 10, 2); // readonly SafeUint[] -> [1, 3, 5, 7, 9]
730
+ * const countdown = Arr.range(5, 0, -1); // readonly SafeInt[] -> [5, 4, 3, 2, 1]
731
+ * const bigStep = Arr.range(0, 20, 5); // readonly SafeUint[] -> [0, 5, 10, 15]
732
+ *
733
+ * // Edge cases that return empty arrays
734
+ * const singleElement = Arr.range(3, 4); // readonly [3]
735
+ * const invalidRange = Arr.range(10, 5, 2); // readonly [] (start > end with positive step)
736
+ * const invalidReverse = Arr.range(1, 10, -1); // readonly [] (start < end with negative step)
737
+ * const zeroRange = Arr.range(42, 42); // readonly [] (start equals end)
738
+ *
739
+ * // Runtime ranges lose precise typing but maintain safety
740
+ * const dynamicStart = Math.floor(Math.random() * 10) as SafeInt;
741
+ * const dynamicEnd = (dynamicStart + 5) as SafeInt;
742
+ * const dynamicRange = Arr.range(dynamicStart, dynamicEnd); // readonly SafeInt[]
743
+ *
744
+ * // Negative numbers and mixed signs
745
+ * const negativeRange = Arr.range(-5, 5); // readonly SafeInt[] -> [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4]
746
+ * const negativeCountdown = Arr.range(0, -5, -1); // readonly SafeInt[] -> [0, -1, -2, -3, -4]
747
+ *
748
+ * // Useful for generating index ranges and iteration
749
+ * const indices = Arr.range(0, 10); // readonly [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
750
+ * const reversedIndices = Arr.range(9, -1, -1); // readonly SafeInt[] -> [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
751
+ *
752
+ * // Functional programming patterns
753
+ * const squares = Arr.range(1, 6).map(x => x * x); // [1, 4, 9, 16, 25]
754
+ * const fibonacci = Arr.range(0, 10).reduce((acc, _, i) => {\n * if (i <= 1) return [...acc, i];\n * return [...acc, acc[i-1] + acc[i-2]];\n * }, [] as number[]); // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
755
+ *
756
+ * // Type inference examples showing precise vs general types
757
+ * expectType<typeof range1to4, readonly [1, 2, 3, 4]>('='); // Precise tuple
758
+ * expectType<typeof emptyRange, readonly []>('='); // Precise empty tuple
759
+ * expectType<typeof evens, readonly SafeUint[]>('='); // General positive array
760
+ * expectType<typeof countdown, readonly SafeInt[]>('='); // General integer array
761
+ * expectType<typeof negativeRange, readonly SafeInt[]>('='); // General integer array
762
+ * expectType<typeof small, readonly [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]>('='); // Precise tuple
763
+ * expectType<typeof beyondSmall, readonly SafeUint[]>('='); // General array (beyond SmallUint)
764
+ * ```
765
+ *
766
+ * @throws Never throws - invalid ranges simply return empty arrays
767
+ * @see {@link seq} for creating sequences starting from 0
768
+ * @see {@link SmallUint} for understanding the constraint that enables precise typing
769
+ * @see {@link SafeInt} and {@link SafeUint} for the safe integer types used
770
+ */
771
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
772
+ export const range: RangeFnOverload = ((
773
+ start: SafeIntWithSmallInt,
774
+ end: SafeIntWithSmallInt,
775
+ step: NonZeroSafeIntWithSmallInt = 1,
776
+ ): readonly SafeInt[] =>
777
+ Array.from(rangeIterator(start, end, step))) as RangeFnOverload;
778
+
779
+ type RangeFnOverload = {
780
+ <S extends SmallUint, E extends SmallUint>(
781
+ start: S,
782
+ end: E,
783
+ step?: 1,
784
+ ): RangeList<S, E>;
785
+
786
+ (
787
+ start: SafeUintWithSmallInt,
788
+ end: SafeUintWithSmallInt,
789
+ step?: PositiveSafeIntWithSmallInt,
790
+ ): readonly SafeUint[];
791
+
792
+ (
793
+ start: SafeIntWithSmallInt,
794
+ end: SafeIntWithSmallInt,
795
+ step?: NonZeroSafeIntWithSmallInt,
796
+ ): readonly SafeInt[];
797
+ };
798
+
799
+ // element access
800
+
801
+ /**
802
+ * Safely retrieves an element at a given index from an array, returning an {@link Optional}.
803
+ *
804
+ * This function provides type-safe array access with support for negative indexing
805
+ * (e.g., -1 for the last element). Unlike direct array access which can return
806
+ * `undefined` for out-of-bounds indices, this function always returns a well-typed
807
+ * {@link Optional} that explicitly represents the possibility of absence.
808
+ *
809
+ * **Negative Indexing:** Negative indices count from the end of the array:
810
+ * - `-1` refers to the last element
811
+ * - `-2` refers to the second-to-last element, etc.
812
+ *
813
+ * **Curried Usage:** This function supports currying - when called with only an index, it returns
814
+ * a function that can be applied to arrays, making it ideal for use in pipe operations.
815
+ *
816
+ * **Optional Return Type:** The return type is always {@link Optional}<E> which provides:
817
+ * - Type-safe access without `undefined` in your business logic
818
+ * - Explicit handling of \"not found\" cases
819
+ * - Composable error handling with {@link Optional} utilities
820
+ *
821
+ * @template E The type of elements in the array.
822
+ * @param array The array to access (when using direct call syntax).
823
+ * @param index The index to access. Must be a branded `SizeType.ArgArr` (safe integer). Can be:
824
+ * - **Positive integer:** 0-based index from the start (0, 1, 2, ...)
825
+ * - **Negative integer:** index from the end (-1 is last element, -2 is second-to-last, etc.)
826
+ * - **Out of bounds:** any index beyond array bounds returns {@link Optional.None}
827
+ * @returns An {@link Optional}<E> containing:
828
+ * - {@link Optional.Some}<E> with the element if the index is valid
829
+ * - {@link Optional.None} if the index is out of bounds (including empty arrays)
830
+ *
831
+ * @example
832
+ * ```typescript
833
+ * // Direct usage with positive indices
834
+ * const fruits = ['apple', 'banana', 'cherry', 'date', 'elderberry'];
835
+ * const first = Arr.at(fruits, 0); // Optional.Some('apple')
836
+ * const third = Arr.at(fruits, 2); // Optional.Some('cherry')
837
+ * const outOfBounds = Arr.at(fruits, 10); // Optional.None
838
+ *
839
+ * // Negative indexing (accessing from the end)
840
+ * const last = Arr.at(fruits, -1); // Optional.Some('elderberry')
841
+ * const secondLast = Arr.at(fruits, -2); // Optional.Some('date')
842
+ * const negativeOutOfBounds = Arr.at(fruits, -10); // Optional.None
843
+ *
844
+ * // Edge cases
845
+ * const emptyResult = Arr.at([], 0); // Optional.None
846
+ * const negativeOnEmpty = Arr.at([], -1); // Optional.None
847
+ * const singleElement = Arr.at(['only'], 0); // Optional.Some('only')
848
+ * const singleNegative = Arr.at(['only'], -1); // Optional.Some('only')
849
+ *
850
+ * // Safe access pattern with type-safe unwrapping
851
+ * const maybeElement = Arr.at(fruits, 2);
852
+ * if (Optional.isSome(maybeElement)) {
853
+ * console.log(`Found: ${maybeElement.value}`); // Type-safe access, no undefined
854
+ * } else {
855
+ * console.log('Index out of bounds');
856
+ * }
857
+ *
858
+ * // Alternative unwrapping with default
859
+ * const elementOrDefault = Optional.unwrapOr(Arr.at(fruits, 100), 'not found');
860
+ * console.log(elementOrDefault); // 'not found'
861
+ *
862
+ * // Curried usage for functional composition
863
+ * const getSecondElement = Arr.at(1);
864
+ * const getLastElement = Arr.at(-1);
865
+ * const getMiddleElement = Arr.at(2);
866
+ *
867
+ * const nestedArrays = [
868
+ * [10, 20, 30, 40],
869
+ * [50, 60],
870
+ * [70]
871
+ * ];
872
+ * const secondElements = nestedArrays.map(getSecondElement);
873
+ * // [Optional.Some(20), Optional.None, Optional.None]
874
+ *
875
+ * const lastElements = nestedArrays.map(getLastElement);
876
+ * // [Optional.Some(40), Optional.Some(60), Optional.Some(70)]
877
+ *
878
+ * // Pipe composition for data processing
879
+ * const processArray = (arr: readonly string[]) => pipe(arr)
880
+ * .map(getSecondElement)
881
+ * .map(opt => Optional.map(opt, s => s.toUpperCase()))
882
+ * .map(opt => Optional.unwrapOr(opt, 'MISSING'))
883
+ * .value;
884
+ *
885
+ * console.log(processArray(['a', 'b', 'c'])); // 'B'
886
+ * console.log(processArray(['x'])); // 'MISSING'
887
+ *
888
+ * // Advanced curried usage with transformation pipelines
889
+ * const extractAndProcess = pipe([
890
+ * ['hello', 'world', 'typescript'],
891
+ * ['functional', 'programming'],
892
+ * ['type', 'safety', 'matters', 'most']
893
+ * ])
894
+ * .map(arr => arr.map(getSecondElement))
895
+ * .map(opts => opts.map(opt => Optional.unwrapOr(opt, '[missing]')))
896
+ * .value;
897
+ * // [['world'], ['[missing]'], ['safety']]
898
+ *
899
+ * // Type inference examples
900
+ * expectType<typeof first, Optional<string>>('=');
901
+ * expectType<typeof getSecondElement, <T>(array: readonly T[]) => Optional<T>>('=');
902
+ * expectType<typeof negativeOutOfBounds, Optional<string>>('=');
903
+ * ```
904
+ *
905
+ * @see {@link head} for getting the first element specifically
906
+ * @see {@link last} for getting the last element specifically
907
+ * @see {@link Optional} for working with the returned Optional values
908
+ * @see {@link Optional.unwrapOr} for safe unwrapping with defaults
909
+ * @see {@link Optional.map} for transforming Optional values
910
+ */
911
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
912
+ export const at: AtFnOverload = (<E,>(
913
+ ...args:
914
+ | readonly [array: readonly E[], index: SizeType.ArgArr]
915
+ | readonly [index: SizeType.ArgArr]
916
+ ): Optional<E> | (<E2>(array: readonly E2[]) => Optional<E2>) => {
917
+ switch (args.length) {
918
+ case 2: {
919
+ const [array, index] = args;
920
+ return pipe(index < 0 ? array.length + index : index).map(
921
+ (normalizedIndex) =>
922
+ normalizedIndex < 0 || normalizedIndex >= array.length
923
+ ? Optional.none
924
+ : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
925
+ Optional.some(array[normalizedIndex]!),
926
+ ).value;
927
+ }
928
+ case 1: {
929
+ const [index] = args;
930
+ return <E2,>(array: readonly E2[]) => at(array, index);
931
+ }
932
+ }
933
+ }) as AtFnOverload;
934
+
935
+ /**
936
+ * Function type for safely accessing an array element at a given index.
937
+ */
938
+ type AtFnOverload = {
939
+ <E>(array: readonly E[], index: SizeType.ArgArr): Optional<E>;
940
+
941
+ // Curried version
942
+ (index: SizeType.ArgArr): <E>(array: readonly E[]) => Optional<E>;
943
+ };
944
+
945
+ /**
946
+ * Returns the first element of an array wrapped in an Optional.
947
+ *
948
+ * This function provides type-safe access to the first element with precise return types:
949
+ * - For empty arrays: returns `Optional.None`
950
+ * - For tuples with known first element: returns `Optional.Some<FirstElementType>`
951
+ * - For non-empty arrays: returns `Optional.Some<ElementType>`
952
+ * - For general arrays: returns `Optional<ElementType>`
953
+ *
954
+ * The function leverages TypeScript's type system to provide the most precise return type
955
+ * based on the input array type, making it safer than direct indexing.
956
+ *
957
+ * @template E The type of elements in the array.
958
+ * @param array The array to get the first element from.
959
+ * @returns An Optional containing the first element:
960
+ * - `Optional.None` if the array is empty
961
+ * - `Optional.Some<E>` containing the first element if the array is non-empty
962
+ *
963
+ * @example
964
+ * ```typescript
965
+ * // Empty array - precise None type
966
+ * const emptyResult = Arr.head([]); // Optional.None
967
+ * console.log(Optional.isNone(emptyResult)); // true
968
+ *
969
+ * // Tuple with known structure - precise Some type
970
+ * const tupleResult = Arr.head(['first', 'second', 'third'] as const);
971
+ * // Type: Optional.Some<'first'>
972
+ * if (Optional.isSome(tupleResult)) {
973
+ * console.log(tupleResult.value); // 'first' - TypeScript knows exact type
974
+ * }
975
+ *
976
+ * // Non-empty array - guaranteed Some type
977
+ * const nonEmpty: NonEmptyArray<number> = [10, 20, 30] as NonEmptyArray<number>;
978
+ * const guaranteedResult = Arr.head(nonEmpty); // Optional.Some<number>
979
+ * // No need to check - always Some for NonEmptyArray
980
+ *
981
+ * // General array - may be Some or None
982
+ * const generalArray: number[] = [1, 2, 3];
983
+ * const maybeResult = Arr.head(generalArray); // Optional<number>
984
+ * if (Optional.isSome(maybeResult)) {
985
+ * console.log(`First element: ${maybeResult.value}`);
986
+ * } else {
987
+ * console.log('Array is empty');
988
+ * }
989
+ *
990
+ * // Working with different types
991
+ * const strings = ['hello', 'world'];
992
+ * const firstString = Arr.head(strings); // Optional<string>
993
+ *
994
+ * const objects = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
995
+ * const firstObject = Arr.head(objects); // Optional<{id: number, name: string}>
996
+ *
997
+ * // Functional composition
998
+ * const getFirstElements = (arrays: readonly number[][]) =>
999
+ * arrays.map(Arr.head).filter(Optional.isSome);
1000
+ *
1001
+ * const nestedArrays = [[1, 2], [3, 4], [], [5]];
1002
+ * const firstElements = getFirstElements(nestedArrays);
1003
+ * // [Optional.Some(1), Optional.Some(3), Optional.Some(5)]
1004
+ *
1005
+ * // Type inference examples
1006
+ * expectType<typeof emptyResult, Optional.None>('=');
1007
+ * expectType<typeof tupleResult, Optional.Some<'first'>>('=');
1008
+ * expectType<typeof guaranteedResult, Optional.Some<number>>('=');
1009
+ * expectType<typeof maybeResult, Optional<number>>('=');
1010
+ * ```
1011
+ *
1012
+ * @see {@link last} for getting the last element
1013
+ * @see {@link at} for accessing elements at specific indices
1014
+ * @see {@link tail} for getting all elements except the first
1015
+ */
1016
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
1017
+ export const head: HeadFnOverload = (<E,>(array: readonly E[]) => {
1018
+ const element = array.at(0);
1019
+ return element === undefined ? Optional.none : Optional.some(element);
1020
+ }) as HeadFnOverload;
1021
+
1022
+ type HeadFnOverload = {
1023
+ /**
1024
+ * Get head of empty array.
1025
+ */
1026
+ (array: readonly []): Optional.None;
1027
+
1028
+ /**
1029
+ * Get head of tuple.
1030
+ */
1031
+ <E, L extends readonly unknown[]>(
1032
+ array: readonly [E, ...L],
1033
+ ): Optional.Some<E>;
1034
+
1035
+ /**
1036
+ * Get head of non-empty array.
1037
+ */
1038
+ <E>(array: NonEmptyArray<E>): Optional.Some<E>;
1039
+
1040
+ /**
1041
+ * Get head of any array.
1042
+ */
1043
+ <E>(array: readonly E[]): Optional<E>;
1044
+ };
1045
+
1046
+ /**
1047
+ * Returns the last element of an array wrapped in an Optional.
1048
+ *
1049
+ * This function provides type-safe access to the last element with precise return types:
1050
+ * - For empty arrays: returns `Optional.None`
1051
+ * - For tuples with known last element: returns `Optional.Some<LastElementType>`
1052
+ * - For non-empty arrays: returns `Optional.Some<ElementType>`
1053
+ * - For general arrays: returns `Optional<ElementType>`
1054
+ *
1055
+ * The function leverages TypeScript's type system to provide the most precise return type
1056
+ * based on the input array type, making it safer than direct indexing.
1057
+ *
1058
+ * @template E The type of elements in the array.
1059
+ * @param array The array to get the last element from.
1060
+ * @returns An Optional containing the last element:
1061
+ * - `Optional.None` if the array is empty
1062
+ * - `Optional.Some<E>` containing the last element if the array is non-empty
1063
+ *
1064
+ * @example
1065
+ * ```typescript
1066
+ * // Empty array - precise None type
1067
+ * const emptyResult = Arr.last([]); // Optional.None
1068
+ * console.log(Optional.isNone(emptyResult)); // true
1069
+ *
1070
+ * // Tuple with known structure - precise Some type
1071
+ * const tupleResult = Arr.last(['first', 'middle', 'last'] as const);
1072
+ * // Type: Optional.Some<'last'>
1073
+ * if (Optional.isSome(tupleResult)) {
1074
+ * console.log(tupleResult.value); // 'last' - TypeScript knows exact type
1075
+ * }
1076
+ *
1077
+ * // Non-empty array - guaranteed Some type
1078
+ * const nonEmpty: NonEmptyArray<number> = [10, 20, 30] as NonEmptyArray<number>;
1079
+ * const guaranteedResult = Arr.last(nonEmpty); // Optional.Some<number>
1080
+ * // No need to check - always Some for NonEmptyArray
1081
+ *
1082
+ * // General array - may be Some or None
1083
+ * const generalArray: number[] = [1, 2, 3];
1084
+ * const maybeResult = Arr.last(generalArray); // Optional<number>
1085
+ * if (Optional.isSome(maybeResult)) {
1086
+ * console.log(`Last element: ${maybeResult.value}`);
1087
+ * } else {
1088
+ * console.log('Array is empty');
1089
+ * }
1090
+ *
1091
+ * // Working with different types
1092
+ * const strings = ['hello', 'world', 'example'];
1093
+ * const lastString = Arr.last(strings); // Optional<string>
1094
+ *
1095
+ * const coordinates = [{x: 0, y: 0}, {x: 1, y: 1}, {x: 2, y: 2}];
1096
+ * const lastCoordinate = Arr.last(coordinates); // Optional<{x: number, y: number}>
1097
+ *
1098
+ * // Single element arrays
1099
+ * const single = [42];
1100
+ * const singleResult = Arr.last(single); // Optional<number> containing 42
1101
+ *
1102
+ * // Functional composition with arrays of arrays
1103
+ * const getLastElements = (arrays: readonly string[][]) =>
1104
+ * arrays.map(Arr.last).filter(Optional.isSome);
1105
+ *
1106
+ * const nestedArrays = [['a', 'b'], ['c'], [], ['d', 'e', 'f']];
1107
+ * const lastElements = getLastElements(nestedArrays);
1108
+ * // [Optional.Some('b'), Optional.Some('c'), Optional.Some('f')]
1109
+ *
1110
+ * // Common pattern: get last element or default
1111
+ * const data = [10, 20, 30];
1112
+ * const lastOrDefault = Optional.unwrapOr(Arr.last(data), 0); // 30
1113
+ * const emptyLastOrDefault = Optional.unwrapOr(Arr.last([]), 0); // 0
1114
+ *
1115
+ * // Type inference examples
1116
+ * expectType<typeof emptyResult, Optional.None>('=');
1117
+ * expectType<typeof tupleResult, Optional.Some<'last'>>('=');
1118
+ * expectType<typeof guaranteedResult, Optional.Some<number>>('=');
1119
+ * expectType<typeof maybeResult, Optional<number>>('=');
1120
+ * ```
1121
+ *
1122
+ * @see {@link head} for getting the first element
1123
+ * @see {@link at} for accessing elements at specific indices with negative indexing support
1124
+ * @see {@link butLast} for getting all elements except the last
1125
+ */
1126
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
1127
+ export const last: LastFnOverload = (<E,>(array: readonly E[]) => {
1128
+ const element = array.at(-1);
1129
+ return element === undefined ? Optional.none : Optional.some(element);
1130
+ }) as LastFnOverload;
1131
+
1132
+ type LastFnOverload = {
1133
+ /**
1134
+ * Get last of empty array.
1135
+ */
1136
+ (array: readonly []): Optional.None;
1137
+
1138
+ /**
1139
+ * Get last of tuple.
1140
+ */
1141
+ <Ar extends readonly unknown[], L>(
1142
+ array: readonly [...Ar, L],
1143
+ ): Optional.Some<L>;
1144
+
1145
+ /**
1146
+ * Get last of non-empty array.
1147
+ */
1148
+ <E>(array: NonEmptyArray<E>): Optional.Some<E>;
1149
+
1150
+ /**
1151
+ * Get last of any array.
1152
+ */
1153
+ <E>(array: readonly E[]): Optional<E>;
1154
+ };
1155
+
1156
+ // slicing
1157
+
1158
+ /**
1159
+ * Slices an array with automatically clamped start and end indices for safe bounds handling.
1160
+ *
1161
+ * This function provides a safer alternative to `Array.slice()` by automatically clamping
1162
+ * the start and end indices to valid bounds, preventing out-of-bounds access and ensuring
1163
+ * consistent behavior regardless of input values.
1164
+ *
1165
+ * **Clamping Behavior:**
1166
+ * - `start` is clamped to `[0, array.length]`
1167
+ * - `end` is clamped to `[clampedStart, array.length]` (ensuring end ≥ start)
1168
+ * - Invalid ranges (start > end after clamping) return empty arrays
1169
+ * - Negative indices are clamped to 0, large indices are clamped to array.length
1170
+ *
1171
+ * **Curried Usage:** This function supports currying - when called with only start and end
1172
+ * indices, it returns a function that can be applied to arrays.
1173
+ *
1174
+ * @template E The type of elements in the array.
1175
+ * @param array The array to slice (when using direct call syntax).
1176
+ * @param start The start index for the slice (inclusive). Will be clamped to valid bounds.
1177
+ * @param end The end index for the slice (exclusive). Will be clamped to valid bounds.
1178
+ * @returns A new immutable array containing the sliced elements. Always returns a valid array,
1179
+ * never throws for out-of-bounds indices.
1180
+ *
1181
+ * @example
1182
+ * ```typescript
1183
+ * const data = [10, 20, 30, 40, 50];
1184
+ *
1185
+ * // Normal slicing
1186
+ * const middle = Arr.sliceClamped(data, 1, 4); // [20, 30, 40]
1187
+ * const beginning = Arr.sliceClamped(data, 0, 2); // [10, 20]
1188
+ * const end = Arr.sliceClamped(data, 3, 5); // [40, 50]
1189
+ *
1190
+ * // Automatic clamping for out-of-bounds indices
1191
+ * const clampedStart = Arr.sliceClamped(data, -10, 3); // [10, 20, 30] (start clamped to 0)
1192
+ * const clampedEnd = Arr.sliceClamped(data, 2, 100); // [30, 40, 50] (end clamped to length)
1193
+ * const bothClamped = Arr.sliceClamped(data, -5, 100); // [10, 20, 30, 40, 50] (entire array)
1194
+ *
1195
+ * // Invalid ranges become empty arrays
1196
+ * const emptyReversed = Arr.sliceClamped(data, 4, 1); // [] (start > end after clamping)
1197
+ * const emptyAtEnd = Arr.sliceClamped(data, 5, 10); // [] (start at end of array)
1198
+ *
1199
+ * // Edge cases
1200
+ * const emptyArray = Arr.sliceClamped([], 0, 5); // [] (empty input)
1201
+ * const singleElement = Arr.sliceClamped([42], 0, 1); // [42]
1202
+ * const fullCopy = Arr.sliceClamped(data, 0, data.length); // [10, 20, 30, 40, 50]
1203
+ *
1204
+ * // Curried usage for functional composition
1205
+ * const takeFirst3 = Arr.sliceClamped(0, 3);
1206
+ * const getMiddle2 = Arr.sliceClamped(1, 3);
1207
+ *
1208
+ * const arrays = [
1209
+ * [1, 2, 3, 4, 5],
1210
+ * [10, 20],
1211
+ * [100, 200, 300, 400, 500, 600]
1212
+ * ];
1213
+ *
1214
+ * const first3Elements = arrays.map(takeFirst3);
1215
+ * // [[1, 2, 3], [10, 20], [100, 200, 300]]
1216
+ *
1217
+ * const middle2Elements = arrays.map(getMiddle2);
1218
+ * // [[2, 3], [20], [200, 300]]
1219
+ *
1220
+ * // Pipe composition
1221
+ * const result = pipe([1, 2, 3, 4, 5, 6])
1222
+ * .map(takeFirst3)
1223
+ * .map(Arr.sum)
1224
+ * .value; // 6 (sum of [1, 2, 3])
1225
+ *
1226
+ * // Comparison with regular Array.slice (which can throw or behave unexpectedly)
1227
+ * try {
1228
+ * // Regular slice with out-of-bounds - works but may be unintuitive
1229
+ * const regularSlice = data.slice(-10, 100); // [10, 20, 30, 40, 50]
1230
+ * // sliceClamped provides same safe behavior explicitly
1231
+ * const clampedSlice = Arr.sliceClamped(data, -10, 100); // [10, 20, 30, 40, 50]
1232
+ * } catch (error) {
1233
+ * // sliceClamped never throws
1234
+ * }
1235
+ * ```
1236
+ *
1237
+ * @see {@link take} for taking the first N elements
1238
+ * @see {@link skip} for skipping the first N elements
1239
+ * @see {@link takeLast} for taking the last N elements
1240
+ */
1241
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
1242
+ export const sliceClamped: SliceClampedFnOverload = (<E,>(
1243
+ ...args:
1244
+ | readonly [readonly E[], SizeType.ArgArr, SizeType.ArgArr]
1245
+ | readonly [SizeType.ArgArr, SizeType.ArgArr]
1246
+ ) => {
1247
+ switch (args.length) {
1248
+ case 3: {
1249
+ const [array, start, end] = args;
1250
+ const startClamped = Num.clamp(0, array.length)(start);
1251
+ // Ensure endClamped is not less than startClamped.
1252
+ const endClamped = Num.clamp(startClamped, array.length)(end);
1253
+ return array.slice(startClamped, endClamped);
1254
+ }
1255
+ case 2: {
1256
+ const [start, end] = args;
1257
+ return <E2,>(array: readonly E2[]) => sliceClamped(array, start, end);
1258
+ }
1259
+ }
1260
+ }) as SliceClampedFnOverload;
1261
+
1262
+ type SliceClampedFnOverload = {
1263
+ <E>(
1264
+ array: readonly E[],
1265
+ start: SizeType.ArgArr,
1266
+ end: SizeType.ArgArr,
1267
+ ): readonly E[];
1268
+
1269
+ // Curried version
1270
+ (
1271
+ start: SizeType.ArgArr,
1272
+ end: SizeType.ArgArr,
1273
+ ): <E>(array: readonly E[]) => readonly E[];
1274
+ };
1275
+
1276
+ /**
1277
+ * Returns all elements of an array except the first one.
1278
+ * @template E The type of the array (can be a tuple for more precise typing).
1279
+ * @param array The input array.
1280
+ * @returns A new array containing all elements except the first. The type is inferred as `List.Tail<T>`.
1281
+ * @example
1282
+ * ```ts
1283
+ * Arr.tail([1, 2, 3] as const); // [2, 3]
1284
+ * Arr.tail([1] as const); // []
1285
+ * Arr.tail([]); // []
1286
+ * ```
1287
+ */
1288
+ export const tail = <Ar extends readonly unknown[]>(
1289
+ array: Ar,
1290
+ ): List.Tail<Ar> =>
1291
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
1292
+ array.slice(1) as unknown as List.Tail<Ar>;
1293
+
1294
+ /**
1295
+ * Returns all elements of an array except the last one.
1296
+ * @template E The type of the array (can be a tuple for more precise typing).
1297
+ * @param array The input array.
1298
+ * @returns A new array containing all elements except the last. The type is inferred as `List.ButLast<T>`.
1299
+ * @example
1300
+ * ```ts
1301
+ * Arr.butLast([1, 2, 3] as const); // [1, 2]
1302
+ * Arr.butLast([1] as const); // []
1303
+ * Arr.butLast([]); // []
1304
+ * ```
1305
+ */
1306
+ export const butLast = <Ar extends readonly unknown[]>(
1307
+ array: Ar,
1308
+ ): List.ButLast<Ar> =>
1309
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
1310
+ (isEmpty(array) ? [] : array.slice(0, -1)) as unknown as List.ButLast<Ar>;
1311
+
1312
+ /**
1313
+ * Takes the first N elements from an array.
1314
+ *
1315
+ * - If the array is a tuple, the return type is inferred as a tuple of the first N elements.
1316
+ * - If the array is a NonEmptyArray and N is a SizeType.ArgArrPositive, returns a NonEmptyArray.
1317
+ * - Otherwise, returns a readonly array of up to N elements.
1318
+ *
1319
+ * @template E The type of the array (can be a tuple for more precise typing).
1320
+ * @template N The number of elements to take, constrained to `SmallUint`.
1321
+ * @param array The input array.
1322
+ * @param num The number of elements to take.
1323
+ * @returns A new array containing the first N elements.
1324
+ * @example
1325
+ * ```ts
1326
+ * // Regular usage
1327
+ * Arr.take([1, 2, 3, 4] as const, 2); // [1, 2]
1328
+ *
1329
+ * // Curried usage for pipe composition
1330
+ * const takeFirst3 = Arr.take(3);
1331
+ * const result = pipe([1, 2, 3, 4, 5]).map(takeFirst3).value;
1332
+ * console.log(result); // [1, 2, 3]
1333
+ * ```
1334
+ */
1335
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
1336
+ export const take: TakeFnOverload = (<Ar extends readonly unknown[]>(
1337
+ ...args:
1338
+ | readonly [array: Ar, num: SizeType.ArgArrNonNegative]
1339
+ | readonly [num: SizeType.ArgArrNonNegative]
1340
+ ) => {
1341
+ switch (args.length) {
1342
+ case 2: {
1343
+ const [array, num] = args;
1344
+ return sliceClamped(array, 0, num);
1345
+ }
1346
+ case 1: {
1347
+ const [num] = args;
1348
+ return <E,>(array: readonly E[]) => take(array, num);
1349
+ }
1350
+ }
1351
+ }) as unknown as TakeFnOverload;
1352
+
1353
+ type TakeFnOverload = {
1354
+ <Ar extends readonly unknown[], N extends SmallUint>(
1355
+ array: Ar,
1356
+ num: N,
1357
+ ): List.Take<N, Ar>;
1358
+
1359
+ // curried version
1360
+ <N extends SmallUint>(
1361
+ num: N,
1362
+ ): <Ar extends readonly unknown[]>(array: Ar) => List.Take<N, Ar>;
1363
+
1364
+ <E>(
1365
+ array: NonEmptyArray<E>,
1366
+ num: SizeType.ArgArrPositive,
1367
+ ): NonEmptyArray<E>;
1368
+
1369
+ // curried version
1370
+ (
1371
+ num: SizeType.ArgArrPositive,
1372
+ ): <E>(array: NonEmptyArray<E>) => NonEmptyArray<E>;
1373
+
1374
+ <E>(array: readonly E[], num: SizeType.ArgArrNonNegative): readonly E[];
1375
+
1376
+ // curried version
1377
+ (num: SizeType.ArgArrNonNegative): <E>(array: readonly E[]) => readonly E[];
1378
+ };
1379
+
1380
+ /**
1381
+ * Takes the last N elements from an array.
1382
+ *
1383
+ * - If the array is a tuple, the return type is inferred as a tuple of the last N elements.
1384
+ * - If the array is a non-empty array and N is a positive integer, returns a non-empty array.
1385
+ * - Otherwise, returns a readonly array of up to N elements.
1386
+ *
1387
+ * @template E The type of the array (can be a tuple for more precise typing).
1388
+ * @template N The number of elements to take, constrained to `SmallUint`.
1389
+ * @param array The input array.
1390
+ * @param num The number of elements to take.
1391
+ * @returns A new array containing the last N elements.
1392
+ * @example
1393
+ * ```ts
1394
+ * // Regular usage
1395
+ * Arr.takeLast([1, 2, 3, 4] as const, 2); // [3, 4]
1396
+ *
1397
+ * // Curried usage for pipe composition
1398
+ * const takeLast2 = Arr.takeLast(2);
1399
+ * const result = pipe([1, 2, 3, 4, 5]).map(takeLast2).value;
1400
+ * console.log(result); // [4, 5]
1401
+ * ```
1402
+ */
1403
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
1404
+ export const takeLast: TakeLastFnOverload = (<Ar extends readonly unknown[]>(
1405
+ ...args:
1406
+ | readonly [array: Ar, num: SizeType.ArgArrNonNegative]
1407
+ | readonly [num: SizeType.ArgArrNonNegative]
1408
+ ) => {
1409
+ switch (args.length) {
1410
+ case 2: {
1411
+ const [array, num] = args;
1412
+ return sliceClamped(array, Uint32.sub(size(array), num), size(array));
1413
+ }
1414
+ case 1: {
1415
+ const [num] = args;
1416
+ return <E,>(array: readonly E[]) => takeLast(array, num);
1417
+ }
1418
+ }
1419
+ }) as unknown as TakeLastFnOverload;
1420
+
1421
+ type TakeLastFnOverload = {
1422
+ <Ar extends readonly unknown[], N extends SmallUint>(
1423
+ array: Ar,
1424
+ num: N,
1425
+ ): List.TakeLast<N, Ar>;
1426
+
1427
+ // curried version
1428
+ <N extends SmallUint>(
1429
+ num: N,
1430
+ ): <Ar extends readonly unknown[]>(array: Ar) => List.TakeLast<N, Ar>;
1431
+
1432
+ <E>(
1433
+ array: NonEmptyArray<E>,
1434
+ num: SizeType.ArgArrPositive,
1435
+ ): NonEmptyArray<E>;
1436
+
1437
+ // curried version
1438
+ (
1439
+ num: SizeType.ArgArrPositive,
1440
+ ): <E>(array: NonEmptyArray<E>) => NonEmptyArray<E>;
1441
+
1442
+ <E>(array: readonly E[], num: SizeType.ArgArrNonNegative): readonly E[];
1443
+
1444
+ // curried version
1445
+ (num: SizeType.ArgArrNonNegative): <E>(array: readonly E[]) => readonly E[];
1446
+ };
1447
+
1448
+ /**
1449
+ * Skips the first N elements of an array.
1450
+ *
1451
+ * - If the array is a tuple, the return type is inferred as a tuple with the first N elements removed.
1452
+ * - If the array is a non-empty array and N is a positive integer, returns a readonly array (may be empty).
1453
+ * - Otherwise, returns a readonly array with the first N elements skipped.
1454
+ *
1455
+ * @template E The type of the array (can be a tuple for more precise typing).
1456
+ * @template N The number of elements to skip, constrained to `SmallUint`.
1457
+ * @param array The input array.
1458
+ * @param num The number of elements to skip.
1459
+ * @returns A new array containing the elements after skipping the first N.
1460
+ * @example
1461
+ * ```ts
1462
+ * // Regular usage
1463
+ * Arr.skip([1, 2, 3, 4] as const, 2); // [3, 4]
1464
+ *
1465
+ * // Curried usage for pipe composition
1466
+ * const skipFirst2 = Arr.skip(2);
1467
+ * const result = pipe([1, 2, 3, 4, 5]).map(skipFirst2).value;
1468
+ * console.log(result); // [3, 4, 5]
1469
+ * ```
1470
+ */
1471
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
1472
+ export const skip: SkipFnOverload = (<E,>(
1473
+ ...args:
1474
+ | readonly [readonly E[], SizeType.ArgArrNonNegative]
1475
+ | readonly [SizeType.ArgArrNonNegative]
1476
+ ) => {
1477
+ switch (args.length) {
1478
+ case 2: {
1479
+ const [array, num] = args;
1480
+ return sliceClamped(array, num, size(array));
1481
+ }
1482
+ case 1: {
1483
+ const [num] = args;
1484
+ return <E2,>(array: readonly E2[]) => skip(array, num);
1485
+ }
1486
+ }
1487
+ }) as SkipFnOverload;
1488
+
1489
+ type SkipFnOverload = {
1490
+ <Ar extends readonly unknown[], N extends SmallUint>(
1491
+ array: Ar,
1492
+ num: N,
1493
+ ): List.Skip<N, Ar>;
1494
+
1495
+ <E>(array: NonEmptyArray<E>, num: SizeType.ArgArrPositive): readonly E[];
1496
+
1497
+ <E>(array: readonly E[], num: SizeType.ArgArrNonNegative): readonly E[];
1498
+
1499
+ // curried version
1500
+ <N extends SmallUint>(
1501
+ num: N,
1502
+ ): <Ar extends readonly unknown[]>(array: Ar) => List.Skip<N, Ar>;
1503
+
1504
+ (
1505
+ num: SizeType.ArgArrPositive,
1506
+ ): <E>(array: NonEmptyArray<E>) => readonly E[];
1507
+
1508
+ (num: SizeType.ArgArrNonNegative): <E>(array: readonly E[]) => readonly E[];
1509
+ };
1510
+
1511
+ /**
1512
+ * Skips the last N elements of an array.
1513
+ *
1514
+ * - If the array is a tuple, the return type is inferred as a tuple with the last N elements removed.
1515
+ * - If the array is a non-empty array and N is a positive integer, returns a readonly array (may be empty).
1516
+ * - Otherwise, returns a readonly array with the last N elements skipped.
1517
+ *
1518
+ * @template E The type of the array (can be a tuple for more precise typing).
1519
+ * @template N The number of elements to skip, constrained to `SmallUint`.
1520
+ * @param array The input array.
1521
+ * @param num The number of elements to skip from the end.
1522
+ * @returns A new array containing the elements after skipping the last N.
1523
+ * @example
1524
+ * ```ts
1525
+ * // Regular usage
1526
+ * Arr.skipLast([1, 2, 3, 4] as const, 2); // [1, 2]
1527
+ *
1528
+ * // Curried usage for pipe composition
1529
+ * const skipLast2 = Arr.skipLast(2);
1530
+ * const result = pipe([1, 2, 3, 4, 5]).map(skipLast2).value;
1531
+ * console.log(result); // [1, 2, 3]
1532
+ * ```
1533
+ */
1534
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
1535
+ export const skipLast: SkipLastFnOverload = (<E,>(
1536
+ ...args:
1537
+ | readonly [array: readonly E[], num: SizeType.ArgArrNonNegative]
1538
+ | readonly [num: SizeType.ArgArrNonNegative]
1539
+ ): readonly E[] | ((array: readonly E[]) => readonly E[]) => {
1540
+ switch (args.length) {
1541
+ case 2: {
1542
+ const [array, num] = args;
1543
+ return sliceClamped(array, 0, Uint32.sub(size(array), num));
1544
+ }
1545
+ case 1: {
1546
+ const [num] = args;
1547
+ return <E2,>(array: readonly E2[]) => skipLast(array, num);
1548
+ }
1549
+ }
1550
+ }) as SkipLastFnOverload;
1551
+
1552
+ type SkipLastFnOverload = {
1553
+ <Ar extends readonly unknown[], N extends SmallUint>(
1554
+ array: Ar,
1555
+ num: N,
1556
+ ): List.SkipLast<N, Ar>;
1557
+
1558
+ <E>(array: readonly E[], num: SizeType.ArgArrNonNegative): readonly E[];
1559
+
1560
+ // curried version
1561
+ <N extends SmallUint>(
1562
+ num: N,
1563
+ ): <Ar extends readonly unknown[]>(array: Ar) => List.SkipLast<N, Ar>;
1564
+
1565
+ (num: SizeType.ArgArrNonNegative): <E>(array: readonly E[]) => readonly E[];
1566
+ };
1567
+
1568
+ // modification (returns new array)
1569
+
1570
+ /**
1571
+ * Returns a new array with the element at the specified index updated by a function.
1572
+ *
1573
+ * This function provides immutable array updates with type-safe bounds checking. It applies an updater
1574
+ * function to the element at the given index and returns a new array with the transformed value.
1575
+ * The original array is never modified, ensuring immutability.
1576
+ *
1577
+ * **Type Union Behavior:** When the updater function returns a different type `U` than the original
1578
+ * element type `E`, the result type becomes `readonly (E | U)[]` to accommodate both original and
1579
+ * updated element types. This ensures type safety when elements have different types after updating.
1580
+ *
1581
+ * **Bounds Checking:** Unlike native array access which can cause runtime errors, this function
1582
+ * performs safe bounds checking:
1583
+ * - **Valid index:** Creates new array with updated element
1584
+ * - **Invalid index:** Returns the original array unchanged (no errors thrown)
1585
+ * - **Negative index:** Treated as invalid (returns original array)
1586
+ *
1587
+ * **Curried Usage:** Supports currying for functional composition - when called with only index and
1588
+ * updater, returns a reusable function that can be applied to arrays.
1589
+ *
1590
+ * @template E The type of elements in the original array.
1591
+ * @template U The type of the value returned by the updater function.
1592
+ * @param array The input array to update. Can be any readonly array.
1593
+ * @param index The index of the element to update. Must be a non-negative {@link SizeType.ArgArrNonNegative}.
1594
+ * - **Valid range:** `0 <= index < array.length`
1595
+ * - **Out of bounds:** Returns original array unchanged
1596
+ * - **Negative values:** Not allowed by type system (non-negative constraint)
1597
+ * @param updater A function `(prev: E) => U` that transforms the existing element:
1598
+ * - **prev:** The current element at the specified index
1599
+ * - **returns:** The new value to place at that index (can be different type)
1600
+ * @returns A new `readonly (E | U)[]` array where:
1601
+ * - All elements except the target index remain unchanged (type `E`)
1602
+ * - The element at the target index is replaced with the updater result (type `U`)
1603
+ * - Type union `E | U` accommodates both original and updated element types
1604
+ * - If index is out of bounds, returns the original array unchanged
1605
+ *
1606
+ * @example
1607
+ * ```typescript
1608
+ * // Basic usage with same type transformation
1609
+ * const numbers = [1, 2, 3, 4, 5];
1610
+ * const doubled = Arr.toUpdated(numbers, 2, x => x * 2);
1611
+ * // readonly number[] -> [1, 2, 6, 4, 5]
1612
+ *
1613
+ * // Type union when updater returns different type
1614
+ * const mixed = Arr.toUpdated(numbers, 1, x => `value: ${x}`);
1615
+ * // readonly (number | string)[] -> [1, 'value: 2', 3, 4, 5]
1616
+ *
1617
+ * // Complex object updates
1618
+ * const users = [
1619
+ * { id: 1, name: 'Alice', active: true },
1620
+ * { id: 2, name: 'Bob', active: false },
1621
+ * { id: 3, name: 'Charlie', active: true }
1622
+ * ];
1623
+ *
1624
+ * const activatedUser = Arr.toUpdated(users, 1, user => ({
1625
+ * ...user,
1626
+ * active: true,
1627
+ * lastUpdated: new Date()
1628
+ * }));
1629
+ * // Bob is now active with lastUpdated field
1630
+ *
1631
+ * // Bounds checking behavior
1632
+ * const safe1 = Arr.toUpdated([1, 2, 3], 10, x => x * 2); // [1, 2, 3] (index out of bounds)
1633
+ * const safe2 = Arr.toUpdated([1, 2, 3], 0, x => x * 2); // [2, 2, 3] (valid index)
1634
+ * const safe3 = Arr.toUpdated([], 0, x => x); // [] (empty array, index out of bounds)
1635
+ *
1636
+ * // Functional transformations
1637
+ * const products = [
1638
+ * { name: 'laptop', price: 1000 },
1639
+ * { name: 'mouse', price: 25 },
1640
+ * { name: 'keyboard', price: 75 }
1641
+ * ];
1642
+ *
1643
+ * const discounted = Arr.toUpdated(products, 0, product => ({
1644
+ * ...product,
1645
+ * price: Math.round(product.price * 0.8), // 20% discount
1646
+ * onSale: true
1647
+ * }));
1648
+ * // First product now has discounted price and onSale flag
1649
+ *
1650
+ * // Curried usage for reusable updates
1651
+ * const doubleAtIndex2 = Arr.toUpdated(2, (x: number) => x * 2);
1652
+ * const capitalizeAtIndex0 = Arr.toUpdated(0, (s: string) => s.toUpperCase());
1653
+ * const markCompleteAtIndex = (index: number) =>
1654
+ * Arr.toUpdated(index, (task: {done: boolean}) => ({...task, done: true}));
1655
+ *
1656
+ * const numberArrays = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
1657
+ * const allDoubled = numberArrays.map(doubleAtIndex2);
1658
+ * // [[1, 2, 6], [4, 5, 12], [7, 8, 18]]
1659
+ *
1660
+ * const words = [['hello', 'world'], ['foo', 'bar'], ['type', 'script']];
1661
+ * const capitalized = words.map(capitalizeAtIndex0);
1662
+ * // [['HELLO', 'world'], ['FOO', 'bar'], ['TYPE', 'script']]
1663
+ *
1664
+ * // Pipe composition for data processing
1665
+ * const processArray = (arr: readonly number[]) => pipe(arr)
1666
+ * .map(Arr.toUpdated(0, x => x * 10)) // Scale first element
1667
+ * .map(Arr.toUpdated(1, x => typeof x === 'number' ? x + 100 : x)) // Add to second if number
1668
+ * .value;
1669
+ *
1670
+ * console.log(processArray([1, 2, 3])); // [10, 102, 3]
1671
+ *
1672
+ * // Multiple sequential updates
1673
+ * const pipeline = (data: readonly number[]) => pipe(data)
1674
+ * .map(Arr.toUpdated(0, x => x * 2))
1675
+ * .map(Arr.toUpdated(1, x => typeof x === 'number' ? x + 10 : x))
1676
+ * .map(Arr.toUpdated(2, x => typeof x === 'number' ? x.toString() : x))
1677
+ * .value;
1678
+ *
1679
+ * console.log(pipeline([1, 2, 3])); // [2, 12, '3'] - readonly (number | string)[]
1680
+ *
1681
+ * // Error-safe updates in data processing
1682
+ * const safeUpdate = <T, U>(
1683
+ * array: readonly T[],
1684
+ * index: number,
1685
+ * updater: (value: T) => U
1686
+ * ) => {
1687
+ * if (index < 0 || index >= array.length) {
1688
+ * console.warn(`Index ${index} out of bounds for array of length ${array.length}`);
1689
+ * return array;
1690
+ * }
1691
+ * return Arr.toUpdated(array, index as SizeType.ArgArrNonNegative, updater);
1692
+ * };
1693
+ *
1694
+ * // Advanced: State management pattern
1695
+ * type AppState = {
1696
+ * users: Array<{id: number, name: string}>;
1697
+ * currentUserId: number;
1698
+ * };
1699
+ *
1700
+ * const updateUserName = (state: AppState, userId: number, newName: string): AppState => {
1701
+ * const userIndex = state.users.findIndex(u => u.id === userId);
1702
+ * if (userIndex === -1) return state;
1703
+ *
1704
+ * return {
1705
+ * ...state,
1706
+ * users: Arr.toUpdated(state.users, userIndex as SizeType.ArgArrNonNegative, user => ({
1707
+ * ...user,
1708
+ * name: newName
1709
+ * }))
1710
+ * };
1711
+ * };
1712
+ *
1713
+ * // Type inference examples showing union types
1714
+ * expectType<typeof doubled, readonly number[]>('='); // Same type
1715
+ * expectType<typeof mixed, readonly (number | string)[]>('='); // Union type
1716
+ * expectType<typeof doubleAtIndex2, <T extends readonly number[]>(array: T) => readonly (number | number)[]>('=');
1717
+ * expectType<typeof safe1, readonly number[]>('='); // Bounds check preserves type
1718
+ * ```
1719
+ *
1720
+ * @see {@link Array.prototype.with} for the native method with different error handling
1721
+ * @see {@link SizeType.ArgArrNonNegative} for the index type constraint
1722
+ * @see Immutable update patterns for functional programming approaches
1723
+ */
1724
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
1725
+ export const toUpdated: ToUpdatedFnOverload = (<E, U>(
1726
+ ...args:
1727
+ | readonly [
1728
+ array: readonly E[],
1729
+ index: SizeType.ArgArrNonNegative,
1730
+ updater: (prev: E) => U,
1731
+ ]
1732
+ | readonly [index: SizeType.ArgArrNonNegative, updater: (prev: E) => U]
1733
+ ) => {
1734
+ switch (args.length) {
1735
+ case 3: {
1736
+ const [array, index, updater] = args;
1737
+ return index < 0 || index >= array.length
1738
+ ? array // Return a copy if index is out of bounds
1739
+ : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unsafe-type-assertion
1740
+ (array as (E | U)[]).with(index, updater(array[index]!));
1741
+ }
1742
+ case 2: {
1743
+ const [index, updater] = args;
1744
+ return (array: readonly E[]) => toUpdated(array, index, updater);
1745
+ }
1746
+ }
1747
+ }) as ToUpdatedFnOverload;
1748
+
1749
+ type ToUpdatedFnOverload = {
1750
+ <E, U>(
1751
+ array: readonly E[],
1752
+ index: SizeType.ArgArrNonNegative,
1753
+ updater: (prev: E) => U,
1754
+ ): readonly (E | U)[];
1755
+
1756
+ // curried version
1757
+ <E, U>(
1758
+ index: SizeType.ArgArrNonNegative,
1759
+ updater: (prev: E) => U,
1760
+ ): (array: readonly E[]) => readonly (E | U)[];
1761
+ };
1762
+
1763
+ /**
1764
+ * Returns a new array with a new value inserted at the specified index.
1765
+ * Index can be out of bounds (e.g., negative or greater than length), `toSpliced` handles this.
1766
+ * @template E The type of elements in the array.
1767
+ * @param array The input array.
1768
+ * @param index The index at which to insert the new value.
1769
+ * @param newValue The value to insert.
1770
+ * @returns A new array with the value inserted.
1771
+ * @example
1772
+ * ```ts
1773
+ * // Regular usage
1774
+ * Arr.toInserted([1, 2, 3], 1, 10); // [1, 10, 2, 3]
1775
+ *
1776
+ * // Curried usage for pipe composition
1777
+ * const insertAtStart = Arr.toInserted(0, 99);
1778
+ * const result = pipe([1, 2, 3]).map(insertAtStart).value;
1779
+ * console.log(result); // [99, 1, 2, 3]
1780
+ * ```
1781
+ */
1782
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
1783
+ export const toInserted: ToInsertedFnOverload = (<E, V = E>(
1784
+ ...args:
1785
+ | readonly [
1786
+ array: readonly E[],
1787
+ index: SizeType.ArgArrNonNegative,
1788
+ newValue: V,
1789
+ ]
1790
+ | readonly [index: SizeType.ArgArrNonNegative, newValue: V]
1791
+ ) => {
1792
+ switch (args.length) {
1793
+ case 3: {
1794
+ const [array, index, newValue] = args;
1795
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
1796
+ return (array as (E | V)[]).toSpliced(
1797
+ index,
1798
+ 0,
1799
+ newValue,
1800
+ ) as MutableNonEmptyArray<E | V>;
1801
+ }
1802
+ case 2: {
1803
+ const [index, newValue] = args;
1804
+ return <E2,>(array: readonly (E2 | V)[]) =>
1805
+ toInserted(array, index, newValue);
1806
+ }
1807
+ }
1808
+ }) as ToInsertedFnOverload;
1809
+
1810
+ type ToInsertedFnOverload = {
1811
+ <E, const V = E>(
1812
+ array: readonly E[],
1813
+ index: SizeType.ArgArrNonNegative,
1814
+ newValue: V,
1815
+ ): NonEmptyArray<E | V>;
1816
+
1817
+ // curried version
1818
+ <E, const V = E>(
1819
+ index: SizeType.ArgArrNonNegative,
1820
+ newValue: V,
1821
+ ): (array: readonly E[]) => NonEmptyArray<E | V>;
1822
+ };
1823
+
1824
+ /**
1825
+ * Returns a new array with the element at the specified index removed.
1826
+ * If index is out of bounds, `toSpliced` handles this (usually by returning a copy).
1827
+ * @template E The type of elements in the array.
1828
+ * @param array The input array.
1829
+ * @param index The index of the element to remove.
1830
+ * @returns A new array with the element removed.
1831
+ * @example
1832
+ * ```ts
1833
+ * // Regular usage
1834
+ * Arr.toRemoved([1, 2, 3], 1); // [1, 3]
1835
+ *
1836
+ * // Curried usage for pipe composition
1837
+ * const removeFirst = Arr.toRemoved(0);
1838
+ * const result = pipe([10, 20, 30]).map(removeFirst).value;
1839
+ * console.log(result); // [20, 30]
1840
+ * ```
1841
+ */
1842
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
1843
+ export const toRemoved: ToRemovedFnOverload = (<E,>(
1844
+ ...args:
1845
+ | readonly [array: readonly E[], index: SizeType.ArgArrNonNegative]
1846
+ | readonly [index: SizeType.ArgArrNonNegative]
1847
+ ) => {
1848
+ switch (args.length) {
1849
+ case 2: {
1850
+ const [array, index] = args;
1851
+ return array.toSpliced(index, 1);
1852
+ }
1853
+ case 1: {
1854
+ const [index] = args;
1855
+ return <E2,>(array: readonly E2[]) => toRemoved(array, index);
1856
+ }
1857
+ }
1858
+ }) as ToRemovedFnOverload;
1859
+
1860
+ type ToRemovedFnOverload = {
1861
+ <E>(array: readonly E[], index: SizeType.ArgArrNonNegative): readonly E[];
1862
+
1863
+ // curried version
1864
+ (
1865
+ index: SizeType.ArgArrNonNegative,
1866
+ ): <E>(array: readonly E[]) => readonly E[];
1867
+ };
1868
+
1869
+ /**
1870
+ * Returns a new array with a value added to the end.
1871
+ * @template E The type of the input array (can be a tuple).
1872
+ * @template V The type of the value to add.
1873
+ * @param array The input array.
1874
+ * @param newValue The value to add.
1875
+ * @returns A new array with the value added to the end. Type is `readonly [...E, V]`.
1876
+ * @example
1877
+ * ```ts
1878
+ * // Regular usage
1879
+ * Arr.toPushed([1, 2] as const, 3); // [1, 2, 3]
1880
+ *
1881
+ * // Curried usage for pipe composition
1882
+ * const addZero = Arr.toPushed(0);
1883
+ * const result = pipe([1, 2, 3]).map(addZero).value;
1884
+ * console.log(result); // [1, 2, 3, 0]
1885
+ * ```
1886
+ */
1887
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
1888
+ export const toPushed: ToPushedFnOverload = (<
1889
+ Ar extends readonly unknown[],
1890
+ const V,
1891
+ >(
1892
+ ...args: readonly [array: Ar, newValue: V] | readonly [newValue: V]
1893
+ ) => {
1894
+ switch (args.length) {
1895
+ case 2: {
1896
+ const [array, newValue] = args;
1897
+ return array.toSpliced(array.length, 0, newValue);
1898
+ }
1899
+ case 1: {
1900
+ const [newValue] = args;
1901
+ return <Ar2 extends readonly unknown[]>(array: Ar2) =>
1902
+ toPushed(array, newValue);
1903
+ }
1904
+ }
1905
+ }) as unknown as ToPushedFnOverload;
1906
+
1907
+ type ToPushedFnOverload = {
1908
+ <Ar extends readonly unknown[], const V>(
1909
+ array: Ar,
1910
+ newValue: V,
1911
+ ): readonly [...Ar, V];
1912
+
1913
+ // curried version
1914
+ <const V>(
1915
+ newValue: V,
1916
+ ): <Ar extends readonly unknown[]>(array: Ar) => readonly [...Ar, V];
1917
+ };
1918
+
1919
+ /**
1920
+ * Returns a new array with a value added to the beginning.
1921
+ * @template E The type of the input array (can be a tuple).
1922
+ * @template V The type of the value to add.
1923
+ * @param array The input array.
1924
+ * @param newValue The value to add.
1925
+ * @returns A new array with the value added to the beginning. Type is `readonly [V, ...E]`.
1926
+ * @example
1927
+ * ```ts
1928
+ * // Regular usage
1929
+ * Arr.toUnshifted([1, 2] as const, 0); // [0, 1, 2]
1930
+ *
1931
+ * // Curried usage for pipe composition
1932
+ * const prependZero = Arr.toUnshifted(0);
1933
+ * const result = pipe([1, 2, 3]).map(prependZero).value;
1934
+ * console.log(result); // [0, 1, 2, 3]
1935
+ * ```
1936
+ */
1937
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
1938
+ export const toUnshifted: ToUnshiftedFnOverload = (<
1939
+ Ar extends readonly unknown[],
1940
+ const V,
1941
+ >(
1942
+ ...args: readonly [array: Ar, newValue: V] | readonly [newValue: V]
1943
+ ) => {
1944
+ switch (args.length) {
1945
+ case 2: {
1946
+ const [array, newValue] = args;
1947
+ return array.toSpliced(0, 0, newValue);
1948
+ }
1949
+ case 1: {
1950
+ const [newValue] = args;
1951
+ return <Ar2 extends readonly unknown[]>(array: Ar2) =>
1952
+ toUnshifted(array, newValue);
1953
+ }
1954
+ }
1955
+ }) as unknown as ToUnshiftedFnOverload;
1956
+
1957
+ type ToUnshiftedFnOverload = {
1958
+ <Ar extends readonly unknown[], const V>(
1959
+ array: Ar,
1960
+ newValue: V,
1961
+ ): readonly [V, ...Ar];
1962
+
1963
+ // curried version
1964
+ <const V>(
1965
+ newValue: V,
1966
+ ): <Ar extends readonly unknown[]>(array: Ar) => readonly [V, ...Ar];
1967
+ };
1968
+
1969
+ /**
1970
+ * Fills an array with a value (creates a new filled array).
1971
+ * @param array The array.
1972
+ * @param value The value to fill with.
1973
+ * @param start The start index.
1974
+ * @param end The end index.
1975
+ * @returns A new filled array.
1976
+ * @example
1977
+ * ```typescript
1978
+ * // Regular usage
1979
+ * const arr = [1, 2, 3, 4, 5];
1980
+ * const result = Arr.toFilled(arr, 0, 1, 4);
1981
+ * console.log(result); // [1, 0, 0, 0, 5]
1982
+ *
1983
+ * // Curried usage for pipe composition
1984
+ * const fillWithZeros = Arr.toFilled(0, 1, 3);
1985
+ * const result2 = pipe([1, 2, 3, 4]).map(fillWithZeros).value;
1986
+ * console.log(result2); // [1, 0, 0, 4]
1987
+ * ```
1988
+ */
1989
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
1990
+ export const toFilled: ToFilledFnOverload = (<E,>(
1991
+ ...args: readonly [array: readonly E[], value: E] | readonly [value: E]
1992
+ ) => {
1993
+ switch (args.length) {
1994
+ case 2: {
1995
+ const [array, value] = args;
1996
+ const cp = castMutable(copy(array));
1997
+ cp.fill(value);
1998
+ return cp;
1999
+ }
2000
+ case 1: {
2001
+ const [value] = args;
2002
+ return (array: readonly E[]) => toFilled(array, value);
2003
+ }
2004
+ }
2005
+ }) as ToFilledFnOverload;
2006
+
2007
+ type ToFilledFnOverload = {
2008
+ <E>(array: readonly E[], value: E): readonly E[];
2009
+
2010
+ // curried version
2011
+ <E>(value: E): (array: readonly E[]) => readonly E[];
2012
+ };
2013
+
2014
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
2015
+ export const toRangeFilled: ToRangeFilledFnOverload = (<E,>(
2016
+ ...args:
2017
+ | readonly [
2018
+ array: readonly E[],
2019
+ value: E,
2020
+ fillRange: readonly [start: SizeType.ArgArr, end: SizeType.ArgArr],
2021
+ ]
2022
+ | readonly [
2023
+ value: E,
2024
+ fillRange: readonly [start: SizeType.ArgArr, end: SizeType.ArgArr],
2025
+ ]
2026
+ ) => {
2027
+ switch (args.length) {
2028
+ case 3: {
2029
+ const [array, value, [start, end]] = args;
2030
+ const cp = castMutable(copy(array));
2031
+ cp.fill(value, start, end);
2032
+ return cp;
2033
+ }
2034
+ case 2: {
2035
+ const [value, fillRange] = args;
2036
+ return (array: readonly unknown[]) =>
2037
+ toRangeFilled(array, value, fillRange);
2038
+ }
2039
+ }
2040
+ }) as ToRangeFilledFnOverload;
2041
+
2042
+ type ToRangeFilledFnOverload = {
2043
+ <E>(
2044
+ array: readonly E[],
2045
+ value: E,
2046
+ fillRange: readonly [start: SizeType.ArgArr, end: SizeType.ArgArr],
2047
+ ): readonly E[];
2048
+
2049
+ // curried version
2050
+ <E>(
2051
+ value: E,
2052
+ fillRange: readonly [start: SizeType.ArgArr, end: SizeType.ArgArr],
2053
+ ): (array: readonly E[]) => readonly E[];
2054
+ };
2055
+
2056
+ // searching
2057
+
2058
+ /**
2059
+ * Safely finds the first element in an array that satisfies a predicate function.
2060
+ *
2061
+ * This function provides type-safe searching with no risk of runtime errors. It returns
2062
+ * the first element that matches the predicate wrapped in an Optional, or Optional.None
2063
+ * if no element is found. The predicate receives the element, its index, and the entire array.
2064
+ *
2065
+ * **Curried Usage:** This function supports currying - when called with only a predicate,
2066
+ * it returns a function that can be applied to arrays, making it ideal for functional composition.
2067
+ *
2068
+ * @template E The type of elements in the array.
2069
+ * @param array The array to search through (when using direct call syntax).
2070
+ * @param predicate A function that tests each element. Called with:
2071
+ * - `value`: The current element being tested
2072
+ * - `index`: The index of the current element (branded as `SizeType.Arr`)
2073
+ * - `arr`: The entire array being searched
2074
+ * @returns An `Optional<E>` containing:
2075
+ * - `Optional.Some<E>` with the first matching element if found
2076
+ * - `Optional.None` if no element satisfies the predicate
2077
+ *
2078
+ * @example
2079
+ * ```typescript
2080
+ * // Basic element finding
2081
+ * const numbers = [1, 2, 3, 4, 5];
2082
+ * const firstEven = Arr.find(numbers, x => x % 2 === 0);
2083
+ * if (Optional.isSome(firstEven)) {
2084
+ * console.log(firstEven.value); // 2 - first even number
2085
+ * }
2086
+ *
2087
+ * // Finding with index information
2088
+ * const findLargeAtEnd = Arr.find(numbers, (value, index) => value > 3 && index > 2);
2089
+ * // Optional.Some(4) - first number > 3 after index 2
2090
+ *
2091
+ * // Finding objects by property
2092
+ * const users = [
2093
+ * { id: 1, name: 'Alice', age: 25 },
2094
+ * { id: 2, name: 'Bob', age: 30 },
2095
+ * { id: 3, name: 'Charlie', age: 35 }
2096
+ * ];
2097
+ *
2098
+ * const adult = Arr.find(users, user => user.age >= 30);
2099
+ * // Optional.Some({ id: 2, name: 'Bob', age: 30 })
2100
+ *
2101
+ * const nonExistent = Arr.find(users, user => user.age > 100);
2102
+ * // Optional.None
2103
+ *
2104
+ * // Empty array handling
2105
+ * const emptyResult = Arr.find([], x => x > 0); // Optional.None
2106
+ *
2107
+ * // Curried usage for functional composition
2108
+ * const findNegative = Arr.find((x: number) => x < 0);
2109
+ * const findLongString = Arr.find((s: string) => s.length > 5);
2110
+ *
2111
+ * const datasets = [
2112
+ * [1, 2, -3, 4],
2113
+ * [5, 6, 7, 8],
2114
+ * [-1, 0, 1]
2115
+ * ];
2116
+ *
2117
+ * const negativeNumbers = datasets.map(findNegative);
2118
+ * // [Optional.Some(-3), Optional.None, Optional.Some(-1)]
2119
+ *
2120
+ * // Pipe composition
2121
+ * const result = pipe(['short', 'medium', 'very long string'])
2122
+ * .map(findLongString)
2123
+ * .map(opt => Optional.unwrapOr(opt, 'default'))
2124
+ * .value; // 'very long string'
2125
+ *
2126
+ * // Complex predicate with all parameters
2127
+ * const findSpecial = (arr: readonly number[]) =>
2128
+ * Arr.find(arr, (value, index, array) => {
2129
+ * return value > 10 && index > 0 && index < array.length - 1;
2130
+ * });
2131
+ *
2132
+ * const hasMiddleSpecial = findSpecial([5, 15, 20, 3]);
2133
+ * // Optional.Some(15) - first value > 10 that's not at start or end
2134
+ *
2135
+ * // Safe unwrapping patterns
2136
+ * const maybeFound = Arr.find(numbers, x => x > 10);
2137
+ * const foundOrDefault = Optional.unwrapOr(maybeFound, 0); // 0 (not found)
2138
+ * const foundOrThrow = Optional.isSome(maybeFound)
2139
+ * ? maybeFound.value
2140
+ * : (() => { throw new Error('Not found'); })();
2141
+ *
2142
+ * // Type inference examples
2143
+ * expectType<typeof firstEven, Optional<number>>('=');
2144
+ * expectType<typeof adult, Optional<{id: number, name: string, age: number}>>('=');
2145
+ * expectType<typeof findNegative, (array: readonly number[]) => Optional<number>>('=');
2146
+ * ```
2147
+ *
2148
+ * @see {@link findIndex} for finding the index instead of the element
2149
+ * @see {@link indexOf} for finding elements by equality
2150
+ * @see {@link Optional} for working with the returned Optional values
2151
+ */
2152
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
2153
+ export const find: FindFnOverload = (<E,>(
2154
+ ...args:
2155
+ | readonly [
2156
+ array: readonly E[],
2157
+ predicate: (
2158
+ value: E,
2159
+ index: SizeType.Arr,
2160
+ arr: readonly E[],
2161
+ ) => boolean,
2162
+ ]
2163
+ | readonly [
2164
+ predicate: (
2165
+ value: E,
2166
+ index: SizeType.Arr,
2167
+ arr: readonly E[],
2168
+ ) => boolean,
2169
+ ]
2170
+ ) => {
2171
+ switch (args.length) {
2172
+ case 2: {
2173
+ const [array, predicate] = args;
2174
+ const foundIndex = array.findIndex(
2175
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
2176
+ predicate as (value: E, index: number, arr: readonly E[]) => boolean,
2177
+ );
2178
+
2179
+ return foundIndex === -1
2180
+ ? Optional.none
2181
+ : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
2182
+ Optional.some(array[foundIndex]!);
2183
+ }
2184
+ case 1: {
2185
+ const [predicate] = args;
2186
+ return (array: readonly E[]) => find(array, predicate);
2187
+ }
2188
+ }
2189
+ }) as FindFnOverload;
2190
+
2191
+ type FindFnOverload = {
2192
+ <E>(
2193
+ array: readonly E[],
2194
+ predicate: (value: E, index: SizeType.Arr, arr: readonly E[]) => boolean,
2195
+ ): Optional<E>;
2196
+
2197
+ // curried version
2198
+ <E>(
2199
+ predicate: (value: E, index: SizeType.Arr, arr: readonly E[]) => boolean,
2200
+ ): (array: readonly E[]) => Optional<E>;
2201
+ };
2202
+
2203
+ /**
2204
+ * Safely finds the index of the first element in an array that satisfies a predicate function.
2205
+ *
2206
+ * This function provides type-safe index searching with no risk of runtime errors. It returns
2207
+ * the index of the first element that matches the predicate wrapped in an Optional, or Optional.None
2208
+ * if no element is found. The returned index is branded as `SizeType.Arr` for type safety.
2209
+ *
2210
+ * **Curried Usage:** This function supports currying - when called with only a predicate,
2211
+ * it returns a function that can be applied to arrays, making it ideal for functional composition.
2212
+ *
2213
+ * @template E The type of elements in the array.
2214
+ * @param array The array to search through (when using direct call syntax).
2215
+ * @param predicate A function that tests each element. Called with:
2216
+ * - `value`: The current element being tested
2217
+ * - `index`: The index of the current element (branded as `SizeType.Arr`)
2218
+ * @returns An `Optional<SizeType.Arr>` containing:
2219
+ * - `Optional.Some<SizeType.Arr>` with the index of the first matching element if found
2220
+ * - `Optional.None` if no element satisfies the predicate
2221
+ *
2222
+ * @example
2223
+ * ```typescript
2224
+ * // Basic index finding
2225
+ * const fruits = ['apple', 'banana', 'cherry', 'banana'];
2226
+ * const bananaIndex = Arr.findIndex(fruits, fruit => fruit === 'banana');
2227
+ * if (Optional.isSome(bananaIndex)) {
2228
+ * console.log(bananaIndex.value); // 1 - index of first 'banana'
2229
+ * }
2230
+ *
2231
+ * // Finding with complex conditions
2232
+ * const numbers = [1, 5, 10, 15, 20];
2233
+ * const firstLargeIndex = Arr.findIndex(numbers, (value, index) =>
2234
+ * value > 10 && index > 1
2235
+ * );
2236
+ * // Optional.Some(3) - index of 15 (first value > 10 after index 1)
2237
+ *
2238
+ * // Finding objects by property
2239
+ * const users = [
2240
+ * { id: 1, active: false },
2241
+ * { id: 2, active: true },
2242
+ * { id: 3, active: true }
2243
+ * ];
2244
+ *
2245
+ * const firstActiveIndex = Arr.findIndex(users, user => user.active);
2246
+ * // Optional.Some(1) - index of first active user
2247
+ *
2248
+ * const inactiveAdminIndex = Arr.findIndex(users, user => !user.active && user.id > 5);
2249
+ * // Optional.None - no inactive user with id > 5
2250
+ *
2251
+ * // Empty array handling
2252
+ * const emptyResult = Arr.findIndex([], x => x > 0); // Optional.None
2253
+ *
2254
+ * // Curried usage for functional composition
2255
+ * const findNegativeIndex = Arr.findIndex((x: number) => x < 0);
2256
+ * const findLongStringIndex = Arr.findIndex((s: string) => s.length > 5);
2257
+ *
2258
+ * const datasets = [
2259
+ * [1, 2, -3, 4], // index 2 has negative
2260
+ * [5, 6, 7, 8], // no negative
2261
+ * [-1, 0, 1] // index 0 has negative
2262
+ * ];
2263
+ *
2264
+ * const negativeIndices = datasets.map(findNegativeIndex);
2265
+ * // [Optional.Some(2), Optional.None, Optional.Some(0)]
2266
+ *
2267
+ * // Using found indices for further operations
2268
+ * const data = ['short', 'medium', 'very long string', 'tiny'];
2269
+ * const longStringIndex = Arr.findIndex(data, s => s.length > 8);
2270
+ *
2271
+ * if (Optional.isSome(longStringIndex)) {
2272
+ * const index = longStringIndex.value;
2273
+ * console.log(`Found at position ${index}: ${data[index]}`);
2274
+ * // "Found at position 2: very long string"
2275
+ * }
2276
+ *
2277
+ * // Pipe composition
2278
+ * const result = pipe(['a', 'bb', 'ccc'])
2279
+ * .map(findLongStringIndex)
2280
+ * .map(opt => Optional.unwrapOr(opt, -1))
2281
+ * .value; // 2 (index of 'ccc')
2282
+ *
2283
+ * // Comparing with native findIndex (which returns -1)
2284
+ * const nativeResult = fruits.findIndex(fruit => fruit === 'grape'); // -1
2285
+ * const safeResult = Arr.findIndex(fruits, fruit => fruit === 'grape'); // Optional.None
2286
+ *
2287
+ * // Safe index usage patterns
2288
+ * const maybeIndex = Arr.findIndex(numbers, x => x > 100);
2289
+ * const indexOrDefault = Optional.unwrapOr(maybeIndex, 0); // 0 (not found)
2290
+ *
2291
+ * // Using index for array access
2292
+ * const foundIndex = Arr.findIndex(fruits, f => f.startsWith('c'));
2293
+ * const foundElement = Optional.isSome(foundIndex)
2294
+ * ? fruits[foundIndex.value]
2295
+ * : 'not found';
2296
+ * // 'cherry'
2297
+ *
2298
+ * // Type inference examples
2299
+ * expectType<typeof bananaIndex, Optional<SizeType.Arr>>('=');
2300
+ * expectType<typeof findNegativeIndex, (array: readonly number[]) => Optional<SizeType.Arr>>('=');
2301
+ * ```
2302
+ *
2303
+ * @see {@link find} for finding the element instead of its index
2304
+ * @see {@link indexOf} for finding elements by equality (not predicate)
2305
+ * @see {@link lastIndexOf} for finding the last occurrence
2306
+ * @see {@link Optional} for working with the returned Optional values
2307
+ */
2308
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
2309
+ export const findIndex: FindIndexFnOverload = (<E,>(
2310
+ ...args:
2311
+ | readonly [
2312
+ array: readonly E[],
2313
+ predicate: (value: E, index: SizeType.Arr) => boolean,
2314
+ ]
2315
+ | readonly [predicate: (value: E, index: SizeType.Arr) => boolean]
2316
+ ) => {
2317
+ switch (args.length) {
2318
+ case 2: {
2319
+ const [array, predicate] = args;
2320
+ return pipe(
2321
+ array.findIndex(
2322
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
2323
+ predicate as (value: E, index: number) => boolean,
2324
+ ),
2325
+ ).map((idx) => (idx >= 0 ? asUint32(idx) : -1)).value;
2326
+ }
2327
+ case 1: {
2328
+ const [predicate] = args;
2329
+ return (array: readonly E[]) => findIndex(array, predicate);
2330
+ }
2331
+ }
2332
+ }) as FindIndexFnOverload;
2333
+
2334
+ type FindIndexFnOverload = {
2335
+ <E>(
2336
+ array: readonly E[],
2337
+ predicate: (value: E, index: SizeType.Arr) => boolean,
2338
+ ): SizeType.Arr | -1;
2339
+
2340
+ // curried version
2341
+ <E>(
2342
+ predicate: (value: E, index: SizeType.Arr) => boolean,
2343
+ ): (array: readonly E[]) => SizeType.Arr | -1;
2344
+ };
2345
+
2346
+ /**
2347
+ * Gets the index of a value in an array.
2348
+ * @param array The array to search.
2349
+ * @param searchElement The element to search for.
2350
+ * @param fromIndex The index to start searching from.
2351
+ * @returns The index if found, -1 otherwise.
2352
+ * @example
2353
+ * ```typescript
2354
+ * // Regular usage
2355
+ * const arr = ['a', 'b', 'c', 'b'];
2356
+ * const result = Arr.indexOf(arr, 'b');
2357
+ * if (Optional.isSome(result)) {
2358
+ * console.log(result.value); // 1 (branded as SizeType.Arr)
2359
+ * }
2360
+ *
2361
+ * // Curried usage for pipe composition
2362
+ * const findB = Arr.indexOf('b');
2363
+ * const result2 = pipe(['a', 'b', 'c']).map(findB).value;
2364
+ * console.log(Optional.unwrapOr(result2, -1)); // 1
2365
+ * ```
2366
+ */
2367
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
2368
+ export const indexOf: IndexOfFnOverload = (<E,>(
2369
+ ...args:
2370
+ | readonly [array: readonly E[], searchElement: E]
2371
+ | readonly [searchElement: E]
2372
+ ) => {
2373
+ switch (args.length) {
2374
+ case 2: {
2375
+ const [array, searchElement] = args;
2376
+
2377
+ const index = array.indexOf(searchElement);
2378
+ return index >= 0 ? asUint32(index) : -1;
2379
+ }
2380
+ case 1: {
2381
+ const [searchElement] = args;
2382
+ return (array: readonly E[]) => indexOf(array, searchElement);
2383
+ }
2384
+ }
2385
+ }) as IndexOfFnOverload;
2386
+
2387
+ type IndexOfFnOverload = {
2388
+ <E>(array: readonly E[], searchElement: E): SizeType.Arr | -1;
2389
+
2390
+ // curried version
2391
+ <E>(searchElement: E): (array: readonly E[]) => SizeType.Arr | -1;
2392
+ };
2393
+
2394
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
2395
+ export const indexOfFrom: IndexOfFromFnOverload = (<E,>(
2396
+ ...args:
2397
+ | readonly [
2398
+ array: readonly E[],
2399
+ searchElement: E,
2400
+ fromIndex: SizeType.ArgArr,
2401
+ ]
2402
+ | readonly [searchElement: E, fromIndex: SizeType.ArgArr]
2403
+ ) => {
2404
+ switch (args.length) {
2405
+ case 3: {
2406
+ const [array, searchElement, fromIndex] = args;
2407
+ const index = array.indexOf(searchElement, fromIndex);
2408
+ return index >= 0 ? asUint32(index) : -1;
2409
+ }
2410
+ case 2: {
2411
+ const [searchElement, fromIndex] = args;
2412
+ return (array: readonly E[]) =>
2413
+ indexOfFrom(array, searchElement, fromIndex);
2414
+ }
2415
+ }
2416
+ }) as IndexOfFromFnOverload;
2417
+
2418
+ type IndexOfFromFnOverload = {
2419
+ <E>(
2420
+ array: readonly E[],
2421
+ searchElement: E,
2422
+ fromIndex: SizeType.ArgArr,
2423
+ ): SizeType.Arr | -1;
2424
+
2425
+ // curried version
2426
+ <E>(
2427
+ searchElement: E,
2428
+ fromIndex: SizeType.ArgArr,
2429
+ ): (array: readonly E[]) => SizeType.Arr | -1;
2430
+ };
2431
+
2432
+ /**
2433
+ * Gets the last index of a value in an array.
2434
+ * @param array The array to search.
2435
+ * @param searchElement The element to search for.
2436
+ * @param fromIndex The index to start searching from (searches backwards).
2437
+ * @returns Optional.Some with the index if found, Optional.None otherwise.
2438
+ * @example
2439
+ * ```typescript
2440
+ * // Regular usage
2441
+ * const arr = ['a', 'b', 'c', 'b'];
2442
+ * const result = Arr.lastIndexOf(arr, 'b');
2443
+ * if (Optional.isSome(result)) {
2444
+ * console.log(result.value); // 3 (branded as SizeType.Arr)
2445
+ * }
2446
+ *
2447
+ * // Curried usage for pipe composition
2448
+ * const findLastB = Arr.lastIndexOf('b');
2449
+ * const result2 = pipe(['a', 'b', 'c', 'b']).map(findLastB).value;
2450
+ * console.log(Optional.unwrapOr(result2, -1)); // 3
2451
+ * ```
2452
+ */
2453
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
2454
+ export const lastIndexOf: LastIndexOfFnOverload = (<E,>(
2455
+ ...args:
2456
+ | readonly [array: readonly E[], searchElement: E]
2457
+ | readonly [searchElement: E]
2458
+ ) => {
2459
+ switch (args.length) {
2460
+ case 2: {
2461
+ const [array, searchElement] = args;
2462
+ const index = array.lastIndexOf(searchElement);
2463
+ return index >= 0 ? asUint32(index) : -1;
2464
+ }
2465
+ case 1: {
2466
+ const [searchElement] = args;
2467
+ return (array: readonly E[]) => lastIndexOf(array, searchElement);
2468
+ }
2469
+ }
2470
+ }) as LastIndexOfFnOverload;
2471
+
2472
+ type LastIndexOfFnOverload = {
2473
+ <E>(array: readonly E[], searchElement: E): SizeType.Arr | -1;
2474
+
2475
+ // curried version
2476
+ <E>(searchElement: E): (array: readonly E[]) => SizeType.Arr | -1;
2477
+ };
2478
+
2479
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
2480
+ export const lastIndexOfFrom: LastIndexOfFromFnOverload = (<E,>(
2481
+ ...args:
2482
+ | readonly [
2483
+ array: readonly E[],
2484
+ searchElement: E,
2485
+ fromIndex: SizeType.ArgArr,
2486
+ ]
2487
+ | readonly [searchElement: E, fromIndex: SizeType.ArgArr]
2488
+ ) => {
2489
+ switch (args.length) {
2490
+ case 3: {
2491
+ const [array, searchElement, fromIndex] = args;
2492
+
2493
+ const index = array.lastIndexOf(searchElement, fromIndex);
2494
+
2495
+ return index >= 0 ? asUint32(index) : -1;
2496
+ }
2497
+ case 2: {
2498
+ const [searchElement, fromIndex] = args;
2499
+ return (array: readonly E[]) =>
2500
+ lastIndexOfFrom(array, searchElement, fromIndex);
2501
+ }
2502
+ }
2503
+ }) as LastIndexOfFromFnOverload;
2504
+
2505
+ type LastIndexOfFromFnOverload = {
2506
+ <E>(
2507
+ array: readonly E[],
2508
+ searchElement: E,
2509
+ fromIndex: SizeType.ArgArr,
2510
+ ): SizeType.Arr | -1;
2511
+
2512
+ // curried version
2513
+ <E>(
2514
+ searchElement: E,
2515
+ fromIndex: SizeType.ArgArr,
2516
+ ): (array: readonly E[]) => SizeType.Arr | -1;
2517
+ };
2518
+
2519
+ // reducing value
2520
+
2521
+ /**
2522
+ * Applies a function against an accumulator and each element in the array (from left to right) to reduce it to a single value.
2523
+ * This is an alias for `Array.prototype.reduce`.
2524
+ * @template E The type of elements in the array.
2525
+ * @template S The type of the accumulated value.
2526
+ * @param array The input array.
2527
+ * @param callbackfn A function to execute on each element in the array: `(previousValue: S, currentValue: A, currentIndex: SizeType.Arr) => S`.
2528
+ * @param initialValue The initial value of the accumulator.
2529
+ * @returns The single value that results from the reduction.
2530
+ * @example
2531
+ * ```ts
2532
+ * // Regular usage
2533
+ * Arr.foldl([1, 2, 3], (sum, n) => sum + n, 0); // 6
2534
+ *
2535
+ * // Curried usage for pipe composition
2536
+ * const sumWithZero = Arr.foldl((sum: number, n: number) => sum + n, 0);
2537
+ * const result = pipe([1, 2, 3, 4]).map(sumWithZero).value;
2538
+ * console.log(result); // 10
2539
+ * ```
2540
+ */
2541
+ export const foldl: FoldlFnOverload = <E, P>(
2542
+ ...args:
2543
+ | readonly [
2544
+ array: readonly E[],
2545
+ callbackfn: (
2546
+ previousValue: P,
2547
+ currentValue: E,
2548
+ currentIndex: SizeType.Arr,
2549
+ ) => P,
2550
+ initialValue: P,
2551
+ ]
2552
+ | readonly [
2553
+ callbackfn: (
2554
+ previousValue: P,
2555
+ currentValue: E,
2556
+ currentIndex: SizeType.Arr,
2557
+ ) => P,
2558
+ initialValue: P,
2559
+ ]
2560
+ ) => {
2561
+ switch (args.length) {
2562
+ case 3: {
2563
+ const [array, callbackfn, initialValue] = args;
2564
+ return array.reduce(
2565
+ (prev, curr, index) => callbackfn(prev, curr, asUint32(index)),
2566
+ initialValue,
2567
+ );
2568
+ }
2569
+ case 2: {
2570
+ const [callbackfn, initialValue] = args;
2571
+ return (array: readonly E[]) => foldl(array, callbackfn, initialValue);
2572
+ }
2573
+ }
2574
+ };
2575
+
2576
+ type FoldlFnOverload = {
2577
+ <E, P>(
2578
+ array: readonly E[],
2579
+ callbackfn: (
2580
+ previousValue: P,
2581
+ currentValue: E,
2582
+ currentIndex: SizeType.Arr,
2583
+ ) => P,
2584
+ initialValue: P,
2585
+ ): P;
2586
+
2587
+ // curried version
2588
+ <E, P>(
2589
+ callbackfn: (
2590
+ previousValue: P,
2591
+ currentValue: E,
2592
+ currentIndex: SizeType.Arr,
2593
+ ) => P,
2594
+ initialValue: P,
2595
+ ): (array: readonly E[]) => P;
2596
+ };
2597
+
2598
+ /**
2599
+ * Applies a function against an accumulator and each element in the array (from right to left) to reduce it to a single value.
2600
+ * This is an alias for `Array.prototype.reduceRight`.
2601
+ * @template E The type of elements in the array.
2602
+ * @template S The type of the accumulated value.
2603
+ * @param array The input array.
2604
+ * @param callbackfn A function to execute on each element in the array: `(previousValue: S, currentValue: A, currentIndex: SizeType.Arr) => S`.
2605
+ * @param initialValue The initial value of the accumulator.
2606
+ * @returns The single value that results from the reduction.
2607
+ * @example
2608
+ * ```ts
2609
+ * // Regular usage
2610
+ * Arr.foldr([1, 2, 3], (sum, n) => sum + n, 0); // 6
2611
+ *
2612
+ * // Curried usage for pipe composition
2613
+ * const concatRight = Arr.foldr((acc: string, curr: string) => curr + acc, '');
2614
+ * const result = pipe(['a', 'b', 'c']).map(concatRight).value;
2615
+ * console.log(result); // "abc"
2616
+ * ```
2617
+ */
2618
+ export const foldr: FoldrFnOverload = <E, P>(
2619
+ ...args:
2620
+ | readonly [
2621
+ array: readonly E[],
2622
+ callbackfn: (
2623
+ previousValue: P,
2624
+ currentValue: E,
2625
+ currentIndex: SizeType.Arr,
2626
+ ) => P,
2627
+ initialValue: P,
2628
+ ]
2629
+ | readonly [
2630
+ callbackfn: (
2631
+ previousValue: P,
2632
+ currentValue: E,
2633
+ currentIndex: SizeType.Arr,
2634
+ ) => P,
2635
+ initialValue: P,
2636
+ ]
2637
+ ) => {
2638
+ switch (args.length) {
2639
+ case 3: {
2640
+ const [array, callbackfn, initialValue] = args;
2641
+ return array.reduceRight(
2642
+ (prev, curr, index) => callbackfn(prev, curr, asUint32(index)),
2643
+ initialValue,
2644
+ );
2645
+ }
2646
+ case 2: {
2647
+ const [callbackfn, initialValue] = args;
2648
+ return (array: readonly E[]) => foldr(array, callbackfn, initialValue);
2649
+ }
2650
+ }
2651
+ };
2652
+
2653
+ type FoldrFnOverload = {
2654
+ <E, P>(
2655
+ array: readonly E[],
2656
+ callbackfn: (
2657
+ previousValue: P,
2658
+ currentValue: E,
2659
+ currentIndex: SizeType.Arr,
2660
+ ) => P,
2661
+ initialValue: P,
2662
+ ): P;
2663
+
2664
+ // curried version
2665
+ <E, P>(
2666
+ callbackfn: (
2667
+ previousValue: P,
2668
+ currentValue: E,
2669
+ currentIndex: SizeType.Arr,
2670
+ ) => P,
2671
+ initialValue: P,
2672
+ ): (array: readonly E[]) => P;
2673
+ };
2674
+
2675
+ /**
2676
+ * Finds the minimum value in an array.
2677
+ * @template E The type of numbers in the array (must extend `number`).
2678
+ * @param array The input array.
2679
+ * @param comparator An optional custom comparator function `(x: N, y: N) => number`. Should return < 0 if x is smaller, 0 if equal, > 0 if x is larger. Defaults to `x - y`.
2680
+ * @returns The minimum value in the array wrapped in Optional.
2681
+ * @example
2682
+ * ```ts
2683
+ * Arr.min([3, 1, 4, 1, 5] as const); // Optional.some(1)
2684
+ * Arr.min([{v:3}, {v:1}], (a,b) => a.v - b.v) // Optional.some({v:1})
2685
+ * Arr.min([]); // Optional.none
2686
+ * ```
2687
+ */
2688
+
2689
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
2690
+ export const min: MinFnOverload = (<E extends number>(
2691
+ array: readonly E[],
2692
+ comparator?: (x: E, y: E) => number,
2693
+ ): Optional<E> => {
2694
+ if (!isNonEmpty(array)) {
2695
+ return Optional.none;
2696
+ }
2697
+
2698
+ const cmp = comparator ?? ((x, y) => Num.from(x) - Num.from(y));
2699
+
2700
+ return Optional.some(
2701
+ array.reduce(
2702
+ (currentMin, curr) => (cmp(curr, currentMin) < 0 ? curr : currentMin),
2703
+ array[0],
2704
+ ),
2705
+ );
2706
+ }) as MinFnOverload;
2707
+
2708
+ type MinFnOverload = {
2709
+ <E extends number>(
2710
+ array: NonEmptyArray<E>,
2711
+ comparator?: (x: E, y: E) => number,
2712
+ ): Optional.Some<E>;
2713
+
2714
+ <E extends number>(
2715
+ array: readonly E[],
2716
+ comparator?: (x: E, y: E) => number,
2717
+ ): Optional<E>;
2718
+
2719
+ <E>(
2720
+ array: NonEmptyArray<E>,
2721
+ comparator: (x: E, y: E) => number,
2722
+ ): Optional.Some<E>;
2723
+
2724
+ <E>(array: readonly E[], comparator: (x: E, y: E) => number): Optional<E>;
2725
+ };
2726
+
2727
+ /**
2728
+ * Finds the maximum value in an array.
2729
+ *
2730
+ * - If the array is non-empty, returns the maximum value.
2731
+ * - If the array is empty, returns `Optional.none`.
2732
+ * - You can provide a custom comparator for arbitrary types.
2733
+ *
2734
+ * @template E The type of elements in the array.
2735
+ * @param array The input array.
2736
+ * @param comparator An optional custom comparator function `(x: A, y: A) => number`. Should return < 0 if x is smaller, 0 if equal, > 0 if x is larger. Defaults to numeric comparison.
2737
+ * @returns The maximum value in the array wrapped in Optional.
2738
+ * @example
2739
+ * ```ts
2740
+ * Arr.max([3, 1, 4, 1, 5] as const); // Optional.some(5)
2741
+ * Arr.max([{v:3}, {v:1}], (a,b) => a.v - b.v) // Optional.some({v:3})
2742
+ * Arr.max([]); // Optional.none
2743
+ * ```
2744
+ */
2745
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
2746
+ export const max: MaxFnOverload = (<E extends number>(
2747
+ array: readonly E[],
2748
+ comparator?: (x: E, y: E) => number,
2749
+ ): Optional<E> => {
2750
+ const cmp = comparator ?? ((x, y) => Num.from(x) - Num.from(y));
2751
+ // Find max by finding min with an inverted comparator
2752
+ return min(array, (x, y) => -cmp(x, y));
2753
+ }) as MaxFnOverload;
2754
+
2755
+ type MaxFnOverload = {
2756
+ <E extends number>(
2757
+ array: NonEmptyArray<E>,
2758
+ comparator?: (x: E, y: E) => number,
2759
+ ): Optional.Some<E>;
2760
+
2761
+ <E extends number>(
2762
+ array: readonly E[],
2763
+ comparator?: (x: E, y: E) => number,
2764
+ ): Optional<E>;
2765
+
2766
+ <E>(
2767
+ array: NonEmptyArray<E>,
2768
+ comparator: (x: E, y: E) => number,
2769
+ ): Optional.Some<E>;
2770
+
2771
+ <E>(array: readonly E[], comparator: (x: E, y: E) => number): Optional<E>;
2772
+ };
2773
+
2774
+ /**
2775
+ * Finds the element with the minimum value according to a mapped numeric value.
2776
+ *
2777
+ * - If the array is non-empty, returns the element with the minimum mapped value.
2778
+ * - If the array is empty, returns `Optional.none`.
2779
+ * - You can provide a custom comparator for the mapped values.
2780
+ *
2781
+ * @template E The type of elements in the array.
2782
+ * @template V The type of the value to compare by.
2783
+ * @param array The input array.
2784
+ * @param comparatorValueMapper A function that maps an element to a value for comparison.
2785
+ * @param comparator An optional custom comparator function for the mapped values.
2786
+ * @returns The element with the minimum mapped value wrapped in Optional.
2787
+ * @example
2788
+ * ```ts
2789
+ * const people = [{ name: 'Alice', age: 30 }, { name: 'Bob', age: 20 }] as const;
2790
+ * Arr.minBy(people, p => p.age); // Optional.some({ name: 'Bob', age: 20 })
2791
+ * Arr.minBy([], p => p.age); // Optional.none
2792
+ * ```
2793
+ */
2794
+
2795
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
2796
+ export const minBy: MinByFnOverload = (<E, V>(
2797
+ array: readonly E[],
2798
+ comparatorValueMapper: (value: E) => V,
2799
+ comparator?: (x: V, y: V) => number,
2800
+ ): Optional<E> =>
2801
+ min(array, (x, y) =>
2802
+ comparator === undefined
2803
+ ? Num.from(comparatorValueMapper(x)) -
2804
+ Num.from(comparatorValueMapper(y))
2805
+ : comparator(comparatorValueMapper(x), comparatorValueMapper(y)),
2806
+ )) as MinByFnOverload;
2807
+
2808
+ type MinByFnOverload = {
2809
+ <E>(
2810
+ array: NonEmptyArray<E>,
2811
+ comparatorValueMapper: (value: E) => number,
2812
+ ): Optional.Some<E>;
2813
+
2814
+ <E>(
2815
+ array: readonly E[],
2816
+ comparatorValueMapper: (value: E) => number,
2817
+ ): Optional<E>;
2818
+
2819
+ <E, V>(
2820
+ array: NonEmptyArray<E>,
2821
+ comparatorValueMapper: (value: E) => V,
2822
+ comparator: (x: V, y: V) => number,
2823
+ ): Optional.Some<E>;
2824
+
2825
+ <E, V>(
2826
+ array: readonly E[],
2827
+ comparatorValueMapper: (value: E) => V,
2828
+ comparator: (x: V, y: V) => number,
2829
+ ): Optional<E>;
2830
+ };
2831
+
2832
+ /**
2833
+ * Finds the element with the maximum value according to a mapped numeric value.
2834
+ *
2835
+ * - If the array is non-empty, returns the element with the maximum mapped value.
2836
+ * - If the array is empty, returns `Optional.none`.
2837
+ * - You can provide a custom comparator for the mapped values.
2838
+ *
2839
+ * @template E The type of elements in the array.
2840
+ * @template V The type of the value to compare by.
2841
+ * @param array The input array.
2842
+ * @param comparatorValueMapper A function that maps an element to a value for comparison.
2843
+ * @param comparator An optional custom comparator function for the mapped values.
2844
+ * @returns The element with the maximum mapped value wrapped in Optional.
2845
+ * @example
2846
+ * ```ts
2847
+ * const people = [{ name: 'Alice', age: 30 }, { name: 'Bob', age: 20 }] as const;
2848
+ * Arr.maxBy(people, p => p.age); // Optional.some({ name: 'Alice', age: 30 })
2849
+ * Arr.maxBy([], p => p.age); // Optional.none
2850
+ * ```
2851
+ */
2852
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
2853
+ export const maxBy: MaxByFnOverload = (<E, V>(
2854
+ array: readonly E[],
2855
+ comparatorValueMapper: (value: E) => V,
2856
+ comparator?: (x: V, y: V) => number,
2857
+ ): Optional<E> =>
2858
+ max(array, (x, y) =>
2859
+ comparator === undefined
2860
+ ? Num.from(comparatorValueMapper(x)) -
2861
+ Num.from(comparatorValueMapper(y))
2862
+ : comparator(comparatorValueMapper(x), comparatorValueMapper(y)),
2863
+ )) as MaxByFnOverload;
2864
+
2865
+ type MaxByFnOverload = {
2866
+ <E>(
2867
+ array: NonEmptyArray<E>,
2868
+ comparatorValueMapper: (value: E) => number,
2869
+ ): Optional.Some<E>;
2870
+
2871
+ <E>(
2872
+ array: readonly E[],
2873
+ comparatorValueMapper: (value: E) => number,
2874
+ ): Optional<E>;
2875
+
2876
+ <E, V>(
2877
+ array: NonEmptyArray<E>,
2878
+ comparatorValueMapper: (value: E) => V,
2879
+ comparator: (x: V, y: V) => number,
2880
+ ): Optional.Some<E>;
2881
+
2882
+ <E, V>(
2883
+ array: readonly E[],
2884
+ comparatorValueMapper: (value: E) => V,
2885
+ comparator: (x: V, y: V) => number,
2886
+ ): Optional<E>;
2887
+ };
2888
+
2889
+ /**
2890
+ * Counts the number of elements in an array that satisfy a predicate.
2891
+ * @template E The type of elements in the array.
2892
+ * @param array The input array.
2893
+ * @param predicate A function `(value: A, index: number) => boolean` to test each element for a condition.
2894
+ * @returns The number of elements that satisfy the predicate.
2895
+ * @example
2896
+ * ```ts
2897
+ * // Regular usage
2898
+ * Arr.count([1, 2, 3, 4], (x) => x > 2); // 2
2899
+ *
2900
+ * // Curried usage for pipe composition
2901
+ * const countEvens = Arr.count((x: number) => x % 2 === 0);
2902
+ * const result = pipe([1, 2, 3, 4, 5, 6]).map(countEvens).value;
2903
+ * console.log(result); // 3
2904
+ * ```
2905
+ */
2906
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
2907
+ export const count: CountFnOverload = (<E,>(
2908
+ ...args:
2909
+ | readonly [
2910
+ array: readonly E[],
2911
+ predicate: (value: E, index: SizeType.Arr) => boolean,
2912
+ ]
2913
+ | readonly [predicate: (value: E, index: SizeType.Arr) => boolean]
2914
+ ) => {
2915
+ switch (args.length) {
2916
+ case 2: {
2917
+ const [array, predicate] = args;
2918
+ return array.reduce<Uint32>(
2919
+ (acc, curr, index) =>
2920
+ predicate(curr, asUint32(index)) ? Uint32.add(acc, 1) : acc,
2921
+ asUint32(0),
2922
+ );
2923
+ }
2924
+ case 1: {
2925
+ const [predicate] = args;
2926
+ return (array: readonly E[]) => count(array, predicate);
2927
+ }
2928
+ }
2929
+ }) as CountFnOverload;
2930
+
2931
+ type CountFnOverload = {
2932
+ <E>(
2933
+ array: readonly E[],
2934
+ predicate: (value: E, index: SizeType.Arr) => boolean,
2935
+ ): SizeType.Arr;
2936
+
2937
+ // curried version
2938
+ <E>(
2939
+ predicate: (value: E, index: SizeType.Arr) => boolean,
2940
+ ): (array: readonly E[]) => SizeType.Arr;
2941
+ };
2942
+
2943
+ /**
2944
+ * Groups elements of an array by a key derived from each element and counts the elements in each group.
2945
+ * @template E The type of elements in the array.
2946
+ * @template G The type of the group key (must be a primitive type: `string`, `number`, `boolean`, `symbol`, `bigint`, `null`, or `undefined`).
2947
+ * @param array The input array.
2948
+ * @param grouper A function `(value: A, index: number) => G` that maps an element and its index to a group key.
2949
+ * @returns An `IMap` where keys are group keys and values are the counts of elements in each group.
2950
+ * @example
2951
+ * ```ts
2952
+ * // Regular usage
2953
+ * Arr.countBy([1, 2, 2, 3, 1, 1], (x) => x);
2954
+ * // IMap { 1 => 3, 2 => 2, 3 => 1 }
2955
+ *
2956
+ * // Curried usage for pipe composition
2957
+ * const countByType = Arr.countBy((x: {type: string}) => x.type);
2958
+ * const data = [{type: 'a'}, {type: 'b'}, {type: 'a'}];
2959
+ * const result = pipe(data).map(countByType).value;
2960
+ * // IMap { 'a' => 2, 'b' => 1 }
2961
+ * ```
2962
+ */
2963
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
2964
+ export const countBy: CountByFnOverload = (<E, G extends MapSetKeyType>(
2965
+ ...args:
2966
+ | readonly [
2967
+ array: readonly E[],
2968
+ grouper: (value: E, index: SizeType.Arr) => G,
2969
+ ]
2970
+ | readonly [grouper: (value: E, index: SizeType.Arr) => G]
2971
+ ) => {
2972
+ switch (args.length) {
2973
+ case 2: {
2974
+ const [array, grouper] = args;
2975
+ const mut_groups = new Map<MapSetKeyType, SizeType.Arr>();
2976
+
2977
+ for (const [index, e] of array.entries()) {
2978
+ const key = grouper(e, asUint32(index));
2979
+ const curr = mut_groups.get(key) ?? 0;
2980
+
2981
+ mut_groups.set(key, asUint32(curr + 1));
2982
+ }
2983
+
2984
+ return IMap.create(mut_groups);
2985
+ }
2986
+ case 1: {
2987
+ const [grouper] = args;
2988
+ return (array: readonly E[]) => countBy(array, grouper);
2989
+ }
2990
+ }
2991
+ }) as CountByFnOverload;
2992
+
2993
+ type CountByFnOverload = {
2994
+ <E, G extends MapSetKeyType>(
2995
+ array: readonly E[],
2996
+ grouper: (value: E, index: SizeType.Arr) => G,
2997
+ ): IMap<G, SizeType.Arr>;
2998
+
2999
+ // curried version
3000
+ <E, G extends MapSetKeyType>(
3001
+ grouper: (value: E, index: SizeType.Arr) => G,
3002
+ ): (array: readonly E[]) => IMap<G, SizeType.Arr>;
3003
+ };
3004
+
3005
+ /**
3006
+ * Calculates the sum of numbers in an array.
3007
+ * @param array The input array of numbers.
3008
+ * @returns The sum of the numbers. Returns 0 for an empty array.
3009
+ * @example
3010
+ * ```ts
3011
+ * Arr.sum([1, 2, 3]); // 6
3012
+ * Arr.sum([]); // 0
3013
+ * Arr.sum([-1, 0, 1]); // 0
3014
+ * ```
3015
+ */
3016
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
3017
+ export const sum: SumFnOverload = ((array: readonly number[]): number =>
3018
+ array.reduce((prev, curr) => prev + curr, 0)) as SumFnOverload;
3019
+
3020
+ type SumFnOverload = {
3021
+ (array: readonly []): 0;
3022
+ <N extends number>(array: readonly [N]): N;
3023
+ (array: readonly Uint[]): Uint;
3024
+ (array: readonly Int[]): Int;
3025
+ (array: readonly number[]): number;
3026
+ };
3027
+
3028
+ /**
3029
+ * Joins array elements into a string.
3030
+ * @param array The array to join.
3031
+ * @param separator The separator string.
3032
+ * @returns Result.Ok with the joined string, Result.Err if the operation throws.
3033
+ * @example
3034
+ * ```typescript
3035
+ * // Regular usage
3036
+ * const arr = ['Hello', 'World'];
3037
+ * const result = Arr.join(arr, ' ');
3038
+ * if (Result.isOk(result)) {
3039
+ * console.log(result.value); // "Hello World"
3040
+ * }
3041
+ *
3042
+ * // Curried usage for pipe composition
3043
+ * const joinWithComma = Arr.join(',');
3044
+ * const result2 = pipe(['a', 'b', 'c']).map(joinWithComma).value;
3045
+ * console.log(Result.unwrapOr(result2, '')); // "a,b,c"
3046
+ * ```
3047
+ */
3048
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
3049
+ export const join: JoinFnOverload = (<E,>(
3050
+ ...args:
3051
+ | readonly [array: readonly E[], separator?: string]
3052
+ | readonly [separator?: string]
3053
+ ) => {
3054
+ switch (args.length) {
3055
+ case 0:
3056
+ return <E2,>(array: readonly E2[]) => joinImpl(array, undefined);
3057
+
3058
+ case 1: {
3059
+ const [arg] = args;
3060
+ if (isString(arg) || isUndefined(arg)) {
3061
+ return <E2,>(array: readonly E2[]) => joinImpl(array, arg);
3062
+ }
3063
+ return joinImpl(arg, undefined);
3064
+ }
3065
+ case 2: {
3066
+ const [array, separator] = args;
3067
+ return joinImpl(array, separator);
3068
+ }
3069
+ }
3070
+ }) as JoinFnOverload;
3071
+
3072
+ const joinImpl = <E,>(
3073
+ array: readonly E[],
3074
+ separator: string | undefined,
3075
+ ): Result<string, Error> => {
3076
+ try {
3077
+ const result = array.join(separator);
3078
+ return Result.ok(result);
3079
+ } catch (error) {
3080
+ return Result.err(
3081
+ error instanceof Error
3082
+ ? error
3083
+ : pipe(unknownToString(error))
3084
+ .map(Result.unwrapOkOr('Failed to join array'))
3085
+ .map((e) => new Error(e)).value,
3086
+ );
3087
+ }
3088
+ };
3089
+
3090
+ type JoinFnOverload = {
3091
+ <E>(array: readonly E[], separator?: string): Result<string, Error>;
3092
+
3093
+ // curried version
3094
+ (separator?: string): <E>(array: readonly E[]) => Result<string, Error>;
3095
+ };
3096
+
3097
+ // transformation
3098
+
3099
+ /**
3100
+ * Creates an array of tuples by pairing up corresponding elements from two arrays.
3101
+ * The resulting array has a length equal to the minimum of the two input array lengths.
3102
+ * @template E1 The type of the first array.
3103
+ * @template E2 The type of the second array.
3104
+ * @param array1 The first array.
3105
+ * @param array2 The second array.
3106
+ * @returns An array of tuples where each tuple contains corresponding elements from both arrays.
3107
+ * @example
3108
+ * ```ts
3109
+ * Arr.zip([1, 2, 3] as const, ['a', 'b', 'c'] as const); // [[1, 'a'], [2, 'b'], [3, 'c']]
3110
+ * Arr.zip([1, 2], ['a', 'b', 'c']); // [[1, 'a'], [2, 'b']]
3111
+ * Arr.zip([1, 2, 3], ['a']); // [[1, 'a']]
3112
+ * Arr.zip([], ['a']); // []
3113
+ * ```
3114
+ */
3115
+ export const zip = <
3116
+ Ar1 extends readonly unknown[],
3117
+ Ar2 extends readonly unknown[],
3118
+ >(
3119
+ array1: Ar1,
3120
+ array2: Ar2,
3121
+ ): List.Zip<Ar1, Ar2> =>
3122
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
3123
+ seq(Uint32.min(size(array1), size(array2))).map((i) =>
3124
+ // Non-null assertion is safe here because `i` is always within bounds of both arrays up to the length of the shorter one.
3125
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
3126
+ tp(array1[i]!, array2[i]!),
3127
+ ) as unknown as List.Zip<Ar1, Ar2>;
3128
+
3129
+ /**
3130
+ * Filters an array by excluding elements for which the predicate returns true.
3131
+ * This is the opposite of `Array.prototype.filter`.
3132
+ * @template E The type of elements in the array.
3133
+ * @param array The input array.
3134
+ * @param predicate A function `(a: A, index: number) => boolean` that returns `true` for elements to be excluded.
3135
+ * @returns A new array with elements for which the predicate returned `false`.
3136
+ * @example
3137
+ * ```ts
3138
+ * // Regular usage
3139
+ * Arr.filterNot([1, 2, 3, 4], (x) => x % 2 === 0); // [1, 3] (excludes even numbers)
3140
+ *
3141
+ * // Curried usage for pipe composition
3142
+ * const excludeEvens = Arr.filterNot((x: number) => x % 2 === 0);
3143
+ * const result = pipe([1, 2, 3, 4, 5, 6]).map(excludeEvens).value;
3144
+ * console.log(result); // [1, 3, 5]
3145
+ * ```
3146
+ */
3147
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
3148
+ export const filterNot: FilterNotFnOverload = (<E,>(
3149
+ ...args:
3150
+ | readonly [
3151
+ array: readonly E[],
3152
+ predicate: (a: E, index: SizeType.Arr) => boolean,
3153
+ ]
3154
+ | readonly [predicate: (a: E, index: SizeType.Arr) => boolean]
3155
+ ) => {
3156
+ switch (args.length) {
3157
+ case 2: {
3158
+ const [array, predicate] = args;
3159
+ return array.filter((a, i) => !predicate(a, asUint32(i)));
3160
+ }
3161
+ case 1: {
3162
+ const [predicate] = args;
3163
+ return (array: readonly E[]) => filterNot(array, predicate);
3164
+ }
3165
+ }
3166
+ }) as FilterNotFnOverload;
3167
+
3168
+ type FilterNotFnOverload = {
3169
+ <E>(
3170
+ array: readonly E[],
3171
+ predicate: (a: E, index: SizeType.Arr) => boolean,
3172
+ ): readonly E[];
3173
+
3174
+ // curried version
3175
+ <E>(
3176
+ predicate: (a: E, index: SizeType.Arr) => boolean,
3177
+ ): (array: readonly E[]) => readonly E[];
3178
+ };
3179
+
3180
+ /**
3181
+ * Concatenates two arrays.
3182
+ * @template E1 The type of the first array (can be a tuple).
3183
+ * @template E2 The type of the second array (can be a tuple).
3184
+ * @param array1 The first array.
3185
+ * @param array2 The second array.
3186
+ * @returns A new array that is the concatenation of the two input arrays. Type is `readonly [...E1, ...E2]`.
3187
+ * @example
3188
+ * ```ts
3189
+ * Arr.concat([1, 2] as const, [3, 4] as const); // [1, 2, 3, 4]
3190
+ * Arr.concat([], [1, 2]); // [1, 2]
3191
+ * Arr.concat([1, 2], []); // [1, 2]
3192
+ * ```
3193
+ */
3194
+ export const concat = <
3195
+ Ar1 extends readonly unknown[],
3196
+ Ar2 extends readonly unknown[],
3197
+ >(
3198
+ array1: Ar1,
3199
+ array2: Ar2,
3200
+ ): readonly [...Ar1, ...Ar2] => [...array1, ...array2];
3201
+
3202
+ /**
3203
+ * Partitions an array into sub-arrays of a specified size.
3204
+ * The last partition may be smaller if the array length is not a multiple of `chunkSize`.
3205
+ * Returns an empty array if chunkSize < 2.
3206
+ * @template N The size of each partition (must be a number type, typically a literal for precise typing).
3207
+ * @template E The type of elements in the array.
3208
+ * @param array The input array.
3209
+ * @param chunkSize The size of each partition.
3210
+ * @returns An array of arrays, where each inner array has up to `chunkSize` elements.
3211
+ * @example
3212
+ * ```ts
3213
+ * // Regular usage
3214
+ * Arr.partition([1, 2, 3, 4, 5, 6], 2); // [[1, 2], [3, 4], [5, 6]]
3215
+ *
3216
+ * // Curried usage for pipe composition
3217
+ * const chunkBy3 = Arr.partition(3);
3218
+ * const result = pipe([1, 2, 3, 4, 5, 6, 7]).map(chunkBy3).value;
3219
+ * console.log(result); // [[1, 2, 3], [4, 5, 6], [7]]
3220
+ * ```
3221
+ */
3222
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
3223
+ export const partition: PartitionFnOverload = (<
3224
+ N extends WithSmallInt<PositiveInt & SizeType.Arr>,
3225
+ E,
3226
+ >(
3227
+ ...args:
3228
+ | readonly [array: readonly E[], chunkSize: N]
3229
+ | readonly [chunkSize: N]
3230
+ ) => {
3231
+ switch (args.length) {
3232
+ case 2: {
3233
+ const [array, chunkSize] = args;
3234
+ return chunkSize < 2
3235
+ ? []
3236
+ : seq(asUint32(Math.ceil(array.length / chunkSize))).map((i) =>
3237
+ array.slice(chunkSize * i, chunkSize * (i + 1)),
3238
+ );
3239
+ }
3240
+ case 1: {
3241
+ const [chunkSize] = args;
3242
+ return (array: readonly E[]) => partition(array, chunkSize);
3243
+ }
3244
+ }
3245
+ }) as PartitionFnOverload;
3246
+
3247
+ type PartitionFnOverload = {
3248
+ <N extends WithSmallInt<PositiveInt & SizeType.Arr>, E>(
3249
+ array: readonly E[],
3250
+ chunkSize: N,
3251
+ ): readonly (readonly E[])[];
3252
+
3253
+ // curried version
3254
+ <N extends WithSmallInt<PositiveInt & SizeType.Arr>>(
3255
+ chunkSize: N,
3256
+ ): <E>(array: readonly E[]) => readonly (readonly E[])[];
3257
+ };
3258
+
3259
+ /**
3260
+ * Sorts an array by a value derived from its elements, using a numeric mapping.
3261
+ * @template E The type of elements in the array.
3262
+ * @param array The input array.
3263
+ * @param comparatorValueMapper A function `(value: A) => number` that maps an element to a number for comparison.
3264
+ * @param comparator An optional custom comparator function `(x: number, y: number) => number` for the mapped numbers. Defaults to ascending sort (x - y).
3265
+ * @returns A new array sorted by the mapped values.
3266
+ * @example
3267
+ * ```ts
3268
+ * const items = [{ name: 'Eve', score: 70 }, { name: 'Adam', score: 90 }, { name: 'Bob', score: 80 }];
3269
+ * Arr.toSortedBy(items, item => item.score);
3270
+ * // [{ name: 'Eve', score: 70 }, { name: 'Bob', score: 80 }, { name: 'Adam', score: 90 }]
3271
+ * Arr.toSortedBy(items, item => item.score, (a, b) => b - a); // Sort descending
3272
+ * // [{ name: 'Adam', score: 90 }, { name: 'Bob', score: 80 }, { name: 'Eve', score: 70 }]
3273
+ * ```
3274
+ */
3275
+ export const toSortedBy: ToSortedByFnOverload = <E, const V>(
3276
+ array: readonly E[],
3277
+ comparatorValueMapper: (value: E) => V,
3278
+ comparator?: (x: V, y: V) => number,
3279
+ ): readonly E[] =>
3280
+ array.toSorted((x, y) =>
3281
+ comparator === undefined
3282
+ ? // This branch assumes B is number if comparator is undefined.
3283
+ // The overloads should handle this, but explicit cast might be needed if B is not number.
3284
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
3285
+ (comparatorValueMapper(x) as number) -
3286
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
3287
+ (comparatorValueMapper(y) as number)
3288
+ : comparator(comparatorValueMapper(x), comparatorValueMapper(y)),
3289
+ );
3290
+
3291
+ type ToSortedByFnOverload = {
3292
+ <E>(
3293
+ array: readonly E[],
3294
+ comparatorValueMapper: (value: E) => number,
3295
+ comparator?: (x: number, y: number) => number,
3296
+ ): readonly E[];
3297
+
3298
+ <E, const V>(
3299
+ array: readonly E[],
3300
+ comparatorValueMapper: (value: E) => V,
3301
+ comparator: (x: V, y: V) => number,
3302
+ ): readonly E[];
3303
+ };
3304
+
3305
+ /**
3306
+ * Returns an array of successively reduced values from an array, starting with an initial value.
3307
+ *
3308
+ * This function creates a \"running tally\" by applying a reducer function to each element and
3309
+ * accumulating the results. Unlike {@link reduce} which returns a single final value, `scan`
3310
+ * returns all intermediate accumulated values, providing visibility into the reduction process.
3311
+ *
3312
+ * **Key Differences from Reduce:**
3313
+ * - {@link reduce}: `[1, 2, 3] -> 6` (final sum only)
3314
+ * - `scan`: `[1, 2, 3] -> [0, 1, 3, 6]` (all intermediate sums including initial value)
3315
+ *
3316
+ * **Guaranteed Non-Empty Return:** The result is always a {@link NonEmptyArray}<S> because it includes
3317
+ * the initial value as the first element, even for empty input arrays. This provides type safety
3318
+ * and eliminates the need for empty array checks.
3319
+ *
3320
+ * **Array Length Relationship:** `result.length === array.length + 1` (includes initial value)
3321
+ *
3322
+ * **Curried Usage:** Supports currying for functional composition - when called with only the reducer
3323
+ * and initial value, returns a reusable function that can be applied to arrays.
3324
+ *
3325
+ * @template E The type of elements in the input array.
3326
+ * @template S The type of the accumulated values and the initial value.
3327
+ * @param array The input array to scan over. Can be empty (result will still contain the initial value).
3328
+ * @param reducer A function `(accumulator: S, currentValue: E, currentIndex: SizeType.Arr) => S` that:
3329
+ * - **accumulator:** The current accumulated value (starts with `init`, then previous results)
3330
+ * - **currentValue:** The current array element being processed
3331
+ * - **currentIndex:** The 0-based index of the current element (typed as {@link SizeType.Arr})
3332
+ * - **returns:** The new accumulated value to include in the result array
3333
+ * @param init The initial accumulated value. Becomes the first element of the result array.
3334
+ * @returns A {@link NonEmptyArray}<S> of accumulated values with length `array.length + 1`:
3335
+ * - `result[0]` is always the `init` value
3336
+ * - `result[i+1]` is the result of applying the reducer to `result[i]` and `array[i]`
3337
+ * - Guaranteed to be non-empty regardless of input array length
3338
+ *
3339
+ * @example
3340
+ * ```typescript
3341
+ * // Basic running sum example
3342
+ * const numbers = [1, 2, 3, 4];
3343
+ * const runningSum = Arr.scan(numbers, (acc, curr) => acc + curr, 0);
3344
+ * // NonEmptyArray<number> -> [0, 1, 3, 6, 10]
3345
+ * // ^ ^ ^ ^ ^
3346
+ * // | | | | └─ 0+1+2+3+4 = 10
3347
+ * // | | | └─ 0+1+2+3 = 6
3348
+ * // | | └─ 0+1+2 = 3
3349
+ * // | └─ 0+1 = 1
3350
+ * // └─ init = 0
3351
+ *
3352
+ * // Difference from reduce
3353
+ * const reduced = numbers.reduce((acc, curr) => acc + curr, 0); // 10 (final only)
3354
+ * const scanned = Arr.scan(numbers, (acc, curr) => acc + curr, 0); // [0, 1, 3, 6, 10] (all steps)
3355
+ *
3356
+ * // Running product
3357
+ * const factorial = Arr.scan([1, 2, 3, 4, 5], (acc, curr) => acc * curr, 1);
3358
+ * // [1, 1, 2, 6, 24, 120] - factorial sequence
3359
+ *
3360
+ * // Running maximum
3361
+ * const temperatures = [20, 25, 18, 30, 22];
3362
+ * const runningMax = Arr.scan(temperatures, (max, temp) => Math.max(max, temp), -Infinity);
3363
+ * // [-Infinity, 20, 25, 25, 30, 30]
3364
+ *
3365
+ * // Building strings incrementally
3366
+ * const words = ['Hello', 'beautiful', 'world'];
3367
+ * const sentences = Arr.scan(words, (sentence, word) => sentence + ' ' + word, '');
3368
+ * // ['', ' Hello', ' Hello beautiful', ' Hello beautiful world']
3369
+ *
3370
+ * // Array accumulation (collecting elements)
3371
+ * const items = ['a', 'b', 'c'];
3372
+ * const growing = Arr.scan(items, (acc, item) => [...acc, item], [] as string[]);
3373
+ * // [[], ['a'], ['a', 'b'], ['a', 'b', 'c']]
3374
+ *
3375
+ * // Financial running balance
3376
+ * const transactions = [100, -20, 50, -30];
3377
+ * const balances = Arr.scan(transactions, (balance, transaction) => balance + transaction, 1000);
3378
+ * // [1000, 1100, 1080, 1130, 1100] - account balance after each transaction
3379
+ *
3380
+ * // Using index information
3381
+ * const letters = ['a', 'b', 'c'];
3382
+ * const indexed = Arr.scan(letters, (acc, letter, index) => acc + `${index}:${letter} `, '');
3383
+ * // ['', '0:a ', '0:a 1:b ', '0:a 1:b 2:c ']
3384
+ *
3385
+ * // Edge cases
3386
+ * const emptyArray: number[] = [];
3387
+ * const emptyResult = Arr.scan(emptyArray, (acc, curr) => acc + curr, 42);
3388
+ * // [42] - NonEmptyArray even for empty input
3389
+ *
3390
+ * const singleElement = Arr.scan([5], (acc, curr) => acc * curr, 2);
3391
+ * // [2, 10] - init value plus one result
3392
+ *
3393
+ * // Complex object accumulation
3394
+ * const sales = [
3395
+ * { product: 'A', amount: 100 },
3396
+ * { product: 'B', amount: 200 },
3397
+ * { product: 'A', amount: 150 }
3398
+ * ];
3399
+ *
3400
+ * const runningSales = Arr.scan(sales, (totals, sale) => ({
3401
+ * ...totals,
3402
+ * [sale.product]: (totals[sale.product] || 0) + sale.amount
3403
+ * }), {} as Record<string, number>);
3404
+ * // [
3405
+ * // {},
3406
+ * // { A: 100 },
3407
+ * // { A: 100, B: 200 },
3408
+ * // { A: 250, B: 200 }
3409
+ * // ]
3410
+ *
3411
+ * // Curried usage for functional composition
3412
+ * const runningSumFn = Arr.scan((acc: number, curr: number) => acc + curr, 0);
3413
+ * const runningProductFn = Arr.scan((acc: number, curr: number) => acc * curr, 1);
3414
+ * const collectingFn = Arr.scan((acc: string[], curr: string) => [...acc, curr], [] as string[]);
3415
+ *
3416
+ * const datasets = [[1, 2, 3], [4, 5], [6, 7, 8, 9]];
3417
+ * const allSums = datasets.map(runningSumFn);
3418
+ * // [
3419
+ * // [0, 1, 3, 6],
3420
+ * // [0, 4, 9],
3421
+ * // [0, 6, 13, 21, 30]
3422
+ * // ]
3423
+ *
3424
+ * // Pipe composition for data analysis
3425
+ * const analysisResult = pipe([10, 20, 30, 40])
3426
+ * .map(runningSumFn)
3427
+ * .map(sums => sums.slice(1)) // Remove initial value to get pure running sums
3428
+ * .map(sums => sums.map((sum, i) => ({ step: i + 1, total: sum })))
3429
+ * .value;
3430
+ * // [{ step: 1, total: 10 }, { step: 2, total: 30 }, { step: 3, total: 60 }, { step: 4, total: 100 }]
3431
+ *
3432
+ * // Advanced: State machine simulation
3433
+ * type State = 'idle' | 'loading' | 'success' | 'error';
3434
+ * type Event = 'start' | 'complete' | 'fail' | 'reset';
3435
+ *
3436
+ * const events: Event[] = ['start', 'complete', 'reset', 'start', 'fail'];
3437
+ * const stateTransition = (state: State, event: Event): State => {
3438
+ * switch (state) {
3439
+ * case 'idle': return event === 'start' ? 'loading' : state;
3440
+ * case 'loading': return event === 'complete' ? 'success' : event === 'fail' ? 'error' : state;
3441
+ * case 'success': return event === 'reset' ? 'idle' : state;
3442
+ * case 'error': return event === 'reset' ? 'idle' : state;
3443
+ * }
3444
+ * };
3445
+ *
3446
+ * const stateHistory = Arr.scan(events, stateTransition, 'idle' as State);
3447
+ * // ['idle', 'loading', 'success', 'idle', 'loading', 'error']
3448
+ *
3449
+ * // Type inference examples
3450
+ * expectType<typeof runningSum, NonEmptyArray<number>>('=');
3451
+ * expectType<typeof emptyResult, NonEmptyArray<number>>('=');
3452
+ * expectType<typeof runningSumFn, <T extends readonly number[]>(array: T) => NonEmptyArray<number>>('=');
3453
+ * expectType<typeof stateHistory, NonEmptyArray<State>>('=');
3454
+ * ```
3455
+ *
3456
+ * @see {@link reduce} for getting only the final accumulated value
3457
+ * @see {@link NonEmptyArray} for understanding the guaranteed non-empty return type
3458
+ * @see {@link SizeType.Arr} for the index parameter type
3459
+ * @see Array.prototype.reduce for the standard reduce function
3460
+ */
3461
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
3462
+ export const scan: ScanFnOverload = (<E, S>(
3463
+ ...args:
3464
+ | readonly [
3465
+ array: readonly E[],
3466
+ reducer: (
3467
+ accumulator: S,
3468
+ currentValue: E,
3469
+ currentIndex: SizeType.Arr,
3470
+ ) => S,
3471
+ init: S,
3472
+ ]
3473
+ | readonly [
3474
+ reducer: (
3475
+ accumulator: S,
3476
+ currentValue: E,
3477
+ currentIndex: SizeType.Arr,
3478
+ ) => S,
3479
+ init: S,
3480
+ ]
3481
+ ) => {
3482
+ switch (args.length) {
3483
+ case 3: {
3484
+ const [array, reducer, init] = args;
3485
+ const mut_result: MutableNonEmptyArray<S> = castMutable(
3486
+ newArray<S>(asPositiveUint32(array.length + 1), init),
3487
+ );
3488
+
3489
+ let mut_acc = init;
3490
+
3491
+ for (const [index, value] of array.entries()) {
3492
+ mut_acc = reducer(mut_acc, value, asUint32(index));
3493
+ mut_result[index + 1] = mut_acc;
3494
+ }
3495
+
3496
+ return mut_result;
3497
+ }
3498
+ case 2: {
3499
+ const [reducer, init] = args;
3500
+ return (array: readonly E[]) => scan(array, reducer, init);
3501
+ }
3502
+ }
3503
+ }) as ScanFnOverload;
3504
+
3505
+ type ScanFnOverload = {
3506
+ <E, S>(
3507
+ array: readonly E[],
3508
+ reducer: (
3509
+ accumulator: S,
3510
+ currentValue: E,
3511
+ currentIndex: SizeType.Arr,
3512
+ ) => S,
3513
+ init: S,
3514
+ ): NonEmptyArray<S>;
3515
+
3516
+ // curried version
3517
+ <E, S>(
3518
+ reducer: (
3519
+ accumulator: S,
3520
+ currentValue: E,
3521
+ currentIndex: SizeType.Arr,
3522
+ ) => S,
3523
+ init: S,
3524
+ ): (array: readonly E[]) => NonEmptyArray<S>;
3525
+ };
3526
+
3527
+ /**
3528
+ * Groups elements of an array by a key derived from each element, returning an immutable {@link IMap}.
3529
+ *
3530
+ * This function categorizes array elements into groups based on a computed key, using the efficient
3531
+ * {@link IMap} data structure for the result. The grouper function receives both the element and its
3532
+ * index, enabling flexible grouping strategies.
3533
+ *
3534
+ * **MapSetKeyType Constraint:** The group key type `G` must extend {@link MapSetKeyType}, which includes
3535
+ * primitive types that can be used as Map keys (string, number, boolean, symbol, null, undefined).
3536
+ * This constraint ensures type safety and efficient key-based operations.
3537
+ *
3538
+ * **IMap Return Type:** Returns an {@link IMap}<G, readonly E[]> where:
3539
+ * - Keys are the computed group identifiers of type `G`
3540
+ * - Values are immutable arrays containing all elements that belong to each group
3541
+ * - Preserves insertion order of first occurrence of each group
3542
+ * - Maintains type safety with precise generic types
3543
+ *
3544
+ * **Curried Usage:** Supports currying for functional composition - when called with only the grouper
3545
+ * function, returns a reusable function that can be applied to arrays.
3546
+ *
3547
+ * @template E The type of elements in the input array.
3548
+ * @template G The type of the group key, constrained to {@link MapSetKeyType} (primitives usable as Map keys).
3549
+ * Must be one of: `string | number | boolean | symbol | null | undefined`
3550
+ * @param array The input array to group. Can be empty (returns empty {@link IMap}).
3551
+ * @param grouper A function `(value: E, index: SizeType.Arr) => G` that computes the group key for each element.
3552
+ * - **value:** The current array element
3553
+ * - **index:** The 0-based index of the element (typed as {@link SizeType.Arr})
3554
+ * - **returns:** The group key (must be {@link MapSetKeyType})
3555
+ * @returns An {@link IMap}<G, readonly E[]> where:
3556
+ * - Keys are unique group identifiers computed by the grouper function
3557
+ * - Values are immutable arrays of elements belonging to each group
3558
+ * - Empty groups are not included (only groups with at least one element)
3559
+ * - Insertion order is preserved based on first occurrence of each group key
3560
+ *
3561
+ * @example
3562
+ * ```typescript
3563
+ * // Basic grouping by object property
3564
+ * const products = [
3565
+ * { type: 'fruit', name: 'apple', price: 1.2 },
3566
+ * { type: 'vegetable', name: 'carrot', price: 0.8 },
3567
+ * { type: 'fruit', name: 'banana', price: 0.9 },
3568
+ * { type: 'vegetable', name: 'broccoli', price: 2.1 },
3569
+ * { type: 'fruit', name: 'orange', price: 1.5 }
3570
+ * ];
3571
+ *
3572
+ * const byType = Arr.groupBy(products, item => item.type);
3573
+ * // IMap<string, readonly Product[]> {
3574
+ * // 'fruit' => [
3575
+ * // { type: 'fruit', name: 'apple', price: 1.2 },
3576
+ * // { type: 'fruit', name: 'banana', price: 0.9 },
3577
+ * // { type: 'fruit', name: 'orange', price: 1.5 }
3578
+ * // ],
3579
+ * // 'vegetable' => [
3580
+ * // { type: 'vegetable', name: 'carrot', price: 0.8 },
3581
+ * // { type: 'vegetable', name: 'broccoli', price: 2.1 }
3582
+ * // ]
3583
+ * // }
3584
+ *
3585
+ * // Access grouped results with IMap methods
3586
+ * const fruits = IMap.get(byType, 'fruit'); // Optional<readonly Product[]>
3587
+ * const fruitCount = Optional.map(fruits, arr => arr.length); // Optional<number>
3588
+ * const fruitNames = Optional.map(fruits, arr => arr.map(p => p.name)); // Optional<string[]>
3589
+ *
3590
+ * // Grouping by computed values
3591
+ * const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
3592
+ * const byParity = Arr.groupBy(numbers, n => n % 2 === 0 ? 'even' : 'odd');
3593
+ * // IMap<string, readonly number[]> {
3594
+ * // 'odd' => [1, 3, 5, 7, 9],
3595
+ * // 'even' => [2, 4, 6, 8, 10]
3596
+ * // }
3597
+ *
3598
+ * // Grouping by price ranges using index information
3599
+ * const byPriceRange = Arr.groupBy(products, (product, index) => {
3600
+ * const category = product.price < 1.0 ? 'cheap' :
3601
+ * product.price < 2.0 ? 'moderate' : 'expensive';
3602
+ * return `${category}_${index < 2 ? 'early' : 'late'}`;
3603
+ * });
3604
+ *
3605
+ * // MapSetKeyType constraint examples (valid key types)
3606
+ * const byStringKey = Arr.groupBy([1, 2, 3], n => `group_${n}`); // string keys
3607
+ * const byNumberKey = Arr.groupBy(['a', 'b', 'c'], (_, i) => i); // number keys
3608
+ * const byBooleanKey = Arr.groupBy([1, 2, 3, 4], n => n > 2); // boolean keys
3609
+ * const bySymbolKey = Arr.groupBy([1, 2], n => Symbol(n.toString())); // symbol keys
3610
+ *
3611
+ * // Edge cases
3612
+ * const emptyGroup = Arr.groupBy([], x => x); // IMap<never, readonly never[]> (empty)
3613
+ * const singleGroup = Arr.groupBy([1, 2, 3], () => 'all'); // All elements in one group
3614
+ * const uniqueGroups = Arr.groupBy([1, 2, 3], x => x); // Each element in its own group
3615
+ *
3616
+ * // Curried usage for functional composition
3617
+ * const groupByType = Arr.groupBy((item: { type: string }) => item.type);
3618
+ * const groupByLength = Arr.groupBy((str: string) => str.length);
3619
+ * const groupByFirstChar = Arr.groupBy((str: string) => str.charAt(0).toLowerCase());
3620
+ *
3621
+ * const datasets = [
3622
+ * [{ type: 'A' }, { type: 'B' }, { type: 'A' }],
3623
+ * [{ type: 'C' }, { type: 'A' }],
3624
+ * [{ type: 'B' }, { type: 'B' }, { type: 'C' }]
3625
+ * ];
3626
+ * const allGrouped = datasets.map(groupByType);
3627
+ * // Array of IMap instances, each grouped by type
3628
+ *
3629
+ * // Pipe composition for complex data processing
3630
+ * const words = ['apple', 'banana', 'apricot', 'blueberry', 'avocado', 'blackberry'];
3631
+ * const processedGroups = pipe(words)
3632
+ * .map(groupByFirstChar)
3633
+ * .map(groupMap => IMap.map(groupMap, (wordsInGroup, firstLetter) => ({
3634
+ * letter: firstLetter,
3635
+ * count: wordsInGroup.length,
3636
+ * longest: wordsInGroup.reduce((longest, word) =>
3637
+ * word.length > longest.length ? word : longest
3638
+ * )
3639
+ * })))
3640
+ * .value;
3641
+ * // IMap<string, {letter: string, count: number, longest: string}>
3642
+ *
3643
+ * // Advanced: Grouping with complex transformations
3644
+ * const students = [
3645
+ * { name: 'Alice', grade: 85, subject: 'Math' },
3646
+ * { name: 'Bob', grade: 92, subject: 'Science' },
3647
+ * { name: 'Charlie', grade: 78, subject: 'Math' },
3648
+ * { name: 'Diana', grade: 96, subject: 'Science' }
3649
+ * ];
3650
+ *
3651
+ * const byGradeLevel = Arr.groupBy(students, student => {
3652
+ * if (student.grade >= 90) return 'A';
3653
+ * if (student.grade >= 80) return 'B';
3654
+ * return 'C';
3655
+ * });
3656
+ *
3657
+ * // Working with the grouped results
3658
+ * const aStudents = Optional.unwrapOr(IMap.get(byGradeLevel, 'A'), []);
3659
+ * const averageAGrade = aStudents.length > 0
3660
+ * ? aStudents.reduce((sum, s) => sum + s.grade, 0) / aStudents.length
3661
+ * : 0;
3662
+ *
3663
+ * // Type inference examples
3664
+ * expectType<typeof byType, IMap<string, readonly typeof products[number][]>>('=');
3665
+ * expectType<typeof byParity, IMap<string, readonly number[]>>('=');
3666
+ * expectType<typeof groupByType, <T extends {type: string}>(array: readonly T[]) => IMap<string, readonly T[]>>('=');
3667
+ * expectType<typeof emptyGroup, IMap<never, readonly never[]>>('=');
3668
+ * ```
3669
+ *
3670
+ * @see {@link IMap} for working with the returned immutable map
3671
+ * @see {@link MapSetKeyType} for understanding valid key types
3672
+ * @see {@link IMap.get} for safely accessing grouped results
3673
+ * @see {@link IMap.map} for transforming grouped data
3674
+ * @see {@link Optional} for handling potentially missing groups
3675
+ */
3676
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
3677
+ export const groupBy: GroupByFnOverload = (<E, G extends MapSetKeyType>(
3678
+ ...args:
3679
+ | readonly [
3680
+ array: readonly E[],
3681
+ grouper: (value: E, index: SizeType.Arr) => G,
3682
+ ]
3683
+ | readonly [grouper: (value: E, index: SizeType.Arr) => G]
3684
+ ) => {
3685
+ switch (args.length) {
3686
+ case 2: {
3687
+ const [array, grouper] = args;
3688
+ const mut_groups = new Map<G, E[]>(); // Store mutable arrays internally
3689
+
3690
+ for (const [index, e] of array.entries()) {
3691
+ const key = grouper(e, asUint32(index)); // Ensure index is treated as SizeType.Arr
3692
+ const mut_group = mut_groups.get(key);
3693
+ if (mut_group !== undefined) {
3694
+ mut_group.push(e);
3695
+ } else {
3696
+ mut_groups.set(key, [e]);
3697
+ }
3698
+ }
3699
+ // Cast to IMap<G, readonly A[]> for the public interface
3700
+ return IMap.create<G, readonly E[]>(mut_groups);
3701
+ }
3702
+ case 1: {
3703
+ const [grouper] = args;
3704
+ return (array: readonly E[]) => groupBy(array, grouper);
3705
+ }
3706
+ }
3707
+ }) as GroupByFnOverload;
3708
+
3709
+ type GroupByFnOverload = {
3710
+ <E, G extends MapSetKeyType>(
3711
+ array: readonly E[],
3712
+ grouper: (value: E, index: SizeType.Arr) => G,
3713
+ ): IMap<G, readonly E[]>;
3714
+
3715
+ // curried version
3716
+ <E, G extends MapSetKeyType>(
3717
+ grouper: (value: E, index: SizeType.Arr) => G,
3718
+ ): (array: readonly E[]) => IMap<G, readonly E[]>;
3719
+ };
3720
+
3721
+ /**
3722
+ * Creates a new array with unique elements from the input array. Order is preserved from the first occurrence.
3723
+ * Uses `Set` internally for efficient uniqueness checking.
3724
+ * @template P The type of elements in the array.
3725
+ * @param array The input array.
3726
+ * @returns A new array with unique elements from the input array. Returns `[]` for an empty input.
3727
+ * @example
3728
+ * ```ts
3729
+ * Arr.uniq([1, 2, 2, 3, 1, 4]); // [1, 2, 3, 4]
3730
+ * Arr.uniq(['a', 'b', 'a']); // ['a', 'b']
3731
+ * ```
3732
+ */
3733
+ export const uniq = <P extends Primitive>(
3734
+ array: readonly P[],
3735
+ ): readonly P[] => Array.from(new Set(array));
3736
+
3737
+ /**
3738
+ * Creates a new array with unique elements from the input array, based on the values returned by `mapFn`.
3739
+ *
3740
+ * - If the input is a non-empty array, returns a non-empty array.
3741
+ * - Otherwise, returns a readonly array.
3742
+ *
3743
+ * @template E The type of elements in the array.
3744
+ * @template P The type of the mapped value (used for uniqueness comparison).
3745
+ * @param array The input array.
3746
+ * @param mapFn A function `(value: A) => P` to map elements to values for uniqueness comparison.
3747
+ * @returns A new array with unique elements based on the mapped values.
3748
+ * @example
3749
+ * ```ts
3750
+ * const users = [
3751
+ * { id: 1, name: 'Alice' },
3752
+ * { id: 2, name: 'Bob' },
3753
+ * { id: 1, name: 'Alicia' }, // Duplicate id
3754
+ * ];
3755
+ * Arr.uniqBy(users, user => user.id); // [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]
3756
+ * ```
3757
+ */
3758
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
3759
+ export const uniqBy: UniqByFnOverload = (<E, P extends Primitive>(
3760
+ array: readonly E[],
3761
+ mapFn: (value: E) => P,
3762
+ ): readonly E[] => {
3763
+ const mut_mappedValues = new Set<P>();
3764
+
3765
+ return array.filter((val) => {
3766
+ const mappedValue = mapFn(val);
3767
+
3768
+ if (mut_mappedValues.has(mappedValue)) return false;
3769
+ mut_mappedValues.add(mappedValue);
3770
+
3771
+ return true;
3772
+ });
3773
+ }) as UniqByFnOverload;
3774
+
3775
+ type UniqByFnOverload = {
3776
+ <E, P extends Primitive>(
3777
+ array: NonEmptyArray<E>,
3778
+ mapFn: (value: E) => P,
3779
+ ): NonEmptyArray<E>;
3780
+
3781
+ <E, P extends Primitive>(
3782
+ array: readonly E[],
3783
+ mapFn: (value: E) => P,
3784
+ ): readonly E[];
3785
+ };
3786
+
3787
+ // set operations & equality
3788
+
3789
+ /**
3790
+ * Checks if two arrays are equal by performing a shallow comparison of their elements.
3791
+ * @template E The type of elements in the arrays.
3792
+ * @param array1 The first array.
3793
+ * @param array2 The second array.
3794
+ * @param equality An optional function `(a: T, b: T) => boolean` to compare elements. Defaults to `Object.is`.
3795
+ * @returns `true` if the arrays have the same length and all corresponding elements are equal according to the `equality` function, `false` otherwise.
3796
+ * @example
3797
+ * ```ts
3798
+ * Arr.eq([1, 2, 3], [1, 2, 3]); // true
3799
+ * Arr.eq([1, 2, 3], [1, 2, 4]); // false
3800
+ * Arr.eq([1, 2], [1, 2, 3]); // false
3801
+ * Arr.eq([{a:1}], [{a:1}]); // false (different object references)
3802
+ * Arr.eq([{a:1}], [{a:1}], (o1, o2) => o1.a === o2.a); // true
3803
+ * ```
3804
+ */
3805
+ export const eq = <E,>(
3806
+ array1: readonly E[],
3807
+ array2: readonly E[],
3808
+ equality: (a: E, b: E) => boolean = Object.is,
3809
+ ): boolean =>
3810
+ array1.length === array2.length &&
3811
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
3812
+ array1.every((v, i) => equality(v, array2[i]!));
3813
+
3814
+ /**
3815
+ * Alias for `eq`.
3816
+ */
3817
+ export const equal = eq;
3818
+
3819
+ /**
3820
+ * Checks if the first array (`array1`) is a subset of the second array (`array2`).
3821
+ * An array `A` is a subset of `B` if all elements of `A` are also present in `B`.
3822
+ * Elements must be primitive types for `includes` to work reliably for comparison.
3823
+ * @template E1 The type of elements in the first array (subset candidate), must be a primitive type.
3824
+ * @template E2 The type of elements in the second array (superset candidate), must be a primitive type.
3825
+ * @param array1 The first array.
3826
+ * @param array2 The second array.
3827
+ * @returns `true` if `array1` is a subset of `array2`, `false` otherwise.
3828
+ * @remarks `array1` ⊂ `array2`
3829
+ * @example
3830
+ * ```ts
3831
+ * Arr.isSubset([1, 2], [1, 2, 3]); // true
3832
+ * Arr.isSubset([1, 2, 3], [1, 2]); // false
3833
+ * Arr.isSubset([], [1, 2, 3]); // true
3834
+ * Arr.isSubset([1, 5], [1, 2, 3]); // false
3835
+ * ```
3836
+ */
3837
+ export const isSubset = <E1 extends Primitive, E2 extends Primitive = E1>(
3838
+ array1: readonly E1[],
3839
+ array2: readonly E2[],
3840
+ ): boolean =>
3841
+ array1.every((a) =>
3842
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
3843
+ array2.includes(a as E1 & E2),
3844
+ );
3845
+
3846
+ /**
3847
+ * Checks if the first array (`array1`) is a superset of the second array (`array2`).
3848
+ * An array `A` is a superset of `B` if all elements of `B` are also present in `A`.
3849
+ * Elements must be primitive types.
3850
+ * @template E1 The type of elements in the first array (superset candidate), must be a primitive type.
3851
+ * @template E2 The type of elements in the second array (subset candidate), must be a primitive type.
3852
+ * @param array1 The first array.
3853
+ * @param array2 The second array.
3854
+ * @returns `true` if `array1` is a superset of `array2`, `false` otherwise.
3855
+ * @remarks `array1` ⊃ `array2`
3856
+ * @example
3857
+ * ```ts
3858
+ * Arr.isSuperset([1, 2, 3], [1, 2]); // true
3859
+ * Arr.isSuperset([1, 2], [1, 2, 3]); // false
3860
+ * Arr.isSuperset([1, 2, 3], []); // true
3861
+ * ```
3862
+ */
3863
+ export const isSuperset = <E1 extends Primitive, E2 extends Primitive = E1>(
3864
+ array1: readonly E1[],
3865
+ array2: readonly E2[],
3866
+ ): boolean => isSubset(array2, array1);
3867
+
3868
+ /**
3869
+ * Returns the intersection of two arrays of primitive types.
3870
+ * The intersection contains elements that are present in both arrays. Order is based on `array1`.
3871
+ * @template E1 The type of elements in the first array (must be a primitive type).
3872
+ * @template E2 The type of elements in the second array (must be a primitive type).
3873
+ * @param array1 The first array.
3874
+ * @param array2 The second array.
3875
+ * @returns A new array containing elements that are in both `array1` and `array2`.
3876
+ * @example
3877
+ * ```ts
3878
+ * Arr.setIntersection([1, 2, 3], [2, 3, 4]); // [2, 3]
3879
+ * Arr.setIntersection(['a', 'b'], ['b', 'c']); // ['b']
3880
+ * Arr.setIntersection([1, 2], [3, 4]); // []
3881
+ * ```
3882
+ */
3883
+ export const setIntersection = <
3884
+ E1 extends Primitive,
3885
+ E2 extends Primitive = E1,
3886
+ >(
3887
+ array1: readonly E1[],
3888
+ array2: readonly E2[],
3889
+ ): readonly (E1 & E2)[] =>
3890
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
3891
+ array1.filter((e) => array2.includes(e as E1 & E2)) as (E1 & E2)[];
3892
+
3893
+ /**
3894
+ * Returns the set difference of two arrays (`array1` - `array2`).
3895
+ * The difference contains elements that are in `array1` but not in `array2`. Order is based on `array1`.
3896
+ * Elements must be primitive types.
3897
+ * @template E The type of elements in the arrays (must be a primitive type).
3898
+ * @param array1 The first array.
3899
+ * @param array2 The second array.
3900
+ * @returns A new array containing elements from `array1` that are not in `array2`.
3901
+ * @example
3902
+ * ```ts
3903
+ * Arr.setDifference([1, 2, 3], [2, 3, 4]); // [1]
3904
+ * Arr.setDifference([1, 2, 3], [1, 2, 3]); // []
3905
+ * Arr.setDifference([1, 2], [3, 4]); // [1, 2]
3906
+ * ```
3907
+ */
3908
+ export const setDifference = <E extends Primitive>(
3909
+ array1: readonly E[],
3910
+ array2: readonly E[],
3911
+ ): readonly E[] => array1.filter((e) => !array2.includes(e));
3912
+
3913
+ /**
3914
+ * Returns the set difference of two sorted arrays of numbers (`sortedList1` - `sortedList2`).
3915
+ * This operation is more efficient for sorted arrays than the generic `setDifference`.
3916
+ * The resulting array is also sorted.
3917
+ * @template E The type of numbers in the arrays (must extend `number`).
3918
+ * @param sortedList1 The first sorted array of numbers.
3919
+ * @param sortedList2 The second sorted array of numbers.
3920
+ * @returns A new sorted array containing numbers from `sortedList1` that are not in `sortedList2`.
3921
+ * @example
3922
+ * ```ts
3923
+ * Arr.sortedNumSetDifference([1, 2, 3, 5], [2, 4, 5]); // [1, 3]
3924
+ * Arr.sortedNumSetDifference([1, 2, 3], [1, 2, 3]); // []
3925
+ * Arr.sortedNumSetDifference([1, 2], [3, 4]); // [1, 2]
3926
+ * ```
3927
+ */
3928
+ export const sortedNumSetDifference = <E extends number>(
3929
+ sortedList1: readonly E[],
3930
+ sortedList2: readonly E[],
3931
+ ): readonly E[] => {
3932
+ const mut_result: E[] = [];
3933
+ let mut_it1 = 0; // iterator for sortedList1
3934
+ let mut_it2 = 0; // iterator for sortedList2
3935
+
3936
+ while (mut_it1 < sortedList1.length && mut_it2 < sortedList2.length) {
3937
+ // Non-null assertions are safe due to loop condition
3938
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
3939
+ const val1 = sortedList1[mut_it1]!;
3940
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
3941
+ const val2 = sortedList2[mut_it2]!;
3942
+
3943
+ if (val1 === val2) {
3944
+ mut_it1 += 1;
3945
+ mut_it2 += 1;
3946
+ } else if (val1 < val2) {
3947
+ mut_result.push(val1);
3948
+ mut_it1 += 1;
3949
+ } else {
3950
+ // val1 > val2
3951
+ mut_it2 += 1;
3952
+ }
3953
+ }
3954
+ // Add remaining elements from sortedList1
3955
+ for (; mut_it1 < sortedList1.length; mut_it1 += 1) {
3956
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
3957
+ mut_result.push(sortedList1[mut_it1]!);
3958
+ }
3959
+
3960
+ return mut_result;
3961
+ };
3962
+
3963
+ // aliases
3964
+
3965
+ /**
3966
+ * Alias for `head`. Returns the first element of an array.
3967
+ * @see {@link head}
3968
+ */
3969
+ export const first = head;
3970
+
3971
+ /**
3972
+ * Alias for `tail`. Returns all elements of an array except the first one.
3973
+ * @see {@link tail}
3974
+ */
3975
+ export const rest = tail;
3976
+
3977
+ /**
3978
+ * Alias for `skip`. Skips the first N elements of an array.
3979
+ * @see {@link skip}
3980
+ */
3981
+ export const drop = skip;
3982
+
3983
+ /**
3984
+ * Alias for `foldl`. Applies a function against an accumulator and each element in the array (from left to right) to reduce it to a single value.
3985
+ * @see {@link foldl}
3986
+ */
3987
+ export const reduce = foldl;
3988
+
3989
+ /**
3990
+ * Alias for `foldr`. Applies a function against an accumulator and each element in the array (from right to left) to reduce it to a single value.
3991
+ * @see {@link foldr}
3992
+ */
3993
+ export const reduceRight = foldr;
3994
+
3995
+ /**
3996
+ * Alias for `partition`. Splits an array into chunks of a specified size.
3997
+ * @see {@link partition}
3998
+ */
3999
+ export const chunk = partition;
4000
+ }