UNPKG

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