@oscarpalmer/atoms 0.173.3 → 0.174.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/array/sort.d.mts +38 -1
- package/dist/array/sort.mjs +51 -17
- package/dist/index.d.mts +38 -1
- package/dist/index.mjs +71 -37
- package/dist/internal/value/compare.mjs +20 -20
- package/package.json +2 -2
- package/src/array/sort.ts +153 -37
- package/src/internal/value/compare.ts +31 -29
package/dist/array/sort.d.mts
CHANGED
|
@@ -65,7 +65,20 @@ type ComparisonSorter<Item> = (first: Item, second: Item) => number;
|
|
|
65
65
|
* Direction to sort by
|
|
66
66
|
*/
|
|
67
67
|
type SortDirection = 'ascending' | 'descending';
|
|
68
|
-
type Sorter<Item> =
|
|
68
|
+
type Sorter<Item> = {
|
|
69
|
+
/**
|
|
70
|
+
* Sort an array of items
|
|
71
|
+
* @param array Array to sort
|
|
72
|
+
* @returns Sorted array
|
|
73
|
+
*/
|
|
74
|
+
(array: Item[]): Item[];
|
|
75
|
+
/**
|
|
76
|
+
* Is the array sorted?
|
|
77
|
+
* @param array Array to check
|
|
78
|
+
* @returns `true` if sorted, otherwise `false`
|
|
79
|
+
*/
|
|
80
|
+
is(array: Item[]): boolean;
|
|
81
|
+
};
|
|
69
82
|
/**
|
|
70
83
|
* Initialize a sort handler with sorters _(and an optional default direction)_
|
|
71
84
|
* @param sorters Sorters to use for sorting
|
|
@@ -86,6 +99,29 @@ declare function initializeSort<Item>(sorter: ArraySorter<Item>, descending?: bo
|
|
|
86
99
|
* @returns Sort handler
|
|
87
100
|
*/
|
|
88
101
|
declare function initializeSort<Item>(descending?: boolean): Sorter<Item>;
|
|
102
|
+
/**
|
|
103
|
+
* Is the array sorted according to the sorters _(and the optional default direction)_?
|
|
104
|
+
* @param array Array to check
|
|
105
|
+
* @param sorters Sorters to use
|
|
106
|
+
* @param descending Sorted in descending order? _(defaults to `false`; overridden by individual sorters)_
|
|
107
|
+
* @returns `true` if sorted, otherwise `false`
|
|
108
|
+
*/
|
|
109
|
+
declare function isSorted<Item>(array: Item[], sorters: Array<ArraySorter<Item>>, descending?: boolean): boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Is the array sorted according to the sorter _(and the optional default direction)_?
|
|
112
|
+
* @param array Array to check
|
|
113
|
+
* @param sorter Sorter to use
|
|
114
|
+
* @param descending Sorted in descending order? _(defaults to `false`; overridden by individual sorters)_
|
|
115
|
+
* @returns `true` if sorted, otherwise `false`
|
|
116
|
+
*/
|
|
117
|
+
declare function isSorted<Item>(array: Item[], sorter: ArraySorter<Item>, descending?: boolean): boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Is the array sorted?
|
|
120
|
+
* @param array Array to check
|
|
121
|
+
* @param descending Sorted in descending order? _(defaults to `false)_
|
|
122
|
+
* @returns `true` if sorted, otherwise `false`
|
|
123
|
+
*/
|
|
124
|
+
declare function isSorted<Item>(array: Item[], descending?: boolean): boolean;
|
|
89
125
|
/**
|
|
90
126
|
* Sort an array of items, using multiple sorters to sort by specific values
|
|
91
127
|
* @param array Array to sort
|
|
@@ -111,6 +147,7 @@ declare function sort<Item>(array: Item[], sorter: ArraySorter<Item>, descending
|
|
|
111
147
|
declare function sort<Item>(array: Item[], descending?: boolean): Item[];
|
|
112
148
|
declare namespace sort {
|
|
113
149
|
var initialize: typeof initializeSort;
|
|
150
|
+
var is: typeof isSorted;
|
|
114
151
|
}
|
|
115
152
|
declare const SORT_DIRECTION_ASCENDING: SortDirection;
|
|
116
153
|
declare const SORT_DIRECTION_DESCENDING: SortDirection;
|
package/dist/array/sort.mjs
CHANGED
|
@@ -9,6 +9,15 @@ function getComparisonSorter(callback, modifier) {
|
|
|
9
9
|
identifier: String(callback)
|
|
10
10
|
};
|
|
11
11
|
}
|
|
12
|
+
function getComparisonValue(first, second, sorters, length) {
|
|
13
|
+
for (let index = 0; index < length; index += 1) {
|
|
14
|
+
const sorter = sorters[index];
|
|
15
|
+
const values = [sorter.get ? sorter.value(first) : first, sorter.get ? sorter.value(second) : second];
|
|
16
|
+
const comparison = (sorter.compare?.complex?.(first, values[0], second, values[1]) ?? sorter.compare?.simple?.(values[0], values[1]) ?? compare(values[0], values[1])) * sorter.modifier;
|
|
17
|
+
if (comparison !== 0) return comparison;
|
|
18
|
+
}
|
|
19
|
+
return 0;
|
|
20
|
+
}
|
|
12
21
|
function getModifier(first, second) {
|
|
13
22
|
return modifiers[first === true || second === true ? SORT_DIRECTION_DESCENDING : SORT_DIRECTION_ASCENDING];
|
|
14
23
|
}
|
|
@@ -39,6 +48,11 @@ function getSorters(value, modifier) {
|
|
|
39
48
|
const sorter = getSorter(item, modifier);
|
|
40
49
|
if (sorter != null) sorters.push(sorter);
|
|
41
50
|
}
|
|
51
|
+
if (sorters.length === 0) return [{
|
|
52
|
+
modifier,
|
|
53
|
+
get: false,
|
|
54
|
+
identifier: "default"
|
|
55
|
+
}];
|
|
42
56
|
return sorters.filter((value, index, array) => array.findIndex((next) => next.identifier === value.identifier) === index);
|
|
43
57
|
}
|
|
44
58
|
function getValueSorter(value, modifier) {
|
|
@@ -50,30 +64,50 @@ function getValueSorter(value, modifier) {
|
|
|
50
64
|
};
|
|
51
65
|
}
|
|
52
66
|
function initializeSort(first, second) {
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
|
|
67
|
+
const sorters = getSorters(first, getModifier(first, second));
|
|
68
|
+
const sorter = (array) => sortArray(array, sorters);
|
|
69
|
+
sorter.is = (array) => isSortedArray(array, sorters);
|
|
70
|
+
return sorter;
|
|
71
|
+
}
|
|
72
|
+
function isSorted(array, first, second) {
|
|
73
|
+
return isSortedArray(array, getSorters(first, getModifier(first, second)));
|
|
74
|
+
}
|
|
75
|
+
function isSortedArray(array, sorters) {
|
|
76
|
+
if (!Array.isArray(array)) return false;
|
|
77
|
+
const { length } = array;
|
|
78
|
+
if (length < 2) return true;
|
|
79
|
+
const sortersLength = sorters.length;
|
|
80
|
+
let offset = 0;
|
|
81
|
+
if (length >= ARRAY_THRESHOLD) {
|
|
82
|
+
offset = Math.round(length / ARRAY_PEEK_PERCENTAGE);
|
|
83
|
+
offset = offset > ARRAY_THRESHOLD ? ARRAY_THRESHOLD : offset;
|
|
84
|
+
for (let index = 0; index < offset; index += 1) {
|
|
85
|
+
const [firstItem, firstOffset] = [array[index], array[index + 1]];
|
|
86
|
+
const [secondItem, secondOffset] = [array[length - index - 2], array[length - index - 1]];
|
|
87
|
+
const [firstComparison, secondComparison] = [getComparisonValue(firstItem, firstOffset, sorters, sortersLength), getComparisonValue(secondItem, secondOffset, sorters, sortersLength)];
|
|
88
|
+
if (firstComparison > 0 || secondComparison > 0) return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const end = length - offset - 1;
|
|
92
|
+
for (let index = offset; index < end; index += 1) {
|
|
93
|
+
const first = array[index];
|
|
94
|
+
const second = array[index + 1];
|
|
95
|
+
if (getComparisonValue(first, second, sorters, sortersLength) > 0) return false;
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
56
98
|
}
|
|
57
99
|
function sort(array, first, second) {
|
|
58
|
-
|
|
59
|
-
return work(array, getSorters(first, modifier), modifier);
|
|
100
|
+
return sortArray(array, getSorters(first, getModifier(first, second)));
|
|
60
101
|
}
|
|
61
|
-
function
|
|
102
|
+
function sortArray(array, sorters) {
|
|
62
103
|
if (!Array.isArray(array)) return [];
|
|
63
|
-
if (array.length < 2) return array;
|
|
64
104
|
const { length } = sorters;
|
|
65
|
-
|
|
66
|
-
return array.sort((first, second) => {
|
|
67
|
-
for (let index = 0; index < length; index += 1) {
|
|
68
|
-
const sorter = sorters[index];
|
|
69
|
-
const values = [sorter.get ? sorter.value(first) : first, sorter.get ? sorter.value(second) : second];
|
|
70
|
-
const comparison = (sorter.compare?.complex?.(first, values[0], second, values[1]) ?? sorter.compare?.simple?.(values[0], values[1]) ?? compare(values[0], values[1])) * sorter.modifier;
|
|
71
|
-
if (comparison !== 0) return comparison;
|
|
72
|
-
}
|
|
73
|
-
return 0;
|
|
74
|
-
});
|
|
105
|
+
return array.length > 1 ? array.sort((first, second) => getComparisonValue(first, second, sorters, length)) : array;
|
|
75
106
|
}
|
|
76
107
|
sort.initialize = initializeSort;
|
|
108
|
+
sort.is = isSorted;
|
|
109
|
+
const ARRAY_PEEK_PERCENTAGE = 10;
|
|
110
|
+
const ARRAY_THRESHOLD = 100;
|
|
77
111
|
const SORT_DIRECTION_ASCENDING = "ascending";
|
|
78
112
|
const SORT_DIRECTION_DESCENDING = "descending";
|
|
79
113
|
const modifiers = {
|
package/dist/index.d.mts
CHANGED
|
@@ -1166,7 +1166,20 @@ type ComparisonSorter<Item> = (first: Item, second: Item) => number;
|
|
|
1166
1166
|
* Direction to sort by
|
|
1167
1167
|
*/
|
|
1168
1168
|
type SortDirection = 'ascending' | 'descending';
|
|
1169
|
-
type Sorter<Item> =
|
|
1169
|
+
type Sorter<Item> = {
|
|
1170
|
+
/**
|
|
1171
|
+
* Sort an array of items
|
|
1172
|
+
* @param array Array to sort
|
|
1173
|
+
* @returns Sorted array
|
|
1174
|
+
*/
|
|
1175
|
+
(array: Item[]): Item[];
|
|
1176
|
+
/**
|
|
1177
|
+
* Is the array sorted?
|
|
1178
|
+
* @param array Array to check
|
|
1179
|
+
* @returns `true` if sorted, otherwise `false`
|
|
1180
|
+
*/
|
|
1181
|
+
is(array: Item[]): boolean;
|
|
1182
|
+
};
|
|
1170
1183
|
/**
|
|
1171
1184
|
* Initialize a sort handler with sorters _(and an optional default direction)_
|
|
1172
1185
|
* @param sorters Sorters to use for sorting
|
|
@@ -1187,6 +1200,29 @@ declare function initializeSort<Item>(sorter: ArraySorter<Item>, descending?: bo
|
|
|
1187
1200
|
* @returns Sort handler
|
|
1188
1201
|
*/
|
|
1189
1202
|
declare function initializeSort<Item>(descending?: boolean): Sorter<Item>;
|
|
1203
|
+
/**
|
|
1204
|
+
* Is the array sorted according to the sorters _(and the optional default direction)_?
|
|
1205
|
+
* @param array Array to check
|
|
1206
|
+
* @param sorters Sorters to use
|
|
1207
|
+
* @param descending Sorted in descending order? _(defaults to `false`; overridden by individual sorters)_
|
|
1208
|
+
* @returns `true` if sorted, otherwise `false`
|
|
1209
|
+
*/
|
|
1210
|
+
declare function isSorted<Item>(array: Item[], sorters: Array<ArraySorter<Item>>, descending?: boolean): boolean;
|
|
1211
|
+
/**
|
|
1212
|
+
* Is the array sorted according to the sorter _(and the optional default direction)_?
|
|
1213
|
+
* @param array Array to check
|
|
1214
|
+
* @param sorter Sorter to use
|
|
1215
|
+
* @param descending Sorted in descending order? _(defaults to `false`; overridden by individual sorters)_
|
|
1216
|
+
* @returns `true` if sorted, otherwise `false`
|
|
1217
|
+
*/
|
|
1218
|
+
declare function isSorted<Item>(array: Item[], sorter: ArraySorter<Item>, descending?: boolean): boolean;
|
|
1219
|
+
/**
|
|
1220
|
+
* Is the array sorted?
|
|
1221
|
+
* @param array Array to check
|
|
1222
|
+
* @param descending Sorted in descending order? _(defaults to `false)_
|
|
1223
|
+
* @returns `true` if sorted, otherwise `false`
|
|
1224
|
+
*/
|
|
1225
|
+
declare function isSorted<Item>(array: Item[], descending?: boolean): boolean;
|
|
1190
1226
|
/**
|
|
1191
1227
|
* Sort an array of items, using multiple sorters to sort by specific values
|
|
1192
1228
|
* @param array Array to sort
|
|
@@ -1212,6 +1248,7 @@ declare function sort<Item>(array: Item[], sorter: ArraySorter<Item>, descending
|
|
|
1212
1248
|
declare function sort<Item>(array: Item[], descending?: boolean): Item[];
|
|
1213
1249
|
declare namespace sort {
|
|
1214
1250
|
var initialize: typeof initializeSort;
|
|
1251
|
+
var is: typeof isSorted;
|
|
1215
1252
|
}
|
|
1216
1253
|
declare const SORT_DIRECTION_ASCENDING: SortDirection;
|
|
1217
1254
|
declare const SORT_DIRECTION_DESCENDING: SortDirection;
|
package/dist/index.mjs
CHANGED
|
@@ -869,26 +869,11 @@ compare.handlers = getCompareHandlers(compare, {
|
|
|
869
869
|
});
|
|
870
870
|
compare.register = registerComparator;
|
|
871
871
|
compare.unregister = unregisterComparator;
|
|
872
|
-
/**
|
|
873
|
-
* Register a custom comparison handler for a class
|
|
874
|
-
* @param constructor Class constructor
|
|
875
|
-
* @param handler Method name or comparison function _(defaults to `compare`)_
|
|
876
|
-
*/
|
|
877
|
-
function registerComparator(constructor, handler) {
|
|
878
|
-
compare.handlers.register(constructor, handler);
|
|
879
|
-
}
|
|
880
|
-
/**
|
|
881
|
-
* Unregister a custom comparison handler for a class
|
|
882
|
-
* @param constructor Class constructor
|
|
883
|
-
*/
|
|
884
|
-
function unregisterComparator(constructor) {
|
|
885
|
-
compare.handlers.unregister(constructor);
|
|
886
|
-
}
|
|
887
872
|
function compareNumbers(first, second) {
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
if (
|
|
891
|
-
return
|
|
873
|
+
if (Object.is(first, second)) return 0;
|
|
874
|
+
if (Number.isNaN(first)) return -1;
|
|
875
|
+
if (Number.isNaN(second)) return 1;
|
|
876
|
+
return first > second ? 1 : -1;
|
|
892
877
|
}
|
|
893
878
|
function compareSymbols(first, second) {
|
|
894
879
|
return getString(first.description ?? first).localeCompare(getString(second.description ?? second));
|
|
@@ -903,9 +888,24 @@ function getComparisonParts(value) {
|
|
|
903
888
|
if (Array.isArray(value)) return value;
|
|
904
889
|
return typeof value === "object" ? [value] : words(getString(value));
|
|
905
890
|
}
|
|
891
|
+
/**
|
|
892
|
+
* Register a custom comparison handler for a class
|
|
893
|
+
* @param constructor Class constructor
|
|
894
|
+
* @param handler Method name or comparison function _(defaults to `compare`)_
|
|
895
|
+
*/
|
|
896
|
+
function registerComparator(constructor, handler) {
|
|
897
|
+
compare.handlers.register(constructor, handler);
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Unregister a custom comparison handler for a class
|
|
901
|
+
* @param constructor Class constructor
|
|
902
|
+
*/
|
|
903
|
+
function unregisterComparator(constructor) {
|
|
904
|
+
compare.handlers.unregister(constructor);
|
|
905
|
+
}
|
|
906
906
|
const comparators = {
|
|
907
907
|
bigint: compareNumbers,
|
|
908
|
-
boolean: compareNumbers,
|
|
908
|
+
boolean: (first, second) => compareNumbers(first ? 1 : 0, second ? 1 : 0),
|
|
909
909
|
number: compareNumbers,
|
|
910
910
|
symbol: compareSymbols
|
|
911
911
|
};
|
|
@@ -919,6 +919,15 @@ function getComparisonSorter(callback, modifier) {
|
|
|
919
919
|
identifier: String(callback)
|
|
920
920
|
};
|
|
921
921
|
}
|
|
922
|
+
function getComparisonValue(first, second, sorters, length) {
|
|
923
|
+
for (let index = 0; index < length; index += 1) {
|
|
924
|
+
const sorter = sorters[index];
|
|
925
|
+
const values = [sorter.get ? sorter.value(first) : first, sorter.get ? sorter.value(second) : second];
|
|
926
|
+
const comparison = (sorter.compare?.complex?.(first, values[0], second, values[1]) ?? sorter.compare?.simple?.(values[0], values[1]) ?? compare(values[0], values[1])) * sorter.modifier;
|
|
927
|
+
if (comparison !== 0) return comparison;
|
|
928
|
+
}
|
|
929
|
+
return 0;
|
|
930
|
+
}
|
|
922
931
|
function getModifier(first, second) {
|
|
923
932
|
return modifiers[first === true || second === true ? SORT_DIRECTION_DESCENDING : SORT_DIRECTION_ASCENDING];
|
|
924
933
|
}
|
|
@@ -949,6 +958,11 @@ function getSorters(value, modifier) {
|
|
|
949
958
|
const sorter = getSorter(item, modifier);
|
|
950
959
|
if (sorter != null) sorters.push(sorter);
|
|
951
960
|
}
|
|
961
|
+
if (sorters.length === 0) return [{
|
|
962
|
+
modifier,
|
|
963
|
+
get: false,
|
|
964
|
+
identifier: "default"
|
|
965
|
+
}];
|
|
952
966
|
return sorters.filter((value, index, array) => array.findIndex((next) => next.identifier === value.identifier) === index);
|
|
953
967
|
}
|
|
954
968
|
function getValueSorter(value, modifier) {
|
|
@@ -960,30 +974,50 @@ function getValueSorter(value, modifier) {
|
|
|
960
974
|
};
|
|
961
975
|
}
|
|
962
976
|
function initializeSort(first, second) {
|
|
963
|
-
const
|
|
964
|
-
const
|
|
965
|
-
|
|
977
|
+
const sorters = getSorters(first, getModifier(first, second));
|
|
978
|
+
const sorter = (array) => sortArray(array, sorters);
|
|
979
|
+
sorter.is = (array) => isSortedArray(array, sorters);
|
|
980
|
+
return sorter;
|
|
981
|
+
}
|
|
982
|
+
function isSorted(array, first, second) {
|
|
983
|
+
return isSortedArray(array, getSorters(first, getModifier(first, second)));
|
|
984
|
+
}
|
|
985
|
+
function isSortedArray(array, sorters) {
|
|
986
|
+
if (!Array.isArray(array)) return false;
|
|
987
|
+
const { length } = array;
|
|
988
|
+
if (length < 2) return true;
|
|
989
|
+
const sortersLength = sorters.length;
|
|
990
|
+
let offset = 0;
|
|
991
|
+
if (length >= ARRAY_THRESHOLD$1) {
|
|
992
|
+
offset = Math.round(length / ARRAY_PEEK_PERCENTAGE$1);
|
|
993
|
+
offset = offset > ARRAY_THRESHOLD$1 ? ARRAY_THRESHOLD$1 : offset;
|
|
994
|
+
for (let index = 0; index < offset; index += 1) {
|
|
995
|
+
const [firstItem, firstOffset] = [array[index], array[index + 1]];
|
|
996
|
+
const [secondItem, secondOffset] = [array[length - index - 2], array[length - index - 1]];
|
|
997
|
+
const [firstComparison, secondComparison] = [getComparisonValue(firstItem, firstOffset, sorters, sortersLength), getComparisonValue(secondItem, secondOffset, sorters, sortersLength)];
|
|
998
|
+
if (firstComparison > 0 || secondComparison > 0) return false;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
const end = length - offset - 1;
|
|
1002
|
+
for (let index = offset; index < end; index += 1) {
|
|
1003
|
+
const first = array[index];
|
|
1004
|
+
const second = array[index + 1];
|
|
1005
|
+
if (getComparisonValue(first, second, sorters, sortersLength) > 0) return false;
|
|
1006
|
+
}
|
|
1007
|
+
return true;
|
|
966
1008
|
}
|
|
967
1009
|
function sort(array, first, second) {
|
|
968
|
-
|
|
969
|
-
return work$1(array, getSorters(first, modifier), modifier);
|
|
1010
|
+
return sortArray(array, getSorters(first, getModifier(first, second)));
|
|
970
1011
|
}
|
|
971
|
-
function
|
|
1012
|
+
function sortArray(array, sorters) {
|
|
972
1013
|
if (!Array.isArray(array)) return [];
|
|
973
|
-
if (array.length < 2) return array;
|
|
974
1014
|
const { length } = sorters;
|
|
975
|
-
|
|
976
|
-
return array.sort((first, second) => {
|
|
977
|
-
for (let index = 0; index < length; index += 1) {
|
|
978
|
-
const sorter = sorters[index];
|
|
979
|
-
const values = [sorter.get ? sorter.value(first) : first, sorter.get ? sorter.value(second) : second];
|
|
980
|
-
const comparison = (sorter.compare?.complex?.(first, values[0], second, values[1]) ?? sorter.compare?.simple?.(values[0], values[1]) ?? compare(values[0], values[1])) * sorter.modifier;
|
|
981
|
-
if (comparison !== 0) return comparison;
|
|
982
|
-
}
|
|
983
|
-
return 0;
|
|
984
|
-
});
|
|
1015
|
+
return array.length > 1 ? array.sort((first, second) => getComparisonValue(first, second, sorters, length)) : array;
|
|
985
1016
|
}
|
|
986
1017
|
sort.initialize = initializeSort;
|
|
1018
|
+
sort.is = isSorted;
|
|
1019
|
+
const ARRAY_PEEK_PERCENTAGE$1 = 10;
|
|
1020
|
+
const ARRAY_THRESHOLD$1 = 100;
|
|
987
1021
|
const SORT_DIRECTION_ASCENDING = "ascending";
|
|
988
1022
|
const SORT_DIRECTION_DESCENDING = "descending";
|
|
989
1023
|
const modifiers = {
|
|
@@ -39,26 +39,11 @@ compare.handlers = getCompareHandlers(compare, {
|
|
|
39
39
|
});
|
|
40
40
|
compare.register = registerComparator;
|
|
41
41
|
compare.unregister = unregisterComparator;
|
|
42
|
-
/**
|
|
43
|
-
* Register a custom comparison handler for a class
|
|
44
|
-
* @param constructor Class constructor
|
|
45
|
-
* @param handler Method name or comparison function _(defaults to `compare`)_
|
|
46
|
-
*/
|
|
47
|
-
function registerComparator(constructor, handler) {
|
|
48
|
-
compare.handlers.register(constructor, handler);
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Unregister a custom comparison handler for a class
|
|
52
|
-
* @param constructor Class constructor
|
|
53
|
-
*/
|
|
54
|
-
function unregisterComparator(constructor) {
|
|
55
|
-
compare.handlers.unregister(constructor);
|
|
56
|
-
}
|
|
57
42
|
function compareNumbers(first, second) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (
|
|
61
|
-
return
|
|
43
|
+
if (Object.is(first, second)) return 0;
|
|
44
|
+
if (Number.isNaN(first)) return -1;
|
|
45
|
+
if (Number.isNaN(second)) return 1;
|
|
46
|
+
return first > second ? 1 : -1;
|
|
62
47
|
}
|
|
63
48
|
function compareSymbols(first, second) {
|
|
64
49
|
return getString(first.description ?? first).localeCompare(getString(second.description ?? second));
|
|
@@ -73,9 +58,24 @@ function getComparisonParts(value) {
|
|
|
73
58
|
if (Array.isArray(value)) return value;
|
|
74
59
|
return typeof value === "object" ? [value] : words(getString(value));
|
|
75
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Register a custom comparison handler for a class
|
|
63
|
+
* @param constructor Class constructor
|
|
64
|
+
* @param handler Method name or comparison function _(defaults to `compare`)_
|
|
65
|
+
*/
|
|
66
|
+
function registerComparator(constructor, handler) {
|
|
67
|
+
compare.handlers.register(constructor, handler);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Unregister a custom comparison handler for a class
|
|
71
|
+
* @param constructor Class constructor
|
|
72
|
+
*/
|
|
73
|
+
function unregisterComparator(constructor) {
|
|
74
|
+
compare.handlers.unregister(constructor);
|
|
75
|
+
}
|
|
76
76
|
const comparators = {
|
|
77
77
|
bigint: compareNumbers,
|
|
78
|
-
boolean: compareNumbers,
|
|
78
|
+
boolean: (first, second) => compareNumbers(first ? 1 : 0, second ? 1 : 0),
|
|
79
79
|
number: compareNumbers,
|
|
80
80
|
symbol: compareSymbols
|
|
81
81
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oscarpalmer/atoms",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.174.0",
|
|
4
4
|
"description": "Atomic utilities for making your JavaScript better.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"helper",
|
|
@@ -230,7 +230,7 @@
|
|
|
230
230
|
},
|
|
231
231
|
"devDependencies": {
|
|
232
232
|
"@oxlint/plugins": "^1.59",
|
|
233
|
-
"@types/node": "^25.
|
|
233
|
+
"@types/node": "^25.6",
|
|
234
234
|
"@vitest/coverage-istanbul": "^4.1",
|
|
235
235
|
"eslint": "^10.2",
|
|
236
236
|
"jsdom": "^29.0",
|
package/src/array/sort.ts
CHANGED
|
@@ -104,7 +104,21 @@ type InternalSorterCompare = {
|
|
|
104
104
|
*/
|
|
105
105
|
export type SortDirection = 'ascending' | 'descending';
|
|
106
106
|
|
|
107
|
-
export type Sorter<Item> =
|
|
107
|
+
export type Sorter<Item> = {
|
|
108
|
+
/**
|
|
109
|
+
* Sort an array of items
|
|
110
|
+
* @param array Array to sort
|
|
111
|
+
* @returns Sorted array
|
|
112
|
+
*/
|
|
113
|
+
(array: Item[]): Item[];
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Is the array sorted?
|
|
117
|
+
* @param array Array to check
|
|
118
|
+
* @returns `true` if sorted, otherwise `false`
|
|
119
|
+
*/
|
|
120
|
+
is(array: Item[]): boolean;
|
|
121
|
+
};
|
|
108
122
|
|
|
109
123
|
// #endregion
|
|
110
124
|
|
|
@@ -121,6 +135,33 @@ function getComparisonSorter(callback: Function, modifier: number): InternalSort
|
|
|
121
135
|
};
|
|
122
136
|
}
|
|
123
137
|
|
|
138
|
+
function getComparisonValue(
|
|
139
|
+
first: unknown,
|
|
140
|
+
second: unknown,
|
|
141
|
+
sorters: InternalSorter[],
|
|
142
|
+
length: number,
|
|
143
|
+
): number {
|
|
144
|
+
for (let index = 0; index < length; index += 1) {
|
|
145
|
+
const sorter = sorters[index];
|
|
146
|
+
|
|
147
|
+
const values = [
|
|
148
|
+
sorter.get ? sorter.value!(first as PlainObject) : first,
|
|
149
|
+
sorter.get ? sorter.value!(second as PlainObject) : second,
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
const comparison =
|
|
153
|
+
(sorter.compare?.complex?.(first, values[0], second, values[1]) ??
|
|
154
|
+
sorter.compare?.simple?.(values[0], values[1]) ??
|
|
155
|
+
compare(values[0], values[1])) * sorter.modifier;
|
|
156
|
+
|
|
157
|
+
if (comparison !== 0) {
|
|
158
|
+
return comparison;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
164
|
+
|
|
124
165
|
function getModifier(first: unknown, second: unknown): number {
|
|
125
166
|
const direction =
|
|
126
167
|
first === true || second === true ? SORT_DIRECTION_DESCENDING : SORT_DIRECTION_ASCENDING;
|
|
@@ -184,6 +225,16 @@ function getSorters(value: unknown, modifier: number): InternalSorter[] {
|
|
|
184
225
|
}
|
|
185
226
|
}
|
|
186
227
|
|
|
228
|
+
if (sorters.length === 0) {
|
|
229
|
+
return [
|
|
230
|
+
{
|
|
231
|
+
modifier,
|
|
232
|
+
get: false,
|
|
233
|
+
identifier: 'default',
|
|
234
|
+
},
|
|
235
|
+
];
|
|
236
|
+
}
|
|
237
|
+
|
|
187
238
|
return sorters.filter(
|
|
188
239
|
(value, index, array) =>
|
|
189
240
|
array.findIndex(next => next.identifier === value.identifier) === index,
|
|
@@ -226,10 +277,97 @@ function initializeSort<Item>(sorter: ArraySorter<Item>, descending?: boolean):
|
|
|
226
277
|
function initializeSort<Item>(descending?: boolean): Sorter<Item>;
|
|
227
278
|
|
|
228
279
|
function initializeSort(first?: unknown, second?: unknown): Sorter<unknown> {
|
|
229
|
-
const
|
|
230
|
-
|
|
280
|
+
const sorters = getSorters(first, getModifier(first, second));
|
|
281
|
+
|
|
282
|
+
const sorter = (array: unknown[]) => sortArray(array, sorters);
|
|
283
|
+
|
|
284
|
+
sorter.is = (array: unknown[]) => isSortedArray(array, sorters);
|
|
231
285
|
|
|
232
|
-
return
|
|
286
|
+
return sorter as unknown as Sorter<unknown>;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Is the array sorted according to the sorters _(and the optional default direction)_?
|
|
291
|
+
* @param array Array to check
|
|
292
|
+
* @param sorters Sorters to use
|
|
293
|
+
* @param descending Sorted in descending order? _(defaults to `false`; overridden by individual sorters)_
|
|
294
|
+
* @returns `true` if sorted, otherwise `false`
|
|
295
|
+
*/
|
|
296
|
+
function isSorted<Item>(
|
|
297
|
+
array: Item[],
|
|
298
|
+
sorters: Array<ArraySorter<Item>>,
|
|
299
|
+
descending?: boolean,
|
|
300
|
+
): boolean;
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Is the array sorted according to the sorter _(and the optional default direction)_?
|
|
304
|
+
* @param array Array to check
|
|
305
|
+
* @param sorter Sorter to use
|
|
306
|
+
* @param descending Sorted in descending order? _(defaults to `false`; overridden by individual sorters)_
|
|
307
|
+
* @returns `true` if sorted, otherwise `false`
|
|
308
|
+
*/
|
|
309
|
+
function isSorted<Item>(array: Item[], sorter: ArraySorter<Item>, descending?: boolean): boolean;
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Is the array sorted?
|
|
313
|
+
* @param array Array to check
|
|
314
|
+
* @param descending Sorted in descending order? _(defaults to `false)_
|
|
315
|
+
* @returns `true` if sorted, otherwise `false`
|
|
316
|
+
*/
|
|
317
|
+
function isSorted<Item>(array: Item[], descending?: boolean): boolean;
|
|
318
|
+
|
|
319
|
+
function isSorted(array: unknown[], first?: unknown, second?: unknown): boolean {
|
|
320
|
+
return isSortedArray(array, getSorters(first, getModifier(first, second)));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function isSortedArray(array: unknown[], sorters: InternalSorter[]): boolean {
|
|
324
|
+
if (!Array.isArray(array)) {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const {length} = array;
|
|
329
|
+
|
|
330
|
+
if (length < 2) {
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const sortersLength = sorters.length;
|
|
335
|
+
|
|
336
|
+
let offset = 0;
|
|
337
|
+
|
|
338
|
+
if (length >= ARRAY_THRESHOLD) {
|
|
339
|
+
offset = Math.round(length / ARRAY_PEEK_PERCENTAGE);
|
|
340
|
+
offset = offset > ARRAY_THRESHOLD ? ARRAY_THRESHOLD : offset;
|
|
341
|
+
|
|
342
|
+
for (let index = 0; index < offset; index += 1) {
|
|
343
|
+
const [firstItem, firstOffset] = [array[index], array[index + 1]];
|
|
344
|
+
const [secondItem, secondOffset] = [array[length - index - 2], array[length - index - 1]];
|
|
345
|
+
|
|
346
|
+
const [firstComparison, secondComparison] = [
|
|
347
|
+
getComparisonValue(firstItem, firstOffset, sorters, sortersLength),
|
|
348
|
+
getComparisonValue(secondItem, secondOffset, sorters, sortersLength),
|
|
349
|
+
];
|
|
350
|
+
|
|
351
|
+
if (firstComparison > 0 || secondComparison > 0) {
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const end = length - offset - 1;
|
|
358
|
+
|
|
359
|
+
for (let index = offset; index < end; index += 1) {
|
|
360
|
+
const first = array[index];
|
|
361
|
+
const second = array[index + 1];
|
|
362
|
+
|
|
363
|
+
const comparison = getComparisonValue(first, second, sorters, sortersLength);
|
|
364
|
+
|
|
365
|
+
if (comparison > 0) {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return true;
|
|
233
371
|
}
|
|
234
372
|
|
|
235
373
|
/**
|
|
@@ -263,55 +401,33 @@ export function sort<Item>(array: Item[], sorter: ArraySorter<Item>, descending?
|
|
|
263
401
|
export function sort<Item>(array: Item[], descending?: boolean): Item[];
|
|
264
402
|
|
|
265
403
|
export function sort(array: unknown[], first?: unknown, second?: unknown): unknown[] {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
return work(array, getSorters(first, modifier), modifier);
|
|
404
|
+
return sortArray(array, getSorters(first, getModifier(first, second)));
|
|
269
405
|
}
|
|
270
406
|
|
|
271
|
-
function
|
|
407
|
+
function sortArray(array: unknown[], sorters: InternalSorter[]): unknown[] {
|
|
272
408
|
if (!Array.isArray(array)) {
|
|
273
409
|
return [];
|
|
274
410
|
}
|
|
275
411
|
|
|
276
|
-
if (array.length < 2) {
|
|
277
|
-
return array;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
412
|
const {length} = sorters;
|
|
281
413
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
return array.sort((first, second) => {
|
|
287
|
-
for (let index = 0; index < length; index += 1) {
|
|
288
|
-
const sorter = sorters[index];
|
|
289
|
-
|
|
290
|
-
const values = [
|
|
291
|
-
sorter.get ? sorter.value!(first as PlainObject) : first,
|
|
292
|
-
sorter.get ? sorter.value!(second as PlainObject) : second,
|
|
293
|
-
];
|
|
294
|
-
|
|
295
|
-
const comparison =
|
|
296
|
-
(sorter.compare?.complex?.(first, values[0], second, values[1]) ??
|
|
297
|
-
sorter.compare?.simple?.(values[0], values[1]) ??
|
|
298
|
-
compare(values[0], values[1])) * sorter.modifier;
|
|
299
|
-
|
|
300
|
-
if (comparison !== 0) {
|
|
301
|
-
return comparison;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
return 0;
|
|
306
|
-
});
|
|
414
|
+
return array.length > 1
|
|
415
|
+
? array.sort((first, second) => getComparisonValue(first, second, sorters, length))
|
|
416
|
+
: array;
|
|
307
417
|
}
|
|
308
418
|
|
|
309
419
|
sort.initialize = initializeSort;
|
|
310
420
|
|
|
421
|
+
sort.is = isSorted;
|
|
422
|
+
|
|
311
423
|
// #endregion
|
|
312
424
|
|
|
313
425
|
// #region Variables
|
|
314
426
|
|
|
427
|
+
const ARRAY_PEEK_PERCENTAGE = 10;
|
|
428
|
+
|
|
429
|
+
const ARRAY_THRESHOLD = 100;
|
|
430
|
+
|
|
315
431
|
export const SORT_DIRECTION_ASCENDING: SortDirection = 'ascending';
|
|
316
432
|
|
|
317
433
|
export const SORT_DIRECTION_DESCENDING: SortDirection = 'descending';
|
|
@@ -80,38 +80,20 @@ compare.register = registerComparator;
|
|
|
80
80
|
|
|
81
81
|
compare.unregister = unregisterComparator;
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
*/
|
|
88
|
-
function registerComparator<Instance>(
|
|
89
|
-
constructor: Constructor<Instance>,
|
|
90
|
-
handler?: string | ((first: Instance, second: Instance) => number),
|
|
91
|
-
): void {
|
|
92
|
-
compare.handlers.register(constructor, handler);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Unregister a custom comparison handler for a class
|
|
97
|
-
* @param constructor Class constructor
|
|
98
|
-
*/
|
|
99
|
-
function unregisterComparator<Instance>(constructor: Constructor<Instance>): void {
|
|
100
|
-
compare.handlers.unregister(constructor);
|
|
101
|
-
}
|
|
83
|
+
function compareNumbers(first: bigint | number, second: bigint | number): number {
|
|
84
|
+
if (Object.is(first, second)) {
|
|
85
|
+
return 0;
|
|
86
|
+
}
|
|
102
87
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
): number {
|
|
107
|
-
const firstNumber = Number(first);
|
|
108
|
-
const secondNumber = Number(second);
|
|
88
|
+
if (Number.isNaN(first)) {
|
|
89
|
+
return -1;
|
|
90
|
+
}
|
|
109
91
|
|
|
110
|
-
if (
|
|
111
|
-
return
|
|
92
|
+
if (Number.isNaN(second)) {
|
|
93
|
+
return 1;
|
|
112
94
|
}
|
|
113
95
|
|
|
114
|
-
return
|
|
96
|
+
return first > second ? 1 : -1;
|
|
115
97
|
}
|
|
116
98
|
|
|
117
99
|
function compareSymbols(first: symbol, second: symbol): number {
|
|
@@ -147,13 +129,33 @@ function getComparisonParts(value: unknown): unknown[] {
|
|
|
147
129
|
return typeof value === 'object' ? [value] : words(getString(value));
|
|
148
130
|
}
|
|
149
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Register a custom comparison handler for a class
|
|
134
|
+
* @param constructor Class constructor
|
|
135
|
+
* @param handler Method name or comparison function _(defaults to `compare`)_
|
|
136
|
+
*/
|
|
137
|
+
function registerComparator<Instance>(
|
|
138
|
+
constructor: Constructor<Instance>,
|
|
139
|
+
handler?: string | ((first: Instance, second: Instance) => number),
|
|
140
|
+
): void {
|
|
141
|
+
compare.handlers.register(constructor, handler);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Unregister a custom comparison handler for a class
|
|
146
|
+
* @param constructor Class constructor
|
|
147
|
+
*/
|
|
148
|
+
function unregisterComparator<Instance>(constructor: Constructor<Instance>): void {
|
|
149
|
+
compare.handlers.unregister(constructor);
|
|
150
|
+
}
|
|
151
|
+
|
|
150
152
|
// #endregion
|
|
151
153
|
|
|
152
154
|
// #region Variables
|
|
153
155
|
|
|
154
156
|
const comparators: Record<string, Comparator> = {
|
|
155
157
|
bigint: compareNumbers,
|
|
156
|
-
boolean: compareNumbers,
|
|
158
|
+
boolean: (first, second) => compareNumbers(first ? 1 : 0, second ? 1 : 0),
|
|
157
159
|
number: compareNumbers,
|
|
158
160
|
symbol: compareSymbols,
|
|
159
161
|
};
|