compareNatural.js (8313B)
1 import naturalSort from 'javascript-natural-sort'; 2 import { isDenseMatrix, isSparseMatrix, typeOf } from '../../utils/is.js'; 3 import { factory } from '../../utils/factory.js'; 4 var name = 'compareNatural'; 5 var dependencies = ['typed', 'compare']; 6 export var createCompareNatural = /* #__PURE__ */factory(name, dependencies, _ref => { 7 var { 8 typed, 9 compare 10 } = _ref; 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 return 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(this, 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 this(x.value, y.value); 121 } // compare by units 122 123 124 return compareArrays(this, 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(this, 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(compareNatural, x, y) { 160 if (isSparseMatrix(x) && isSparseMatrix(y)) { 161 return compareArrays(compareNatural, x.toJSON().values, y.toJSON().values); 162 } 163 164 if (isSparseMatrix(x)) { 165 // note: convert to array is expensive 166 return compareMatricesAndArrays(compareNatural, x.toArray(), y); 167 } 168 169 if (isSparseMatrix(y)) { 170 // note: convert to array is expensive 171 return compareMatricesAndArrays(compareNatural, x, y.toArray()); 172 } // convert DenseArray into Array 173 174 175 if (isDenseMatrix(x)) { 176 return compareMatricesAndArrays(compareNatural, x.toJSON().data, y); 177 } 178 179 if (isDenseMatrix(y)) { 180 return compareMatricesAndArrays(compareNatural, x, y.toJSON().data); 181 } // convert scalars to array 182 183 184 if (!Array.isArray(x)) { 185 return compareMatricesAndArrays(compareNatural, [x], y); 186 } 187 188 if (!Array.isArray(y)) { 189 return compareMatricesAndArrays(compareNatural, x, [y]); 190 } 191 192 return compareArrays(compareNatural, 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(compareNatural, 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(compareNatural, 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(compareNatural, 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 /** 266 * Compare two complex numbers, `x` and `y`: 267 * 268 * - First, compare the real values of `x` and `y` 269 * - If equal, compare the imaginary values of `x` and `y` 270 * 271 * @params {Complex} x 272 * @params {Complex} y 273 * @returns {number} Returns the comparison result: -1, 0, or 1 274 */ 275 276 function compareComplexNumbers(x, y) { 277 if (x.re > y.re) { 278 return 1; 279 } 280 281 if (x.re < y.re) { 282 return -1; 283 } 284 285 if (x.im > y.im) { 286 return 1; 287 } 288 289 if (x.im < y.im) { 290 return -1; 291 } 292 293 return 0; 294 }