UNPKG

8.17 kBJavaScriptView Raw
1import naturalSort from 'javascript-natural-sort';
2import { isDenseMatrix, isSparseMatrix, typeOf } from '../../utils/is';
3import { factory } from '../../utils/factory';
4var name = 'compareNatural';
5var dependencies = ['typed', 'compare'];
6export var createCompareNatural =
7/* #__PURE__ */
8factory(name, dependencies, function (_ref) {
9 var typed = _ref.typed,
10 compare = _ref.compare;
11 var compareBooleans = compare.signatures['boolean,boolean'];
12 /**
13 * Compare two values of any type in a deterministic, natural way.
14 *
15 * For numeric values, the function works the same as `math.compare`.
16 * For types of values that can't be compared mathematically,
17 * the function compares in a natural way.
18 *
19 * For numeric values, x and y are considered equal when the relative
20 * difference between x and y is smaller than the configured epsilon.
21 * The function cannot be used to compare values smaller than
22 * approximately 2.22e-16.
23 *
24 * For Complex numbers, first the real parts are compared. If equal,
25 * the imaginary parts are compared.
26 *
27 * Strings are compared with a natural sorting algorithm, which
28 * orders strings in a "logic" way following some heuristics.
29 * This differs from the function `compare`, which converts the string
30 * into a numeric value and compares that. The function `compareText`
31 * on the other hand compares text lexically.
32 *
33 * Arrays and Matrices are compared value by value until there is an
34 * unequal pair of values encountered. Objects are compared by sorted
35 * keys until the keys or their values are unequal.
36 *
37 * Syntax:
38 *
39 * math.compareNatural(x, y)
40 *
41 * Examples:
42 *
43 * math.compareNatural(6, 1) // returns 1
44 * math.compareNatural(2, 3) // returns -1
45 * math.compareNatural(7, 7) // returns 0
46 *
47 * math.compareNatural('10', '2') // returns 1
48 * math.compareText('10', '2') // returns -1
49 * math.compare('10', '2') // returns 1
50 *
51 * math.compareNatural('Answer: 10', 'Answer: 2') // returns 1
52 * math.compareText('Answer: 10', 'Answer: 2') // returns -1
53 * math.compare('Answer: 10', 'Answer: 2')
54 * // Error: Cannot convert "Answer: 10" to a number
55 *
56 * const a = math.unit('5 cm')
57 * const b = math.unit('40 mm')
58 * math.compareNatural(a, b) // returns 1
59 *
60 * const c = math.complex('2 + 3i')
61 * const d = math.complex('2 + 4i')
62 * math.compareNatural(c, d) // returns -1
63 *
64 * math.compareNatural([1, 2, 4], [1, 2, 3]) // returns 1
65 * math.compareNatural([1, 2, 3], [1, 2]) // returns 1
66 * math.compareNatural([1, 5], [1, 2, 3]) // returns 1
67 * math.compareNatural([1, 2], [1, 2]) // returns 0
68 *
69 * math.compareNatural({a: 2}, {a: 4}) // returns -1
70 *
71 * See also:
72 *
73 * compare, compareText
74 *
75 * @param {*} x First value to compare
76 * @param {*} y Second value to compare
77 * @return {number} Returns the result of the comparison:
78 * 1 when x > y, -1 when x < y, and 0 when x == y.
79 */
80
81 var compareNatural = typed(name, {
82 'any, any': function anyAny(x, y) {
83 var typeX = typeOf(x);
84 var typeY = typeOf(y);
85 var c; // numeric types
86
87 if ((typeX === 'number' || typeX === 'BigNumber' || typeX === 'Fraction') && (typeY === 'number' || typeY === 'BigNumber' || typeY === 'Fraction')) {
88 c = compare(x, y);
89
90 if (c.toString() !== '0') {
91 // c can be number, BigNumber, or Fraction
92 return c > 0 ? 1 : -1; // return a number
93 } else {
94 return naturalSort(typeX, typeY);
95 }
96 } // matrix types
97
98
99 if (typeX === 'Array' || typeX === 'Matrix' || typeY === 'Array' || typeY === 'Matrix') {
100 c = compareMatricesAndArrays(x, y);
101
102 if (c !== 0) {
103 return c;
104 } else {
105 return naturalSort(typeX, typeY);
106 }
107 } // in case of different types, order by name of type, i.e. 'BigNumber' < 'Complex'
108
109
110 if (typeX !== typeY) {
111 return naturalSort(typeX, typeY);
112 }
113
114 if (typeX === 'Complex') {
115 return compareComplexNumbers(x, y);
116 }
117
118 if (typeX === 'Unit') {
119 if (x.equalBase(y)) {
120 return compareNatural(x.value, y.value);
121 } // compare by units
122
123
124 return compareArrays(x.formatUnits(), y.formatUnits());
125 }
126
127 if (typeX === 'boolean') {
128 return compareBooleans(x, y);
129 }
130
131 if (typeX === 'string') {
132 return naturalSort(x, y);
133 }
134
135 if (typeX === 'Object') {
136 return compareObjects(x, y);
137 }
138
139 if (typeX === 'null') {
140 return 0;
141 }
142
143 if (typeX === 'undefined') {
144 return 0;
145 } // this should not occur...
146
147
148 throw new TypeError('Unsupported type of value "' + typeX + '"');
149 }
150 });
151 /**
152 * Compare mixed matrix/array types, by converting to same-shaped array.
153 * This comparator is non-deterministic regarding input types.
154 * @param {Array | SparseMatrix | DenseMatrix | *} x
155 * @param {Array | SparseMatrix | DenseMatrix | *} y
156 * @returns {number} Returns the comparison result: -1, 0, or 1
157 */
158
159 function compareMatricesAndArrays(x, y) {
160 if (isSparseMatrix(x) && isSparseMatrix(y)) {
161 return compareArrays(x.toJSON().values, y.toJSON().values);
162 }
163
164 if (isSparseMatrix(x)) {
165 // note: convert to array is expensive
166 return compareMatricesAndArrays(x.toArray(), y);
167 }
168
169 if (isSparseMatrix(y)) {
170 // note: convert to array is expensive
171 return compareMatricesAndArrays(x, y.toArray());
172 } // convert DenseArray into Array
173
174
175 if (isDenseMatrix(x)) {
176 return compareMatricesAndArrays(x.toJSON().data, y);
177 }
178
179 if (isDenseMatrix(y)) {
180 return compareMatricesAndArrays(x, y.toJSON().data);
181 } // convert scalars to array
182
183
184 if (!Array.isArray(x)) {
185 return compareMatricesAndArrays([x], y);
186 }
187
188 if (!Array.isArray(y)) {
189 return compareMatricesAndArrays(x, [y]);
190 }
191
192 return compareArrays(x, y);
193 }
194 /**
195 * Compare two Arrays
196 *
197 * - First, compares value by value
198 * - Next, if all corresponding values are equal,
199 * look at the length: longest array will be considered largest
200 *
201 * @param {Array} x
202 * @param {Array} y
203 * @returns {number} Returns the comparison result: -1, 0, or 1
204 */
205
206
207 function compareArrays(x, y) {
208 // compare each value
209 for (var i = 0, ii = Math.min(x.length, y.length); i < ii; i++) {
210 var v = compareNatural(x[i], y[i]);
211
212 if (v !== 0) {
213 return v;
214 }
215 } // compare the size of the arrays
216
217
218 if (x.length > y.length) {
219 return 1;
220 }
221
222 if (x.length < y.length) {
223 return -1;
224 } // both Arrays have equal size and content
225
226
227 return 0;
228 }
229 /**
230 * Compare two objects
231 *
232 * - First, compare sorted property names
233 * - Next, compare the property values
234 *
235 * @param {Object} x
236 * @param {Object} y
237 * @returns {number} Returns the comparison result: -1, 0, or 1
238 */
239
240
241 function compareObjects(x, y) {
242 var keysX = Object.keys(x);
243 var keysY = Object.keys(y); // compare keys
244
245 keysX.sort(naturalSort);
246 keysY.sort(naturalSort);
247 var c = compareArrays(keysX, keysY);
248
249 if (c !== 0) {
250 return c;
251 } // compare values
252
253
254 for (var i = 0; i < keysX.length; i++) {
255 var v = compareNatural(x[keysX[i]], y[keysY[i]]);
256
257 if (v !== 0) {
258 return v;
259 }
260 }
261
262 return 0;
263 }
264
265 return compareNatural;
266});
267/**
268 * Compare two complex numbers, `x` and `y`:
269 *
270 * - First, compare the real values of `x` and `y`
271 * - If equal, compare the imaginary values of `x` and `y`
272 *
273 * @params {Complex} x
274 * @params {Complex} y
275 * @returns {number} Returns the comparison result: -1, 0, or 1
276 */
277
278function compareComplexNumbers(x, y) {
279 if (x.re > y.re) {
280 return 1;
281 }
282
283 if (x.re < y.re) {
284 return -1;
285 }
286
287 if (x.im > y.im) {
288 return 1;
289 }
290
291 if (x.im < y.im) {
292 return -1;
293 }
294
295 return 0;
296}
\No newline at end of file