UNPKG

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