simple-squiggle

A restricted subset of Squiggle
Log | Files | Refs | README

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 }