@oscarpalmer/atoms 0.173.3 → 0.175.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.
@@ -65,7 +65,61 @@ 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> = (array: Item[]) => 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
+ * Get the index for an item _(to be inserted into an array of items)_
77
+ *
78
+ * _(If the array is not sorted, it will be treated as sorted, and the result may be inaccurate)_
79
+ * @param array Array to get the index from
80
+ * @param item Item to get the index for
81
+ * @returns Index for item
82
+ */
83
+ index(array: Item[], item: Item): number;
84
+ /**
85
+ * Is the array sorted?
86
+ * @param array Array to check
87
+ * @returns `true` if sorted, otherwise `false`
88
+ */
89
+ is(array: Item[]): boolean;
90
+ };
91
+ /**
92
+ * Get the index for an item _(to be inserted into an array of items)_ based on sorters _(and an optional default direction)_
93
+ *
94
+ * _(If the array is not sorted, it will be treated as sorted, and the result may be inaccurate)_
95
+ * @param array Array to get the index from
96
+ * @param item Item to get the index for
97
+ * @param sorters Sorters to use to determine sorting
98
+ * @param descending Sorted in descending order? _(defaults to `false`; overridden by individual sorters)_
99
+ * @returns Index for item
100
+ */
101
+ declare function getIndex<Item>(array: Item[], item: Item, sorters: Array<ArraySorter<Item>>, descending?: boolean): number;
102
+ /**
103
+ * Get the index for an item _(to be inserted into an array of items)_ based on a sorter _(and an optional default direction)_
104
+ *
105
+ * _(If the array is not sorted, it will be treated as sorted, and the result may be inaccurate)_
106
+ * @param array Array to get the index from
107
+ * @param item Item to get the index for
108
+ * @param sorter Sorter to use to determine sorting
109
+ * @param descending Sorted in descending order? _(defaults to `false`; overridden by individual sorters)_
110
+ * @returns Index for item
111
+ */
112
+ declare function getIndex<Item>(array: Item[], item: Item, sorter: ArraySorter<Item>, descending?: boolean): number;
113
+ /**
114
+ * Get the index for an item _(to be inserted into an array of items)_ based on an optional default direction_
115
+ *
116
+ * _(If the array is not sorted, it will be treated as sorted, and the result may be inaccurate)_
117
+ * @param array Array to get the index from
118
+ * @param item Item to get the index for
119
+ * @param descending Sorted in descending order? _(defaults to `false`)_
120
+ * @returns Index for item
121
+ */
122
+ declare function getIndex<Item>(array: Item[], item: Item, descending?: boolean): number;
69
123
  /**
70
124
  * Initialize a sort handler with sorters _(and an optional default direction)_
71
125
  * @param sorters Sorters to use for sorting
@@ -86,6 +140,29 @@ declare function initializeSort<Item>(sorter: ArraySorter<Item>, descending?: bo
86
140
  * @returns Sort handler
87
141
  */
88
142
  declare function initializeSort<Item>(descending?: boolean): Sorter<Item>;
143
+ /**
144
+ * Is the array sorted according to the sorters _(and the optional default direction)_?
145
+ * @param array Array to check
146
+ * @param sorters Sorters to determine sorting
147
+ * @param descending Sorted in descending order? _(defaults to `false`; overridden by individual sorters)_
148
+ * @returns `true` if sorted, otherwise `false`
149
+ */
150
+ declare function isSorted<Item>(array: Item[], sorters: Array<ArraySorter<Item>>, descending?: boolean): boolean;
151
+ /**
152
+ * Is the array sorted according to the sorter _(and the optional default direction)_?
153
+ * @param array Array to check
154
+ * @param sorter Sorter to determine sorting
155
+ * @param descending Sorted in descending order? _(defaults to `false`; overridden by individual sorters)_
156
+ * @returns `true` if sorted, otherwise `false`
157
+ */
158
+ declare function isSorted<Item>(array: Item[], sorter: ArraySorter<Item>, descending?: boolean): boolean;
159
+ /**
160
+ * Is the array sorted?
161
+ * @param array Array to check
162
+ * @param descending Sorted in descending order? _(defaults to `false`)_
163
+ * @returns `true` if sorted, otherwise `false`
164
+ */
165
+ declare function isSorted<Item>(array: Item[], descending?: boolean): boolean;
89
166
  /**
90
167
  * Sort an array of items, using multiple sorters to sort by specific values
91
168
  * @param array Array to sort
@@ -110,7 +187,9 @@ declare function sort<Item>(array: Item[], sorter: ArraySorter<Item>, descending
110
187
  */
111
188
  declare function sort<Item>(array: Item[], descending?: boolean): Item[];
112
189
  declare namespace sort {
190
+ var index: typeof getIndex;
113
191
  var initialize: typeof initializeSort;
192
+ var is: typeof isSorted;
114
193
  }
115
194
  declare const SORT_DIRECTION_ASCENDING: SortDirection;
116
195
  declare const SORT_DIRECTION_DESCENDING: SortDirection;
@@ -9,6 +9,18 @@ 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
+ }
21
+ function getIndex(array, item, first, second) {
22
+ return getSortedIndex(array, item, getSorters(first, getModifier(first, second)));
23
+ }
12
24
  function getModifier(first, second) {
13
25
  return modifiers[first === true || second === true ? SORT_DIRECTION_DESCENDING : SORT_DIRECTION_ASCENDING];
14
26
  }
@@ -22,6 +34,22 @@ function getObjectSorter(obj, modifier) {
22
34
  if (sorter != null && typeof obj.direction === "string") sorter.modifier = modifiers[obj.direction] ?? modifier;
23
35
  return sorter;
24
36
  }
37
+ function getSortedIndex(array, item, sorters) {
38
+ if (!Array.isArray(array)) return -1;
39
+ const { length } = array;
40
+ if (length === 0) return 0;
41
+ const sortersLength = sorters.length;
42
+ if (getComparisonValue(item, array[0], sorters, sortersLength) < 0) return 0;
43
+ if (getComparisonValue(item, array[length - 1], sorters, sortersLength) >= 0) return length;
44
+ let low = 0;
45
+ let high = length - 1;
46
+ while (low <= high) {
47
+ const mid = Math.floor((low + high) / 2);
48
+ if (getComparisonValue(item, array[mid], sorters, sortersLength) < 0) high = mid - 1;
49
+ else low = mid + 1;
50
+ }
51
+ return low;
52
+ }
25
53
  function getSorter(value, modifier) {
26
54
  switch (true) {
27
55
  case typeof value === "function": return getComparisonSorter(value, modifier);
@@ -39,6 +67,11 @@ function getSorters(value, modifier) {
39
67
  const sorter = getSorter(item, modifier);
40
68
  if (sorter != null) sorters.push(sorter);
41
69
  }
70
+ if (sorters.length === 0) return [{
71
+ modifier,
72
+ get: false,
73
+ identifier: "default"
74
+ }];
42
75
  return sorters.filter((value, index, array) => array.findIndex((next) => next.identifier === value.identifier) === index);
43
76
  }
44
77
  function getValueSorter(value, modifier) {
@@ -50,30 +83,52 @@ function getValueSorter(value, modifier) {
50
83
  };
51
84
  }
52
85
  function initializeSort(first, second) {
53
- const modifier = getModifier(first, second);
54
- const sorters = getSorters(first, modifier);
55
- return (array) => work(array, sorters, modifier);
86
+ const sorters = getSorters(first, getModifier(first, second));
87
+ const sorter = (array) => sortArray(array, sorters);
88
+ sorter.index = (array, item) => getSortedIndex(array, item, sorters);
89
+ sorter.is = (array) => isSortedArray(array, sorters);
90
+ return sorter;
91
+ }
92
+ function isSorted(array, first, second) {
93
+ return isSortedArray(array, getSorters(first, getModifier(first, second)));
94
+ }
95
+ function isSortedArray(array, sorters) {
96
+ if (!Array.isArray(array)) return false;
97
+ const { length } = array;
98
+ if (length < 2) return true;
99
+ const sortersLength = sorters.length;
100
+ let offset = 0;
101
+ if (length >= ARRAY_THRESHOLD) {
102
+ offset = Math.round(length / ARRAY_PEEK_PERCENTAGE);
103
+ offset = offset > ARRAY_THRESHOLD ? ARRAY_THRESHOLD : offset;
104
+ for (let index = 0; index < offset; index += 1) {
105
+ const [firstItem, firstOffset] = [array[index], array[index + 1]];
106
+ const [secondItem, secondOffset] = [array[length - index - 2], array[length - index - 1]];
107
+ const [firstComparison, secondComparison] = [getComparisonValue(firstItem, firstOffset, sorters, sortersLength), getComparisonValue(secondItem, secondOffset, sorters, sortersLength)];
108
+ if (firstComparison > 0 || secondComparison > 0) return false;
109
+ }
110
+ }
111
+ const end = length - offset - 1;
112
+ for (let index = offset; index < end; index += 1) {
113
+ const first = array[index];
114
+ const second = array[index + 1];
115
+ if (getComparisonValue(first, second, sorters, sortersLength) > 0) return false;
116
+ }
117
+ return true;
56
118
  }
57
119
  function sort(array, first, second) {
58
- const modifier = getModifier(first, second);
59
- return work(array, getSorters(first, modifier), modifier);
120
+ return sortArray(array, getSorters(first, getModifier(first, second)));
60
121
  }
61
- function work(array, sorters, modifier) {
122
+ function sortArray(array, sorters) {
62
123
  if (!Array.isArray(array)) return [];
63
- if (array.length < 2) return array;
64
124
  const { length } = sorters;
65
- if (length === 0) return array.sort((first, second) => compare(first, second) * modifier);
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
- });
125
+ return array.length > 1 ? array.sort((first, second) => getComparisonValue(first, second, sorters, length)) : array;
75
126
  }
127
+ sort.index = getIndex;
76
128
  sort.initialize = initializeSort;
129
+ sort.is = isSorted;
130
+ const ARRAY_PEEK_PERCENTAGE = 10;
131
+ const ARRAY_THRESHOLD = 100;
77
132
  const SORT_DIRECTION_ASCENDING = "ascending";
78
133
  const SORT_DIRECTION_DESCENDING = "descending";
79
134
  const modifiers = {
package/dist/index.d.mts CHANGED
@@ -1166,7 +1166,61 @@ 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> = (array: Item[]) => 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
+ * Get the index for an item _(to be inserted into an array of items)_
1178
+ *
1179
+ * _(If the array is not sorted, it will be treated as sorted, and the result may be inaccurate)_
1180
+ * @param array Array to get the index from
1181
+ * @param item Item to get the index for
1182
+ * @returns Index for item
1183
+ */
1184
+ index(array: Item[], item: Item): number;
1185
+ /**
1186
+ * Is the array sorted?
1187
+ * @param array Array to check
1188
+ * @returns `true` if sorted, otherwise `false`
1189
+ */
1190
+ is(array: Item[]): boolean;
1191
+ };
1192
+ /**
1193
+ * Get the index for an item _(to be inserted into an array of items)_ based on sorters _(and an optional default direction)_
1194
+ *
1195
+ * _(If the array is not sorted, it will be treated as sorted, and the result may be inaccurate)_
1196
+ * @param array Array to get the index from
1197
+ * @param item Item to get the index for
1198
+ * @param sorters Sorters to use to determine sorting
1199
+ * @param descending Sorted in descending order? _(defaults to `false`; overridden by individual sorters)_
1200
+ * @returns Index for item
1201
+ */
1202
+ declare function getIndex<Item>(array: Item[], item: Item, sorters: Array<ArraySorter<Item>>, descending?: boolean): number;
1203
+ /**
1204
+ * Get the index for an item _(to be inserted into an array of items)_ based on a sorter _(and an optional default direction)_
1205
+ *
1206
+ * _(If the array is not sorted, it will be treated as sorted, and the result may be inaccurate)_
1207
+ * @param array Array to get the index from
1208
+ * @param item Item to get the index for
1209
+ * @param sorter Sorter to use to determine sorting
1210
+ * @param descending Sorted in descending order? _(defaults to `false`; overridden by individual sorters)_
1211
+ * @returns Index for item
1212
+ */
1213
+ declare function getIndex<Item>(array: Item[], item: Item, sorter: ArraySorter<Item>, descending?: boolean): number;
1214
+ /**
1215
+ * Get the index for an item _(to be inserted into an array of items)_ based on an optional default direction_
1216
+ *
1217
+ * _(If the array is not sorted, it will be treated as sorted, and the result may be inaccurate)_
1218
+ * @param array Array to get the index from
1219
+ * @param item Item to get the index for
1220
+ * @param descending Sorted in descending order? _(defaults to `false`)_
1221
+ * @returns Index for item
1222
+ */
1223
+ declare function getIndex<Item>(array: Item[], item: Item, descending?: boolean): number;
1170
1224
  /**
1171
1225
  * Initialize a sort handler with sorters _(and an optional default direction)_
1172
1226
  * @param sorters Sorters to use for sorting
@@ -1187,6 +1241,29 @@ declare function initializeSort<Item>(sorter: ArraySorter<Item>, descending?: bo
1187
1241
  * @returns Sort handler
1188
1242
  */
1189
1243
  declare function initializeSort<Item>(descending?: boolean): Sorter<Item>;
1244
+ /**
1245
+ * Is the array sorted according to the sorters _(and the optional default direction)_?
1246
+ * @param array Array to check
1247
+ * @param sorters Sorters to determine sorting
1248
+ * @param descending Sorted in descending order? _(defaults to `false`; overridden by individual sorters)_
1249
+ * @returns `true` if sorted, otherwise `false`
1250
+ */
1251
+ declare function isSorted<Item>(array: Item[], sorters: Array<ArraySorter<Item>>, descending?: boolean): boolean;
1252
+ /**
1253
+ * Is the array sorted according to the sorter _(and the optional default direction)_?
1254
+ * @param array Array to check
1255
+ * @param sorter Sorter to determine sorting
1256
+ * @param descending Sorted in descending order? _(defaults to `false`; overridden by individual sorters)_
1257
+ * @returns `true` if sorted, otherwise `false`
1258
+ */
1259
+ declare function isSorted<Item>(array: Item[], sorter: ArraySorter<Item>, descending?: boolean): boolean;
1260
+ /**
1261
+ * Is the array sorted?
1262
+ * @param array Array to check
1263
+ * @param descending Sorted in descending order? _(defaults to `false`)_
1264
+ * @returns `true` if sorted, otherwise `false`
1265
+ */
1266
+ declare function isSorted<Item>(array: Item[], descending?: boolean): boolean;
1190
1267
  /**
1191
1268
  * Sort an array of items, using multiple sorters to sort by specific values
1192
1269
  * @param array Array to sort
@@ -1211,7 +1288,9 @@ declare function sort<Item>(array: Item[], sorter: ArraySorter<Item>, descending
1211
1288
  */
1212
1289
  declare function sort<Item>(array: Item[], descending?: boolean): Item[];
1213
1290
  declare namespace sort {
1291
+ var index: typeof getIndex;
1214
1292
  var initialize: typeof initializeSort;
1293
+ var is: typeof isSorted;
1215
1294
  }
1216
1295
  declare const SORT_DIRECTION_ASCENDING: SortDirection;
1217
1296
  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
- const firstNumber = Number(first);
889
- const secondNumber = Number(second);
890
- if (Object.is(firstNumber, secondNumber)) return 0;
891
- return firstNumber > secondNumber ? 1 : -1;
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,18 @@ 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
+ }
931
+ function getIndex(array, item, first, second) {
932
+ return getSortedIndex(array, item, getSorters(first, getModifier(first, second)));
933
+ }
922
934
  function getModifier(first, second) {
923
935
  return modifiers[first === true || second === true ? SORT_DIRECTION_DESCENDING : SORT_DIRECTION_ASCENDING];
924
936
  }
@@ -932,6 +944,22 @@ function getObjectSorter(obj, modifier) {
932
944
  if (sorter != null && typeof obj.direction === "string") sorter.modifier = modifiers[obj.direction] ?? modifier;
933
945
  return sorter;
934
946
  }
947
+ function getSortedIndex(array, item, sorters) {
948
+ if (!Array.isArray(array)) return -1;
949
+ const { length } = array;
950
+ if (length === 0) return 0;
951
+ const sortersLength = sorters.length;
952
+ if (getComparisonValue(item, array[0], sorters, sortersLength) < 0) return 0;
953
+ if (getComparisonValue(item, array[length - 1], sorters, sortersLength) >= 0) return length;
954
+ let low = 0;
955
+ let high = length - 1;
956
+ while (low <= high) {
957
+ const mid = Math.floor((low + high) / 2);
958
+ if (getComparisonValue(item, array[mid], sorters, sortersLength) < 0) high = mid - 1;
959
+ else low = mid + 1;
960
+ }
961
+ return low;
962
+ }
935
963
  function getSorter(value, modifier) {
936
964
  switch (true) {
937
965
  case typeof value === "function": return getComparisonSorter(value, modifier);
@@ -949,6 +977,11 @@ function getSorters(value, modifier) {
949
977
  const sorter = getSorter(item, modifier);
950
978
  if (sorter != null) sorters.push(sorter);
951
979
  }
980
+ if (sorters.length === 0) return [{
981
+ modifier,
982
+ get: false,
983
+ identifier: "default"
984
+ }];
952
985
  return sorters.filter((value, index, array) => array.findIndex((next) => next.identifier === value.identifier) === index);
953
986
  }
954
987
  function getValueSorter(value, modifier) {
@@ -960,30 +993,52 @@ function getValueSorter(value, modifier) {
960
993
  };
961
994
  }
962
995
  function initializeSort(first, second) {
963
- const modifier = getModifier(first, second);
964
- const sorters = getSorters(first, modifier);
965
- return (array) => work$1(array, sorters, modifier);
996
+ const sorters = getSorters(first, getModifier(first, second));
997
+ const sorter = (array) => sortArray(array, sorters);
998
+ sorter.index = (array, item) => getSortedIndex(array, item, sorters);
999
+ sorter.is = (array) => isSortedArray(array, sorters);
1000
+ return sorter;
1001
+ }
1002
+ function isSorted(array, first, second) {
1003
+ return isSortedArray(array, getSorters(first, getModifier(first, second)));
1004
+ }
1005
+ function isSortedArray(array, sorters) {
1006
+ if (!Array.isArray(array)) return false;
1007
+ const { length } = array;
1008
+ if (length < 2) return true;
1009
+ const sortersLength = sorters.length;
1010
+ let offset = 0;
1011
+ if (length >= ARRAY_THRESHOLD$1) {
1012
+ offset = Math.round(length / ARRAY_PEEK_PERCENTAGE$1);
1013
+ offset = offset > ARRAY_THRESHOLD$1 ? ARRAY_THRESHOLD$1 : offset;
1014
+ for (let index = 0; index < offset; index += 1) {
1015
+ const [firstItem, firstOffset] = [array[index], array[index + 1]];
1016
+ const [secondItem, secondOffset] = [array[length - index - 2], array[length - index - 1]];
1017
+ const [firstComparison, secondComparison] = [getComparisonValue(firstItem, firstOffset, sorters, sortersLength), getComparisonValue(secondItem, secondOffset, sorters, sortersLength)];
1018
+ if (firstComparison > 0 || secondComparison > 0) return false;
1019
+ }
1020
+ }
1021
+ const end = length - offset - 1;
1022
+ for (let index = offset; index < end; index += 1) {
1023
+ const first = array[index];
1024
+ const second = array[index + 1];
1025
+ if (getComparisonValue(first, second, sorters, sortersLength) > 0) return false;
1026
+ }
1027
+ return true;
966
1028
  }
967
1029
  function sort(array, first, second) {
968
- const modifier = getModifier(first, second);
969
- return work$1(array, getSorters(first, modifier), modifier);
1030
+ return sortArray(array, getSorters(first, getModifier(first, second)));
970
1031
  }
971
- function work$1(array, sorters, modifier) {
1032
+ function sortArray(array, sorters) {
972
1033
  if (!Array.isArray(array)) return [];
973
- if (array.length < 2) return array;
974
1034
  const { length } = sorters;
975
- if (length === 0) return array.sort((first, second) => compare(first, second) * modifier);
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
- });
1035
+ return array.length > 1 ? array.sort((first, second) => getComparisonValue(first, second, sorters, length)) : array;
985
1036
  }
1037
+ sort.index = getIndex;
986
1038
  sort.initialize = initializeSort;
1039
+ sort.is = isSorted;
1040
+ const ARRAY_PEEK_PERCENTAGE$1 = 10;
1041
+ const ARRAY_THRESHOLD$1 = 100;
987
1042
  const SORT_DIRECTION_ASCENDING = "ascending";
988
1043
  const SORT_DIRECTION_DESCENDING = "descending";
989
1044
  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
- const firstNumber = Number(first);
59
- const secondNumber = Number(second);
60
- if (Object.is(firstNumber, secondNumber)) return 0;
61
- return firstNumber > secondNumber ? 1 : -1;
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.173.3",
3
+ "version": "0.175.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.5",
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,31 @@ type InternalSorterCompare = {
104
104
  */
105
105
  export type SortDirection = 'ascending' | 'descending';
106
106
 
107
- export type Sorter<Item> = (array: Item[]) => 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
+ * Get the index for an item _(to be inserted into an array of items)_
117
+ *
118
+ * _(If the array is not sorted, it will be treated as sorted, and the result may be inaccurate)_
119
+ * @param array Array to get the index from
120
+ * @param item Item to get the index for
121
+ * @returns Index for item
122
+ */
123
+ index(array: Item[], item: Item): number;
124
+
125
+ /**
126
+ * Is the array sorted?
127
+ * @param array Array to check
128
+ * @returns `true` if sorted, otherwise `false`
129
+ */
130
+ is(array: Item[]): boolean;
131
+ };
108
132
 
109
133
  // #endregion
110
134
 
@@ -121,6 +145,82 @@ function getComparisonSorter(callback: Function, modifier: number): InternalSort
121
145
  };
122
146
  }
123
147
 
148
+ function getComparisonValue(
149
+ first: unknown,
150
+ second: unknown,
151
+ sorters: InternalSorter[],
152
+ length: number,
153
+ ): number {
154
+ for (let index = 0; index < length; index += 1) {
155
+ const sorter = sorters[index];
156
+
157
+ const values = [
158
+ sorter.get ? sorter.value!(first as PlainObject) : first,
159
+ sorter.get ? sorter.value!(second as PlainObject) : second,
160
+ ];
161
+
162
+ const comparison =
163
+ (sorter.compare?.complex?.(first, values[0], second, values[1]) ??
164
+ sorter.compare?.simple?.(values[0], values[1]) ??
165
+ compare(values[0], values[1])) * sorter.modifier;
166
+
167
+ if (comparison !== 0) {
168
+ return comparison;
169
+ }
170
+ }
171
+
172
+ return 0;
173
+ }
174
+
175
+ /**
176
+ * Get the index for an item _(to be inserted into an array of items)_ based on sorters _(and an optional default direction)_
177
+ *
178
+ * _(If the array is not sorted, it will be treated as sorted, and the result may be inaccurate)_
179
+ * @param array Array to get the index from
180
+ * @param item Item to get the index for
181
+ * @param sorters Sorters to use to determine sorting
182
+ * @param descending Sorted in descending order? _(defaults to `false`; overridden by individual sorters)_
183
+ * @returns Index for item
184
+ */
185
+ function getIndex<Item>(
186
+ array: Item[],
187
+ item: Item,
188
+ sorters: Array<ArraySorter<Item>>,
189
+ descending?: boolean,
190
+ ): number;
191
+
192
+ /**
193
+ * Get the index for an item _(to be inserted into an array of items)_ based on a sorter _(and an optional default direction)_
194
+ *
195
+ * _(If the array is not sorted, it will be treated as sorted, and the result may be inaccurate)_
196
+ * @param array Array to get the index from
197
+ * @param item Item to get the index for
198
+ * @param sorter Sorter to use to determine sorting
199
+ * @param descending Sorted in descending order? _(defaults to `false`; overridden by individual sorters)_
200
+ * @returns Index for item
201
+ */
202
+ function getIndex<Item>(
203
+ array: Item[],
204
+ item: Item,
205
+ sorter: ArraySorter<Item>,
206
+ descending?: boolean,
207
+ ): number;
208
+
209
+ /**
210
+ * Get the index for an item _(to be inserted into an array of items)_ based on an optional default direction_
211
+ *
212
+ * _(If the array is not sorted, it will be treated as sorted, and the result may be inaccurate)_
213
+ * @param array Array to get the index from
214
+ * @param item Item to get the index for
215
+ * @param descending Sorted in descending order? _(defaults to `false`)_
216
+ * @returns Index for item
217
+ */
218
+ function getIndex<Item>(array: Item[], item: Item, descending?: boolean): number;
219
+
220
+ function getIndex(array: unknown[], item: unknown, first?: unknown, second?: unknown): number {
221
+ return getSortedIndex(array, item, getSorters(first, getModifier(first, second)));
222
+ }
223
+
124
224
  function getModifier(first: unknown, second: unknown): number {
125
225
  const direction =
126
226
  first === true || second === true ? SORT_DIRECTION_DESCENDING : SORT_DIRECTION_ASCENDING;
@@ -152,6 +252,43 @@ function getObjectSorter(obj: PlainObject, modifier: number): InternalSorter | u
152
252
  return sorter;
153
253
  }
154
254
 
255
+ function getSortedIndex(array: unknown[], item: unknown, sorters: InternalSorter[]): number {
256
+ if (!Array.isArray(array)) {
257
+ return -1;
258
+ }
259
+
260
+ const {length} = array;
261
+
262
+ if (length === 0) {
263
+ return 0;
264
+ }
265
+
266
+ const sortersLength = sorters.length;
267
+
268
+ if (getComparisonValue(item, array[0], sorters, sortersLength) < 0) {
269
+ return 0;
270
+ }
271
+
272
+ if (getComparisonValue(item, array[length - 1], sorters, sortersLength) >= 0) {
273
+ return length;
274
+ }
275
+
276
+ let low = 0;
277
+ let high = length - 1;
278
+
279
+ while (low <= high) {
280
+ const mid = Math.floor((low + high) / 2);
281
+
282
+ if (getComparisonValue(item, array[mid], sorters, sortersLength) < 0) {
283
+ high = mid - 1;
284
+ } else {
285
+ low = mid + 1;
286
+ }
287
+ }
288
+
289
+ return low;
290
+ }
291
+
155
292
  function getSorter(value: unknown, modifier: number): InternalSorter | undefined {
156
293
  switch (true) {
157
294
  case typeof value === 'function':
@@ -184,6 +321,16 @@ function getSorters(value: unknown, modifier: number): InternalSorter[] {
184
321
  }
185
322
  }
186
323
 
324
+ if (sorters.length === 0) {
325
+ return [
326
+ {
327
+ modifier,
328
+ get: false,
329
+ identifier: 'default',
330
+ },
331
+ ];
332
+ }
333
+
187
334
  return sorters.filter(
188
335
  (value, index, array) =>
189
336
  array.findIndex(next => next.identifier === value.identifier) === index,
@@ -226,10 +373,98 @@ function initializeSort<Item>(sorter: ArraySorter<Item>, descending?: boolean):
226
373
  function initializeSort<Item>(descending?: boolean): Sorter<Item>;
227
374
 
228
375
  function initializeSort(first?: unknown, second?: unknown): Sorter<unknown> {
229
- const modifier = getModifier(first, second);
230
- const sorters = getSorters(first, modifier);
376
+ const sorters = getSorters(first, getModifier(first, second));
377
+
378
+ const sorter = (array: unknown[]) => sortArray(array, sorters);
231
379
 
232
- return array => work(array, sorters, modifier);
380
+ sorter.index = (array: unknown[], item: unknown) => getSortedIndex(array, item, sorters);
381
+ sorter.is = (array: unknown[]) => isSortedArray(array, sorters);
382
+
383
+ return sorter as unknown as Sorter<unknown>;
384
+ }
385
+
386
+ /**
387
+ * Is the array sorted according to the sorters _(and the optional default direction)_?
388
+ * @param array Array to check
389
+ * @param sorters Sorters to determine sorting
390
+ * @param descending Sorted in descending order? _(defaults to `false`; overridden by individual sorters)_
391
+ * @returns `true` if sorted, otherwise `false`
392
+ */
393
+ function isSorted<Item>(
394
+ array: Item[],
395
+ sorters: Array<ArraySorter<Item>>,
396
+ descending?: boolean,
397
+ ): boolean;
398
+
399
+ /**
400
+ * Is the array sorted according to the sorter _(and the optional default direction)_?
401
+ * @param array Array to check
402
+ * @param sorter Sorter to determine sorting
403
+ * @param descending Sorted in descending order? _(defaults to `false`; overridden by individual sorters)_
404
+ * @returns `true` if sorted, otherwise `false`
405
+ */
406
+ function isSorted<Item>(array: Item[], sorter: ArraySorter<Item>, descending?: boolean): boolean;
407
+
408
+ /**
409
+ * Is the array sorted?
410
+ * @param array Array to check
411
+ * @param descending Sorted in descending order? _(defaults to `false`)_
412
+ * @returns `true` if sorted, otherwise `false`
413
+ */
414
+ function isSorted<Item>(array: Item[], descending?: boolean): boolean;
415
+
416
+ function isSorted(array: unknown[], first?: unknown, second?: unknown): boolean {
417
+ return isSortedArray(array, getSorters(first, getModifier(first, second)));
418
+ }
419
+
420
+ function isSortedArray(array: unknown[], sorters: InternalSorter[]): boolean {
421
+ if (!Array.isArray(array)) {
422
+ return false;
423
+ }
424
+
425
+ const {length} = array;
426
+
427
+ if (length < 2) {
428
+ return true;
429
+ }
430
+
431
+ const sortersLength = sorters.length;
432
+
433
+ let offset = 0;
434
+
435
+ if (length >= ARRAY_THRESHOLD) {
436
+ offset = Math.round(length / ARRAY_PEEK_PERCENTAGE);
437
+ offset = offset > ARRAY_THRESHOLD ? ARRAY_THRESHOLD : offset;
438
+
439
+ for (let index = 0; index < offset; index += 1) {
440
+ const [firstItem, firstOffset] = [array[index], array[index + 1]];
441
+ const [secondItem, secondOffset] = [array[length - index - 2], array[length - index - 1]];
442
+
443
+ const [firstComparison, secondComparison] = [
444
+ getComparisonValue(firstItem, firstOffset, sorters, sortersLength),
445
+ getComparisonValue(secondItem, secondOffset, sorters, sortersLength),
446
+ ];
447
+
448
+ if (firstComparison > 0 || secondComparison > 0) {
449
+ return false;
450
+ }
451
+ }
452
+ }
453
+
454
+ const end = length - offset - 1;
455
+
456
+ for (let index = offset; index < end; index += 1) {
457
+ const first = array[index];
458
+ const second = array[index + 1];
459
+
460
+ const comparison = getComparisonValue(first, second, sorters, sortersLength);
461
+
462
+ if (comparison > 0) {
463
+ return false;
464
+ }
465
+ }
466
+
467
+ return true;
233
468
  }
234
469
 
235
470
  /**
@@ -263,55 +498,35 @@ export function sort<Item>(array: Item[], sorter: ArraySorter<Item>, descending?
263
498
  export function sort<Item>(array: Item[], descending?: boolean): Item[];
264
499
 
265
500
  export function sort(array: unknown[], first?: unknown, second?: unknown): unknown[] {
266
- const modifier = getModifier(first, second);
267
-
268
- return work(array, getSorters(first, modifier), modifier);
501
+ return sortArray(array, getSorters(first, getModifier(first, second)));
269
502
  }
270
503
 
271
- function work(array: unknown[], sorters: InternalSorter[], modifier: number): unknown[] {
504
+ function sortArray(array: unknown[], sorters: InternalSorter[]): unknown[] {
272
505
  if (!Array.isArray(array)) {
273
506
  return [];
274
507
  }
275
508
 
276
- if (array.length < 2) {
277
- return array;
278
- }
279
-
280
509
  const {length} = sorters;
281
510
 
282
- if (length === 0) {
283
- return array.sort((first, second) => compare(first, second) * modifier);
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
- });
511
+ return array.length > 1
512
+ ? array.sort((first, second) => getComparisonValue(first, second, sorters, length))
513
+ : array;
307
514
  }
308
515
 
516
+ sort.index = getIndex;
517
+
309
518
  sort.initialize = initializeSort;
310
519
 
520
+ sort.is = isSorted;
521
+
311
522
  // #endregion
312
523
 
313
524
  // #region Variables
314
525
 
526
+ const ARRAY_PEEK_PERCENTAGE = 10;
527
+
528
+ const ARRAY_THRESHOLD = 100;
529
+
315
530
  export const SORT_DIRECTION_ASCENDING: SortDirection = 'ascending';
316
531
 
317
532
  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
- * Register a custom comparison handler for a class
85
- * @param constructor Class constructor
86
- * @param handler Method name or comparison function _(defaults to `compare`)_
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
- function compareNumbers(
104
- first: bigint | boolean | number,
105
- second: bigint | boolean | number,
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 (Object.is(firstNumber, secondNumber)) {
111
- return 0;
92
+ if (Number.isNaN(second)) {
93
+ return 1;
112
94
  }
113
95
 
114
- return firstNumber > secondNumber ? 1 : -1;
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
  };