@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.
@@ -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> = (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
+ * 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;
@@ -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 modifier = getModifier(first, second);
54
- const sorters = getSorters(first, modifier);
55
- return (array) => work(array, sorters, modifier);
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
- const modifier = getModifier(first, second);
59
- return work(array, getSorters(first, modifier), modifier);
100
+ return sortArray(array, getSorters(first, getModifier(first, second)));
60
101
  }
61
- function work(array, sorters, modifier) {
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
- 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
- });
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> = (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
+ * 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
- 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,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 modifier = getModifier(first, second);
964
- const sorters = getSorters(first, modifier);
965
- return (array) => work$1(array, sorters, modifier);
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
- const modifier = getModifier(first, second);
969
- return work$1(array, getSorters(first, modifier), modifier);
1010
+ return sortArray(array, getSorters(first, getModifier(first, second)));
970
1011
  }
971
- function work$1(array, sorters, modifier) {
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
- 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
- });
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
- 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.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.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,21 @@ 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
+ * 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 modifier = getModifier(first, second);
230
- const sorters = getSorters(first, modifier);
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 array => work(array, sorters, modifier);
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
- const modifier = getModifier(first, second);
267
-
268
- return work(array, getSorters(first, modifier), modifier);
404
+ return sortArray(array, getSorters(first, getModifier(first, second)));
269
405
  }
270
406
 
271
- function work(array: unknown[], sorters: InternalSorter[], modifier: number): unknown[] {
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
- 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
- });
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
- * 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
  };