compareNatural.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  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. return typed(name, {
  81. 'any, any': _compareNatural
  82. }); // just to check # args
  83. function _compareNatural(x, y) {
  84. var typeX = typeOf(x);
  85. var typeY = typeOf(y);
  86. var c;
  87. // numeric types
  88. if ((typeX === 'number' || typeX === 'BigNumber' || typeX === 'Fraction') && (typeY === 'number' || typeY === 'BigNumber' || typeY === 'Fraction')) {
  89. c = compare(x, y);
  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. }
  97. // matrix types
  98. var matTypes = ['Array', 'DenseMatrix', 'SparseMatrix'];
  99. if (matTypes.includes(typeX) || matTypes.includes(typeY)) {
  100. c = compareMatricesAndArrays(_compareNatural, x, y);
  101. if (c !== 0) {
  102. return c;
  103. } else {
  104. return naturalSort(typeX, typeY);
  105. }
  106. }
  107. // in case of different types, order by name of type, i.e. 'BigNumber' < 'Complex'
  108. if (typeX !== typeY) {
  109. return naturalSort(typeX, typeY);
  110. }
  111. if (typeX === 'Complex') {
  112. return compareComplexNumbers(x, y);
  113. }
  114. if (typeX === 'Unit') {
  115. if (x.equalBase(y)) {
  116. return _compareNatural(x.value, y.value);
  117. }
  118. // compare by units
  119. return compareArrays(_compareNatural, x.formatUnits(), y.formatUnits());
  120. }
  121. if (typeX === 'boolean') {
  122. return compareBooleans(x, y);
  123. }
  124. if (typeX === 'string') {
  125. return naturalSort(x, y);
  126. }
  127. if (typeX === 'Object') {
  128. return compareObjects(_compareNatural, x, y);
  129. }
  130. if (typeX === 'null') {
  131. return 0;
  132. }
  133. if (typeX === 'undefined') {
  134. return 0;
  135. }
  136. // this should not occur...
  137. throw new TypeError('Unsupported type of value "' + typeX + '"');
  138. }
  139. /**
  140. * Compare mixed matrix/array types, by converting to same-shaped array.
  141. * This comparator is non-deterministic regarding input types.
  142. * @param {Array | SparseMatrix | DenseMatrix | *} x
  143. * @param {Array | SparseMatrix | DenseMatrix | *} y
  144. * @returns {number} Returns the comparison result: -1, 0, or 1
  145. */
  146. function compareMatricesAndArrays(compareNatural, x, y) {
  147. if (isSparseMatrix(x) && isSparseMatrix(y)) {
  148. return compareArrays(compareNatural, x.toJSON().values, y.toJSON().values);
  149. }
  150. if (isSparseMatrix(x)) {
  151. // note: convert to array is expensive
  152. return compareMatricesAndArrays(compareNatural, x.toArray(), y);
  153. }
  154. if (isSparseMatrix(y)) {
  155. // note: convert to array is expensive
  156. return compareMatricesAndArrays(compareNatural, x, y.toArray());
  157. }
  158. // convert DenseArray into Array
  159. if (isDenseMatrix(x)) {
  160. return compareMatricesAndArrays(compareNatural, x.toJSON().data, y);
  161. }
  162. if (isDenseMatrix(y)) {
  163. return compareMatricesAndArrays(compareNatural, x, y.toJSON().data);
  164. }
  165. // convert scalars to array
  166. if (!Array.isArray(x)) {
  167. return compareMatricesAndArrays(compareNatural, [x], y);
  168. }
  169. if (!Array.isArray(y)) {
  170. return compareMatricesAndArrays(compareNatural, x, [y]);
  171. }
  172. return compareArrays(compareNatural, x, y);
  173. }
  174. /**
  175. * Compare two Arrays
  176. *
  177. * - First, compares value by value
  178. * - Next, if all corresponding values are equal,
  179. * look at the length: longest array will be considered largest
  180. *
  181. * @param {Array} x
  182. * @param {Array} y
  183. * @returns {number} Returns the comparison result: -1, 0, or 1
  184. */
  185. function compareArrays(compareNatural, x, y) {
  186. // compare each value
  187. for (var i = 0, ii = Math.min(x.length, y.length); i < ii; i++) {
  188. var v = compareNatural(x[i], y[i]);
  189. if (v !== 0) {
  190. return v;
  191. }
  192. }
  193. // compare the size of the arrays
  194. if (x.length > y.length) {
  195. return 1;
  196. }
  197. if (x.length < y.length) {
  198. return -1;
  199. }
  200. // both Arrays have equal size and content
  201. return 0;
  202. }
  203. /**
  204. * Compare two objects
  205. *
  206. * - First, compare sorted property names
  207. * - Next, compare the property values
  208. *
  209. * @param {Object} x
  210. * @param {Object} y
  211. * @returns {number} Returns the comparison result: -1, 0, or 1
  212. */
  213. function compareObjects(compareNatural, x, y) {
  214. var keysX = Object.keys(x);
  215. var keysY = Object.keys(y);
  216. // compare keys
  217. keysX.sort(naturalSort);
  218. keysY.sort(naturalSort);
  219. var c = compareArrays(compareNatural, keysX, keysY);
  220. if (c !== 0) {
  221. return c;
  222. }
  223. // compare values
  224. for (var i = 0; i < keysX.length; i++) {
  225. var v = compareNatural(x[keysX[i]], y[keysY[i]]);
  226. if (v !== 0) {
  227. return v;
  228. }
  229. }
  230. return 0;
  231. }
  232. });
  233. /**
  234. * Compare two complex numbers, `x` and `y`:
  235. *
  236. * - First, compare the real values of `x` and `y`
  237. * - If equal, compare the imaginary values of `x` and `y`
  238. *
  239. * @params {Complex} x
  240. * @params {Complex} y
  241. * @returns {number} Returns the comparison result: -1, 0, or 1
  242. */
  243. function compareComplexNumbers(x, y) {
  244. if (x.re > y.re) {
  245. return 1;
  246. }
  247. if (x.re < y.re) {
  248. return -1;
  249. }
  250. if (x.im > y.im) {
  251. return 1;
  252. }
  253. if (x.im < y.im) {
  254. return -1;
  255. }
  256. return 0;
  257. }