array.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.arraySize = arraySize;
  6. exports.contains = contains;
  7. exports.filter = filter;
  8. exports.filterRegExp = filterRegExp;
  9. exports.flatten = flatten;
  10. exports.forEach = forEach;
  11. exports.generalize = generalize;
  12. exports.getArrayDataType = getArrayDataType;
  13. exports.identify = identify;
  14. exports.initial = initial;
  15. exports.join = join;
  16. exports.last = last;
  17. exports.map = map;
  18. exports.processSizesWildcard = processSizesWildcard;
  19. exports.reshape = reshape;
  20. exports.resize = resize;
  21. exports.squeeze = squeeze;
  22. exports.unsqueeze = unsqueeze;
  23. exports.validate = validate;
  24. exports.validateIndex = validateIndex;
  25. var _number = require("./number.js");
  26. var _is = require("./is.js");
  27. var _string = require("./string.js");
  28. var _DimensionError = require("../error/DimensionError.js");
  29. var _IndexError = require("../error/IndexError.js");
  30. /**
  31. * Calculate the size of a multi dimensional array.
  32. * This function checks the size of the first entry, it does not validate
  33. * whether all dimensions match. (use function `validate` for that)
  34. * @param {Array} x
  35. * @Return {Number[]} size
  36. */
  37. function arraySize(x) {
  38. var s = [];
  39. while (Array.isArray(x)) {
  40. s.push(x.length);
  41. x = x[0];
  42. }
  43. return s;
  44. }
  45. /**
  46. * Recursively validate whether each element in a multi dimensional array
  47. * has a size corresponding to the provided size array.
  48. * @param {Array} array Array to be validated
  49. * @param {number[]} size Array with the size of each dimension
  50. * @param {number} dim Current dimension
  51. * @throws DimensionError
  52. * @private
  53. */
  54. function _validate(array, size, dim) {
  55. var i;
  56. var len = array.length;
  57. if (len !== size[dim]) {
  58. throw new _DimensionError.DimensionError(len, size[dim]);
  59. }
  60. if (dim < size.length - 1) {
  61. // recursively validate each child array
  62. var dimNext = dim + 1;
  63. for (i = 0; i < len; i++) {
  64. var child = array[i];
  65. if (!Array.isArray(child)) {
  66. throw new _DimensionError.DimensionError(size.length - 1, size.length, '<');
  67. }
  68. _validate(array[i], size, dimNext);
  69. }
  70. } else {
  71. // last dimension. none of the childs may be an array
  72. for (i = 0; i < len; i++) {
  73. if (Array.isArray(array[i])) {
  74. throw new _DimensionError.DimensionError(size.length + 1, size.length, '>');
  75. }
  76. }
  77. }
  78. }
  79. /**
  80. * Validate whether each element in a multi dimensional array has
  81. * a size corresponding to the provided size array.
  82. * @param {Array} array Array to be validated
  83. * @param {number[]} size Array with the size of each dimension
  84. * @throws DimensionError
  85. */
  86. function validate(array, size) {
  87. var isScalar = size.length === 0;
  88. if (isScalar) {
  89. // scalar
  90. if (Array.isArray(array)) {
  91. throw new _DimensionError.DimensionError(array.length, 0);
  92. }
  93. } else {
  94. // array
  95. _validate(array, size, 0);
  96. }
  97. }
  98. /**
  99. * Test whether index is an integer number with index >= 0 and index < length
  100. * when length is provided
  101. * @param {number} index Zero-based index
  102. * @param {number} [length] Length of the array
  103. */
  104. function validateIndex(index, length) {
  105. if (!(0, _is.isNumber)(index) || !(0, _number.isInteger)(index)) {
  106. throw new TypeError('Index must be an integer (value: ' + index + ')');
  107. }
  108. if (index < 0 || typeof length === 'number' && index >= length) {
  109. throw new _IndexError.IndexError(index, length);
  110. }
  111. }
  112. /**
  113. * Resize a multi dimensional array. The resized array is returned.
  114. * @param {Array} array Array to be resized
  115. * @param {Array.<number>} size Array with the size of each dimension
  116. * @param {*} [defaultValue=0] Value to be filled in in new entries,
  117. * zero by default. Specify for example `null`,
  118. * to clearly see entries that are not explicitly
  119. * set.
  120. * @return {Array} array The resized array
  121. */
  122. function resize(array, size, defaultValue) {
  123. // TODO: add support for scalars, having size=[] ?
  124. // check the type of the arguments
  125. if (!Array.isArray(array) || !Array.isArray(size)) {
  126. throw new TypeError('Array expected');
  127. }
  128. if (size.length === 0) {
  129. throw new Error('Resizing to scalar is not supported');
  130. }
  131. // check whether size contains positive integers
  132. size.forEach(function (value) {
  133. if (!(0, _is.isNumber)(value) || !(0, _number.isInteger)(value) || value < 0) {
  134. throw new TypeError('Invalid size, must contain positive integers ' + '(size: ' + (0, _string.format)(size) + ')');
  135. }
  136. });
  137. // recursively resize the array
  138. var _defaultValue = defaultValue !== undefined ? defaultValue : 0;
  139. _resize(array, size, 0, _defaultValue);
  140. return array;
  141. }
  142. /**
  143. * Recursively resize a multi dimensional array
  144. * @param {Array} array Array to be resized
  145. * @param {number[]} size Array with the size of each dimension
  146. * @param {number} dim Current dimension
  147. * @param {*} [defaultValue] Value to be filled in in new entries,
  148. * undefined by default.
  149. * @private
  150. */
  151. function _resize(array, size, dim, defaultValue) {
  152. var i;
  153. var elem;
  154. var oldLen = array.length;
  155. var newLen = size[dim];
  156. var minLen = Math.min(oldLen, newLen);
  157. // apply new length
  158. array.length = newLen;
  159. if (dim < size.length - 1) {
  160. // non-last dimension
  161. var dimNext = dim + 1;
  162. // resize existing child arrays
  163. for (i = 0; i < minLen; i++) {
  164. // resize child array
  165. elem = array[i];
  166. if (!Array.isArray(elem)) {
  167. elem = [elem]; // add a dimension
  168. array[i] = elem;
  169. }
  170. _resize(elem, size, dimNext, defaultValue);
  171. }
  172. // create new child arrays
  173. for (i = minLen; i < newLen; i++) {
  174. // get child array
  175. elem = [];
  176. array[i] = elem;
  177. // resize new child array
  178. _resize(elem, size, dimNext, defaultValue);
  179. }
  180. } else {
  181. // last dimension
  182. // remove dimensions of existing values
  183. for (i = 0; i < minLen; i++) {
  184. while (Array.isArray(array[i])) {
  185. array[i] = array[i][0];
  186. }
  187. }
  188. // fill new elements with the default value
  189. for (i = minLen; i < newLen; i++) {
  190. array[i] = defaultValue;
  191. }
  192. }
  193. }
  194. /**
  195. * Re-shape a multi dimensional array to fit the specified dimensions
  196. * @param {Array} array Array to be reshaped
  197. * @param {Array.<number>} sizes List of sizes for each dimension
  198. * @returns {Array} Array whose data has been formatted to fit the
  199. * specified dimensions
  200. *
  201. * @throws {DimensionError} If the product of the new dimension sizes does
  202. * not equal that of the old ones
  203. */
  204. function reshape(array, sizes) {
  205. var flatArray = flatten(array);
  206. var currentLength = flatArray.length;
  207. if (!Array.isArray(array) || !Array.isArray(sizes)) {
  208. throw new TypeError('Array expected');
  209. }
  210. if (sizes.length === 0) {
  211. throw new _DimensionError.DimensionError(0, currentLength, '!=');
  212. }
  213. sizes = processSizesWildcard(sizes, currentLength);
  214. var newLength = product(sizes);
  215. if (currentLength !== newLength) {
  216. throw new _DimensionError.DimensionError(newLength, currentLength, '!=');
  217. }
  218. try {
  219. return _reshape(flatArray, sizes);
  220. } catch (e) {
  221. if (e instanceof _DimensionError.DimensionError) {
  222. throw new _DimensionError.DimensionError(newLength, currentLength, '!=');
  223. }
  224. throw e;
  225. }
  226. }
  227. /**
  228. * Replaces the wildcard -1 in the sizes array.
  229. * @param {Array.<number>} sizes List of sizes for each dimension. At most on wildcard.
  230. * @param {number} currentLength Number of elements in the array.
  231. * @throws {Error} If more than one wildcard or unable to replace it.
  232. * @returns {Array.<number>} The sizes array with wildcard replaced.
  233. */
  234. function processSizesWildcard(sizes, currentLength) {
  235. var newLength = product(sizes);
  236. var processedSizes = sizes.slice();
  237. var WILDCARD = -1;
  238. var wildCardIndex = sizes.indexOf(WILDCARD);
  239. var isMoreThanOneWildcard = sizes.indexOf(WILDCARD, wildCardIndex + 1) >= 0;
  240. if (isMoreThanOneWildcard) {
  241. throw new Error('More than one wildcard in sizes');
  242. }
  243. var hasWildcard = wildCardIndex >= 0;
  244. var canReplaceWildcard = currentLength % newLength === 0;
  245. if (hasWildcard) {
  246. if (canReplaceWildcard) {
  247. processedSizes[wildCardIndex] = -currentLength / newLength;
  248. } else {
  249. throw new Error('Could not replace wildcard, since ' + currentLength + ' is no multiple of ' + -newLength);
  250. }
  251. }
  252. return processedSizes;
  253. }
  254. /**
  255. * Computes the product of all array elements.
  256. * @param {Array<number>} array Array of factors
  257. * @returns {number} Product of all elements
  258. */
  259. function product(array) {
  260. return array.reduce(function (prev, curr) {
  261. return prev * curr;
  262. }, 1);
  263. }
  264. /**
  265. * Iteratively re-shape a multi dimensional array to fit the specified dimensions
  266. * @param {Array} array Array to be reshaped
  267. * @param {Array.<number>} sizes List of sizes for each dimension
  268. * @returns {Array} Array whose data has been formatted to fit the
  269. * specified dimensions
  270. */
  271. function _reshape(array, sizes) {
  272. // testing if there are enough elements for the requested shape
  273. var tmpArray = array;
  274. var tmpArray2;
  275. // for each dimensions starting by the last one and ignoring the first one
  276. for (var sizeIndex = sizes.length - 1; sizeIndex > 0; sizeIndex--) {
  277. var size = sizes[sizeIndex];
  278. tmpArray2 = [];
  279. // aggregate the elements of the current tmpArray in elements of the requested size
  280. var length = tmpArray.length / size;
  281. for (var i = 0; i < length; i++) {
  282. tmpArray2.push(tmpArray.slice(i * size, (i + 1) * size));
  283. }
  284. // set it as the new tmpArray for the next loop turn or for return
  285. tmpArray = tmpArray2;
  286. }
  287. return tmpArray;
  288. }
  289. /**
  290. * Squeeze a multi dimensional array
  291. * @param {Array} array
  292. * @param {Array} [size]
  293. * @returns {Array} returns the array itself
  294. */
  295. function squeeze(array, size) {
  296. var s = size || arraySize(array);
  297. // squeeze outer dimensions
  298. while (Array.isArray(array) && array.length === 1) {
  299. array = array[0];
  300. s.shift();
  301. }
  302. // find the first dimension to be squeezed
  303. var dims = s.length;
  304. while (s[dims - 1] === 1) {
  305. dims--;
  306. }
  307. // squeeze inner dimensions
  308. if (dims < s.length) {
  309. array = _squeeze(array, dims, 0);
  310. s.length = dims;
  311. }
  312. return array;
  313. }
  314. /**
  315. * Recursively squeeze a multi dimensional array
  316. * @param {Array} array
  317. * @param {number} dims Required number of dimensions
  318. * @param {number} dim Current dimension
  319. * @returns {Array | *} Returns the squeezed array
  320. * @private
  321. */
  322. function _squeeze(array, dims, dim) {
  323. var i, ii;
  324. if (dim < dims) {
  325. var next = dim + 1;
  326. for (i = 0, ii = array.length; i < ii; i++) {
  327. array[i] = _squeeze(array[i], dims, next);
  328. }
  329. } else {
  330. while (Array.isArray(array)) {
  331. array = array[0];
  332. }
  333. }
  334. return array;
  335. }
  336. /**
  337. * Unsqueeze a multi dimensional array: add dimensions when missing
  338. *
  339. * Paramter `size` will be mutated to match the new, unqueezed matrix size.
  340. *
  341. * @param {Array} array
  342. * @param {number} dims Desired number of dimensions of the array
  343. * @param {number} [outer] Number of outer dimensions to be added
  344. * @param {Array} [size] Current size of array.
  345. * @returns {Array} returns the array itself
  346. * @private
  347. */
  348. function unsqueeze(array, dims, outer, size) {
  349. var s = size || arraySize(array);
  350. // unsqueeze outer dimensions
  351. if (outer) {
  352. for (var i = 0; i < outer; i++) {
  353. array = [array];
  354. s.unshift(1);
  355. }
  356. }
  357. // unsqueeze inner dimensions
  358. array = _unsqueeze(array, dims, 0);
  359. while (s.length < dims) {
  360. s.push(1);
  361. }
  362. return array;
  363. }
  364. /**
  365. * Recursively unsqueeze a multi dimensional array
  366. * @param {Array} array
  367. * @param {number} dims Required number of dimensions
  368. * @param {number} dim Current dimension
  369. * @returns {Array | *} Returns the squeezed array
  370. * @private
  371. */
  372. function _unsqueeze(array, dims, dim) {
  373. var i, ii;
  374. if (Array.isArray(array)) {
  375. var next = dim + 1;
  376. for (i = 0, ii = array.length; i < ii; i++) {
  377. array[i] = _unsqueeze(array[i], dims, next);
  378. }
  379. } else {
  380. for (var d = dim; d < dims; d++) {
  381. array = [array];
  382. }
  383. }
  384. return array;
  385. }
  386. /**
  387. * Flatten a multi dimensional array, put all elements in a one dimensional
  388. * array
  389. * @param {Array} array A multi dimensional array
  390. * @return {Array} The flattened array (1 dimensional)
  391. */
  392. function flatten(array) {
  393. if (!Array.isArray(array)) {
  394. // if not an array, return as is
  395. return array;
  396. }
  397. var flat = [];
  398. array.forEach(function callback(value) {
  399. if (Array.isArray(value)) {
  400. value.forEach(callback); // traverse through sub-arrays recursively
  401. } else {
  402. flat.push(value);
  403. }
  404. });
  405. return flat;
  406. }
  407. /**
  408. * A safe map
  409. * @param {Array} array
  410. * @param {function} callback
  411. */
  412. function map(array, callback) {
  413. return Array.prototype.map.call(array, callback);
  414. }
  415. /**
  416. * A safe forEach
  417. * @param {Array} array
  418. * @param {function} callback
  419. */
  420. function forEach(array, callback) {
  421. Array.prototype.forEach.call(array, callback);
  422. }
  423. /**
  424. * A safe filter
  425. * @param {Array} array
  426. * @param {function} callback
  427. */
  428. function filter(array, callback) {
  429. if (arraySize(array).length !== 1) {
  430. throw new Error('Only one dimensional matrices supported');
  431. }
  432. return Array.prototype.filter.call(array, callback);
  433. }
  434. /**
  435. * Filter values in a callback given a regular expression
  436. * @param {Array} array
  437. * @param {RegExp} regexp
  438. * @return {Array} Returns the filtered array
  439. * @private
  440. */
  441. function filterRegExp(array, regexp) {
  442. if (arraySize(array).length !== 1) {
  443. throw new Error('Only one dimensional matrices supported');
  444. }
  445. return Array.prototype.filter.call(array, function (entry) {
  446. return regexp.test(entry);
  447. });
  448. }
  449. /**
  450. * A safe join
  451. * @param {Array} array
  452. * @param {string} separator
  453. */
  454. function join(array, separator) {
  455. return Array.prototype.join.call(array, separator);
  456. }
  457. /**
  458. * Assign a numeric identifier to every element of a sorted array
  459. * @param {Array} a An array
  460. * @return {Array} An array of objects containing the original value and its identifier
  461. */
  462. function identify(a) {
  463. if (!Array.isArray(a)) {
  464. throw new TypeError('Array input expected');
  465. }
  466. if (a.length === 0) {
  467. return a;
  468. }
  469. var b = [];
  470. var count = 0;
  471. b[0] = {
  472. value: a[0],
  473. identifier: 0
  474. };
  475. for (var i = 1; i < a.length; i++) {
  476. if (a[i] === a[i - 1]) {
  477. count++;
  478. } else {
  479. count = 0;
  480. }
  481. b.push({
  482. value: a[i],
  483. identifier: count
  484. });
  485. }
  486. return b;
  487. }
  488. /**
  489. * Remove the numeric identifier from the elements
  490. * @param {array} a An array
  491. * @return {array} An array of values without identifiers
  492. */
  493. function generalize(a) {
  494. if (!Array.isArray(a)) {
  495. throw new TypeError('Array input expected');
  496. }
  497. if (a.length === 0) {
  498. return a;
  499. }
  500. var b = [];
  501. for (var i = 0; i < a.length; i++) {
  502. b.push(a[i].value);
  503. }
  504. return b;
  505. }
  506. /**
  507. * Check the datatype of a given object
  508. * This is a low level implementation that should only be used by
  509. * parent Matrix classes such as SparseMatrix or DenseMatrix
  510. * This method does not validate Array Matrix shape
  511. * @param {Array} array
  512. * @param {function} typeOf Callback function to use to determine the type of a value
  513. * @return {string}
  514. */
  515. function getArrayDataType(array, typeOf) {
  516. var type; // to hold type info
  517. var length = 0; // to hold length value to ensure it has consistent sizes
  518. for (var i = 0; i < array.length; i++) {
  519. var item = array[i];
  520. var isArray = Array.isArray(item);
  521. // Saving the target matrix row size
  522. if (i === 0 && isArray) {
  523. length = item.length;
  524. }
  525. // If the current item is an array but the length does not equal the targetVectorSize
  526. if (isArray && item.length !== length) {
  527. return undefined;
  528. }
  529. var itemType = isArray ? getArrayDataType(item, typeOf) // recurse into a nested array
  530. : typeOf(item);
  531. if (type === undefined) {
  532. type = itemType; // first item
  533. } else if (type !== itemType) {
  534. return 'mixed';
  535. } else {
  536. // we're good, everything has the same type so far
  537. }
  538. }
  539. return type;
  540. }
  541. /**
  542. * Return the last item from an array
  543. * @param array
  544. * @returns {*}
  545. */
  546. function last(array) {
  547. return array[array.length - 1];
  548. }
  549. /**
  550. * Get all but the last element of array.
  551. */
  552. function initial(array) {
  553. return array.slice(0, array.length - 1);
  554. }
  555. /**
  556. * Test whether an array or string contains an item
  557. * @param {Array | string} array
  558. * @param {*} item
  559. * @return {boolean}
  560. */
  561. function contains(array, item) {
  562. return array.indexOf(item) !== -1;
  563. }