UNPKG

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