@fluixi/utils 1.0.0-alpha.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +73 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/index.mjs +1 -0
- package/dist/lib/array/advanced.cjs +1 -0
- package/dist/lib/array/advanced.d.ts +1116 -0
- package/dist/lib/array/advanced.d.ts.map +1 -0
- package/dist/lib/array/advanced.js +2429 -0
- package/dist/lib/array/advanced.mjs +1 -0
- package/dist/lib/array/array.cjs +1 -0
- package/dist/lib/array/array.d.ts +6 -0
- package/dist/lib/array/array.d.ts.map +1 -0
- package/dist/lib/array/array.js +31 -0
- package/dist/lib/array/array.mjs +1 -0
- package/dist/lib/array/index.cjs +1 -0
- package/dist/lib/array/index.d.ts +4 -0
- package/dist/lib/array/index.d.ts.map +1 -0
- package/dist/lib/array/index.js +3 -0
- package/dist/lib/array/index.mjs +1 -0
- package/dist/lib/array/utils.cjs +1 -0
- package/dist/lib/array/utils.d.ts +100 -0
- package/dist/lib/array/utils.d.ts.map +1 -0
- package/dist/lib/array/utils.js +165 -0
- package/dist/lib/array/utils.mjs +1 -0
- package/dist/lib/color/color.cjs +1 -0
- package/dist/lib/color/color.d.ts +366 -0
- package/dist/lib/color/color.d.ts.map +1 -0
- package/dist/lib/color/color.js +722 -0
- package/dist/lib/color/color.mjs +1 -0
- package/dist/lib/color/constants.cjs +1 -0
- package/dist/lib/color/constants.d.ts +361 -0
- package/dist/lib/color/constants.d.ts.map +1 -0
- package/dist/lib/color/constants.js +358 -0
- package/dist/lib/color/constants.mjs +1 -0
- package/dist/lib/color/conversions.cjs +1 -0
- package/dist/lib/color/conversions.d.ts +166 -0
- package/dist/lib/color/conversions.d.ts.map +1 -0
- package/dist/lib/color/conversions.js +766 -0
- package/dist/lib/color/conversions.mjs +1 -0
- package/dist/lib/color/helpers.cjs +1 -0
- package/dist/lib/color/helpers.d.ts +75 -0
- package/dist/lib/color/helpers.d.ts.map +1 -0
- package/dist/lib/color/helpers.js +419 -0
- package/dist/lib/color/helpers.mjs +1 -0
- package/dist/lib/color/index.cjs +1 -0
- package/dist/lib/color/index.d.ts +12 -0
- package/dist/lib/color/index.d.ts.map +1 -0
- package/dist/lib/color/index.js +15 -0
- package/dist/lib/color/index.mjs +1 -0
- package/dist/lib/color/types.cjs +1 -0
- package/dist/lib/color/types.d.ts +179 -0
- package/dist/lib/color/types.d.ts.map +1 -0
- package/dist/lib/color/types.js +1 -0
- package/dist/lib/color/types.mjs +0 -0
- package/dist/lib/color/utils.cjs +1 -0
- package/dist/lib/color/utils.d.ts +199 -0
- package/dist/lib/color/utils.d.ts.map +1 -0
- package/dist/lib/color/utils.js +858 -0
- package/dist/lib/color/utils.mjs +1 -0
- package/dist/lib/compare/comparators.cjs +1 -0
- package/dist/lib/compare/comparators.d.ts +65 -0
- package/dist/lib/compare/comparators.d.ts.map +1 -0
- package/dist/lib/compare/comparators.js +210 -0
- package/dist/lib/compare/comparators.mjs +1 -0
- package/dist/lib/compare/index.cjs +1 -0
- package/dist/lib/compare/index.d.ts +2 -0
- package/dist/lib/compare/index.d.ts.map +1 -0
- package/dist/lib/compare/index.js +1 -0
- package/dist/lib/compare/index.mjs +1 -0
- package/dist/lib/crypto/hash.cjs +1 -0
- package/dist/lib/crypto/hash.d.ts +6 -0
- package/dist/lib/crypto/hash.d.ts.map +1 -0
- package/dist/lib/crypto/hash.js +53 -0
- package/dist/lib/crypto/hash.mjs +1 -0
- package/dist/lib/crypto/index.cjs +1 -0
- package/dist/lib/crypto/index.d.ts +2 -0
- package/dist/lib/crypto/index.d.ts.map +1 -0
- package/dist/lib/crypto/index.js +1 -0
- package/dist/lib/crypto/index.mjs +1 -0
- package/dist/lib/dom/events.cjs +1 -0
- package/dist/lib/dom/events.d.ts +159 -0
- package/dist/lib/dom/events.d.ts.map +1 -0
- package/dist/lib/dom/events.js +50 -0
- package/dist/lib/dom/events.mjs +1 -0
- package/dist/lib/dom/index.cjs +1 -0
- package/dist/lib/dom/index.d.ts +3 -0
- package/dist/lib/dom/index.d.ts.map +1 -0
- package/dist/lib/dom/index.js +2 -0
- package/dist/lib/dom/index.mjs +1 -0
- package/dist/lib/dom/utils.cjs +1 -0
- package/dist/lib/dom/utils.d.ts +6 -0
- package/dist/lib/dom/utils.d.ts.map +1 -0
- package/dist/lib/dom/utils.js +37 -0
- package/dist/lib/dom/utils.mjs +1 -0
- package/dist/lib/functions/functions.cjs +1 -0
- package/dist/lib/functions/functions.d.ts +329 -0
- package/dist/lib/functions/functions.d.ts.map +1 -0
- package/dist/lib/functions/functions.js +585 -0
- package/dist/lib/functions/functions.mjs +1 -0
- package/dist/lib/functions/index.cjs +1 -0
- package/dist/lib/functions/index.d.ts +3 -0
- package/dist/lib/functions/index.d.ts.map +1 -0
- package/dist/lib/functions/index.js +1 -0
- package/dist/lib/functions/index.mjs +1 -0
- package/dist/lib/functions/types.cjs +1 -0
- package/dist/lib/functions/types.d.ts +2 -0
- package/dist/lib/functions/types.d.ts.map +1 -0
- package/dist/lib/functions/types.js +1 -0
- package/dist/lib/functions/types.mjs +0 -0
- package/dist/lib/helpers.cjs +1 -0
- package/dist/lib/helpers.d.ts +13 -0
- package/dist/lib/helpers.d.ts.map +1 -0
- package/dist/lib/helpers.js +66 -0
- package/dist/lib/helpers.mjs +1 -0
- package/dist/lib/index.cjs +1 -0
- package/dist/lib/index.d.ts +6 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +8 -0
- package/dist/lib/index.mjs +1 -0
- package/dist/lib/maths/constants.cjs +1 -0
- package/dist/lib/maths/constants.d.ts +84 -0
- package/dist/lib/maths/constants.d.ts.map +1 -0
- package/dist/lib/maths/constants.js +79 -0
- package/dist/lib/maths/constants.mjs +1 -0
- package/dist/lib/maths/fn/advanced.cjs +1 -0
- package/dist/lib/maths/fn/advanced.d.ts +149 -0
- package/dist/lib/maths/fn/advanced.d.ts.map +1 -0
- package/dist/lib/maths/fn/advanced.js +264 -0
- package/dist/lib/maths/fn/advanced.mjs +1 -0
- package/dist/lib/maths/fn/basic.cjs +1 -0
- package/dist/lib/maths/fn/basic.d.ts +182 -0
- package/dist/lib/maths/fn/basic.d.ts.map +1 -0
- package/dist/lib/maths/fn/basic.js +343 -0
- package/dist/lib/maths/fn/basic.mjs +1 -0
- package/dist/lib/maths/fn/complex.cjs +1 -0
- package/dist/lib/maths/fn/complex.d.ts +153 -0
- package/dist/lib/maths/fn/complex.d.ts.map +1 -0
- package/dist/lib/maths/fn/complex.js +383 -0
- package/dist/lib/maths/fn/complex.mjs +1 -0
- package/dist/lib/maths/fn/index.cjs +1 -0
- package/dist/lib/maths/fn/index.d.ts +10 -0
- package/dist/lib/maths/fn/index.d.ts.map +1 -0
- package/dist/lib/maths/fn/index.js +9 -0
- package/dist/lib/maths/fn/index.mjs +1 -0
- package/dist/lib/maths/fn/trigo.cjs +1 -0
- package/dist/lib/maths/fn/trigo.d.ts +147 -0
- package/dist/lib/maths/fn/trigo.d.ts.map +1 -0
- package/dist/lib/maths/fn/trigo.js +227 -0
- package/dist/lib/maths/fn/trigo.mjs +1 -0
- package/dist/lib/maths/fn/vector.cjs +1 -0
- package/dist/lib/maths/fn/vector.d.ts +512 -0
- package/dist/lib/maths/fn/vector.d.ts.map +1 -0
- package/dist/lib/maths/fn/vector.js +1136 -0
- package/dist/lib/maths/fn/vector.mjs +1 -0
- package/dist/lib/maths/formatter.cjs +1 -0
- package/dist/lib/maths/formatter.d.ts +35 -0
- package/dist/lib/maths/formatter.d.ts.map +1 -0
- package/dist/lib/maths/formatter.js +56 -0
- package/dist/lib/maths/formatter.mjs +1 -0
- package/dist/lib/maths/guards.cjs +1 -0
- package/dist/lib/maths/guards.d.ts +166 -0
- package/dist/lib/maths/guards.d.ts.map +1 -0
- package/dist/lib/maths/guards.js +315 -0
- package/dist/lib/maths/guards.mjs +1 -0
- package/dist/lib/maths/index.cjs +1 -0
- package/dist/lib/maths/index.d.ts +24 -0
- package/dist/lib/maths/index.d.ts.map +1 -0
- package/dist/lib/maths/index.js +29 -0
- package/dist/lib/maths/index.mjs +1 -0
- package/dist/lib/maths/parser.cjs +1 -0
- package/dist/lib/maths/parser.d.ts +20 -0
- package/dist/lib/maths/parser.d.ts.map +1 -0
- package/dist/lib/maths/parser.js +78 -0
- package/dist/lib/maths/parser.mjs +1 -0
- package/dist/lib/maths/random.cjs +1 -0
- package/dist/lib/maths/random.d.ts +27 -0
- package/dist/lib/maths/random.d.ts.map +1 -0
- package/dist/lib/maths/random.js +49 -0
- package/dist/lib/maths/random.mjs +1 -0
- package/dist/lib/maths/types/advanced.cjs +1 -0
- package/dist/lib/maths/types/advanced.d.ts +357 -0
- package/dist/lib/maths/types/advanced.d.ts.map +1 -0
- package/dist/lib/maths/types/advanced.js +5 -0
- package/dist/lib/maths/types/advanced.mjs +0 -0
- package/dist/lib/maths/types/basic.cjs +1 -0
- package/dist/lib/maths/types/basic.d.ts +95 -0
- package/dist/lib/maths/types/basic.d.ts.map +1 -0
- package/dist/lib/maths/types/basic.js +5 -0
- package/dist/lib/maths/types/basic.mjs +0 -0
- package/dist/lib/maths/types/index.cjs +1 -0
- package/dist/lib/maths/types/index.d.ts +4 -0
- package/dist/lib/maths/types/index.d.ts.map +1 -0
- package/dist/lib/maths/types/index.js +3 -0
- package/dist/lib/maths/types/index.mjs +0 -0
- package/dist/lib/maths/types/negative.cjs +1 -0
- package/dist/lib/maths/types/negative.d.ts +51 -0
- package/dist/lib/maths/types/negative.d.ts.map +1 -0
- package/dist/lib/maths/types/negative.js +5 -0
- package/dist/lib/maths/types/negative.mjs +0 -0
- package/dist/lib/maths/types/operations.cjs +1 -0
- package/dist/lib/maths/types/operations.d.ts +188 -0
- package/dist/lib/maths/types/operations.d.ts.map +1 -0
- package/dist/lib/maths/types/operations.js +6 -0
- package/dist/lib/maths/types/operations.mjs +0 -0
- package/dist/lib/object/clone.cjs +1 -0
- package/dist/lib/object/clone.d.ts +46 -0
- package/dist/lib/object/clone.d.ts.map +1 -0
- package/dist/lib/object/clone.js +104 -0
- package/dist/lib/object/clone.mjs +1 -0
- package/dist/lib/object/diff.cjs +1 -0
- package/dist/lib/object/diff.d.ts +203 -0
- package/dist/lib/object/diff.d.ts.map +1 -0
- package/dist/lib/object/diff.js +805 -0
- package/dist/lib/object/diff.mjs +1 -0
- package/dist/lib/object/getter-setter.cjs +1 -0
- package/dist/lib/object/getter-setter.d.ts +361 -0
- package/dist/lib/object/getter-setter.d.ts.map +1 -0
- package/dist/lib/object/getter-setter.js +1771 -0
- package/dist/lib/object/getter-setter.mjs +1 -0
- package/dist/lib/object/index.cjs +6 -0
- package/dist/lib/object/index.d.ts +13 -0
- package/dist/lib/object/index.d.ts.map +1 -0
- package/dist/lib/object/index.js +12 -0
- package/dist/lib/object/index.mjs +6 -0
- package/dist/lib/object/merge.cjs +1 -0
- package/dist/lib/object/merge.d.ts +93 -0
- package/dist/lib/object/merge.d.ts.map +1 -0
- package/dist/lib/object/merge.js +474 -0
- package/dist/lib/object/merge.mjs +1 -0
- package/dist/lib/object/object.cjs +1 -0
- package/dist/lib/object/object.d.ts +128 -0
- package/dist/lib/object/object.d.ts.map +1 -0
- package/dist/lib/object/object.js +432 -0
- package/dist/lib/object/object.mjs +1 -0
- package/dist/lib/object/omit.cjs +1 -0
- package/dist/lib/object/omit.d.ts +351 -0
- package/dist/lib/object/omit.d.ts.map +1 -0
- package/dist/lib/object/omit.js +656 -0
- package/dist/lib/object/omit.mjs +1 -0
- package/dist/lib/object/reconcile.cjs +1 -0
- package/dist/lib/object/reconcile.d.ts +7 -0
- package/dist/lib/object/reconcile.d.ts.map +1 -0
- package/dist/lib/object/reconcile.js +122 -0
- package/dist/lib/object/reconcile.mjs +1 -0
- package/dist/lib/object/selector.cjs +1 -0
- package/dist/lib/object/selector.d.ts +50 -0
- package/dist/lib/object/selector.d.ts.map +1 -0
- package/dist/lib/object/selector.js +371 -0
- package/dist/lib/object/selector.mjs +1 -0
- package/dist/lib/object/sort.cjs +1 -0
- package/dist/lib/object/sort.d.ts +149 -0
- package/dist/lib/object/sort.d.ts.map +1 -0
- package/dist/lib/object/sort.js +298 -0
- package/dist/lib/object/sort.mjs +1 -0
- package/dist/lib/object/transform.cjs +1 -0
- package/dist/lib/object/transform.d.ts +620 -0
- package/dist/lib/object/transform.d.ts.map +1 -0
- package/dist/lib/object/transform.js +1058 -0
- package/dist/lib/object/transform.mjs +1 -0
- package/dist/lib/object/types.cjs +1 -0
- package/dist/lib/object/types.d.ts +250 -0
- package/dist/lib/object/types.d.ts.map +1 -0
- package/dist/lib/object/types.js +1 -0
- package/dist/lib/object/types.mjs +0 -0
- package/dist/lib/object/utils.cjs +6 -0
- package/dist/lib/object/utils.d.ts +165 -0
- package/dist/lib/object/utils.d.ts.map +1 -0
- package/dist/lib/object/utils.js +444 -0
- package/dist/lib/object/utils.mjs +6 -0
- package/dist/lib/primitive/boolean/boolean.cjs +1 -0
- package/dist/lib/primitive/boolean/boolean.d.ts +276 -0
- package/dist/lib/primitive/boolean/boolean.d.ts.map +1 -0
- package/dist/lib/primitive/boolean/boolean.js +615 -0
- package/dist/lib/primitive/boolean/boolean.mjs +1 -0
- package/dist/lib/primitive/boolean/index.cjs +1 -0
- package/dist/lib/primitive/boolean/index.d.ts +3 -0
- package/dist/lib/primitive/boolean/index.d.ts.map +1 -0
- package/dist/lib/primitive/boolean/index.js +2 -0
- package/dist/lib/primitive/boolean/index.mjs +1 -0
- package/dist/lib/primitive/boolean/types.cjs +1 -0
- package/dist/lib/primitive/boolean/types.d.ts +14 -0
- package/dist/lib/primitive/boolean/types.d.ts.map +1 -0
- package/dist/lib/primitive/boolean/types.js +1 -0
- package/dist/lib/primitive/boolean/types.mjs +0 -0
- package/dist/lib/primitive/date/date.cjs +1 -0
- package/dist/lib/primitive/date/date.d.ts +641 -0
- package/dist/lib/primitive/date/date.d.ts.map +1 -0
- package/dist/lib/primitive/date/date.js +1998 -0
- package/dist/lib/primitive/date/date.mjs +1 -0
- package/dist/lib/primitive/date/index.cjs +1 -0
- package/dist/lib/primitive/date/index.d.ts +3 -0
- package/dist/lib/primitive/date/index.d.ts.map +1 -0
- package/dist/lib/primitive/date/index.js +2 -0
- package/dist/lib/primitive/date/index.mjs +1 -0
- package/dist/lib/primitive/date/types.cjs +1 -0
- package/dist/lib/primitive/date/types.d.ts +205 -0
- package/dist/lib/primitive/date/types.d.ts.map +1 -0
- package/dist/lib/primitive/date/types.js +7 -0
- package/dist/lib/primitive/date/types.mjs +0 -0
- package/dist/lib/primitive/index.cjs +5 -0
- package/dist/lib/primitive/index.d.ts +5 -0
- package/dist/lib/primitive/index.d.ts.map +1 -0
- package/dist/lib/primitive/index.js +4 -0
- package/dist/lib/primitive/index.mjs +5 -0
- package/dist/lib/primitive/string/index.cjs +5 -0
- package/dist/lib/primitive/string/index.d.ts +3 -0
- package/dist/lib/primitive/string/index.d.ts.map +1 -0
- package/dist/lib/primitive/string/index.js +2 -0
- package/dist/lib/primitive/string/index.mjs +5 -0
- package/dist/lib/primitive/string/parser-2.cjs +8 -0
- package/dist/lib/primitive/string/parser-2.d.ts +17 -0
- package/dist/lib/primitive/string/parser-2.d.ts.map +1 -0
- package/dist/lib/primitive/string/parser-2.js +656 -0
- package/dist/lib/primitive/string/parser-2.mjs +8 -0
- package/dist/lib/primitive/string/parser.cjs +3 -0
- package/dist/lib/primitive/string/parser.d.ts +9 -0
- package/dist/lib/primitive/string/parser.d.ts.map +1 -0
- package/dist/lib/primitive/string/parser.js +775 -0
- package/dist/lib/primitive/string/parser.mjs +3 -0
- package/dist/lib/primitive/string/string.cjs +5 -0
- package/dist/lib/primitive/string/string.d.ts +535 -0
- package/dist/lib/primitive/string/string.d.ts.map +1 -0
- package/dist/lib/primitive/string/string.js +1301 -0
- package/dist/lib/primitive/string/string.mjs +5 -0
- package/dist/lib/primitive/string/types.cjs +1 -0
- package/dist/lib/primitive/string/types.d.ts +318 -0
- package/dist/lib/primitive/string/types.d.ts.map +1 -0
- package/dist/lib/primitive/string/types.js +5 -0
- package/dist/lib/primitive/string/types.mjs +0 -0
- package/dist/lib/primitive/types.cjs +1 -0
- package/dist/lib/primitive/types.d.ts +27 -0
- package/dist/lib/primitive/types.d.ts.map +1 -0
- package/dist/lib/primitive/types.js +1 -0
- package/dist/lib/primitive/types.mjs +0 -0
- package/dist/lib/url/index.cjs +1 -0
- package/dist/lib/url/index.d.ts +3 -0
- package/dist/lib/url/index.d.ts.map +1 -0
- package/dist/lib/url/index.js +2 -0
- package/dist/lib/url/index.mjs +1 -0
- package/dist/lib/url/parser.cjs +1 -0
- package/dist/lib/url/parser.d.ts +216 -0
- package/dist/lib/url/parser.d.ts.map +1 -0
- package/dist/lib/url/parser.js +488 -0
- package/dist/lib/url/parser.mjs +1 -0
- package/dist/lib/url/types.cjs +1 -0
- package/dist/lib/url/types.d.ts +18 -0
- package/dist/lib/url/types.d.ts.map +1 -0
- package/dist/lib/url/types.js +1 -0
- package/dist/lib/url/types.mjs +0 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/package.json +150 -0
|
@@ -0,0 +1,2429 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Advanced array utilities with object integration
|
|
3
|
+
* Provides powerful array manipulation functions that work seamlessly with objects
|
|
4
|
+
*/
|
|
5
|
+
import { isObject } from '../object/utils.js';
|
|
6
|
+
import { deepClone } from '../object/clone.js';
|
|
7
|
+
import { get } from '../object/getter-setter.js';
|
|
8
|
+
/**
|
|
9
|
+
* Chunks an array into smaller arrays of specified size
|
|
10
|
+
* @param array - Source array
|
|
11
|
+
* @param size - Chunk size
|
|
12
|
+
* @returns Array of chunks
|
|
13
|
+
* @example
|
|
14
|
+
* chunk([1, 2, 3, 4, 5], 2) // [[1, 2], [3, 4], [5]]
|
|
15
|
+
*/
|
|
16
|
+
export function chunk(array, size) {
|
|
17
|
+
if (!Array.isArray(array) || size < 1) {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
const chunks = [];
|
|
21
|
+
for (let i = 0; i < array.length; i += size) {
|
|
22
|
+
chunks.push(array.slice(i, i + size));
|
|
23
|
+
}
|
|
24
|
+
return chunks;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Flattens array by one level
|
|
28
|
+
* @param array - Source array
|
|
29
|
+
* @returns Flattened array
|
|
30
|
+
* @example
|
|
31
|
+
* flatten([[1, 2], [3, 4]]) // [1, 2, 3, 4]
|
|
32
|
+
*/
|
|
33
|
+
export function flatten(array) {
|
|
34
|
+
return array.reduce((acc, val) => {
|
|
35
|
+
return acc.concat(val);
|
|
36
|
+
}, []);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Deeply flattens nested arrays
|
|
40
|
+
* @param array - Source array
|
|
41
|
+
* @param depth - Maximum depth (default: Infinity)
|
|
42
|
+
* @returns Flattened array
|
|
43
|
+
* @example
|
|
44
|
+
* flattenDeep([[1, [2, [3, [4]]]]) // [1, 2, 3, 4]
|
|
45
|
+
*/
|
|
46
|
+
export function flattenDeep(array, depth = Infinity) {
|
|
47
|
+
if (depth < 1) {
|
|
48
|
+
return array.slice();
|
|
49
|
+
}
|
|
50
|
+
return array.reduce((acc, val) => {
|
|
51
|
+
if (Array.isArray(val)) {
|
|
52
|
+
acc.push(...flattenDeep(val, depth - 1));
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
acc.push(val);
|
|
56
|
+
}
|
|
57
|
+
return acc;
|
|
58
|
+
}, []);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Returns unique values from array
|
|
62
|
+
* @param array - Source array
|
|
63
|
+
* @param selector - Optional key path or function for comparison
|
|
64
|
+
* @returns Array with unique values
|
|
65
|
+
* @example
|
|
66
|
+
* unique([1, 2, 2, 3]) // [1, 2, 3]
|
|
67
|
+
* unique([{id: 1}, {id: 2}, {id: 1}], 'id') // [{id: 1}, {id: 2}]
|
|
68
|
+
* unique([{x: 1}, {x: 2}, {x: 1}], item => item.x) // [{x: 1}, {x: 2}]
|
|
69
|
+
*/
|
|
70
|
+
export function unique(array, selector) {
|
|
71
|
+
if (!Array.isArray(array)) {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
if (!selector) {
|
|
75
|
+
return [...new Set(array)];
|
|
76
|
+
}
|
|
77
|
+
const seen = new Set();
|
|
78
|
+
return array.filter((item) => {
|
|
79
|
+
const value = typeof selector === 'function'
|
|
80
|
+
? selector(item)
|
|
81
|
+
: isObject(item)
|
|
82
|
+
? get(item, selector)
|
|
83
|
+
: item;
|
|
84
|
+
if (seen.has(value)) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
seen.add(value);
|
|
88
|
+
return true;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Groups array elements by key or callback
|
|
93
|
+
* @param array - Source array
|
|
94
|
+
* @param keyOrFn - Key name or grouping function
|
|
95
|
+
* @returns Grouped object
|
|
96
|
+
* @example
|
|
97
|
+
* groupBy([{type: 'a', v: 1}, {type: 'b', v: 2}, {type: 'a', v: 3}], 'type')
|
|
98
|
+
* // {a: [{type: 'a', v: 1}, {type: 'a', v: 3}], b: [{type: 'b', v: 2}]}
|
|
99
|
+
*/
|
|
100
|
+
export function groupBy(array, keyOrFn) {
|
|
101
|
+
if (!Array.isArray(array)) {
|
|
102
|
+
return {};
|
|
103
|
+
}
|
|
104
|
+
return array.reduce((acc, item) => {
|
|
105
|
+
let key;
|
|
106
|
+
if (typeof keyOrFn === 'function') {
|
|
107
|
+
key = String(keyOrFn(item));
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
key = String(isObject(item) ? get(item, keyOrFn) : item);
|
|
111
|
+
}
|
|
112
|
+
if (!acc[key]) {
|
|
113
|
+
acc[key] = [];
|
|
114
|
+
}
|
|
115
|
+
acc[key].push(item);
|
|
116
|
+
return acc;
|
|
117
|
+
}, {});
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Counts occurrences of each value
|
|
121
|
+
* @param array - Source array
|
|
122
|
+
* @param key - Optional key for object arrays
|
|
123
|
+
* @returns Count object
|
|
124
|
+
* @example
|
|
125
|
+
* countBy(['a', 'b', 'a', 'c']) // {a: 2, b: 1, c: 1}
|
|
126
|
+
*/
|
|
127
|
+
export function countBy(array, key) {
|
|
128
|
+
if (!Array.isArray(array)) {
|
|
129
|
+
return {};
|
|
130
|
+
}
|
|
131
|
+
return array.reduce((acc, item) => {
|
|
132
|
+
let countKey;
|
|
133
|
+
if (typeof key === 'function') {
|
|
134
|
+
countKey = String(key(item));
|
|
135
|
+
}
|
|
136
|
+
else if (key) {
|
|
137
|
+
countKey = String(isObject(item) ? get(item, key) : item);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
countKey = String(item);
|
|
141
|
+
}
|
|
142
|
+
acc[countKey] = (acc[countKey] || 0) + 1;
|
|
143
|
+
return acc;
|
|
144
|
+
}, {});
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Creates object from array using key/value extractors
|
|
148
|
+
* @param array - Source array
|
|
149
|
+
* @param keyFn - Key extractor function or key name
|
|
150
|
+
* @param valueFn - Value extractor function or value name (optional)
|
|
151
|
+
* @returns Object
|
|
152
|
+
* @example
|
|
153
|
+
* keyBy([{id: 1, name: 'a'}, {id: 2, name: 'b'}], 'id')
|
|
154
|
+
* // {1: {id: 1, name: 'a'}, 2: {id: 2, name: 'b'}}
|
|
155
|
+
*/
|
|
156
|
+
export function keyBy(array, keyFn, valueFn) {
|
|
157
|
+
if (!Array.isArray(array)) {
|
|
158
|
+
return {};
|
|
159
|
+
}
|
|
160
|
+
return array.reduce((acc, item) => {
|
|
161
|
+
let key;
|
|
162
|
+
if (typeof keyFn === 'function') {
|
|
163
|
+
key = String(keyFn(item));
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
key = String(isObject(item) ? get(item, keyFn) : item);
|
|
167
|
+
}
|
|
168
|
+
let value;
|
|
169
|
+
if (valueFn) {
|
|
170
|
+
if (typeof valueFn === 'function') {
|
|
171
|
+
value = valueFn(item);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
value = get(item, valueFn);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
value = item;
|
|
179
|
+
}
|
|
180
|
+
acc[key] = value;
|
|
181
|
+
return acc;
|
|
182
|
+
}, {});
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Sorts array by key or multiple keys
|
|
186
|
+
* @param array - Source array
|
|
187
|
+
* @param keys - Sort key(s) with optional direction
|
|
188
|
+
* @returns Sorted array (new array)
|
|
189
|
+
* @example
|
|
190
|
+
* sortBy([{a: 3}, {a: 1}, {a: 2}], 'a') // [{a: 1}, {a: 2}, {a: 3}]
|
|
191
|
+
* sortBy([{a: 1, b: 3}, {a: 1, b: 1}], ['a', {key: 'b', dir: 'desc'}])
|
|
192
|
+
*/
|
|
193
|
+
export function sortBy(array, keys) {
|
|
194
|
+
if (!Array.isArray(array) || array.length === 0) {
|
|
195
|
+
return [];
|
|
196
|
+
}
|
|
197
|
+
const sorted = [...array];
|
|
198
|
+
const keysArray = Array.isArray(keys) ? keys : [keys];
|
|
199
|
+
const normalizedKeys = keysArray.map((k) => {
|
|
200
|
+
if (typeof k === 'object' && 'key' in k) {
|
|
201
|
+
return { key: k.key, dir: k.dir || 'asc' };
|
|
202
|
+
}
|
|
203
|
+
return { key: k, dir: 'asc' };
|
|
204
|
+
});
|
|
205
|
+
sorted.sort((a, b) => {
|
|
206
|
+
for (const { key, dir } of normalizedKeys) {
|
|
207
|
+
const aVal = isObject(a) ? get(a, key) : a;
|
|
208
|
+
const bVal = isObject(b) ? get(b, key) : b;
|
|
209
|
+
if (aVal === bVal)
|
|
210
|
+
continue;
|
|
211
|
+
const comparison = aVal < bVal ? -1 : 1;
|
|
212
|
+
return dir === 'asc' ? comparison : -comparison;
|
|
213
|
+
}
|
|
214
|
+
return 0;
|
|
215
|
+
});
|
|
216
|
+
return sorted;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Finds difference between two arrays
|
|
220
|
+
* @param array1 - First array
|
|
221
|
+
* @param array2 - Second array
|
|
222
|
+
* @param key - Optional key for object comparison
|
|
223
|
+
* @returns Elements in array1 not in array2
|
|
224
|
+
* @example
|
|
225
|
+
* difference([1, 2, 3], [2, 3, 4]) // [1]
|
|
226
|
+
*/
|
|
227
|
+
export function difference(array1, array2, key) {
|
|
228
|
+
if (!Array.isArray(array1))
|
|
229
|
+
return [];
|
|
230
|
+
if (!Array.isArray(array2))
|
|
231
|
+
return array1;
|
|
232
|
+
if (!key) {
|
|
233
|
+
const set2 = new Set(array2);
|
|
234
|
+
return array1.filter((item) => !set2.has(item));
|
|
235
|
+
}
|
|
236
|
+
const values2 = new Set(array2.map((item) => isObject(item) ? get(item, key) : item));
|
|
237
|
+
return array1.filter((item) => {
|
|
238
|
+
const value = isObject(item) ? get(item, key) : item;
|
|
239
|
+
return !values2.has(value);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Finds intersection of two arrays
|
|
244
|
+
* @param array1 - First array
|
|
245
|
+
* @param array2 - Second array
|
|
246
|
+
* @param key - Optional key for object comparison
|
|
247
|
+
* @returns Common elements
|
|
248
|
+
* @example
|
|
249
|
+
* intersection([1, 2, 3], [2, 3, 4]) // [2, 3]
|
|
250
|
+
*/
|
|
251
|
+
export function intersection(array1, array2, key) {
|
|
252
|
+
if (!Array.isArray(array1) || !Array.isArray(array2)) {
|
|
253
|
+
return [];
|
|
254
|
+
}
|
|
255
|
+
if (!key) {
|
|
256
|
+
const set2 = new Set(array2);
|
|
257
|
+
return array1.filter((item) => set2.has(item));
|
|
258
|
+
}
|
|
259
|
+
const values2 = new Set(array2.map((item) => isObject(item) ? get(item, key) : item));
|
|
260
|
+
return array1.filter((item) => {
|
|
261
|
+
const value = isObject(item) ? get(item, key) : item;
|
|
262
|
+
return values2.has(value);
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Finds union of two arrays (unique elements from both)
|
|
267
|
+
* @param array1 - First array
|
|
268
|
+
* @param array2 - Second array
|
|
269
|
+
* @param key - Optional key for object comparison
|
|
270
|
+
* @returns Union of arrays
|
|
271
|
+
* @example
|
|
272
|
+
* union([1, 2], [2, 3]) // [1, 2, 3]
|
|
273
|
+
*/
|
|
274
|
+
export function union(array1, array2, key) {
|
|
275
|
+
if (!Array.isArray(array1) && !Array.isArray(array2)) {
|
|
276
|
+
return [];
|
|
277
|
+
}
|
|
278
|
+
if (!Array.isArray(array1))
|
|
279
|
+
return unique(array2, key);
|
|
280
|
+
if (!Array.isArray(array2))
|
|
281
|
+
return unique(array1, key);
|
|
282
|
+
return unique([...array1, ...array2], key);
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Partitions array into two groups based on predicate
|
|
286
|
+
* @param array - Source array
|
|
287
|
+
* @param predicate - Test function
|
|
288
|
+
* @returns [truthy, falsy]
|
|
289
|
+
* @example
|
|
290
|
+
* partition([1, 2, 3, 4], x => x % 2 === 0) // [[2, 4], [1, 3]]
|
|
291
|
+
*/
|
|
292
|
+
export function partition(array, predicate) {
|
|
293
|
+
if (!Array.isArray(array)) {
|
|
294
|
+
return [[], []];
|
|
295
|
+
}
|
|
296
|
+
return array.reduce((acc, item, index) => {
|
|
297
|
+
acc[predicate(item, index) ? 0 : 1].push(item);
|
|
298
|
+
return acc;
|
|
299
|
+
}, [[], []]);
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Takes first n elements from array
|
|
303
|
+
* @param array - Source array
|
|
304
|
+
* @param n - Number of elements
|
|
305
|
+
* @returns First n elements
|
|
306
|
+
* @example
|
|
307
|
+
* take([1, 2, 3, 4], 2) // [1, 2]
|
|
308
|
+
*/
|
|
309
|
+
export function take(array, n) {
|
|
310
|
+
if (!Array.isArray(array) || n <= 0) {
|
|
311
|
+
return [];
|
|
312
|
+
}
|
|
313
|
+
return array.slice(0, n);
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Takes last n elements from array
|
|
317
|
+
* @param array - Source array
|
|
318
|
+
* @param n - Number of elements
|
|
319
|
+
* @returns Last n elements
|
|
320
|
+
* @example
|
|
321
|
+
* takeLast([1, 2, 3, 4], 2) // [3, 4]
|
|
322
|
+
*/
|
|
323
|
+
export function takeLast(array, n) {
|
|
324
|
+
if (!Array.isArray(array) || n <= 0) {
|
|
325
|
+
return [];
|
|
326
|
+
}
|
|
327
|
+
return array.slice(-n);
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Takes elements while predicate is true
|
|
331
|
+
* @param array - Source array
|
|
332
|
+
* @param predicate - Test function
|
|
333
|
+
* @returns Elements taken
|
|
334
|
+
* @example
|
|
335
|
+
* takeWhile([1, 2, 3, 4], x => x < 3) // [1, 2]
|
|
336
|
+
*/
|
|
337
|
+
export function takeWhile(array, predicate) {
|
|
338
|
+
if (!Array.isArray(array)) {
|
|
339
|
+
return [];
|
|
340
|
+
}
|
|
341
|
+
const result = [];
|
|
342
|
+
for (let i = 0; i < array.length; i++) {
|
|
343
|
+
if (!predicate(array[i], i))
|
|
344
|
+
break;
|
|
345
|
+
result.push(array[i]);
|
|
346
|
+
}
|
|
347
|
+
return result;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Drops first n elements from array
|
|
351
|
+
* @param array - Source array
|
|
352
|
+
* @param n - Number of elements to drop
|
|
353
|
+
* @returns Remaining elements
|
|
354
|
+
* @example
|
|
355
|
+
* drop([1, 2, 3, 4], 2) // [3, 4]
|
|
356
|
+
*/
|
|
357
|
+
export function drop(array, n) {
|
|
358
|
+
if (!Array.isArray(array) || n <= 0) {
|
|
359
|
+
return array ? [...array] : [];
|
|
360
|
+
}
|
|
361
|
+
return array.slice(n);
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Drops elements while predicate is true
|
|
365
|
+
* @param array - Source array
|
|
366
|
+
* @param predicate - Test function
|
|
367
|
+
* @returns Remaining elements
|
|
368
|
+
* @example
|
|
369
|
+
* dropWhile([1, 2, 3, 4], x => x < 3) // [3, 4]
|
|
370
|
+
*/
|
|
371
|
+
export function dropWhile(array, predicate) {
|
|
372
|
+
if (!Array.isArray(array)) {
|
|
373
|
+
return [];
|
|
374
|
+
}
|
|
375
|
+
let index = 0;
|
|
376
|
+
while (index < array.length && predicate(array[index], index)) {
|
|
377
|
+
index++;
|
|
378
|
+
}
|
|
379
|
+
return array.slice(index);
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Compacts array by removing falsy values
|
|
383
|
+
* @param array - Source array
|
|
384
|
+
* @returns Array without falsy values
|
|
385
|
+
* @example
|
|
386
|
+
* compact([0, 1, false, 2, '', 3, null]) // [1, 2, 3]
|
|
387
|
+
*/
|
|
388
|
+
export function compact(array) {
|
|
389
|
+
if (!Array.isArray(array)) {
|
|
390
|
+
return [];
|
|
391
|
+
}
|
|
392
|
+
return array.filter(Boolean);
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Zips multiple arrays together
|
|
396
|
+
* @param arrays - Arrays to zip
|
|
397
|
+
* @returns Zipped array
|
|
398
|
+
* @example
|
|
399
|
+
* zip([1, 2], ['a', 'b'], [true, false]) // [[1, 'a', true], [2, 'b', false]]
|
|
400
|
+
*/
|
|
401
|
+
export function zip(...arrays) {
|
|
402
|
+
if (arrays.length === 0)
|
|
403
|
+
return [];
|
|
404
|
+
const maxLength = Math.max(...arrays.map((arr) => (Array.isArray(arr) ? arr.length : 0)));
|
|
405
|
+
const result = [];
|
|
406
|
+
for (let i = 0; i < maxLength; i++) {
|
|
407
|
+
const tuple = [];
|
|
408
|
+
for (const array of arrays) {
|
|
409
|
+
tuple.push(Array.isArray(array) ? array[i] : undefined);
|
|
410
|
+
}
|
|
411
|
+
result.push(tuple);
|
|
412
|
+
}
|
|
413
|
+
return result;
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Unzips array of arrays
|
|
417
|
+
* @param array - Array to unzip
|
|
418
|
+
* @returns Unzipped arrays
|
|
419
|
+
* @example
|
|
420
|
+
* unzip([[1, 'a'], [2, 'b']]) // [[1, 2], ['a', 'b']]
|
|
421
|
+
*/
|
|
422
|
+
export function unzip(array) {
|
|
423
|
+
if (!Array.isArray(array) || array.length === 0) {
|
|
424
|
+
return [];
|
|
425
|
+
}
|
|
426
|
+
const maxLength = Math.max(...array.map((arr) => (Array.isArray(arr) ? arr.length : 0)));
|
|
427
|
+
const result = [];
|
|
428
|
+
for (let i = 0; i < maxLength; i++) {
|
|
429
|
+
const tuple = [];
|
|
430
|
+
for (const arr of array) {
|
|
431
|
+
if (Array.isArray(arr)) {
|
|
432
|
+
tuple.push(arr[i]);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
result.push(tuple);
|
|
436
|
+
}
|
|
437
|
+
return result;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Shuffles array randomly
|
|
441
|
+
* @param array - Source array
|
|
442
|
+
* @returns Shuffled array (new array)
|
|
443
|
+
* @example
|
|
444
|
+
* shuffle([1, 2, 3, 4, 5])
|
|
445
|
+
*/
|
|
446
|
+
export function shuffle(array) {
|
|
447
|
+
if (!Array.isArray(array)) {
|
|
448
|
+
return [];
|
|
449
|
+
}
|
|
450
|
+
const result = [...array];
|
|
451
|
+
for (let i = result.length - 1; i > 0; i--) {
|
|
452
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
453
|
+
[result[i], result[j]] = [result[j], result[i]];
|
|
454
|
+
}
|
|
455
|
+
return result;
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Gets random element from array
|
|
459
|
+
* @param array - Source array
|
|
460
|
+
* @returns Random element
|
|
461
|
+
* @example
|
|
462
|
+
* sample([1, 2, 3, 4, 5]) // Random element
|
|
463
|
+
*/
|
|
464
|
+
export function sample(array) {
|
|
465
|
+
if (!Array.isArray(array) || array.length === 0) {
|
|
466
|
+
return undefined;
|
|
467
|
+
}
|
|
468
|
+
return array[Math.floor(Math.random() * array.length)];
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Gets n random elements from array
|
|
472
|
+
* @param array - Source array
|
|
473
|
+
* @param n - Number of elements
|
|
474
|
+
* @returns Random elements
|
|
475
|
+
* @example
|
|
476
|
+
* sampleSize([1, 2, 3, 4, 5], 3) // 3 random elements
|
|
477
|
+
*/
|
|
478
|
+
export function sampleSize(array, n) {
|
|
479
|
+
if (!Array.isArray(array) || n <= 0) {
|
|
480
|
+
return [];
|
|
481
|
+
}
|
|
482
|
+
const shuffled = shuffle(array);
|
|
483
|
+
return shuffled.slice(0, Math.min(n, array.length));
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Finds index of element using predicate
|
|
487
|
+
* @param array - Source array
|
|
488
|
+
* @param predicate - Search function
|
|
489
|
+
* @param fromIndex - Start index
|
|
490
|
+
* @returns Index or -1
|
|
491
|
+
* @example
|
|
492
|
+
* findIndex([1, 2, 3, 4], x => x > 2) // 2
|
|
493
|
+
*/
|
|
494
|
+
export function findIndex(array, predicate, fromIndex = 0) {
|
|
495
|
+
if (!Array.isArray(array)) {
|
|
496
|
+
return -1;
|
|
497
|
+
}
|
|
498
|
+
for (let i = fromIndex; i < array.length; i++) {
|
|
499
|
+
if (predicate(array[i], i)) {
|
|
500
|
+
return i;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return -1;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Finds last index of element using predicate
|
|
507
|
+
* @param array - Source array
|
|
508
|
+
* @param predicate - Search function
|
|
509
|
+
* @param fromIndex - Start index (from end)
|
|
510
|
+
* @returns Index or -1
|
|
511
|
+
* @example
|
|
512
|
+
* findLastIndex([1, 2, 3, 4, 3], x => x === 3) // 4
|
|
513
|
+
*/
|
|
514
|
+
export function findLastIndex(array, predicate, fromIndex) {
|
|
515
|
+
if (!Array.isArray(array)) {
|
|
516
|
+
return -1;
|
|
517
|
+
}
|
|
518
|
+
const startIndex = fromIndex !== undefined ? fromIndex : array.length - 1;
|
|
519
|
+
for (let i = startIndex; i >= 0; i--) {
|
|
520
|
+
if (predicate(array[i], i)) {
|
|
521
|
+
return i;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return -1;
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Creates array of values at given paths
|
|
528
|
+
* @param array - Source array of objects
|
|
529
|
+
* @param path - Property path
|
|
530
|
+
* @returns Array of values
|
|
531
|
+
* @example
|
|
532
|
+
* pluck([{a: {b: 1}}, {a: {b: 2}}], 'a.b') // [1, 2]
|
|
533
|
+
*/
|
|
534
|
+
export function pluck(array, path) {
|
|
535
|
+
if (!Array.isArray(array)) {
|
|
536
|
+
return [];
|
|
537
|
+
}
|
|
538
|
+
return array.map((item) => get(item, path));
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Extracts object properties from array
|
|
542
|
+
* @param array - Source array of objects
|
|
543
|
+
* @param keys - Keys to pick
|
|
544
|
+
* @returns Array of objects with picked keys
|
|
545
|
+
* @example
|
|
546
|
+
* pick([{a: 1, b: 2}, {a: 3, b: 4}], ['a']) // [{a: 1}, {a: 3}]
|
|
547
|
+
*/
|
|
548
|
+
export function pick(array, keys) {
|
|
549
|
+
if (!Array.isArray(array) || !Array.isArray(keys)) {
|
|
550
|
+
return [];
|
|
551
|
+
}
|
|
552
|
+
return array.map((item) => {
|
|
553
|
+
const result = {};
|
|
554
|
+
for (const key of keys) {
|
|
555
|
+
if (key in item) {
|
|
556
|
+
result[key] = item[key];
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return result;
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Omits object properties from array
|
|
564
|
+
* @param array - Source array of objects
|
|
565
|
+
* @param keys - Keys to omit
|
|
566
|
+
* @returns Array of objects without omitted keys
|
|
567
|
+
* @example
|
|
568
|
+
* omit([{a: 1, b: 2}, {a: 3, b: 4}], ['b']) // [{a: 1}, {a: 3}]
|
|
569
|
+
*/
|
|
570
|
+
export function omit(array, keys) {
|
|
571
|
+
if (!Array.isArray(array) || !Array.isArray(keys)) {
|
|
572
|
+
return [];
|
|
573
|
+
}
|
|
574
|
+
return array.map((item) => {
|
|
575
|
+
const result = { ...item };
|
|
576
|
+
for (const key of keys) {
|
|
577
|
+
delete result[key];
|
|
578
|
+
}
|
|
579
|
+
return result;
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Maps and flattens array in one operation
|
|
584
|
+
* @param array - Source array
|
|
585
|
+
* @param callback - Mapping function
|
|
586
|
+
* @returns Flattened mapped array
|
|
587
|
+
* @example
|
|
588
|
+
* flatMap([1, 2, 3], x => [x, x * 2]) // [1, 2, 2, 4, 3, 6]
|
|
589
|
+
*/
|
|
590
|
+
export function flatMap(array, callback) {
|
|
591
|
+
if (!Array.isArray(array)) {
|
|
592
|
+
return [];
|
|
593
|
+
}
|
|
594
|
+
return array.reduce((acc, item, index) => {
|
|
595
|
+
const result = callback(item, index);
|
|
596
|
+
return acc.concat(result);
|
|
597
|
+
}, []);
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Reduces array from right to left
|
|
601
|
+
* @param array - Source array
|
|
602
|
+
* @param callback - Reducer function
|
|
603
|
+
* @param initialValue - Initial value
|
|
604
|
+
* @returns Reduced value
|
|
605
|
+
* @example
|
|
606
|
+
* reduceRight([1, 2, 3], (acc, val) => acc + val, 0) // 6
|
|
607
|
+
*/
|
|
608
|
+
export function reduceRight(array, callback, initialValue) {
|
|
609
|
+
if (!Array.isArray(array)) {
|
|
610
|
+
return initialValue;
|
|
611
|
+
}
|
|
612
|
+
let accumulator = initialValue;
|
|
613
|
+
for (let i = array.length - 1; i >= 0; i--) {
|
|
614
|
+
accumulator = callback(accumulator, array[i], i);
|
|
615
|
+
}
|
|
616
|
+
return accumulator;
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Creates array of numbers in range
|
|
620
|
+
* @param start - Start value
|
|
621
|
+
* @param end - End value (exclusive)
|
|
622
|
+
* @param step - Step value
|
|
623
|
+
* @returns Range array
|
|
624
|
+
* @example
|
|
625
|
+
* range(0, 5) // [0, 1, 2, 3, 4]
|
|
626
|
+
* range(0, 10, 2) // [0, 2, 4, 6, 8]
|
|
627
|
+
*/
|
|
628
|
+
export function range(start, end, step = 1) {
|
|
629
|
+
if (step === 0) {
|
|
630
|
+
return [];
|
|
631
|
+
}
|
|
632
|
+
const result = [];
|
|
633
|
+
if (step > 0) {
|
|
634
|
+
for (let i = start; i < end; i += step) {
|
|
635
|
+
result.push(i);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
for (let i = start; i > end; i += step) {
|
|
640
|
+
result.push(i);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return result;
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Fills array with value (similar to Array.prototype.fill)
|
|
647
|
+
* @param array - Source array
|
|
648
|
+
* @param value - Fill value
|
|
649
|
+
* @param start - Start index (default: 0)
|
|
650
|
+
* @param end - End index (default: array.length)
|
|
651
|
+
* @returns New array with filled values
|
|
652
|
+
* @example
|
|
653
|
+
* fill([1, 2, 3], 0) // [0, 0, 0]
|
|
654
|
+
* fill([1, 2, 3, 4, 5], 9, 1, 3) // [1, 9, 9, 4, 5]
|
|
655
|
+
*/
|
|
656
|
+
export function fill(array, value, start = 0, end) {
|
|
657
|
+
if (!Array.isArray(array)) {
|
|
658
|
+
return [];
|
|
659
|
+
}
|
|
660
|
+
const result = [...array];
|
|
661
|
+
const actualEnd = end === undefined ? result.length : end;
|
|
662
|
+
for (let i = start; i < actualEnd && i < result.length; i++) {
|
|
663
|
+
result[i] = value;
|
|
664
|
+
}
|
|
665
|
+
return result;
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Deeply clones array (including nested objects)
|
|
669
|
+
* @param array - Source array
|
|
670
|
+
* @returns Cloned array
|
|
671
|
+
* @example
|
|
672
|
+
* deepCloneArray([{a: 1}, {b: 2}])
|
|
673
|
+
*/
|
|
674
|
+
export function deepCloneArray(array) {
|
|
675
|
+
if (!Array.isArray(array)) {
|
|
676
|
+
return [];
|
|
677
|
+
}
|
|
678
|
+
return deepClone(array);
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Merges multiple arrays of objects by key
|
|
682
|
+
* @param arr1 - First array
|
|
683
|
+
* @param arr2 - Second array (or key if only merging arr1)
|
|
684
|
+
* @param keyOrMore - Key to merge by, or more arrays
|
|
685
|
+
* @returns Merged array
|
|
686
|
+
* @example
|
|
687
|
+
* mergeByKey([{id: 1, a: 1}], [{id: 1, b: 2}], 'id')
|
|
688
|
+
* // [{id: 1, a: 1, b: 2}]
|
|
689
|
+
*/
|
|
690
|
+
export function mergeByKey(arr1, arr2, ...keyOrMore) {
|
|
691
|
+
// Determine the key and arrays
|
|
692
|
+
let key;
|
|
693
|
+
let arrays;
|
|
694
|
+
if (typeof arr2 === 'string') {
|
|
695
|
+
// Form: mergeByKey([arr], 'key')
|
|
696
|
+
key = arr2;
|
|
697
|
+
arrays = [arr1];
|
|
698
|
+
}
|
|
699
|
+
else {
|
|
700
|
+
// Form: mergeByKey(arr1, arr2, 'key') or mergeByKey(arr1, arr2, arr3, 'key')
|
|
701
|
+
const lastArg = keyOrMore[keyOrMore.length - 1];
|
|
702
|
+
if (typeof lastArg === 'string') {
|
|
703
|
+
key = lastArg;
|
|
704
|
+
arrays = [
|
|
705
|
+
arr1,
|
|
706
|
+
arr2,
|
|
707
|
+
...keyOrMore.slice(0, -1).filter(Array.isArray),
|
|
708
|
+
];
|
|
709
|
+
}
|
|
710
|
+
else {
|
|
711
|
+
// No key provided, default to 'id'
|
|
712
|
+
key = 'id';
|
|
713
|
+
arrays = [arr1, arr2, ...keyOrMore.filter(Array.isArray)];
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
if (arrays.length === 0) {
|
|
717
|
+
return [];
|
|
718
|
+
}
|
|
719
|
+
const map = new Map();
|
|
720
|
+
for (const array of arrays) {
|
|
721
|
+
if (Array.isArray(array)) {
|
|
722
|
+
for (const item of array) {
|
|
723
|
+
const keyValue = isObject(item)
|
|
724
|
+
? get(item, key)
|
|
725
|
+
: undefined;
|
|
726
|
+
if (keyValue !== undefined) {
|
|
727
|
+
const existing = map.get(keyValue);
|
|
728
|
+
map.set(keyValue, existing ? { ...existing, ...item } : item);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
return Array.from(map.values());
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Moves array element from one index to another
|
|
737
|
+
* @param array - Source array
|
|
738
|
+
* @param fromIndex - Source index
|
|
739
|
+
* @param toIndex - Destination index
|
|
740
|
+
* @returns New array with moved element
|
|
741
|
+
* @example
|
|
742
|
+
* move([1, 2, 3, 4], 0, 2) // [2, 3, 1, 4]
|
|
743
|
+
*/
|
|
744
|
+
export function move(array, fromIndex, toIndex) {
|
|
745
|
+
if (!Array.isArray(array) || fromIndex === toIndex) {
|
|
746
|
+
return array ? [...array] : [];
|
|
747
|
+
}
|
|
748
|
+
const result = [...array];
|
|
749
|
+
const [removed] = result.splice(fromIndex, 1);
|
|
750
|
+
result.splice(toIndex, 0, removed);
|
|
751
|
+
return result;
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Rotates array elements
|
|
755
|
+
* @param array - Source array
|
|
756
|
+
* @param n - Number of positions (positive = right, negative = left)
|
|
757
|
+
* @returns Rotated array
|
|
758
|
+
* @example
|
|
759
|
+
* rotate([1, 2, 3, 4], 1) // [4, 1, 2, 3]
|
|
760
|
+
* rotate([1, 2, 3, 4], -1) // [2, 3, 4, 1]
|
|
761
|
+
*/
|
|
762
|
+
export function rotate(array, n) {
|
|
763
|
+
if (!Array.isArray(array) || array.length === 0 || n === 0) {
|
|
764
|
+
return array ? [...array] : [];
|
|
765
|
+
}
|
|
766
|
+
const len = array.length;
|
|
767
|
+
const rotations = ((n % len) + len) % len;
|
|
768
|
+
return [...array.slice(-rotations), ...array.slice(0, -rotations)];
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Inserts element at index
|
|
772
|
+
* @param array - Source array
|
|
773
|
+
* @param index - Insert index
|
|
774
|
+
* @param items - Items to insert
|
|
775
|
+
* @returns New array
|
|
776
|
+
* @example
|
|
777
|
+
* insert([1, 2, 4], 2, 3) // [1, 2, 3, 4]
|
|
778
|
+
*/
|
|
779
|
+
export function insert(array, index, ...items) {
|
|
780
|
+
if (!Array.isArray(array)) {
|
|
781
|
+
return items;
|
|
782
|
+
}
|
|
783
|
+
const result = [...array];
|
|
784
|
+
result.splice(index, 0, ...items);
|
|
785
|
+
return result;
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Removes element at index
|
|
789
|
+
* @param array - Source array
|
|
790
|
+
* @param index - Remove index
|
|
791
|
+
* @param count - Number of elements to remove
|
|
792
|
+
* @returns New array
|
|
793
|
+
* @example
|
|
794
|
+
* remove([1, 2, 3, 4], 1, 2) // [1, 4]
|
|
795
|
+
*/
|
|
796
|
+
export function remove(array, index, count = 1) {
|
|
797
|
+
if (!Array.isArray(array)) {
|
|
798
|
+
return [];
|
|
799
|
+
}
|
|
800
|
+
const result = [...array];
|
|
801
|
+
result.splice(index, count);
|
|
802
|
+
return result;
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Replaces element at index
|
|
806
|
+
* @param array - Source array
|
|
807
|
+
* @param index - Replace index
|
|
808
|
+
* @param item - New item
|
|
809
|
+
* @returns New array
|
|
810
|
+
* @example
|
|
811
|
+
* replace([1, 2, 3], 1, 5) // [1, 5, 3]
|
|
812
|
+
*/
|
|
813
|
+
export function replace(array, index, item) {
|
|
814
|
+
if (!Array.isArray(array)) {
|
|
815
|
+
return [];
|
|
816
|
+
}
|
|
817
|
+
const result = [...array];
|
|
818
|
+
result[index] = item;
|
|
819
|
+
return result;
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Filters array and removes null/undefined
|
|
823
|
+
* @param array - Source array
|
|
824
|
+
* @param predicate - Filter function
|
|
825
|
+
* @returns Filtered array without null/undefined
|
|
826
|
+
* @example
|
|
827
|
+
* filterCompact([1, null, 2, undefined, 3], x => x) // [1, 2, 3]
|
|
828
|
+
*/
|
|
829
|
+
export function filterCompact(array, predicate) {
|
|
830
|
+
if (!Array.isArray(array)) {
|
|
831
|
+
return [];
|
|
832
|
+
}
|
|
833
|
+
const filtered = array.filter((item) => item != null);
|
|
834
|
+
return predicate ? filtered.filter(predicate) : filtered;
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Creates cartesian product of arrays
|
|
838
|
+
* @param arrays - Arrays to combine
|
|
839
|
+
* @returns Cartesian product
|
|
840
|
+
* @example
|
|
841
|
+
* cartesian([1, 2], ['a', 'b']) // [[1, 'a'], [1, 'b'], [2, 'a'], [2, 'b']]
|
|
842
|
+
*/
|
|
843
|
+
export function cartesian(...arrays) {
|
|
844
|
+
if (arrays.length === 0)
|
|
845
|
+
return [[]];
|
|
846
|
+
if (arrays.length === 1)
|
|
847
|
+
return arrays[0].map((item) => [item]);
|
|
848
|
+
return arrays.reduce((acc, array) => {
|
|
849
|
+
return acc.flatMap((x) => array.map((y) => [...x, y]));
|
|
850
|
+
}, [[]]);
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Finds all indices where predicate is true
|
|
854
|
+
* @param array - Source array
|
|
855
|
+
* @param predicate - Test function
|
|
856
|
+
* @returns Array of indices
|
|
857
|
+
* @example
|
|
858
|
+
* findAllIndices([1, 2, 3, 2, 1], x => x === 2) // [1, 3]
|
|
859
|
+
*/
|
|
860
|
+
export function findAllIndices(array, predicate) {
|
|
861
|
+
if (!Array.isArray(array)) {
|
|
862
|
+
return [];
|
|
863
|
+
}
|
|
864
|
+
const indices = [];
|
|
865
|
+
for (let i = 0; i < array.length; i++) {
|
|
866
|
+
if (predicate(array[i], i)) {
|
|
867
|
+
indices.push(i);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
return indices;
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* Checks if arrays are equal
|
|
874
|
+
* @param array1 - First array
|
|
875
|
+
* @param array2 - Second array
|
|
876
|
+
* @param deep - Deep comparison
|
|
877
|
+
* @returns True if equal
|
|
878
|
+
* @example
|
|
879
|
+
* isEqual([1, 2, 3], [1, 2, 3]) // true
|
|
880
|
+
*/
|
|
881
|
+
export function isEqual(array1, array2, deep = true) {
|
|
882
|
+
if (!Array.isArray(array1) || !Array.isArray(array2)) {
|
|
883
|
+
return false;
|
|
884
|
+
}
|
|
885
|
+
if (array1.length !== array2.length) {
|
|
886
|
+
return false;
|
|
887
|
+
}
|
|
888
|
+
for (let i = 0; i < array1.length; i++) {
|
|
889
|
+
if (deep && isObject(array1[i]) && isObject(array2[i])) {
|
|
890
|
+
// Deep comparison using JSON (simple approach)
|
|
891
|
+
if (JSON.stringify(array1[i]) !== JSON.stringify(array2[i])) {
|
|
892
|
+
return false;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
else if (array1[i] !== array2[i]) {
|
|
896
|
+
return false;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
return true;
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Splits array into chunks where predicate changes
|
|
903
|
+
* @param array - Source array
|
|
904
|
+
* @param predicate - Grouping function
|
|
905
|
+
* @returns Chunked array
|
|
906
|
+
* @example
|
|
907
|
+
* chunkBy([1, 1, 2, 2, 3], x => x) // [[1, 1], [2, 2], [3]]
|
|
908
|
+
*/
|
|
909
|
+
export function chunkBy(array, predicate) {
|
|
910
|
+
if (!Array.isArray(array) || array.length === 0) {
|
|
911
|
+
return [];
|
|
912
|
+
}
|
|
913
|
+
const result = [];
|
|
914
|
+
let currentChunk = [array[0]];
|
|
915
|
+
let currentKey = predicate(array[0], 0);
|
|
916
|
+
for (let i = 1; i < array.length; i++) {
|
|
917
|
+
const key = predicate(array[i], i);
|
|
918
|
+
if (key === currentKey) {
|
|
919
|
+
currentChunk.push(array[i]);
|
|
920
|
+
}
|
|
921
|
+
else {
|
|
922
|
+
result.push(currentChunk);
|
|
923
|
+
currentChunk = [array[i]];
|
|
924
|
+
currentKey = key;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
result.push(currentChunk);
|
|
928
|
+
return result;
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Updates array elements by predicate or key/value
|
|
932
|
+
* @param array - Source array
|
|
933
|
+
* @param predicate - Predicate function or key to match
|
|
934
|
+
* @param updates - Updates to apply (or value if predicate is a key)
|
|
935
|
+
* @param updatesOrUndefined - Updates if using key/value form
|
|
936
|
+
* @returns Updated array
|
|
937
|
+
* @example
|
|
938
|
+
* updateBy([{id: 1, name: 'a'}], x => x.id === 1, {name: 'b'}) // [{id: 1, name: 'b'}]
|
|
939
|
+
* updateBy([{id: 1, name: 'a'}], 'id', 1, {name: 'b'}) // [{id: 1, name: 'b'}]
|
|
940
|
+
*/
|
|
941
|
+
export function updateBy(array, predicate, updates, updatesOrUndefined) {
|
|
942
|
+
if (!Array.isArray(array)) {
|
|
943
|
+
return [];
|
|
944
|
+
}
|
|
945
|
+
// Determine if we're using predicate form or key/value form
|
|
946
|
+
const isPredicate = typeof predicate === 'function';
|
|
947
|
+
const actualUpdates = isPredicate ? updates : updatesOrUndefined;
|
|
948
|
+
return array.map((item) => {
|
|
949
|
+
let shouldUpdate = false;
|
|
950
|
+
if (isPredicate) {
|
|
951
|
+
shouldUpdate = predicate(item);
|
|
952
|
+
}
|
|
953
|
+
else {
|
|
954
|
+
const itemValue = isObject(item)
|
|
955
|
+
? get(item, predicate)
|
|
956
|
+
: item;
|
|
957
|
+
shouldUpdate = itemValue === updates;
|
|
958
|
+
}
|
|
959
|
+
if (shouldUpdate) {
|
|
960
|
+
return { ...item, ...actualUpdates };
|
|
961
|
+
}
|
|
962
|
+
return item;
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* Removes duplicates and returns removed items
|
|
967
|
+
* @param array - Source array
|
|
968
|
+
* @param key - Optional key for objects
|
|
969
|
+
* @returns Object with unique and duplicates arrays
|
|
970
|
+
* @example
|
|
971
|
+
* splitDuplicates([1, 2, 2, 3, 3]) // { unique: [1, 2, 3], duplicates: [2, 3] }
|
|
972
|
+
*/
|
|
973
|
+
export function splitDuplicates(array, key) {
|
|
974
|
+
if (!Array.isArray(array)) {
|
|
975
|
+
return { unique: [], duplicates: [] };
|
|
976
|
+
}
|
|
977
|
+
const seen = new Set();
|
|
978
|
+
const unique = [];
|
|
979
|
+
const duplicates = [];
|
|
980
|
+
for (const item of array) {
|
|
981
|
+
const value = key && isObject(item) ? get(item, key) : item;
|
|
982
|
+
if (seen.has(value)) {
|
|
983
|
+
duplicates.push(item);
|
|
984
|
+
}
|
|
985
|
+
else {
|
|
986
|
+
seen.add(value);
|
|
987
|
+
unique.push(item);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
return { unique, duplicates };
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Ensures array (converts non-array to array)
|
|
994
|
+
* @param value - Value to ensure as array
|
|
995
|
+
* @returns Array
|
|
996
|
+
* @example
|
|
997
|
+
* ensureArray(1) // [1]
|
|
998
|
+
* ensureArray([1, 2]) // [1, 2]
|
|
999
|
+
*/
|
|
1000
|
+
export function ensureArray(value) {
|
|
1001
|
+
if (Array.isArray(value)) {
|
|
1002
|
+
return value;
|
|
1003
|
+
}
|
|
1004
|
+
return value === undefined || value === null ? [] : [value];
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* Wraps value in array if not already an array
|
|
1008
|
+
* @param value - Value to wrap
|
|
1009
|
+
* @returns Array
|
|
1010
|
+
* @example
|
|
1011
|
+
* wrap(1) // [1]
|
|
1012
|
+
* wrap([1]) // [1]
|
|
1013
|
+
*/
|
|
1014
|
+
export function wrap(value) {
|
|
1015
|
+
return ensureArray(value);
|
|
1016
|
+
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Creates sliding windows of specified size over the array
|
|
1019
|
+
* @param array - Source array
|
|
1020
|
+
* @param size - Window size
|
|
1021
|
+
* @param step - Step size between windows (default: 1)
|
|
1022
|
+
* @returns Array of windows
|
|
1023
|
+
* @example
|
|
1024
|
+
* slidingWindow([1, 2, 3, 4, 5], 3) // [[1, 2, 3], [2, 3, 4], [3, 4, 5]]
|
|
1025
|
+
* slidingWindow([1, 2, 3, 4, 5], 2, 2) // [[1, 2], [3, 4]]
|
|
1026
|
+
*/
|
|
1027
|
+
export function slidingWindow(array, size, step = 1) {
|
|
1028
|
+
if (!Array.isArray(array) || size < 1 || step < 1) {
|
|
1029
|
+
return [];
|
|
1030
|
+
}
|
|
1031
|
+
const windows = [];
|
|
1032
|
+
for (let i = 0; i <= array.length - size; i += step) {
|
|
1033
|
+
windows.push(array.slice(i, i + size));
|
|
1034
|
+
}
|
|
1035
|
+
return windows;
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Finds the minimum value in an array by comparing a key/selector
|
|
1039
|
+
* @param array - Source array
|
|
1040
|
+
* @param selector - Key path or selector function
|
|
1041
|
+
* @returns Element with minimum value, or undefined if array is empty
|
|
1042
|
+
* @example
|
|
1043
|
+
* minBy([{ age: 30 }, { age: 20 }], 'age') // { age: 20 }
|
|
1044
|
+
* minBy([{ price: 100 }, { price: 50 }], obj => obj.price) // { price: 50 }
|
|
1045
|
+
*/
|
|
1046
|
+
export function minBy(array, selector) {
|
|
1047
|
+
if (!Array.isArray(array) || array.length === 0) {
|
|
1048
|
+
return undefined;
|
|
1049
|
+
}
|
|
1050
|
+
let minItem = array[0];
|
|
1051
|
+
let minValue = typeof selector === 'function'
|
|
1052
|
+
? selector(minItem)
|
|
1053
|
+
: get(minItem, selector);
|
|
1054
|
+
for (let i = 1; i < array.length; i++) {
|
|
1055
|
+
const item = array[i];
|
|
1056
|
+
const value = typeof selector === 'function'
|
|
1057
|
+
? selector(item)
|
|
1058
|
+
: get(item, selector);
|
|
1059
|
+
if (value < minValue) {
|
|
1060
|
+
minValue = value;
|
|
1061
|
+
minItem = item;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
return minItem;
|
|
1065
|
+
}
|
|
1066
|
+
/**
|
|
1067
|
+
* Finds the maximum value in an array by comparing a key/selector
|
|
1068
|
+
* @param array - Source array
|
|
1069
|
+
* @param selector - Key path or selector function
|
|
1070
|
+
* @returns Element with maximum value, or undefined if array is empty
|
|
1071
|
+
* @example
|
|
1072
|
+
* maxBy([{ age: 30 }, { age: 20 }], 'age') // { age: 30 }
|
|
1073
|
+
* maxBy([{ price: 100 }, { price: 50 }], obj => obj.price) // { price: 100 }
|
|
1074
|
+
*/
|
|
1075
|
+
export function maxBy(array, selector) {
|
|
1076
|
+
if (!Array.isArray(array) || array.length === 0) {
|
|
1077
|
+
return undefined;
|
|
1078
|
+
}
|
|
1079
|
+
let maxItem = array[0];
|
|
1080
|
+
let maxValue = typeof selector === 'function'
|
|
1081
|
+
? selector(maxItem)
|
|
1082
|
+
: get(maxItem, selector);
|
|
1083
|
+
for (let i = 1; i < array.length; i++) {
|
|
1084
|
+
const item = array[i];
|
|
1085
|
+
const value = typeof selector === 'function'
|
|
1086
|
+
? selector(item)
|
|
1087
|
+
: get(item, selector);
|
|
1088
|
+
if (value > maxValue) {
|
|
1089
|
+
maxValue = value;
|
|
1090
|
+
maxItem = item;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
return maxItem;
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Calculates sum of values in array by key/selector
|
|
1097
|
+
* @param array - Source array
|
|
1098
|
+
* @param selector - Key path or selector function
|
|
1099
|
+
* @returns Sum of values
|
|
1100
|
+
* @example
|
|
1101
|
+
* sumBy([{ price: 10 }, { price: 20 }], 'price') // 30
|
|
1102
|
+
* sumBy([{ x: 5 }, { x: 10 }], obj => obj.x) // 15
|
|
1103
|
+
*/
|
|
1104
|
+
export function sumBy(array, selector) {
|
|
1105
|
+
if (!Array.isArray(array) || array.length === 0) {
|
|
1106
|
+
return 0;
|
|
1107
|
+
}
|
|
1108
|
+
let sum = 0;
|
|
1109
|
+
for (const item of array) {
|
|
1110
|
+
const value = typeof selector === 'function'
|
|
1111
|
+
? selector(item)
|
|
1112
|
+
: get(item, selector);
|
|
1113
|
+
if (typeof value === 'number' && !isNaN(value)) {
|
|
1114
|
+
sum += value;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
return sum;
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* Calculates average of values in array by key/selector
|
|
1121
|
+
* @param array - Source array
|
|
1122
|
+
* @param selector - Key path or selector function
|
|
1123
|
+
* @returns Average of values, or 0 if array is empty
|
|
1124
|
+
* @example
|
|
1125
|
+
* avgBy([{ score: 80 }, { score: 90 }], 'score') // 85
|
|
1126
|
+
* avgBy([{ x: 10 }, { x: 20 }], obj => obj.x) // 15
|
|
1127
|
+
*/
|
|
1128
|
+
export function avgBy(array, selector) {
|
|
1129
|
+
if (!Array.isArray(array) || array.length === 0) {
|
|
1130
|
+
return 0;
|
|
1131
|
+
}
|
|
1132
|
+
const sum = sumBy(array, selector);
|
|
1133
|
+
return sum / array.length;
|
|
1134
|
+
}
|
|
1135
|
+
/**
|
|
1136
|
+
* Counts frequency of each unique value in array
|
|
1137
|
+
* @param array - Source array
|
|
1138
|
+
* @returns Map of value to frequency count
|
|
1139
|
+
* @example
|
|
1140
|
+
* frequencies([1, 2, 2, 3, 3, 3]) // Map { 1 => 1, 2 => 2, 3 => 3 }
|
|
1141
|
+
* frequencies(['a', 'b', 'a']) // Map { 'a' => 2, 'b' => 1 }
|
|
1142
|
+
*/
|
|
1143
|
+
export function frequencies(array) {
|
|
1144
|
+
const freqMap = new Map();
|
|
1145
|
+
if (!Array.isArray(array)) {
|
|
1146
|
+
return freqMap;
|
|
1147
|
+
}
|
|
1148
|
+
for (const item of array) {
|
|
1149
|
+
freqMap.set(item, (freqMap.get(item) || 0) + 1);
|
|
1150
|
+
}
|
|
1151
|
+
return freqMap;
|
|
1152
|
+
}
|
|
1153
|
+
/**
|
|
1154
|
+
* Finds the most common value(s) in array
|
|
1155
|
+
* @param array - Source array
|
|
1156
|
+
* @param limit - Maximum number of values to return (default: 1)
|
|
1157
|
+
* @returns Array of most common values
|
|
1158
|
+
* @example
|
|
1159
|
+
* mostCommon([1, 2, 2, 3, 3, 3]) // [3]
|
|
1160
|
+
* mostCommon([1, 1, 2, 2, 3], 2) // [1, 2] or [2, 1]
|
|
1161
|
+
*/
|
|
1162
|
+
export function mostCommon(array, limit = 1) {
|
|
1163
|
+
if (!Array.isArray(array) || array.length === 0 || limit < 1) {
|
|
1164
|
+
return [];
|
|
1165
|
+
}
|
|
1166
|
+
const freqMap = frequencies(array);
|
|
1167
|
+
const sorted = Array.from(freqMap.entries()).sort((a, b) => b[1] - a[1]);
|
|
1168
|
+
return sorted.slice(0, limit).map(([value]) => value);
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Finds the least common value(s) in array
|
|
1172
|
+
* @param array - Source array
|
|
1173
|
+
* @param limit - Maximum number of values to return (default: 1)
|
|
1174
|
+
* @returns Array of least common values
|
|
1175
|
+
* @example
|
|
1176
|
+
* leastCommon([1, 2, 2, 3, 3, 3]) // [1]
|
|
1177
|
+
* leastCommon([1, 1, 2, 2, 3, 3], 3) // [1, 2, 3] or any order
|
|
1178
|
+
*/
|
|
1179
|
+
export function leastCommon(array, limit = 1) {
|
|
1180
|
+
if (!Array.isArray(array) || array.length === 0 || limit < 1) {
|
|
1181
|
+
return [];
|
|
1182
|
+
}
|
|
1183
|
+
const freqMap = frequencies(array);
|
|
1184
|
+
const sorted = Array.from(freqMap.entries()).sort((a, b) => a[1] - b[1]);
|
|
1185
|
+
return sorted.slice(0, limit).map(([value]) => value);
|
|
1186
|
+
}
|
|
1187
|
+
/**
|
|
1188
|
+
* Processes array in batches with async callback
|
|
1189
|
+
* @param array - Source array
|
|
1190
|
+
* @param batchSize - Size of each batch
|
|
1191
|
+
* @param callback - Async function to process each batch
|
|
1192
|
+
* @returns Promise that resolves when all batches are processed
|
|
1193
|
+
* @example
|
|
1194
|
+
* await batchProcess([1, 2, 3, 4], 2, async (batch) => {
|
|
1195
|
+
* await saveToDatabase(batch);
|
|
1196
|
+
* });
|
|
1197
|
+
*/
|
|
1198
|
+
export async function batchProcess(array, batchSize, callback) {
|
|
1199
|
+
if (!Array.isArray(array) || batchSize < 1) {
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
const batches = chunk(array, batchSize);
|
|
1203
|
+
for (let i = 0; i < batches.length; i++) {
|
|
1204
|
+
await callback(batches[i], i);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1208
|
+
* Processes array in batches in parallel with concurrency limit
|
|
1209
|
+
* @param array - Source array
|
|
1210
|
+
* @param batchSize - Size of each batch
|
|
1211
|
+
* @param concurrency - Maximum number of concurrent batches
|
|
1212
|
+
* @param callback - Async function to process each batch
|
|
1213
|
+
* @returns Promise that resolves when all batches are processed
|
|
1214
|
+
* @example
|
|
1215
|
+
* await batchProcessParallel([1, 2, 3, 4, 5, 6], 2, 2, async (batch) => {
|
|
1216
|
+
* await saveToDatabase(batch);
|
|
1217
|
+
* });
|
|
1218
|
+
*/
|
|
1219
|
+
export async function batchProcessParallel(array, batchSize, concurrency, callback) {
|
|
1220
|
+
if (!Array.isArray(array) || batchSize < 1 || concurrency < 1) {
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
const batches = chunk(array, batchSize);
|
|
1224
|
+
const batchChunks = chunk(batches.map((batch, index) => ({ batch, index })), concurrency);
|
|
1225
|
+
for (const batchChunk of batchChunks) {
|
|
1226
|
+
await Promise.all(batchChunk.map(({ batch, index }) => callback(batch, index)));
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Transposes a 2D array (rows become columns, columns become rows)
|
|
1231
|
+
* @param matrix - 2D array to transpose
|
|
1232
|
+
* @returns Transposed 2D array
|
|
1233
|
+
* @example
|
|
1234
|
+
* transpose([[1, 2], [3, 4], [5, 6]]) // [[1, 3, 5], [2, 4, 6]]
|
|
1235
|
+
*/
|
|
1236
|
+
export function transpose(matrix) {
|
|
1237
|
+
if (!Array.isArray(matrix) || matrix.length === 0) {
|
|
1238
|
+
return [];
|
|
1239
|
+
}
|
|
1240
|
+
const maxLength = Math.max(...matrix.map((row) => row.length));
|
|
1241
|
+
const result = [];
|
|
1242
|
+
for (let col = 0; col < maxLength; col++) {
|
|
1243
|
+
const column = [];
|
|
1244
|
+
for (let row = 0; row < matrix.length; row++) {
|
|
1245
|
+
if (col < matrix[row].length) {
|
|
1246
|
+
column.push(matrix[row][col]);
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
result.push(column);
|
|
1250
|
+
}
|
|
1251
|
+
return result;
|
|
1252
|
+
}
|
|
1253
|
+
/**
|
|
1254
|
+
* Checks if two arrays are equal (shallow comparison)
|
|
1255
|
+
* @param array1 - First array
|
|
1256
|
+
* @param array2 - Second array
|
|
1257
|
+
* @returns True if arrays are equal
|
|
1258
|
+
* @example
|
|
1259
|
+
* isArrayEqual([1, 2, 3], [1, 2, 3]) // true
|
|
1260
|
+
* isArrayEqual([1, 2], [1, 2, 3]) // false
|
|
1261
|
+
*/
|
|
1262
|
+
export function isArrayEqual(array1, array2) {
|
|
1263
|
+
if (!Array.isArray(array1) || !Array.isArray(array2)) {
|
|
1264
|
+
return false;
|
|
1265
|
+
}
|
|
1266
|
+
if (array1.length !== array2.length) {
|
|
1267
|
+
return false;
|
|
1268
|
+
}
|
|
1269
|
+
for (let i = 0; i < array1.length; i++) {
|
|
1270
|
+
if (array1[i] !== array2[i]) {
|
|
1271
|
+
return false;
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
return true;
|
|
1275
|
+
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Checks if two arrays contain the same elements (order doesn't matter)
|
|
1278
|
+
* @param array1 - First array
|
|
1279
|
+
* @param array2 - Second array
|
|
1280
|
+
* @returns True if arrays contain same elements
|
|
1281
|
+
* @example
|
|
1282
|
+
* hasSameElements([1, 2, 3], [3, 2, 1]) // true
|
|
1283
|
+
* hasSameElements([1, 2], [1, 2, 2]) // false
|
|
1284
|
+
*/
|
|
1285
|
+
export function hasSameElements(array1, array2) {
|
|
1286
|
+
if (!Array.isArray(array1) || !Array.isArray(array2)) {
|
|
1287
|
+
return false;
|
|
1288
|
+
}
|
|
1289
|
+
if (array1.length !== array2.length) {
|
|
1290
|
+
return false;
|
|
1291
|
+
}
|
|
1292
|
+
const freq1 = frequencies(array1);
|
|
1293
|
+
const freq2 = frequencies(array2);
|
|
1294
|
+
if (freq1.size !== freq2.size) {
|
|
1295
|
+
return false;
|
|
1296
|
+
}
|
|
1297
|
+
for (const [key, count] of freq1) {
|
|
1298
|
+
if (freq2.get(key) !== count) {
|
|
1299
|
+
return false;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
return true;
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1305
|
+
* Checks if array1 is a subset of array2 (all elements in array1 exist in array2)
|
|
1306
|
+
* @param array1 - Subset array
|
|
1307
|
+
* @param array2 - Superset array
|
|
1308
|
+
* @returns True if array1 is a subset of array2
|
|
1309
|
+
* @example
|
|
1310
|
+
* isSubset([1, 2], [1, 2, 3, 4]) // true
|
|
1311
|
+
* isSubset([1, 5], [1, 2, 3, 4]) // false
|
|
1312
|
+
*/
|
|
1313
|
+
export function isSubset(array1, array2) {
|
|
1314
|
+
if (!Array.isArray(array1) || !Array.isArray(array2)) {
|
|
1315
|
+
return false;
|
|
1316
|
+
}
|
|
1317
|
+
const set2 = new Set(array2);
|
|
1318
|
+
return array1.every((item) => set2.has(item));
|
|
1319
|
+
}
|
|
1320
|
+
/**
|
|
1321
|
+
* Checks if array1 is a superset of array2 (all elements in array2 exist in array1)
|
|
1322
|
+
* @param array1 - Superset array
|
|
1323
|
+
* @param array2 - Subset array
|
|
1324
|
+
* @returns True if array1 is a superset of array2
|
|
1325
|
+
* @example
|
|
1326
|
+
* isSuperset([1, 2, 3, 4], [1, 2]) // true
|
|
1327
|
+
* isSuperset([1, 2, 3], [1, 5]) // false
|
|
1328
|
+
*/
|
|
1329
|
+
export function isSuperset(array1, array2) {
|
|
1330
|
+
return isSubset(array2, array1);
|
|
1331
|
+
}
|
|
1332
|
+
/**
|
|
1333
|
+
* Interleaves multiple arrays (takes one element from each in turn)
|
|
1334
|
+
* @param arrays - Arrays to interleave
|
|
1335
|
+
* @returns Interleaved array
|
|
1336
|
+
* @example
|
|
1337
|
+
* interleave([1, 2], ['a', 'b'], [true, false]) // [1, 'a', true, 2, 'b', false]
|
|
1338
|
+
*/
|
|
1339
|
+
export function interleave(...arrays) {
|
|
1340
|
+
if (arrays.length === 0) {
|
|
1341
|
+
return [];
|
|
1342
|
+
}
|
|
1343
|
+
const maxLength = Math.max(...arrays.map((arr) => arr.length));
|
|
1344
|
+
const result = [];
|
|
1345
|
+
for (let i = 0; i < maxLength; i++) {
|
|
1346
|
+
for (const arr of arrays) {
|
|
1347
|
+
if (i < arr.length) {
|
|
1348
|
+
result.push(arr[i]);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
return result;
|
|
1353
|
+
}
|
|
1354
|
+
/**
|
|
1355
|
+
* Splits array into two arrays based on a predicate
|
|
1356
|
+
* Similar to partition but returns object with named properties
|
|
1357
|
+
* @param array - Source array
|
|
1358
|
+
* @param predicate - Function to test each element
|
|
1359
|
+
* @returns Object with 'pass' and 'fail' arrays
|
|
1360
|
+
* @example
|
|
1361
|
+
* bifurcate([1, 2, 3, 4], x => x % 2 === 0) // { pass: [2, 4], fail: [1, 3] }
|
|
1362
|
+
*/
|
|
1363
|
+
export function bifurcate(array, predicate) {
|
|
1364
|
+
const pass = [];
|
|
1365
|
+
const fail = [];
|
|
1366
|
+
if (!Array.isArray(array)) {
|
|
1367
|
+
return { pass, fail };
|
|
1368
|
+
}
|
|
1369
|
+
for (let i = 0; i < array.length; i++) {
|
|
1370
|
+
if (predicate(array[i], i)) {
|
|
1371
|
+
pass.push(array[i]);
|
|
1372
|
+
}
|
|
1373
|
+
else {
|
|
1374
|
+
fail.push(array[i]);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
return { pass, fail };
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* Creates an array with a specified length filled with values from a generator function
|
|
1381
|
+
* @param length - Length of array
|
|
1382
|
+
* @param generator - Function that generates value for each index
|
|
1383
|
+
* @returns Generated array
|
|
1384
|
+
* @example
|
|
1385
|
+
* generate(5, i => i * 2) // [0, 2, 4, 6, 8]
|
|
1386
|
+
* generate(3, () => Math.random()) // [0.123, 0.456, 0.789]
|
|
1387
|
+
*/
|
|
1388
|
+
export function generate(length, generator) {
|
|
1389
|
+
if (length < 0) {
|
|
1390
|
+
return [];
|
|
1391
|
+
}
|
|
1392
|
+
const result = [];
|
|
1393
|
+
for (let i = 0; i < length; i++) {
|
|
1394
|
+
result.push(generator(i));
|
|
1395
|
+
}
|
|
1396
|
+
return result;
|
|
1397
|
+
}
|
|
1398
|
+
/**
|
|
1399
|
+
* Repeats an array n times
|
|
1400
|
+
* @param array - Source array
|
|
1401
|
+
* @param times - Number of times to repeat
|
|
1402
|
+
* @returns New array with repeated elements
|
|
1403
|
+
* @example
|
|
1404
|
+
* repeat([1, 2], 3) // [1, 2, 1, 2, 1, 2]
|
|
1405
|
+
*/
|
|
1406
|
+
export function repeat(array, times) {
|
|
1407
|
+
if (!Array.isArray(array) || times < 0) {
|
|
1408
|
+
return [];
|
|
1409
|
+
}
|
|
1410
|
+
const result = [];
|
|
1411
|
+
for (let i = 0; i < times; i++) {
|
|
1412
|
+
result.push(...array);
|
|
1413
|
+
}
|
|
1414
|
+
return result;
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* Finds the median value in a numeric array
|
|
1418
|
+
* @param array - Array of numbers
|
|
1419
|
+
* @returns Median value, or undefined if array is empty
|
|
1420
|
+
* @example
|
|
1421
|
+
* median([1, 2, 3, 4, 5]) // 3
|
|
1422
|
+
* median([1, 2, 3, 4]) // 2.5
|
|
1423
|
+
*/
|
|
1424
|
+
export function median(array) {
|
|
1425
|
+
if (!Array.isArray(array) || array.length === 0) {
|
|
1426
|
+
return undefined;
|
|
1427
|
+
}
|
|
1428
|
+
const sorted = [...array].sort((a, b) => a - b);
|
|
1429
|
+
const mid = Math.floor(sorted.length / 2);
|
|
1430
|
+
if (sorted.length % 2 === 0) {
|
|
1431
|
+
return (sorted[mid - 1] + sorted[mid]) / 2;
|
|
1432
|
+
}
|
|
1433
|
+
return sorted[mid];
|
|
1434
|
+
}
|
|
1435
|
+
/**
|
|
1436
|
+
* Calculates the mode (most frequent value) in an array
|
|
1437
|
+
* @param array - Source array
|
|
1438
|
+
* @returns Most frequent value, or undefined if array is empty
|
|
1439
|
+
* @example
|
|
1440
|
+
* mode([1, 2, 2, 3, 3, 3]) // 3
|
|
1441
|
+
* mode(['a', 'b', 'b']) // 'b'
|
|
1442
|
+
*/
|
|
1443
|
+
export function mode(array) {
|
|
1444
|
+
const common = mostCommon(array, 1);
|
|
1445
|
+
return common.length > 0 ? common[0] : undefined;
|
|
1446
|
+
}
|
|
1447
|
+
/**
|
|
1448
|
+
* Removes all occurrences of specified values from array
|
|
1449
|
+
* @param array - Source array
|
|
1450
|
+
* @param values - Values to remove
|
|
1451
|
+
* @returns New array without specified values
|
|
1452
|
+
* @example
|
|
1453
|
+
* without([1, 2, 3, 2, 4], 2, 3) // [1, 4]
|
|
1454
|
+
*/
|
|
1455
|
+
export function without(array, ...values) {
|
|
1456
|
+
if (!Array.isArray(array)) {
|
|
1457
|
+
return [];
|
|
1458
|
+
}
|
|
1459
|
+
const excludeSet = new Set(values);
|
|
1460
|
+
return array.filter((item) => !excludeSet.has(item));
|
|
1461
|
+
}
|
|
1462
|
+
/**
|
|
1463
|
+
* Creates array of unique values that are in the first array but not in other arrays
|
|
1464
|
+
* @param array - Source array
|
|
1465
|
+
* @param others - Arrays to exclude
|
|
1466
|
+
* @returns Array with values only in first array
|
|
1467
|
+
* @example
|
|
1468
|
+
* differenceWith([1, 2, 3], [2], [3]) // [1]
|
|
1469
|
+
*/
|
|
1470
|
+
export function differenceWith(array, ...others) {
|
|
1471
|
+
if (!Array.isArray(array)) {
|
|
1472
|
+
return [];
|
|
1473
|
+
}
|
|
1474
|
+
const excludeSet = new Set(others.flat());
|
|
1475
|
+
return array.filter((item) => !excludeSet.has(item));
|
|
1476
|
+
}
|
|
1477
|
+
/**
|
|
1478
|
+
* Groups consecutive identical values together
|
|
1479
|
+
* @param array - Source array
|
|
1480
|
+
* @returns Array of arrays with consecutive identical values
|
|
1481
|
+
* @example
|
|
1482
|
+
* groupConsecutive([1, 1, 2, 2, 2, 3, 1]) // [[1, 1], [2, 2, 2], [3], [1]]
|
|
1483
|
+
*/
|
|
1484
|
+
export function groupConsecutive(array) {
|
|
1485
|
+
if (!Array.isArray(array) || array.length === 0) {
|
|
1486
|
+
return [];
|
|
1487
|
+
}
|
|
1488
|
+
const result = [];
|
|
1489
|
+
let currentGroup = [array[0]];
|
|
1490
|
+
for (let i = 1; i < array.length; i++) {
|
|
1491
|
+
if (array[i] === array[i - 1]) {
|
|
1492
|
+
currentGroup.push(array[i]);
|
|
1493
|
+
}
|
|
1494
|
+
else {
|
|
1495
|
+
result.push(currentGroup);
|
|
1496
|
+
currentGroup = [array[i]];
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
result.push(currentGroup);
|
|
1500
|
+
return result;
|
|
1501
|
+
}
|
|
1502
|
+
/**
|
|
1503
|
+
* Removes consecutive duplicate values
|
|
1504
|
+
* @param array - Source array
|
|
1505
|
+
* @returns Array without consecutive duplicates
|
|
1506
|
+
* @example
|
|
1507
|
+
* dedupeConsecutive([1, 1, 2, 2, 2, 3, 1]) // [1, 2, 3, 1]
|
|
1508
|
+
*/
|
|
1509
|
+
export function dedupeConsecutive(array) {
|
|
1510
|
+
if (!Array.isArray(array) || array.length === 0) {
|
|
1511
|
+
return [];
|
|
1512
|
+
}
|
|
1513
|
+
const result = [array[0]];
|
|
1514
|
+
for (let i = 1; i < array.length; i++) {
|
|
1515
|
+
if (array[i] !== array[i - 1]) {
|
|
1516
|
+
result.push(array[i]);
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
return result;
|
|
1520
|
+
}
|
|
1521
|
+
/**
|
|
1522
|
+
* Assigns ranks to array elements (1-based, lower values get lower ranks)
|
|
1523
|
+
* @param array - Array of numbers
|
|
1524
|
+
* @param method - Ranking method: 'standard', 'dense', or 'ordinal'
|
|
1525
|
+
* @returns Array of ranks corresponding to input array
|
|
1526
|
+
* @example
|
|
1527
|
+
* rank([10, 20, 20, 30]) // [1, 2, 2, 4] (standard)
|
|
1528
|
+
* rank([10, 20, 20, 30], 'dense') // [1, 2, 2, 3]
|
|
1529
|
+
* rank([10, 20, 20, 30], 'ordinal') // [1, 2, 3, 4]
|
|
1530
|
+
*/
|
|
1531
|
+
export function rank(array, method = 'standard') {
|
|
1532
|
+
if (!Array.isArray(array) || array.length === 0) {
|
|
1533
|
+
return [];
|
|
1534
|
+
}
|
|
1535
|
+
const indexed = array.map((value, index) => ({ value, index }));
|
|
1536
|
+
indexed.sort((a, b) => a.value - b.value);
|
|
1537
|
+
const ranks = new Array(array.length);
|
|
1538
|
+
if (method === 'ordinal') {
|
|
1539
|
+
for (let i = 0; i < indexed.length; i++) {
|
|
1540
|
+
ranks[indexed[i].index] = i + 1;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
else if (method === 'dense') {
|
|
1544
|
+
let denseRank = 1;
|
|
1545
|
+
ranks[indexed[0].index] = denseRank;
|
|
1546
|
+
for (let i = 1; i < indexed.length; i++) {
|
|
1547
|
+
if (indexed[i].value !== indexed[i - 1].value) {
|
|
1548
|
+
denseRank++;
|
|
1549
|
+
}
|
|
1550
|
+
ranks[indexed[i].index] = denseRank;
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
else {
|
|
1554
|
+
// standard
|
|
1555
|
+
let i = 0;
|
|
1556
|
+
while (i < indexed.length) {
|
|
1557
|
+
let j = i;
|
|
1558
|
+
// Find all elements with same value
|
|
1559
|
+
while (j < indexed.length && indexed[j].value === indexed[i].value) {
|
|
1560
|
+
j++;
|
|
1561
|
+
}
|
|
1562
|
+
// Assign average rank to all tied elements
|
|
1563
|
+
const rankValue = i + 1;
|
|
1564
|
+
for (let k = i; k < j; k++) {
|
|
1565
|
+
ranks[indexed[k].index] = rankValue;
|
|
1566
|
+
}
|
|
1567
|
+
i = j;
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
return ranks;
|
|
1571
|
+
}
|
|
1572
|
+
/**
|
|
1573
|
+
* Calculates the percentile value from a sorted or unsorted array
|
|
1574
|
+
* @param array - Array of numbers
|
|
1575
|
+
* @param percentile - Percentile to calculate (0-100)
|
|
1576
|
+
* @returns Percentile value, or undefined if array is empty
|
|
1577
|
+
* @example
|
|
1578
|
+
* percentile([1, 2, 3, 4, 5], 50) // 3 (median)
|
|
1579
|
+
* percentile([1, 2, 3, 4, 5, 6], 75) // 4.75
|
|
1580
|
+
*/
|
|
1581
|
+
export function percentile(array, percentile) {
|
|
1582
|
+
if (!Array.isArray(array) || array.length === 0) {
|
|
1583
|
+
return undefined;
|
|
1584
|
+
}
|
|
1585
|
+
if (percentile < 0 || percentile > 100) {
|
|
1586
|
+
return undefined;
|
|
1587
|
+
}
|
|
1588
|
+
const sorted = [...array].sort((a, b) => a - b);
|
|
1589
|
+
const index = (percentile / 100) * (sorted.length - 1);
|
|
1590
|
+
const lower = Math.floor(index);
|
|
1591
|
+
const upper = Math.ceil(index);
|
|
1592
|
+
const weight = index - lower;
|
|
1593
|
+
if (lower === upper) {
|
|
1594
|
+
return sorted[lower];
|
|
1595
|
+
}
|
|
1596
|
+
return sorted[lower] * (1 - weight) + sorted[upper] * weight;
|
|
1597
|
+
}
|
|
1598
|
+
/**
|
|
1599
|
+
* Calculates quartiles (Q1, Q2/median, Q3) of an array
|
|
1600
|
+
* @param array - Array of numbers
|
|
1601
|
+
* @returns Object with q1, q2 (median), q3, and iqr (interquartile range)
|
|
1602
|
+
* @example
|
|
1603
|
+
* quartiles([1, 2, 3, 4, 5]) // { q1: 1.5, q2: 3, q3: 4.5, iqr: 3 }
|
|
1604
|
+
*/
|
|
1605
|
+
export function quartiles(array) {
|
|
1606
|
+
if (!Array.isArray(array) || array.length === 0) {
|
|
1607
|
+
return undefined;
|
|
1608
|
+
}
|
|
1609
|
+
const q1 = percentile(array, 25);
|
|
1610
|
+
const q2 = percentile(array, 50);
|
|
1611
|
+
const q3 = percentile(array, 75);
|
|
1612
|
+
return {
|
|
1613
|
+
q1,
|
|
1614
|
+
q2,
|
|
1615
|
+
q3,
|
|
1616
|
+
iqr: q3 - q1,
|
|
1617
|
+
};
|
|
1618
|
+
}
|
|
1619
|
+
/**
|
|
1620
|
+
* Calculates standard deviation of numeric array
|
|
1621
|
+
* @param array - Array of numbers
|
|
1622
|
+
* @param sample - If true, calculates sample standard deviation (default: false for population)
|
|
1623
|
+
* @returns Standard deviation, or undefined if array is empty
|
|
1624
|
+
* @example
|
|
1625
|
+
* standardDeviation([2, 4, 4, 4, 5, 5, 7, 9]) // 2
|
|
1626
|
+
*/
|
|
1627
|
+
export function standardDeviation(array, sample = false) {
|
|
1628
|
+
if (!Array.isArray(array) || array.length === 0) {
|
|
1629
|
+
return undefined;
|
|
1630
|
+
}
|
|
1631
|
+
if (sample && array.length === 1) {
|
|
1632
|
+
return undefined;
|
|
1633
|
+
}
|
|
1634
|
+
const avg = array.reduce((sum, val) => sum + val, 0) / array.length;
|
|
1635
|
+
const squareDiffs = array.map((value) => Math.pow(value - avg, 2));
|
|
1636
|
+
const avgSquareDiff = squareDiffs.reduce((sum, val) => sum + val, 0) /
|
|
1637
|
+
(sample ? array.length - 1 : array.length);
|
|
1638
|
+
return Math.sqrt(avgSquareDiff);
|
|
1639
|
+
}
|
|
1640
|
+
/**
|
|
1641
|
+
* Calculates variance of numeric array
|
|
1642
|
+
* @param array - Array of numbers
|
|
1643
|
+
* @param sample - If true, calculates sample variance (default: false for population)
|
|
1644
|
+
* @returns Variance, or undefined if array is empty
|
|
1645
|
+
* @example
|
|
1646
|
+
* variance([2, 4, 4, 4, 5, 5, 7, 9]) // 4
|
|
1647
|
+
*/
|
|
1648
|
+
export function variance(array, sample = false) {
|
|
1649
|
+
const std = standardDeviation(array, sample);
|
|
1650
|
+
return std !== undefined ? Math.pow(std, 2) : undefined;
|
|
1651
|
+
}
|
|
1652
|
+
/**
|
|
1653
|
+
* Normalizes array values to 0-1 range (min-max normalization)
|
|
1654
|
+
* @param array - Array of numbers
|
|
1655
|
+
* @returns Normalized array
|
|
1656
|
+
* @example
|
|
1657
|
+
* normalize([1, 2, 3, 4, 5]) // [0, 0.25, 0.5, 0.75, 1]
|
|
1658
|
+
*/
|
|
1659
|
+
export function normalize(array) {
|
|
1660
|
+
if (!Array.isArray(array) || array.length === 0) {
|
|
1661
|
+
return [];
|
|
1662
|
+
}
|
|
1663
|
+
const min = Math.min(...array);
|
|
1664
|
+
const max = Math.max(...array);
|
|
1665
|
+
const range = max - min;
|
|
1666
|
+
if (range === 0) {
|
|
1667
|
+
return array.map(() => 0);
|
|
1668
|
+
}
|
|
1669
|
+
return array.map((value) => (value - min) / range);
|
|
1670
|
+
}
|
|
1671
|
+
/**
|
|
1672
|
+
* Standardizes array values (z-score normalization)
|
|
1673
|
+
* @param array - Array of numbers
|
|
1674
|
+
* @returns Standardized array with mean 0 and standard deviation 1
|
|
1675
|
+
* @example
|
|
1676
|
+
* standardize([2, 4, 4, 4, 5, 5, 7, 9]) // [-1.5, -0.5, -0.5, -0.5, 0, 0, 1, 2]
|
|
1677
|
+
*/
|
|
1678
|
+
export function standardize(array) {
|
|
1679
|
+
if (!Array.isArray(array) || array.length === 0) {
|
|
1680
|
+
return [];
|
|
1681
|
+
}
|
|
1682
|
+
const avg = array.reduce((sum, val) => sum + val, 0) / array.length;
|
|
1683
|
+
const std = standardDeviation(array);
|
|
1684
|
+
if (std === undefined || std === 0) {
|
|
1685
|
+
return array.map(() => 0);
|
|
1686
|
+
}
|
|
1687
|
+
return array.map((value) => (value - avg) / std);
|
|
1688
|
+
}
|
|
1689
|
+
/**
|
|
1690
|
+
* Applies a function cumulatively to array elements (like reduce but returns all intermediate results)
|
|
1691
|
+
* @param array - Source array
|
|
1692
|
+
* @param fn - Accumulator function
|
|
1693
|
+
* @param initial - Initial value
|
|
1694
|
+
* @returns Array of cumulative results
|
|
1695
|
+
* @example
|
|
1696
|
+
* scan([1, 2, 3, 4], (acc, x) => acc + x, 0) // [1, 3, 6, 10]
|
|
1697
|
+
*/
|
|
1698
|
+
export function scan(array, fn, initial) {
|
|
1699
|
+
if (!Array.isArray(array)) {
|
|
1700
|
+
return [];
|
|
1701
|
+
}
|
|
1702
|
+
const result = [];
|
|
1703
|
+
let accumulator = initial;
|
|
1704
|
+
for (let i = 0; i < array.length; i++) {
|
|
1705
|
+
accumulator = fn(accumulator, array[i], i);
|
|
1706
|
+
result.push(accumulator);
|
|
1707
|
+
}
|
|
1708
|
+
return result;
|
|
1709
|
+
}
|
|
1710
|
+
/**
|
|
1711
|
+
* Creates cumulative sum array
|
|
1712
|
+
* @param array - Array of numbers
|
|
1713
|
+
* @returns Array of cumulative sums
|
|
1714
|
+
* @example
|
|
1715
|
+
* cumulativeSum([1, 2, 3, 4]) // [1, 3, 6, 10]
|
|
1716
|
+
*/
|
|
1717
|
+
export function cumulativeSum(array) {
|
|
1718
|
+
return scan(array, (acc, x) => acc + x, 0);
|
|
1719
|
+
}
|
|
1720
|
+
/**
|
|
1721
|
+
* Creates array of differences between consecutive elements
|
|
1722
|
+
* @param array - Array of numbers
|
|
1723
|
+
* @returns Array of differences
|
|
1724
|
+
* @example
|
|
1725
|
+
* diff([1, 3, 6, 10]) // [2, 3, 4]
|
|
1726
|
+
*/
|
|
1727
|
+
export function diff(array) {
|
|
1728
|
+
if (!Array.isArray(array) || array.length < 2) {
|
|
1729
|
+
return [];
|
|
1730
|
+
}
|
|
1731
|
+
const result = [];
|
|
1732
|
+
for (let i = 1; i < array.length; i++) {
|
|
1733
|
+
result.push(array[i] - array[i - 1]);
|
|
1734
|
+
}
|
|
1735
|
+
return result;
|
|
1736
|
+
}
|
|
1737
|
+
/**
|
|
1738
|
+
* Pads array to specified length with a value
|
|
1739
|
+
* @param array - Source array
|
|
1740
|
+
* @param length - Target length
|
|
1741
|
+
* @param value - Value to pad with
|
|
1742
|
+
* @param position - Where to add padding: 'start' or 'end' (default: 'end')
|
|
1743
|
+
* @returns Padded array
|
|
1744
|
+
* @example
|
|
1745
|
+
* pad([1, 2, 3], 5, 0) // [1, 2, 3, 0, 0]
|
|
1746
|
+
* pad([1, 2, 3], 5, 0, 'start') // [0, 0, 1, 2, 3]
|
|
1747
|
+
*/
|
|
1748
|
+
export function pad(array, length, value, position = 'end') {
|
|
1749
|
+
if (!Array.isArray(array) || length <= array.length) {
|
|
1750
|
+
return [...array];
|
|
1751
|
+
}
|
|
1752
|
+
const padding = new Array(length - array.length).fill(value);
|
|
1753
|
+
return position === 'start' ? [...padding, ...array] : [...array, ...padding];
|
|
1754
|
+
}
|
|
1755
|
+
/**
|
|
1756
|
+
* Trims array to remove elements from start and end that match predicate
|
|
1757
|
+
* @param array - Source array
|
|
1758
|
+
* @param predicate - Function to test elements to trim
|
|
1759
|
+
* @returns Trimmed array
|
|
1760
|
+
* @example
|
|
1761
|
+
* trim([0, 0, 1, 2, 3, 0, 0], x => x === 0) // [1, 2, 3]
|
|
1762
|
+
*/
|
|
1763
|
+
export function trim(array, predicate) {
|
|
1764
|
+
if (!Array.isArray(array) || array.length === 0) {
|
|
1765
|
+
return [];
|
|
1766
|
+
}
|
|
1767
|
+
let start = 0;
|
|
1768
|
+
let end = array.length - 1;
|
|
1769
|
+
while (start <= end && predicate(array[start])) {
|
|
1770
|
+
start++;
|
|
1771
|
+
}
|
|
1772
|
+
while (end >= start && predicate(array[end])) {
|
|
1773
|
+
end--;
|
|
1774
|
+
}
|
|
1775
|
+
return array.slice(start, end + 1);
|
|
1776
|
+
}
|
|
1777
|
+
/**
|
|
1778
|
+
* Creates pairs of adjacent elements
|
|
1779
|
+
* @param array - Source array
|
|
1780
|
+
* @returns Array of pairs
|
|
1781
|
+
* @example
|
|
1782
|
+
* pairwise([1, 2, 3, 4]) // [[1, 2], [2, 3], [3, 4]]
|
|
1783
|
+
*/
|
|
1784
|
+
export function pairwise(array) {
|
|
1785
|
+
if (!Array.isArray(array) || array.length < 2) {
|
|
1786
|
+
return [];
|
|
1787
|
+
}
|
|
1788
|
+
const result = [];
|
|
1789
|
+
for (let i = 0; i < array.length - 1; i++) {
|
|
1790
|
+
result.push([array[i], array[i + 1]]);
|
|
1791
|
+
}
|
|
1792
|
+
return result;
|
|
1793
|
+
}
|
|
1794
|
+
/**
|
|
1795
|
+
* Creates array of index-value pairs
|
|
1796
|
+
* @param array - Source array
|
|
1797
|
+
* @returns Array of [index, value] tuples
|
|
1798
|
+
* @example
|
|
1799
|
+
* enumerate(['a', 'b', 'c']) // [[0, 'a'], [1, 'b'], [2, 'c']]
|
|
1800
|
+
*/
|
|
1801
|
+
export function enumerate(array) {
|
|
1802
|
+
if (!Array.isArray(array)) {
|
|
1803
|
+
return [];
|
|
1804
|
+
}
|
|
1805
|
+
return array.map((value, index) => [index, value]);
|
|
1806
|
+
}
|
|
1807
|
+
/**
|
|
1808
|
+
* Applies different functions to array elements based on their index
|
|
1809
|
+
* @param array - Source array
|
|
1810
|
+
* @param functions - Array of functions to apply
|
|
1811
|
+
* @returns Array with functions applied
|
|
1812
|
+
* @example
|
|
1813
|
+
* applyByIndex([1, 2, 3], [x => x * 2, x => x + 10, x => x ** 2]) // [2, 12, 9]
|
|
1814
|
+
*/
|
|
1815
|
+
export function applyByIndex(array, functions) {
|
|
1816
|
+
if (!Array.isArray(array)) {
|
|
1817
|
+
return [];
|
|
1818
|
+
}
|
|
1819
|
+
return array.map((item, index) => {
|
|
1820
|
+
const fn = functions[index % functions.length];
|
|
1821
|
+
return fn(item);
|
|
1822
|
+
});
|
|
1823
|
+
}
|
|
1824
|
+
/**
|
|
1825
|
+
* Rotates elements to left by n positions
|
|
1826
|
+
* @param array - Source array
|
|
1827
|
+
* @param n - Number of positions to rotate (default: 1)
|
|
1828
|
+
* @returns Rotated array
|
|
1829
|
+
* @example
|
|
1830
|
+
* rotateLeft([1, 2, 3, 4, 5], 2) // [3, 4, 5, 1, 2]
|
|
1831
|
+
*/
|
|
1832
|
+
export function rotateLeft(array, n = 1) {
|
|
1833
|
+
if (!Array.isArray(array) || array.length === 0) {
|
|
1834
|
+
return [];
|
|
1835
|
+
}
|
|
1836
|
+
const len = array.length;
|
|
1837
|
+
const rotations = ((n % len) + len) % len;
|
|
1838
|
+
return [...array.slice(rotations), ...array.slice(0, rotations)];
|
|
1839
|
+
}
|
|
1840
|
+
/**
|
|
1841
|
+
* Rotates elements to right by n positions
|
|
1842
|
+
* @param array - Source array
|
|
1843
|
+
* @param n - Number of positions to rotate (default: 1)
|
|
1844
|
+
* @returns Rotated array
|
|
1845
|
+
* @example
|
|
1846
|
+
* rotateRight([1, 2, 3, 4, 5], 2) // [4, 5, 1, 2, 3]
|
|
1847
|
+
*/
|
|
1848
|
+
export function rotateRight(array, n = 1) {
|
|
1849
|
+
return rotateLeft(array, -n);
|
|
1850
|
+
}
|
|
1851
|
+
/**
|
|
1852
|
+
* Swaps two elements in array by their indices
|
|
1853
|
+
* @param array - Source array
|
|
1854
|
+
* @param i - First index
|
|
1855
|
+
* @param j - Second index
|
|
1856
|
+
* @returns New array with swapped elements
|
|
1857
|
+
* @example
|
|
1858
|
+
* swap([1, 2, 3, 4], 0, 3) // [4, 2, 3, 1]
|
|
1859
|
+
*/
|
|
1860
|
+
export function swap(array, i, j) {
|
|
1861
|
+
if (!Array.isArray(array) ||
|
|
1862
|
+
i < 0 ||
|
|
1863
|
+
j < 0 ||
|
|
1864
|
+
i >= array.length ||
|
|
1865
|
+
j >= array.length) {
|
|
1866
|
+
return [...array];
|
|
1867
|
+
}
|
|
1868
|
+
const result = [...array];
|
|
1869
|
+
[result[i], result[j]] = [result[j], result[i]];
|
|
1870
|
+
return result;
|
|
1871
|
+
}
|
|
1872
|
+
/**
|
|
1873
|
+
* Reverses array segments between specified indices
|
|
1874
|
+
* @param array - Source array
|
|
1875
|
+
* @param start - Start index (inclusive)
|
|
1876
|
+
* @param end - End index (inclusive)
|
|
1877
|
+
* @returns Array with reversed segment
|
|
1878
|
+
* @example
|
|
1879
|
+
* reverseSegment([1, 2, 3, 4, 5], 1, 3) // [1, 4, 3, 2, 5]
|
|
1880
|
+
*/
|
|
1881
|
+
export function reverseSegment(array, start, end) {
|
|
1882
|
+
if (!Array.isArray(array) ||
|
|
1883
|
+
start < 0 ||
|
|
1884
|
+
end >= array.length ||
|
|
1885
|
+
start >= end) {
|
|
1886
|
+
return [...array];
|
|
1887
|
+
}
|
|
1888
|
+
const result = [...array];
|
|
1889
|
+
const segment = result.slice(start, end + 1).reverse();
|
|
1890
|
+
result.splice(start, end - start + 1, ...segment);
|
|
1891
|
+
return result;
|
|
1892
|
+
}
|
|
1893
|
+
/**
|
|
1894
|
+
* Checks if array is sorted in ascending order
|
|
1895
|
+
* @param array - Array to check
|
|
1896
|
+
* @param compareFn - Optional comparison function
|
|
1897
|
+
* @returns True if array is sorted
|
|
1898
|
+
* @example
|
|
1899
|
+
* isSorted([1, 2, 3, 4]) // true
|
|
1900
|
+
* isSorted([1, 3, 2, 4]) // false
|
|
1901
|
+
*/
|
|
1902
|
+
export function isSorted(array, compareFn) {
|
|
1903
|
+
if (!Array.isArray(array) || array.length <= 1) {
|
|
1904
|
+
return true;
|
|
1905
|
+
}
|
|
1906
|
+
const compare = compareFn || ((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
1907
|
+
for (let i = 1; i < array.length; i++) {
|
|
1908
|
+
if (compare(array[i - 1], array[i]) > 0) {
|
|
1909
|
+
return false;
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
return true;
|
|
1913
|
+
}
|
|
1914
|
+
/**
|
|
1915
|
+
* Checks if array contains duplicates
|
|
1916
|
+
* @param array - Array to check
|
|
1917
|
+
* @returns True if array has duplicates
|
|
1918
|
+
* @example
|
|
1919
|
+
* hasDuplicates([1, 2, 3]) // false
|
|
1920
|
+
* hasDuplicates([1, 2, 2, 3]) // true
|
|
1921
|
+
*/
|
|
1922
|
+
export function hasDuplicates(array) {
|
|
1923
|
+
if (!Array.isArray(array)) {
|
|
1924
|
+
return false;
|
|
1925
|
+
}
|
|
1926
|
+
return new Set(array).size !== array.length;
|
|
1927
|
+
}
|
|
1928
|
+
/**
|
|
1929
|
+
* Extracts values from array of objects by multiple keys
|
|
1930
|
+
* @param array - Array of objects
|
|
1931
|
+
* @param keys - Keys to extract
|
|
1932
|
+
* @returns Array of objects with only specified keys
|
|
1933
|
+
* @example
|
|
1934
|
+
* pickMany([{a: 1, b: 2, c: 3}], ['a', 'c']) // [{a: 1, c: 3}]
|
|
1935
|
+
*/
|
|
1936
|
+
export function pickMany(array, keys) {
|
|
1937
|
+
if (!Array.isArray(array)) {
|
|
1938
|
+
return [];
|
|
1939
|
+
}
|
|
1940
|
+
return array.map((obj) => {
|
|
1941
|
+
const result = {};
|
|
1942
|
+
for (const key of keys) {
|
|
1943
|
+
if (key in obj) {
|
|
1944
|
+
result[key] = obj[key];
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
return result;
|
|
1948
|
+
});
|
|
1949
|
+
}
|
|
1950
|
+
/**
|
|
1951
|
+
* Maps array to object using key and value selectors
|
|
1952
|
+
* @param array - Source array
|
|
1953
|
+
* @param keySelector - Function to extract key
|
|
1954
|
+
* @param valueSelector - Function to extract value (optional, defaults to identity)
|
|
1955
|
+
* @returns Object with mapped keys and values
|
|
1956
|
+
* @example
|
|
1957
|
+
* toObject([{id: 1, name: 'A'}], x => x.id, x => x.name) // {1: 'A'}
|
|
1958
|
+
*/
|
|
1959
|
+
export function toObject(array, keySelector, valueSelector) {
|
|
1960
|
+
if (!Array.isArray(array)) {
|
|
1961
|
+
return {};
|
|
1962
|
+
}
|
|
1963
|
+
const result = {};
|
|
1964
|
+
for (let i = 0; i < array.length; i++) {
|
|
1965
|
+
const key = keySelector(array[i], i);
|
|
1966
|
+
const value = valueSelector
|
|
1967
|
+
? valueSelector(array[i], i)
|
|
1968
|
+
: array[i];
|
|
1969
|
+
result[key] = value;
|
|
1970
|
+
}
|
|
1971
|
+
return result;
|
|
1972
|
+
}
|
|
1973
|
+
/**
|
|
1974
|
+
* Maps array elements asynchronously in sequence
|
|
1975
|
+
* @param array - Source array
|
|
1976
|
+
* @param fn - Async mapping function
|
|
1977
|
+
* @returns Promise resolving to mapped array
|
|
1978
|
+
* @example
|
|
1979
|
+
* await mapAsync([1, 2, 3], async x => await fetchData(x))
|
|
1980
|
+
*/
|
|
1981
|
+
export async function mapAsync(array, fn) {
|
|
1982
|
+
if (!Array.isArray(array)) {
|
|
1983
|
+
return [];
|
|
1984
|
+
}
|
|
1985
|
+
const result = [];
|
|
1986
|
+
for (let i = 0; i < array.length; i++) {
|
|
1987
|
+
result.push(await fn(array[i], i));
|
|
1988
|
+
}
|
|
1989
|
+
return result;
|
|
1990
|
+
}
|
|
1991
|
+
/**
|
|
1992
|
+
* Maps array elements asynchronously in parallel
|
|
1993
|
+
* @param array - Source array
|
|
1994
|
+
* @param fn - Async mapping function
|
|
1995
|
+
* @returns Promise resolving to mapped array
|
|
1996
|
+
* @example
|
|
1997
|
+
* await mapAsyncParallel([1, 2, 3], async x => await fetchData(x))
|
|
1998
|
+
*/
|
|
1999
|
+
export async function mapAsyncParallel(array, fn) {
|
|
2000
|
+
if (!Array.isArray(array)) {
|
|
2001
|
+
return [];
|
|
2002
|
+
}
|
|
2003
|
+
return Promise.all(array.map((item, index) => fn(item, index)));
|
|
2004
|
+
}
|
|
2005
|
+
/**
|
|
2006
|
+
* Filters array elements asynchronously
|
|
2007
|
+
* @param array - Source array
|
|
2008
|
+
* @param predicate - Async predicate function
|
|
2009
|
+
* @returns Promise resolving to filtered array
|
|
2010
|
+
* @example
|
|
2011
|
+
* await filterAsync([1, 2, 3], async x => await isValid(x))
|
|
2012
|
+
*/
|
|
2013
|
+
export async function filterAsync(array, predicate) {
|
|
2014
|
+
if (!Array.isArray(array)) {
|
|
2015
|
+
return [];
|
|
2016
|
+
}
|
|
2017
|
+
const result = [];
|
|
2018
|
+
for (let i = 0; i < array.length; i++) {
|
|
2019
|
+
if (await predicate(array[i], i)) {
|
|
2020
|
+
result.push(array[i]);
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
return result;
|
|
2024
|
+
}
|
|
2025
|
+
/**
|
|
2026
|
+
* Reduces array asynchronously
|
|
2027
|
+
* @param array - Source array
|
|
2028
|
+
* @param fn - Async reducer function
|
|
2029
|
+
* @param initial - Initial accumulator value
|
|
2030
|
+
* @returns Promise resolving to final accumulator value
|
|
2031
|
+
* @example
|
|
2032
|
+
* await reduceAsync([1, 2, 3], async (acc, x) => acc + await process(x), 0)
|
|
2033
|
+
*/
|
|
2034
|
+
export async function reduceAsync(array, fn, initial) {
|
|
2035
|
+
if (!Array.isArray(array)) {
|
|
2036
|
+
return initial;
|
|
2037
|
+
}
|
|
2038
|
+
let accumulator = initial;
|
|
2039
|
+
for (let i = 0; i < array.length; i++) {
|
|
2040
|
+
accumulator = await fn(accumulator, array[i], i);
|
|
2041
|
+
}
|
|
2042
|
+
return accumulator;
|
|
2043
|
+
}
|
|
2044
|
+
/**
|
|
2045
|
+
* Finds first element that satisfies async predicate
|
|
2046
|
+
* @param array - Source array
|
|
2047
|
+
* @param predicate - Async predicate function
|
|
2048
|
+
* @returns Promise resolving to found element or undefined
|
|
2049
|
+
* @example
|
|
2050
|
+
* await findAsync([1, 2, 3], async x => await isValid(x))
|
|
2051
|
+
*/
|
|
2052
|
+
export async function findAsync(array, predicate) {
|
|
2053
|
+
if (!Array.isArray(array)) {
|
|
2054
|
+
return undefined;
|
|
2055
|
+
}
|
|
2056
|
+
for (let i = 0; i < array.length; i++) {
|
|
2057
|
+
if (await predicate(array[i], i)) {
|
|
2058
|
+
return array[i];
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
return undefined;
|
|
2062
|
+
}
|
|
2063
|
+
/**
|
|
2064
|
+
* Checks if some element satisfies async predicate
|
|
2065
|
+
* @param array - Source array
|
|
2066
|
+
* @param predicate - Async predicate function
|
|
2067
|
+
* @returns Promise resolving to true if any element matches
|
|
2068
|
+
* @example
|
|
2069
|
+
* await someAsync([1, 2, 3], async x => await isValid(x))
|
|
2070
|
+
*/
|
|
2071
|
+
export async function someAsync(array, predicate) {
|
|
2072
|
+
if (!Array.isArray(array)) {
|
|
2073
|
+
return false;
|
|
2074
|
+
}
|
|
2075
|
+
for (let i = 0; i < array.length; i++) {
|
|
2076
|
+
if (await predicate(array[i], i)) {
|
|
2077
|
+
return true;
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
return false;
|
|
2081
|
+
}
|
|
2082
|
+
/**
|
|
2083
|
+
* Checks if every element satisfies async predicate
|
|
2084
|
+
* @param array - Source array
|
|
2085
|
+
* @param predicate - Async predicate function
|
|
2086
|
+
* @returns Promise resolving to true if all elements match
|
|
2087
|
+
* @example
|
|
2088
|
+
* await everyAsync([1, 2, 3], async x => await isValid(x))
|
|
2089
|
+
*/
|
|
2090
|
+
export async function everyAsync(array, predicate) {
|
|
2091
|
+
if (!Array.isArray(array)) {
|
|
2092
|
+
return true;
|
|
2093
|
+
}
|
|
2094
|
+
for (let i = 0; i < array.length; i++) {
|
|
2095
|
+
if (!(await predicate(array[i], i))) {
|
|
2096
|
+
return false;
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
return true;
|
|
2100
|
+
}
|
|
2101
|
+
/**
|
|
2102
|
+
* Calculates weighted average from array of {value, weight} objects or using selectors
|
|
2103
|
+
* @param array - Source array
|
|
2104
|
+
* @param valueSelector - Function to extract value
|
|
2105
|
+
* @param weightSelector - Function to extract weight
|
|
2106
|
+
* @returns Weighted average, or 0 if total weight is 0
|
|
2107
|
+
* @example
|
|
2108
|
+
* weightedAverage([{score: 80, credits: 3}, {score: 90, credits: 2}], x => x.score, x => x.credits) // 84
|
|
2109
|
+
*/
|
|
2110
|
+
export function weightedAverage(array, valueSelector, weightSelector) {
|
|
2111
|
+
if (!Array.isArray(array) || array.length === 0) {
|
|
2112
|
+
return 0;
|
|
2113
|
+
}
|
|
2114
|
+
let totalWeight = 0;
|
|
2115
|
+
let weightedSum = 0;
|
|
2116
|
+
for (const item of array) {
|
|
2117
|
+
const value = typeof valueSelector === 'function'
|
|
2118
|
+
? valueSelector(item)
|
|
2119
|
+
: get(item, valueSelector);
|
|
2120
|
+
const weight = typeof weightSelector === 'function'
|
|
2121
|
+
? weightSelector(item)
|
|
2122
|
+
: get(item, weightSelector);
|
|
2123
|
+
if (typeof value === 'number' && typeof weight === 'number') {
|
|
2124
|
+
weightedSum += value * weight;
|
|
2125
|
+
totalWeight += weight;
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
return totalWeight === 0 ? 0 : weightedSum / totalWeight;
|
|
2129
|
+
}
|
|
2130
|
+
/**
|
|
2131
|
+
* Randomly selects elements with weights
|
|
2132
|
+
* @param array - Source array
|
|
2133
|
+
* @param weightSelector - Function to extract weight
|
|
2134
|
+
* @param count - Number of elements to select (default: 1)
|
|
2135
|
+
* @returns Array of selected elements
|
|
2136
|
+
* @example
|
|
2137
|
+
* weightedSample([{item: 'A', prob: 0.7}, {item: 'B', prob: 0.3}], x => x.prob, 10)
|
|
2138
|
+
*/
|
|
2139
|
+
export function weightedSample(array, weightSelector, count = 1) {
|
|
2140
|
+
if (!Array.isArray(array) || array.length === 0 || count < 1) {
|
|
2141
|
+
return [];
|
|
2142
|
+
}
|
|
2143
|
+
// Calculate cumulative weights
|
|
2144
|
+
const weights = [];
|
|
2145
|
+
let totalWeight = 0;
|
|
2146
|
+
for (const item of array) {
|
|
2147
|
+
const weight = typeof weightSelector === 'function'
|
|
2148
|
+
? weightSelector(item)
|
|
2149
|
+
: get(item, weightSelector);
|
|
2150
|
+
totalWeight += typeof weight === 'number' && weight > 0 ? weight : 0;
|
|
2151
|
+
weights.push(totalWeight);
|
|
2152
|
+
}
|
|
2153
|
+
if (totalWeight === 0) {
|
|
2154
|
+
return [];
|
|
2155
|
+
}
|
|
2156
|
+
const result = [];
|
|
2157
|
+
for (let i = 0; i < count; i++) {
|
|
2158
|
+
const random = Math.random() * totalWeight;
|
|
2159
|
+
const index = weights.findIndex((w) => random < w);
|
|
2160
|
+
if (index !== -1) {
|
|
2161
|
+
result.push(array[index]);
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
return result;
|
|
2165
|
+
}
|
|
2166
|
+
/**
|
|
2167
|
+
* Splits array into segments of varying sizes
|
|
2168
|
+
* @param array - Source array
|
|
2169
|
+
* @param sizes - Array of segment sizes
|
|
2170
|
+
* @returns Array of segments
|
|
2171
|
+
* @example
|
|
2172
|
+
* segment([1, 2, 3, 4, 5, 6], [2, 3, 1]) // [[1, 2], [3, 4, 5], [6]]
|
|
2173
|
+
*/
|
|
2174
|
+
export function segment(array, sizes) {
|
|
2175
|
+
if (!Array.isArray(array) || !Array.isArray(sizes)) {
|
|
2176
|
+
return [];
|
|
2177
|
+
}
|
|
2178
|
+
const result = [];
|
|
2179
|
+
let offset = 0;
|
|
2180
|
+
for (const size of sizes) {
|
|
2181
|
+
if (offset >= array.length)
|
|
2182
|
+
break;
|
|
2183
|
+
result.push(array.slice(offset, offset + size));
|
|
2184
|
+
offset += size;
|
|
2185
|
+
}
|
|
2186
|
+
return result;
|
|
2187
|
+
}
|
|
2188
|
+
/**
|
|
2189
|
+
* Creates array with elements that appear in first array specified number of times
|
|
2190
|
+
* @param array - Source array
|
|
2191
|
+
* @param count - Required occurrence count
|
|
2192
|
+
* @returns Array with elements appearing exactly count times
|
|
2193
|
+
* @example
|
|
2194
|
+
* selectByCount([1, 1, 2, 2, 2, 3], 2) // [1, 1, 2, 2]
|
|
2195
|
+
*/
|
|
2196
|
+
export function selectByCount(array, count) {
|
|
2197
|
+
if (!Array.isArray(array) || count < 1) {
|
|
2198
|
+
return [];
|
|
2199
|
+
}
|
|
2200
|
+
const freq = frequencies(array);
|
|
2201
|
+
const result = [];
|
|
2202
|
+
for (const item of array) {
|
|
2203
|
+
if (freq.get(item) === count) {
|
|
2204
|
+
result.push(item);
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
return result;
|
|
2208
|
+
}
|
|
2209
|
+
/**
|
|
2210
|
+
* Filters array to keep only elements that pass predicate n consecutive times
|
|
2211
|
+
* @param array - Source array
|
|
2212
|
+
* @param n - Number of consecutive passes required
|
|
2213
|
+
* @param predicate - Predicate function
|
|
2214
|
+
* @returns Filtered array
|
|
2215
|
+
* @example
|
|
2216
|
+
* filterConsecutive([1, 2, 2, 3, 3, 3], 2, x => x > 1) // [2, 2, 3, 3, 3]
|
|
2217
|
+
*/
|
|
2218
|
+
export function filterConsecutive(array, n, predicate) {
|
|
2219
|
+
if (!Array.isArray(array) || n < 1) {
|
|
2220
|
+
return [];
|
|
2221
|
+
}
|
|
2222
|
+
const result = [];
|
|
2223
|
+
let consecutiveCount = 0;
|
|
2224
|
+
const buffer = [];
|
|
2225
|
+
for (const item of array) {
|
|
2226
|
+
if (predicate(item)) {
|
|
2227
|
+
consecutiveCount++;
|
|
2228
|
+
buffer.push(item);
|
|
2229
|
+
if (consecutiveCount >= n) {
|
|
2230
|
+
result.push(...buffer.splice(0, buffer.length - n + 1));
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
else {
|
|
2234
|
+
consecutiveCount = 0;
|
|
2235
|
+
buffer.length = 0;
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
if (consecutiveCount >= n) {
|
|
2239
|
+
result.push(...buffer);
|
|
2240
|
+
}
|
|
2241
|
+
return result;
|
|
2242
|
+
}
|
|
2243
|
+
/**
|
|
2244
|
+
* Maps each element using accumulated context from previous elements
|
|
2245
|
+
* @param array - Source array
|
|
2246
|
+
* @param fn - Mapping function that receives accumulated context
|
|
2247
|
+
* @param initialContext - Initial context value
|
|
2248
|
+
* @returns Array of mapped values
|
|
2249
|
+
* @example
|
|
2250
|
+
* mapWithContext([1, 2, 3], (x, ctx) => ({value: x + ctx, newCtx: ctx + x}), 0)
|
|
2251
|
+
* // [{value: 1, newCtx: 1}, {value: 3, newCtx: 3}, {value: 6, newCtx: 6}]
|
|
2252
|
+
*/
|
|
2253
|
+
export function mapWithContext(array, fn, initialContext) {
|
|
2254
|
+
if (!Array.isArray(array)) {
|
|
2255
|
+
return [];
|
|
2256
|
+
}
|
|
2257
|
+
const result = [];
|
|
2258
|
+
let context = initialContext;
|
|
2259
|
+
for (let i = 0; i < array.length; i++) {
|
|
2260
|
+
const { value, newContext } = fn(array[i], context, i);
|
|
2261
|
+
result.push(value);
|
|
2262
|
+
context = newContext;
|
|
2263
|
+
}
|
|
2264
|
+
return result;
|
|
2265
|
+
}
|
|
2266
|
+
/**
|
|
2267
|
+
* Groups array elements into fixed-size batches, with last batch potentially smaller
|
|
2268
|
+
* Similar to chunk but with better naming for batch processing scenarios
|
|
2269
|
+
* @param array - Source array
|
|
2270
|
+
* @param batchSize - Size of each batch
|
|
2271
|
+
* @returns Array of batches
|
|
2272
|
+
* @example
|
|
2273
|
+
* batch([1, 2, 3, 4, 5], 2) // [[1, 2], [3, 4], [5]]
|
|
2274
|
+
*/
|
|
2275
|
+
export function batch(array, batchSize) {
|
|
2276
|
+
return chunk(array, batchSize);
|
|
2277
|
+
}
|
|
2278
|
+
/**
|
|
2279
|
+
* Applies a function to pairs of elements from two arrays
|
|
2280
|
+
* @param array1 - First array
|
|
2281
|
+
* @param array2 - Second array
|
|
2282
|
+
* @param fn - Function to combine elements
|
|
2283
|
+
* @returns Array of combined results
|
|
2284
|
+
* @example
|
|
2285
|
+
* zipWith([1, 2, 3], [4, 5, 6], (a, b) => a + b) // [5, 7, 9]
|
|
2286
|
+
*/
|
|
2287
|
+
export function zipWith(array1, array2, fn) {
|
|
2288
|
+
if (!Array.isArray(array1) || !Array.isArray(array2)) {
|
|
2289
|
+
return [];
|
|
2290
|
+
}
|
|
2291
|
+
const length = Math.min(array1.length, array2.length);
|
|
2292
|
+
const result = [];
|
|
2293
|
+
for (let i = 0; i < length; i++) {
|
|
2294
|
+
result.push(fn(array1[i], array2[i]));
|
|
2295
|
+
}
|
|
2296
|
+
return result;
|
|
2297
|
+
}
|
|
2298
|
+
/**
|
|
2299
|
+
* Merges multiple arrays of objects by a key, with custom merge strategy
|
|
2300
|
+
* @param arrays - Arrays to merge
|
|
2301
|
+
* @param key - Key to merge by
|
|
2302
|
+
* @param mergeStrategy - How to merge: 'first', 'last', 'concat', or custom function
|
|
2303
|
+
* @returns Merged array
|
|
2304
|
+
* @example
|
|
2305
|
+
* mergeArraysByKey(
|
|
2306
|
+
* [[{id: 1, a: 1}], [{id: 1, b: 2}]],
|
|
2307
|
+
* 'id',
|
|
2308
|
+
* (existing, incoming) => ({...existing, ...incoming})
|
|
2309
|
+
* )
|
|
2310
|
+
*/
|
|
2311
|
+
export function mergeArraysByKey(arrays, key, mergeStrategy = 'last') {
|
|
2312
|
+
if (!Array.isArray(arrays) || arrays.length === 0) {
|
|
2313
|
+
return [];
|
|
2314
|
+
}
|
|
2315
|
+
const map = new Map();
|
|
2316
|
+
for (const array of arrays) {
|
|
2317
|
+
if (!Array.isArray(array))
|
|
2318
|
+
continue;
|
|
2319
|
+
for (const item of array) {
|
|
2320
|
+
const keyValue = get(item, key);
|
|
2321
|
+
if (mergeStrategy === 'first') {
|
|
2322
|
+
if (!map.has(keyValue)) {
|
|
2323
|
+
map.set(keyValue, item);
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
else if (mergeStrategy === 'last') {
|
|
2327
|
+
map.set(keyValue, item);
|
|
2328
|
+
}
|
|
2329
|
+
else if (mergeStrategy === 'concat') {
|
|
2330
|
+
const existing = map.get(keyValue);
|
|
2331
|
+
if (Array.isArray(existing)) {
|
|
2332
|
+
existing.push(item);
|
|
2333
|
+
}
|
|
2334
|
+
else if (existing) {
|
|
2335
|
+
map.set(keyValue, [existing, item]);
|
|
2336
|
+
}
|
|
2337
|
+
else {
|
|
2338
|
+
map.set(keyValue, item);
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
else if (typeof mergeStrategy === 'function') {
|
|
2342
|
+
const existing = map.get(keyValue);
|
|
2343
|
+
if (existing && !Array.isArray(existing)) {
|
|
2344
|
+
map.set(keyValue, mergeStrategy(existing, item));
|
|
2345
|
+
}
|
|
2346
|
+
else {
|
|
2347
|
+
map.set(keyValue, item);
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
const result = [];
|
|
2353
|
+
for (const value of map.values()) {
|
|
2354
|
+
if (Array.isArray(value) && mergeStrategy === 'concat') {
|
|
2355
|
+
result.push(...value);
|
|
2356
|
+
}
|
|
2357
|
+
else if (!Array.isArray(value)) {
|
|
2358
|
+
result.push(value);
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
return result;
|
|
2362
|
+
}
|
|
2363
|
+
/**
|
|
2364
|
+
* Creates a lookup/index object for fast access by key
|
|
2365
|
+
* @param array - Array of objects
|
|
2366
|
+
* @param key - Key to index by
|
|
2367
|
+
* @returns Object mapping key values to array elements
|
|
2368
|
+
* @example
|
|
2369
|
+
* indexBy([{id: 1, name: 'A'}, {id: 2, name: 'B'}], 'id')
|
|
2370
|
+
* // {1: {id: 1, name: 'A'}, 2: {id: 2, name: 'B'}}
|
|
2371
|
+
*/
|
|
2372
|
+
export function indexBy(array, key) {
|
|
2373
|
+
return toObject(array, (item) => String(get(item, key)));
|
|
2374
|
+
}
|
|
2375
|
+
/**
|
|
2376
|
+
* Performs binary search on sorted array
|
|
2377
|
+
* @param array - Sorted array
|
|
2378
|
+
* @param target - Value to search for
|
|
2379
|
+
* @param compareFn - Optional comparison function
|
|
2380
|
+
* @returns Index of target, or -1 if not found
|
|
2381
|
+
* @example
|
|
2382
|
+
* binarySearch([1, 2, 3, 4, 5], 3) // 2
|
|
2383
|
+
* binarySearch([1, 2, 4, 5], 3) // -1
|
|
2384
|
+
*/
|
|
2385
|
+
export function binarySearch(array, target, compareFn) {
|
|
2386
|
+
if (!Array.isArray(array) || array.length === 0) {
|
|
2387
|
+
return -1;
|
|
2388
|
+
}
|
|
2389
|
+
const compare = compareFn || ((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
2390
|
+
let left = 0;
|
|
2391
|
+
let right = array.length - 1;
|
|
2392
|
+
while (left <= right) {
|
|
2393
|
+
const mid = Math.floor((left + right) / 2);
|
|
2394
|
+
const comparison = compare(array[mid], target);
|
|
2395
|
+
if (comparison === 0) {
|
|
2396
|
+
return mid;
|
|
2397
|
+
}
|
|
2398
|
+
else if (comparison < 0) {
|
|
2399
|
+
left = mid + 1;
|
|
2400
|
+
}
|
|
2401
|
+
else {
|
|
2402
|
+
right = mid - 1;
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
return -1;
|
|
2406
|
+
}
|
|
2407
|
+
/**
|
|
2408
|
+
* Inserts value into sorted array maintaining sort order
|
|
2409
|
+
* @param array - Sorted array
|
|
2410
|
+
* @param value - Value to insert
|
|
2411
|
+
* @param compareFn - Optional comparison function
|
|
2412
|
+
* @returns New sorted array with value inserted
|
|
2413
|
+
* @example
|
|
2414
|
+
* insertSorted([1, 3, 5, 7], 4) // [1, 3, 4, 5, 7]
|
|
2415
|
+
*/
|
|
2416
|
+
export function insertSorted(array, value, compareFn) {
|
|
2417
|
+
if (!Array.isArray(array)) {
|
|
2418
|
+
return [value];
|
|
2419
|
+
}
|
|
2420
|
+
const compare = compareFn || ((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
2421
|
+
const result = [...array];
|
|
2422
|
+
let insertIndex = 0;
|
|
2423
|
+
while (insertIndex < result.length &&
|
|
2424
|
+
compare(result[insertIndex], value) < 0) {
|
|
2425
|
+
insertIndex++;
|
|
2426
|
+
}
|
|
2427
|
+
result.splice(insertIndex, 0, value);
|
|
2428
|
+
return result;
|
|
2429
|
+
}
|