jupyter-ijavascript-utils 1.45.0 → 1.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DOCS.md +5 -1
- package/Dockerfile +1 -1
- package/README.md +5 -0
- package/package.json +1 -1
- package/src/TableGenerator.js +202 -4
- package/src/array.js +453 -0
- package/src/chain.js +21 -1
- package/src/format.js +6 -0
- package/src/object.js +238 -1
package/DOCS.md
CHANGED
|
@@ -74,7 +74,11 @@ Give it a try here:
|
|
|
74
74
|
[](https://mybinder.org/v2/gh/paulroth3d/jupyter-ijavascript-utils/main?labpath=example.ipynb)
|
|
75
75
|
|
|
76
76
|
## What's New
|
|
77
|
-
|
|
77
|
+
* 1.47 - Correct table rendering html if filter was used ({@link https://github.com/paulroth3d/jupyter-ijavascript-utils/issues/64|#64})
|
|
78
|
+
* allow conversion from a Collection of Objects to Arrays and back with object. ({@link module:object.objectCollectionFromArray|object.objectCollectionFromArray}, {@link module:object.objectCollectionToArray|object.objectCollectionToArray})
|
|
79
|
+
* Easier iterating over values and peeking with array with {@link module:array~PeekableArrayIterator|PeekableArrayIterator}
|
|
80
|
+
* Support for delayed and asynchronous chaining of functions ({@link module:array.delayedFn|delayedFn}, {@link module:array.chainFunctions|chainFunctions}, etc)
|
|
81
|
+
* 1.46 - Make it easier to extract data from "hard-spaced arrays" - {@link module:array.multiLineSubstr}, {@link module:array.multiStepReduce}
|
|
78
82
|
* 1.45 - more ways to understand the data - {@link module:aggregate.coalesce|aggregate.coalesce()}, convert properties to arrow/dot notation / reverse it {@link module:object.flatten|object.flatten()} / {@link module:object.expand|object.expand()} and {@link module:object.isObject|object.isObject()}
|
|
79
83
|
* 1.43 - esm module fix since still not supported yet in ijavascript
|
|
80
84
|
* 1.41 - {@link module:object.propertyInherit|object.propertyInherit} - to simplify inheriting values from one record to the next
|
package/Dockerfile
CHANGED
package/README.md
CHANGED
|
@@ -54,6 +54,11 @@ This is not intended to be the only way to accomplish many of these tasks, and a
|
|
|
54
54
|

|
|
55
55
|
|
|
56
56
|
# What's New
|
|
57
|
+
* 1.47 - Correct table rendering html if filter was used (#46)
|
|
58
|
+
* allow conversion from a Collection of Objects to Arrays and back with object. (objectCollectionFromArray, objectCollectionToArray)
|
|
59
|
+
* Easier iterating over values and peeking with array with PeekableArrayIterator
|
|
60
|
+
* Support for delayed and asynchronous chaining of functions (array.delayedFn, chainFunction, etc)
|
|
61
|
+
* 1.46 - Make it easier to extract data from "hard-spaced arrays" - (array.multiLineSubstr, array.multiStepReduce)
|
|
57
62
|
* 1.45 - more ways to understand the data - aggregate.coalesce(), convert properties to arrow/dot notation / reverse it : object.flatten() / object.expand() and Object.isObject()
|
|
58
63
|
* 1.43 - esm module fix since still not supported yet in ijavascript
|
|
59
64
|
* 1.41 - object.propertyInherit - to simplify inheriting values from one record to the next
|
package/package.json
CHANGED
package/src/TableGenerator.js
CHANGED
|
@@ -337,8 +337,6 @@ class TableGenerator {
|
|
|
337
337
|
this.#isTransposed = false;
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
-
//-- GETTER SETTERS
|
|
341
|
-
|
|
342
340
|
/**
|
|
343
341
|
* Assigns the data to be used in generating the table.
|
|
344
342
|
* @param {Array} collection -
|
|
@@ -365,6 +363,182 @@ class TableGenerator {
|
|
|
365
363
|
return this;
|
|
366
364
|
}
|
|
367
365
|
|
|
366
|
+
/**
|
|
367
|
+
* Assigns the data by importing in a collections of objects.
|
|
368
|
+
*
|
|
369
|
+
* Note: this is the default functionality / syntatic sugar - as data is expected as a collection of objects.
|
|
370
|
+
* @param {Object[]} collection -
|
|
371
|
+
* @returns {TableGenerator}
|
|
372
|
+
* @example
|
|
373
|
+
*
|
|
374
|
+
* dataSet = [{temp: 37, type: 'C'}, {temp: 310, type: 'K'}, {temp: 98, type: 'F'}];
|
|
375
|
+
*
|
|
376
|
+
* //-- simple example where the temp property is converted, and type property overwritten
|
|
377
|
+
* new TableGenerator()
|
|
378
|
+
* .data(dataSet)
|
|
379
|
+
* .generateMarkdown()
|
|
380
|
+
*
|
|
381
|
+
* //-- gives
|
|
382
|
+
* temp | type
|
|
383
|
+
* ---- | ----
|
|
384
|
+
* 37 | C
|
|
385
|
+
* 310 | K
|
|
386
|
+
* 98 | F
|
|
387
|
+
*/
|
|
388
|
+
fromObjectCollection(data) {
|
|
389
|
+
this.data(data);
|
|
390
|
+
return this;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Assigns the data by importing a 2 dimensional array.
|
|
395
|
+
*
|
|
396
|
+
* If headers are not provided, then the first row of the collection is assumed.
|
|
397
|
+
*
|
|
398
|
+
* If there is no header provided (by default) - then the first row is assumed.
|
|
399
|
+
*
|
|
400
|
+
* ```
|
|
401
|
+
* dataSet = [ [ 'temp', 'type' ], [ 37, 'C' ], [ 310, 'K' ], [ 98, 'F' ] ];
|
|
402
|
+
*
|
|
403
|
+
* new TableGenerator()
|
|
404
|
+
* .fromArray(dataSet)
|
|
405
|
+
* .generateMarkdown();
|
|
406
|
+
* ```
|
|
407
|
+
*
|
|
408
|
+
* temp | type
|
|
409
|
+
* ---- | ----
|
|
410
|
+
* 37 | C
|
|
411
|
+
* 310 | K
|
|
412
|
+
* 98 | F
|
|
413
|
+
*
|
|
414
|
+
* However, if there is a header provided, it assumes there is none in teh first row.
|
|
415
|
+
*
|
|
416
|
+
* ```
|
|
417
|
+
* headers = [ 'temp', 'type' ];
|
|
418
|
+
* dataSet = [[ 37, 'C' ], [ 310, 'K' ], [ 98, 'F' ] ];
|
|
419
|
+
*
|
|
420
|
+
* new TableGenerator()
|
|
421
|
+
* .fromArray(dataSet)
|
|
422
|
+
* .generateMarkdown();
|
|
423
|
+
* ```
|
|
424
|
+
*
|
|
425
|
+
* temp | type
|
|
426
|
+
* ---- | ----
|
|
427
|
+
* 37 | C
|
|
428
|
+
* 310 | K
|
|
429
|
+
* 98 | F
|
|
430
|
+
*
|
|
431
|
+
* @param {Array<Array>} collection -
|
|
432
|
+
* @returns {TableGenerator}
|
|
433
|
+
* @see {TableGenerator.data}
|
|
434
|
+
* @see {TableGenerator.fromList}
|
|
435
|
+
*/
|
|
436
|
+
fromArray(arrayCollection, headers = null) {
|
|
437
|
+
if (!arrayCollection) {
|
|
438
|
+
this.data(null);
|
|
439
|
+
return this;
|
|
440
|
+
}
|
|
441
|
+
this.data(ObjectUtils.objectCollectionFromArray(arrayCollection, headers));
|
|
442
|
+
|
|
443
|
+
return this;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Assigns the data from a single 1 dimensional array.
|
|
448
|
+
*
|
|
449
|
+
* Is syntatic sugar to simply wrap the 1 dimensional array into a 2 dimensional array.
|
|
450
|
+
*
|
|
451
|
+
* ```
|
|
452
|
+
* let precip = [
|
|
453
|
+
* 1, 0, 2, 3, 4
|
|
454
|
+
* ];
|
|
455
|
+
*
|
|
456
|
+
* utils.table().fromList(precip).render()
|
|
457
|
+
* ```
|
|
458
|
+
*
|
|
459
|
+
* _
|
|
460
|
+
* --
|
|
461
|
+
* 1
|
|
462
|
+
* 0
|
|
463
|
+
* 2
|
|
464
|
+
* 3
|
|
465
|
+
* 4
|
|
466
|
+
*
|
|
467
|
+
* @param {Array} array1d
|
|
468
|
+
* @returns {TableGenerator}
|
|
469
|
+
* @see {TableGenerator.fromArray}
|
|
470
|
+
*/
|
|
471
|
+
fromList(array1d) {
|
|
472
|
+
if (!array1d) {
|
|
473
|
+
this.data(null);
|
|
474
|
+
return this;
|
|
475
|
+
}
|
|
476
|
+
this.data(array1d.map((v) => ({ _: v })));
|
|
477
|
+
return this;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Initializes the data in the tableGenerator with an object holding
|
|
482
|
+
* 1d tensor properties.
|
|
483
|
+
*
|
|
484
|
+
* ```
|
|
485
|
+
* dfObject = {
|
|
486
|
+
* id: [
|
|
487
|
+
* 1, 0, 2, 3, 4,
|
|
488
|
+
* 5, 6, 8, 7
|
|
489
|
+
* ],
|
|
490
|
+
* city: [
|
|
491
|
+
* 'Seattle', 'Seattle',
|
|
492
|
+
* 'Seattle', 'New York',
|
|
493
|
+
* 'New York', 'New York',
|
|
494
|
+
* 'Chicago', 'Chicago',
|
|
495
|
+
* 'Chicago'
|
|
496
|
+
* ],
|
|
497
|
+
* month: [
|
|
498
|
+
* 'Aug', 'Apr',
|
|
499
|
+
* 'Dec', 'Apr',
|
|
500
|
+
* 'Aug', 'Dec',
|
|
501
|
+
* 'Apr', 'Dec',
|
|
502
|
+
* 'Aug'
|
|
503
|
+
* ],
|
|
504
|
+
* precip: [
|
|
505
|
+
* 0.87, 2.68, 5.31,
|
|
506
|
+
* 3.94, 4.13, 3.58,
|
|
507
|
+
* 3.62, 2.56, 3.98
|
|
508
|
+
* ]
|
|
509
|
+
* }
|
|
510
|
+
*
|
|
511
|
+
* utils.table().fromDataFrameObject(dfObject).render()
|
|
512
|
+
* ```
|
|
513
|
+
*
|
|
514
|
+
* id|city |month|precip
|
|
515
|
+
* --|-- |-- |--
|
|
516
|
+
* 1 |Seattle |Aug |0.87
|
|
517
|
+
* 0 |Seattle |Apr |2.68
|
|
518
|
+
* 2 |Seattle |Dec |5.31
|
|
519
|
+
* 3 |New York|Apr |3.94
|
|
520
|
+
* 4 |New York|Aug |4.13
|
|
521
|
+
* 5 |New York|Dec |3.58
|
|
522
|
+
* 6 |Chicago |Apr |3.62
|
|
523
|
+
* 8 |Chicago |Dec |2.56
|
|
524
|
+
* 7 |Chicago |Aug |3.98
|
|
525
|
+
*
|
|
526
|
+
* @param {Object} dataFrameObject - DataFrame with 1d tensor properties
|
|
527
|
+
* @returns {TableGenerator}
|
|
528
|
+
* @see https://danfo.jsdata.org/api-reference/dataframe/creating-a-dataframe#creating-a-dataframe-from-an-object
|
|
529
|
+
* @see {TableGenerator.fromList}
|
|
530
|
+
* @see {TableGenerator.fromObjectCollection}
|
|
531
|
+
* @see {TableGenerator.data}
|
|
532
|
+
*/
|
|
533
|
+
fromDataFrameObject(dataFrameObject) {
|
|
534
|
+
if (!dataFrameObject) {
|
|
535
|
+
this.data(null);
|
|
536
|
+
return this;
|
|
537
|
+
}
|
|
538
|
+
this.data(ObjectUtils.objectCollectionFromDataFrameObject(dataFrameObject));
|
|
539
|
+
return this;
|
|
540
|
+
}
|
|
541
|
+
|
|
368
542
|
/**
|
|
369
543
|
* Augments data with additional fields
|
|
370
544
|
*
|
|
@@ -1276,7 +1450,12 @@ class TableGenerator {
|
|
|
1276
1450
|
|
|
1277
1451
|
const printBody = (collection) => collection
|
|
1278
1452
|
.map((dataRow, rowIndex) => {
|
|
1279
|
-
|
|
1453
|
+
let record;
|
|
1454
|
+
if (this.#filterFn) {
|
|
1455
|
+
record = results.headers.reduce((result, header, headerIndex) => ObjectUtils.assign(result, header, dataRow[headerIndex]), {});
|
|
1456
|
+
} else {
|
|
1457
|
+
record = this.#data[rowIndex];
|
|
1458
|
+
}
|
|
1280
1459
|
const rowStyle = !styleRowFn ? null : styleRowFn({ rowIndex, row: dataRow, record }) || '';
|
|
1281
1460
|
|
|
1282
1461
|
return `<tr ${printInlineCSS(rowStyle)}>\n\t`
|
|
@@ -1507,7 +1686,26 @@ class TableGenerator {
|
|
|
1507
1686
|
*/
|
|
1508
1687
|
generateArray2() {
|
|
1509
1688
|
const results = this.prepare();
|
|
1510
|
-
return [
|
|
1689
|
+
return [results.headers, ...results.data];
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
generateObjectCollection() {
|
|
1693
|
+
return ObjectUtils.objectCollectionFromArray(this.generateArray2());
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
generateDataFrameObject() {
|
|
1697
|
+
const prepResults = this.prepare();
|
|
1698
|
+
const results = {};
|
|
1699
|
+
const createFrameList = () => new Array(prepResults.data.length).fill(undefined);
|
|
1700
|
+
prepResults.headers.forEach((header) => ObjectUtils.assign(results, header, createFrameList()));
|
|
1701
|
+
|
|
1702
|
+
prepResults.data.forEach((row, rowIndex) => {
|
|
1703
|
+
row.forEach((value, valIndex) => {
|
|
1704
|
+
results[prepResults.headers[valIndex]][rowIndex] = value;
|
|
1705
|
+
});
|
|
1706
|
+
});
|
|
1707
|
+
|
|
1708
|
+
return results;
|
|
1511
1709
|
}
|
|
1512
1710
|
|
|
1513
1711
|
static hasRenderedCSS = false;
|
package/src/array.js
CHANGED
|
@@ -27,12 +27,26 @@ require('./_types/global');
|
|
|
27
27
|
* * {@link module:array.pickRows|array.pickRows} - picks a row from a 2d array
|
|
28
28
|
* * {@link module:array.pickColumns|array.pickColumns} - picks a column from a 2d array
|
|
29
29
|
* * {@link module:array.pick|array.pick} - picks either/or rows and columns
|
|
30
|
+
* * Extracting Array Values
|
|
30
31
|
* * {@link module:array.extract|array.extract} - synonym to array.pick to pick either a row or column from an array
|
|
32
|
+
* * {@link module:array.multiLineSubstr|array.multiLineSubstr} - Extract
|
|
33
|
+
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substr|Substr}
|
|
34
|
+
* from a multi-line string or array of strings
|
|
35
|
+
* * {@link module:array.multiLineSubstring|array.multiLineSubstring} - Extract
|
|
36
|
+
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substring|Substring}
|
|
37
|
+
* from a multi-line string or array of strings
|
|
38
|
+
* * {@link module:array.multiStepReduce|array.multiStepReduce} - Performs reduce, and returns the value of reduce at each step
|
|
31
39
|
* * Applying a value
|
|
32
40
|
* * {@link module:array.applyArrayValue|array.applyArrayValue} - applies a value deeply into an array safely
|
|
33
41
|
* * {@link module:array.applyArrayValues|array.applyArrayValues} - applies a value / multiple values deeply into an array safely
|
|
34
42
|
* * Understanding Values
|
|
35
43
|
* * {@link module:array.isMultiDimensional|array.isMultiDimensional} - determines if an array is multi-dimensional
|
|
44
|
+
* * Custom Iterators
|
|
45
|
+
* * {@link module:array~PeekableArrayIterator|PeekableArrayIterator} - Iterator that lets you peek ahead while not moving the iterator.
|
|
46
|
+
* * Iterating over values
|
|
47
|
+
* * {@link module:array.delayedFn|delayedFn} - Similar to Function.bind() - you specify a function and arguments only to be called when you ask
|
|
48
|
+
* * {@link module:array.chainFunctions|chainFunctions} - Chain a set of functions to be called one after another.
|
|
49
|
+
* * {@link module:array.asyncWaitAndChain|asyncWaitAndChain} - Chains a set of functions to run one after another, but with a delay between.
|
|
36
50
|
*
|
|
37
51
|
* @module array
|
|
38
52
|
* @exports array
|
|
@@ -470,6 +484,7 @@ module.exports.applyArrayValues = function applyArrayValues(collection, path, va
|
|
|
470
484
|
* @param {Number} length - the length of the new array
|
|
471
485
|
* @param {any} defaultValue - the new value to put in each cell
|
|
472
486
|
* @see {@link module:array.arrange} for values based on the index
|
|
487
|
+
* @see https://stackoverflow.com/questions/35578478/array-prototype-fill-with-object-passes-reference-and-not-new-instance
|
|
473
488
|
* @returns {Array} - an array of length size with default values
|
|
474
489
|
*/
|
|
475
490
|
module.exports.size = function size(length, defaultValue) {
|
|
@@ -895,3 +910,441 @@ module.exports.indexify = function indexify(source, ...sectionIndicatorFunctions
|
|
|
895
910
|
|
|
896
911
|
return results;
|
|
897
912
|
};
|
|
913
|
+
|
|
914
|
+
/**
|
|
915
|
+
* Parse a fixed length table of strings (often in markdown format)
|
|
916
|
+
*
|
|
917
|
+
* For example, say you got a string formatted like this:
|
|
918
|
+
*
|
|
919
|
+
* ```
|
|
920
|
+
* hardSpacedString = `
|
|
921
|
+
* id first_name last_name city email gender ip_address airport_code car_model_year
|
|
922
|
+
* -- ---------- ---------- ----------- ---------------------------- ------ --------------- ------------ --------------
|
|
923
|
+
* 1 Thekla Brokenshaw Chicago tbrokenshaw0@kickstarter.com Female 81.118.170.238 CXI 2003
|
|
924
|
+
* 2 Lexi Dugall New York ldugall1@fc2.com Female 255.140.25.31 LBH 2005
|
|
925
|
+
* 3 Shawna Burghill London sburghill2@scribd.com Female 149.240.166.189 GBA 2004
|
|
926
|
+
* 4 Ginger Tween Lainqu gtween3@wordpress.com Female 132.67.225.203 EMS 1993
|
|
927
|
+
* 5 Elbertina Setford Los Angeles esetford4@ted.com Female 247.123.242.49 MEK 1989 `;
|
|
928
|
+
* ```
|
|
929
|
+
*
|
|
930
|
+
* This can be a bit hard to parse, because the space delimiter is a valid character in the `city` column, ex: `New York`.
|
|
931
|
+
*
|
|
932
|
+
* Instead, we can use the starting index and number of characters, to extract the data out
|
|
933
|
+
*
|
|
934
|
+
* ```
|
|
935
|
+
* const carModelYears = ArrayUtils.multiLineSubstr(hardSpacedString, 102);
|
|
936
|
+
* // ['car_model_year', '--------------', '2003 ', '2005 ', '2004 ', '1993 ', '1989'];
|
|
937
|
+
* const ipAddresses = ArrayUtils.multiLineSubstr(hardSpacedString, 73, 14);
|
|
938
|
+
* // ['ip_address ', '--------------', '81.118.170.238', '255.140.25.31 ', '149.240.166.18', '132.67.225.203', '247.123.242.49'];
|
|
939
|
+
* ```
|
|
940
|
+
* @see {@link module:array.multiLineSubstring|multiLineSubstring} - to use start and end character positions
|
|
941
|
+
* @see {@link module:array.multiStepReduce|multiStepReduce} - for example on how to extract data from hard spaced arrays
|
|
942
|
+
*
|
|
943
|
+
* {@link module:array.size|array.size(size, default)} - generate array of a specific size and CONSISTENT default value
|
|
944
|
+
*
|
|
945
|
+
* @param {String|String[]} str - multi-line string or array of strings
|
|
946
|
+
* @param {Number} start - the starting index to substr
|
|
947
|
+
* @param {Number} [len=0] - optional length of string to substr
|
|
948
|
+
* @returns {String[]} - substr values from each line
|
|
949
|
+
*/
|
|
950
|
+
module.exports.multiLineSubstr = function multiLineSubstr(target, start, length) {
|
|
951
|
+
const lines = (() => {
|
|
952
|
+
if (Array.isArray(target)) {
|
|
953
|
+
return target;
|
|
954
|
+
} else if (typeof target === 'string') {
|
|
955
|
+
return target.split(/\n/); //.trim()
|
|
956
|
+
}
|
|
957
|
+
throw Error('multiLineSubstr(target, start, length): target is assumed a multi-line string or array of strings');
|
|
958
|
+
})();
|
|
959
|
+
|
|
960
|
+
return lines.map((line) => line.substr(start, length));
|
|
961
|
+
};
|
|
962
|
+
|
|
963
|
+
/**
|
|
964
|
+
* Parse a fixed length table of strings (often in markdown format)
|
|
965
|
+
*
|
|
966
|
+
* For example, say you got a string formatted like this:
|
|
967
|
+
*
|
|
968
|
+
* ```
|
|
969
|
+
* hardSpacedString = `
|
|
970
|
+
* id first_name last_name city email gender ip_address airport_code car_model_year
|
|
971
|
+
* -- ---------- ---------- ----------- ---------------------------- ------ --------------- ------------ --------------
|
|
972
|
+
* 1 Thekla Brokenshaw Chicago tbrokenshaw0@kickstarter.com Female 81.118.170.238 CXI 2003
|
|
973
|
+
* 2 Lexi Dugall New York ldugall1@fc2.com Female 255.140.25.31 LBH 2005
|
|
974
|
+
* 3 Shawna Burghill London sburghill2@scribd.com Female 149.240.166.189 GBA 2004
|
|
975
|
+
* 4 Ginger Tween Lainqu gtween3@wordpress.com Female 132.67.225.203 EMS 1993
|
|
976
|
+
* 5 Elbertina Setford Los Angeles esetford4@ted.com Female 247.123.242.49 MEK 1989 `;
|
|
977
|
+
* ```
|
|
978
|
+
*
|
|
979
|
+
* This can be a bit hard to parse, because the space delimiter is a valid character in the `city` column, ex: `New York`.
|
|
980
|
+
*
|
|
981
|
+
* Instead, we can use the starting index and number of characters, to extract the data out.
|
|
982
|
+
*
|
|
983
|
+
* Note, this function uses the starting and ending character positions, to extract,
|
|
984
|
+
* where {@link module:array.multiLineSubstr|multiLineSubstr} - uses the start and character length instead.
|
|
985
|
+
*
|
|
986
|
+
* ```
|
|
987
|
+
* const carModelYears = ArrayUtils.multiLineSubstring(hardSpacedString, 102);
|
|
988
|
+
* // ['car_model_year', '--------------', '2003 ', '2005 ', '2004 ', '1993 ', '1989'];
|
|
989
|
+
* const ipAddresses = ArrayUtils.multiLineSubstring(hardSpacedString, 73, 87);
|
|
990
|
+
* // ['ip_address ', '--------------', '81.118.170.238', '255.140.25.31 ', '149.240.166.18', '132.67.225.203', '247.123.242.49'];
|
|
991
|
+
* ```
|
|
992
|
+
* @see {@link module:array.multiLineSubstr|multiLineSubstr} - to use character start and length
|
|
993
|
+
* @see {@link module:array.multiStepReduce|multiStepReduce} - for example on how to extract data from hard spaced arrays
|
|
994
|
+
*
|
|
995
|
+
* @param {String|String[]} str - multi-line string or array of strings
|
|
996
|
+
* @param {Number} startPosition - the starting index to extract out - using the standard `substring` method
|
|
997
|
+
* @param {Number} [endPosition] - the ending endex to extract out
|
|
998
|
+
* @returns {String[]} - substr values from each line
|
|
999
|
+
*/
|
|
1000
|
+
module.exports.multiLineSubstring = function multiLineSubstring(target, startPosition, endPosition) {
|
|
1001
|
+
const lines = (() => {
|
|
1002
|
+
if (Array.isArray(target)) {
|
|
1003
|
+
return target;
|
|
1004
|
+
} else if (typeof target === 'string') {
|
|
1005
|
+
return target.trim().split(/\n/);
|
|
1006
|
+
}
|
|
1007
|
+
throw Error('multiLineSubstring(target, startPosition, endPosition): target is assumed a multi-line string or array of strings');
|
|
1008
|
+
})();
|
|
1009
|
+
|
|
1010
|
+
return lines.map((line) => line.substring(startPosition, endPosition));
|
|
1011
|
+
};
|
|
1012
|
+
|
|
1013
|
+
/**
|
|
1014
|
+
* Returns the reduce at each step along the way.
|
|
1015
|
+
*
|
|
1016
|
+
* For example, if you have a set of column widths
|
|
1017
|
+
* and would like to know how wide the table is after each column.
|
|
1018
|
+
*
|
|
1019
|
+
* For example:
|
|
1020
|
+
*
|
|
1021
|
+
* ```
|
|
1022
|
+
* hardSpacedString = `
|
|
1023
|
+
* id first_name last_name city email gender ip_address airport_code car_model_year
|
|
1024
|
+
* -- ---------- ---------- ----------- ---------------------------- ------ --------------- ------------ --------------
|
|
1025
|
+
* 1 Thekla Brokenshaw Chicago tbrokenshaw0@kickstarter.com Female 81.118.170.238 CXI 2003
|
|
1026
|
+
* 2 Lexi Dugall New York ldugall1@fc2.com Female 255.140.25.31 LBH 2005
|
|
1027
|
+
* 3 Shawna Burghill London sburghill2@scribd.com Female 149.240.166.189 GBA 2004
|
|
1028
|
+
* 4 Ginger Tween Lainqu gtween3@wordpress.com Female 132.67.225.203 EMS 1993
|
|
1029
|
+
* 5 Elbertina Setford Los Angeles esetford4@ted.com Female 247.123.242.49 MEK 1989 `;
|
|
1030
|
+
*
|
|
1031
|
+
* columnWidths = [3, 11, 11, 12, 29, 7, 16, 13, 15];
|
|
1032
|
+
* sumFn = (a, b) => a + b;
|
|
1033
|
+
*
|
|
1034
|
+
* //-- get the starting position for each column,
|
|
1035
|
+
* //-- ex: column 3 is sum of columnWidths[0..3] or 0 + 3 + 11 + 11 or 25
|
|
1036
|
+
* columnStops = utils.format.multiStepReduce( columnWidths, (a,b) => a + b, 0);
|
|
1037
|
+
* // [0, 3, 14, 25, 37, 66, 73, 89, 102, 117];
|
|
1038
|
+
* ```
|
|
1039
|
+
*
|
|
1040
|
+
* We can then pair with the column widths - to get exactly the starting position and width of each column.
|
|
1041
|
+
*
|
|
1042
|
+
* ```
|
|
1043
|
+
* substrPairs = columnWidths.map((value, index) => [columnStops[index], value]);
|
|
1044
|
+
* // [[0, 3], [3, 11], [14, 11], [25, 12], [37, 29], [66, 7], [73, 16], [89, 13], [102, 15]];
|
|
1045
|
+
* ```
|
|
1046
|
+
*
|
|
1047
|
+
* Now that we know how the starting positions for each of the columns, we can try picking one column out:
|
|
1048
|
+
*
|
|
1049
|
+
* ```
|
|
1050
|
+
* //-- we can get a single column like this:
|
|
1051
|
+
* cityStartingCharacter = substrPairs[3][0]; // 25
|
|
1052
|
+
* cityColumnLength = substrPairs[3][0]; // 12
|
|
1053
|
+
*
|
|
1054
|
+
* cityData = ArrayUtils.multiLineSubstr(hardSpacedString, cityStartingCharacter, cityColumnLength);
|
|
1055
|
+
* // ['city ', '----------- ', 'Chicago ', 'New York ', 'London ', 'Lainqu ', 'Los Angeles ']
|
|
1056
|
+
* ```
|
|
1057
|
+
*
|
|
1058
|
+
* Or we can get all columns with something like this:
|
|
1059
|
+
*
|
|
1060
|
+
* ```
|
|
1061
|
+
* results = substrPairs.map(
|
|
1062
|
+
* ([startingPos, length]) => ArrayUtils.multiLineSubstr(hardSpacedString, startingPos, length)
|
|
1063
|
+
* );
|
|
1064
|
+
*
|
|
1065
|
+
* [['id ', '-- ', '1 ', '2 ', '3 ', '4 ', '5 '],
|
|
1066
|
+
* ['first_name ', '---------- ', 'Thekla ', 'Lexi ', 'Shawna ', 'Gin...', ...],
|
|
1067
|
+
* ['last_name ', '---------- ', 'Brokenshaw ', 'Dugall ', 'Burghill ', 'Twe...', ...],
|
|
1068
|
+
* ['city ', '----------- ', 'Chicago ', 'New York ', 'London ', ...],
|
|
1069
|
+
* ['email ', '---------------------------- ', 'tbrokenshaw0...', ...],
|
|
1070
|
+
* ['gender ', '------ ', 'Female ', 'Female ', 'Female ', 'Female ', 'Female '],
|
|
1071
|
+
* ['ip_address ', '--------------- ', '81.118.170.238 ', '255.140.25.31 ', ...],
|
|
1072
|
+
* ['airport_code ', '------------ ', 'CXI ', 'LBH ', 'GBA ...', ...],
|
|
1073
|
+
* ['car_model_year', '--------------', '2003 ', '2005 ', '2004 ...', ...]]
|
|
1074
|
+
* ```
|
|
1075
|
+
*
|
|
1076
|
+
* We can then transpose the array to give us the format we might expect (non DataFrame centric)
|
|
1077
|
+
*
|
|
1078
|
+
* ```
|
|
1079
|
+
* resultsData = utils.array.transpose(results);
|
|
1080
|
+
*
|
|
1081
|
+
* utils.table(resultsData).render();
|
|
1082
|
+
* ```
|
|
1083
|
+
*
|
|
1084
|
+
* 0 |1 |2 |3 |4 |5 |6 |7 |8
|
|
1085
|
+
-- |-- |-- |-- |-- |-- |-- |-- |--
|
|
1086
|
+
id |first_name |last_name |city |email |gender |ip_address |airport_code |car_model_year
|
|
1087
|
+
-- |---------- |---------- |----------- |---------------------------- |------ |--------------- |------------ |--------------
|
|
1088
|
+
1 |Thekla |Brokenshaw |Chicago |tbrokenshaw0@kickstarter.com |Female |81.118.170.238 |CXI |2003
|
|
1089
|
+
2 |Lexi |Dugall |New York |ldugall1@fc2.com |Female |255.140.25.31 |LBH |2005
|
|
1090
|
+
3 |Shawna |Burghill |London |sburghill2@scribd.com |Female |149.240.166.189 |GBA |2004
|
|
1091
|
+
4 |Ginger |Tween |Lainqu |gtween3@wordpress.com |Female |132.67.225.203 |EMS |1993
|
|
1092
|
+
5 |Elbertina |Setford |Los Angeles |esetford4@ted.com |Female |247.123.242.49 |MEK |1989
|
|
1093
|
+
*
|
|
1094
|
+
* This can also be helpful with coming up with complex
|
|
1095
|
+
* {@link module:array.arrange|array.arrange(size, start, step)}
|
|
1096
|
+
* collections.
|
|
1097
|
+
*/
|
|
1098
|
+
module.exports.multiStepReduce = function multiStepReduce(list, fn, initialValue = undefined) {
|
|
1099
|
+
const results = [initialValue];
|
|
1100
|
+
let currentResult = initialValue;
|
|
1101
|
+
list.forEach((val, index) => {
|
|
1102
|
+
currentResult = fn(currentResult, val, index, list);
|
|
1103
|
+
results.push(currentResult);
|
|
1104
|
+
});
|
|
1105
|
+
return results;
|
|
1106
|
+
};
|
|
1107
|
+
|
|
1108
|
+
/**
|
|
1109
|
+
* Create an iterator for an array that allows for peeking next values.
|
|
1110
|
+
*
|
|
1111
|
+
* @see https://www.npmjs.com/package/peekable-array-iterator
|
|
1112
|
+
* @example
|
|
1113
|
+
*
|
|
1114
|
+
* source = [0, 1, 2, 3, 4, 5];
|
|
1115
|
+
*
|
|
1116
|
+
* // also quite helpful for document.querySelector(...)
|
|
1117
|
+
* itr = new utils.array.PeekableArrayIterator(source);
|
|
1118
|
+
*
|
|
1119
|
+
* console.log(itr.next()); // { done: false, value: 0 }
|
|
1120
|
+
*
|
|
1121
|
+
* //-- peek without moving the iterator
|
|
1122
|
+
* const peekItr = itr.peek();
|
|
1123
|
+
* console.log(peekItr.next()); // { done: false, value: 1 }
|
|
1124
|
+
* console.log(peekItr.next()); // { done: false, value: 2 }
|
|
1125
|
+
* console.log(peekItr.next()); // { done: false, value: 3 }
|
|
1126
|
+
* console.log(peekItr.next()); // { done: false, value: 4 }
|
|
1127
|
+
* console.log(peekItr.next()); // { done: true, value: 5 }
|
|
1128
|
+
*
|
|
1129
|
+
* //-- move the main iterator
|
|
1130
|
+
* console.log(itr.next()); // { done: false, value: 1 }
|
|
1131
|
+
*
|
|
1132
|
+
* Of course, for each will always work
|
|
1133
|
+
* or
|
|
1134
|
+
* for (let i of new utils.array.PeekableArrayIterator(list)) {
|
|
1135
|
+
* console.log(i);
|
|
1136
|
+
* }
|
|
1137
|
+
* // 1\n2\n3\n4\n5
|
|
1138
|
+
*/
|
|
1139
|
+
class PeekableArrayIterator {
|
|
1140
|
+
/**
|
|
1141
|
+
* Constructor
|
|
1142
|
+
*
|
|
1143
|
+
* @param {Iterable} array - something we can iterate over
|
|
1144
|
+
* @param {Number} start - the starting index
|
|
1145
|
+
*/
|
|
1146
|
+
constructor(source, start = -1) {
|
|
1147
|
+
this.array = Array.isArray(source) ? source : [...source];
|
|
1148
|
+
this.i = start;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
[Symbol.iterator]() { return this; }
|
|
1152
|
+
|
|
1153
|
+
/* eslint-disable wrap-iife */
|
|
1154
|
+
next() {
|
|
1155
|
+
const self = this;
|
|
1156
|
+
this.peek = (function* peek() {
|
|
1157
|
+
for (let peekI = self.i + 1; peekI < self.array.length; peekI += 1) {
|
|
1158
|
+
yield self.array[peekI];
|
|
1159
|
+
}
|
|
1160
|
+
return undefined;
|
|
1161
|
+
})();
|
|
1162
|
+
|
|
1163
|
+
this.i += 1;
|
|
1164
|
+
return { done: this.i >= this.array.length - 1, value: this.array[this.i] };
|
|
1165
|
+
}
|
|
1166
|
+
/* eslint-enable wrap-iife */
|
|
1167
|
+
}
|
|
1168
|
+
module.exports.PeekableArrayIterator = PeekableArrayIterator;
|
|
1169
|
+
|
|
1170
|
+
/**
|
|
1171
|
+
* Defines a function and arguments that will only be called,
|
|
1172
|
+
* only when the delayedFunction is called.
|
|
1173
|
+
*
|
|
1174
|
+
* (Similar to Function.bind - but supports Function.call(1,2,3) or Function.apply([1,2,3]) syntax)
|
|
1175
|
+
*
|
|
1176
|
+
* Example, these are equivalent, but the delayedFn does not mess with `this`
|
|
1177
|
+
*
|
|
1178
|
+
* sayFn = (...rest) => console.log(...rest);
|
|
1179
|
+
* arguments = [0, 1, 2, 3, 4, 5];
|
|
1180
|
+
*
|
|
1181
|
+
* Spy(sayFn);
|
|
1182
|
+
*
|
|
1183
|
+
* delayedFnA = sayFn.bind(globalThis, arguments);
|
|
1184
|
+
* ...
|
|
1185
|
+
* sayFn.calledCount; // 0
|
|
1186
|
+
* delayedFnA(); // consoles: [0, 1, 2, 3, 4, 5];
|
|
1187
|
+
* sayFn.calledCount; // 1
|
|
1188
|
+
*
|
|
1189
|
+
* delayedFnB = utils.array.delayedFn(sayFn, 0, 1, 2, 3, 4, 5);
|
|
1190
|
+
* ...
|
|
1191
|
+
* delayedFnB(); // consoles: [0, 1, 2, 3, 4, 5];
|
|
1192
|
+
*
|
|
1193
|
+
* @param {Function} fn - Function to be executed at a later time
|
|
1194
|
+
* @param {...any} rest - arguments to be passed to fn
|
|
1195
|
+
* @returns {Function} - function that can be called to execute fn with the arguments
|
|
1196
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
|
|
1197
|
+
*/
|
|
1198
|
+
module.exports.delayedFn = (fn, ...rest) => () => {
|
|
1199
|
+
const cleanRest = rest.length === 1 && Array.isArray(rest[0]) ? rest[0] : rest;
|
|
1200
|
+
return fn.apply(this, cleanRest);
|
|
1201
|
+
};
|
|
1202
|
+
|
|
1203
|
+
/**
|
|
1204
|
+
* Chain a set of functions to be called one after another
|
|
1205
|
+
* (supports functions returning promises)
|
|
1206
|
+
*
|
|
1207
|
+
* This is especially helpful if calls need to be rate limited to only having 1 occur at a time.
|
|
1208
|
+
*
|
|
1209
|
+
* The resulting promise will return a list, with each entry corresponding with the row of arguments sent.
|
|
1210
|
+
*
|
|
1211
|
+
* ```
|
|
1212
|
+
* const sumValues = (...rest) => rest.reduce((result, val) => result + val, 0);
|
|
1213
|
+
*
|
|
1214
|
+
* const arguments = [
|
|
1215
|
+
* [1],
|
|
1216
|
+
* [1, 1],
|
|
1217
|
+
* [1, 1, 2],
|
|
1218
|
+
* [1, 1, 2, 3]
|
|
1219
|
+
* ];
|
|
1220
|
+
*
|
|
1221
|
+
* utils.array.chainFunctions(sumValues, arguments)
|
|
1222
|
+
* .then((results) => console.log(`fibonnacci numbers: ${results}`));
|
|
1223
|
+
* // fibonacci numbers: [1, 2, 4, 7]
|
|
1224
|
+
* ```
|
|
1225
|
+
*
|
|
1226
|
+
* @param {Function} fn - the function to be called
|
|
1227
|
+
* @param {Array<any[]>} rows - Array where each row are arguments to be applied to fn
|
|
1228
|
+
* @returns {Promise<any>} - promise that will resolve when the last delayed function finishes
|
|
1229
|
+
* @see {@link https://rxjs.dev/guide/overview|rxjs} if you would like to have more than one active at a time.
|
|
1230
|
+
* @see {@link module:array.asyncWaitAndChain|asyncWaitAndChain} - if you would like a delay between executions
|
|
1231
|
+
*/
|
|
1232
|
+
module.exports.chainFunctions = (fn, rows) => {
|
|
1233
|
+
const delayedFunctions = rows.map((val) => ArrayUtils.delayedFn(fn, val));
|
|
1234
|
+
const delayedIterator = delayedFunctions.values();
|
|
1235
|
+
const answers = [];
|
|
1236
|
+
let isFirstCall = true;
|
|
1237
|
+
|
|
1238
|
+
return new Promise((resolve, reject) => {
|
|
1239
|
+
const callNext = (result) => {
|
|
1240
|
+
if (isFirstCall) {
|
|
1241
|
+
isFirstCall = false;
|
|
1242
|
+
} else {
|
|
1243
|
+
answers.push(result);
|
|
1244
|
+
}
|
|
1245
|
+
try {
|
|
1246
|
+
const nextVal = delayedIterator.next();
|
|
1247
|
+
const { value: delayedFunction, done } = nextVal;
|
|
1248
|
+
if (!done) {
|
|
1249
|
+
const fnResult = delayedFunction();
|
|
1250
|
+
if (fnResult instanceof Promise) {
|
|
1251
|
+
fnResult.then(callNext);
|
|
1252
|
+
} else {
|
|
1253
|
+
callNext(fnResult);
|
|
1254
|
+
}
|
|
1255
|
+
} else {
|
|
1256
|
+
resolve(answers);
|
|
1257
|
+
}
|
|
1258
|
+
} catch (err) {
|
|
1259
|
+
reject(err);
|
|
1260
|
+
}
|
|
1261
|
+
};
|
|
1262
|
+
return callNext();
|
|
1263
|
+
});
|
|
1264
|
+
};
|
|
1265
|
+
|
|
1266
|
+
/**
|
|
1267
|
+
* Executes a function with arguments after a few second delay.
|
|
1268
|
+
*
|
|
1269
|
+
* @param {Number} seconds - number of seconds to wait before calling
|
|
1270
|
+
* @param {Function} fn - Function to call
|
|
1271
|
+
* @param {...any} rest - arguments to send to the function when it is executed.
|
|
1272
|
+
* @returns {@Promise<any>} - that then executes when the timer is up
|
|
1273
|
+
* @private
|
|
1274
|
+
*/
|
|
1275
|
+
const asyncWaitThenRun = (seconds, fn, ...rest) => new Promise(
|
|
1276
|
+
(resolve, reject) => {
|
|
1277
|
+
setTimeout(() => {
|
|
1278
|
+
try {
|
|
1279
|
+
const results = fn(...rest);
|
|
1280
|
+
if (results instanceof Promise) {
|
|
1281
|
+
results.then((promiseResults) => {
|
|
1282
|
+
resolve(promiseResults);
|
|
1283
|
+
});
|
|
1284
|
+
} else {
|
|
1285
|
+
resolve(results);
|
|
1286
|
+
}
|
|
1287
|
+
} catch (err) {
|
|
1288
|
+
reject(err);
|
|
1289
|
+
}
|
|
1290
|
+
}, seconds * 1000);
|
|
1291
|
+
}
|
|
1292
|
+
);
|
|
1293
|
+
|
|
1294
|
+
/**
|
|
1295
|
+
* Similar to chainFunctions - in that only one delayed function will occur at a time,
|
|
1296
|
+
* but adds a delay between calls.
|
|
1297
|
+
*
|
|
1298
|
+
* This also supports functions returning promises.
|
|
1299
|
+
*
|
|
1300
|
+
*
|
|
1301
|
+
* ```
|
|
1302
|
+
* const sumValues = (...rest) => rest.reduce((result, val) => result + val, 0);
|
|
1303
|
+
*
|
|
1304
|
+
* const arguments = [
|
|
1305
|
+
* [1],
|
|
1306
|
+
* [1, 1],
|
|
1307
|
+
* [1, 1, 2],
|
|
1308
|
+
* [1, 1, 2, 3]
|
|
1309
|
+
* ];
|
|
1310
|
+
*
|
|
1311
|
+
* utils.array.asyncWaitAndChain(3, sumValues, arguments)
|
|
1312
|
+
* .then((results) => console.log(`fibonnacci numbers: ${results}`));
|
|
1313
|
+
* // fibonacci numbers: [1, 2, 4, 7], but took 9 seconds to accomplish
|
|
1314
|
+
* ```
|
|
1315
|
+
*
|
|
1316
|
+
* @param {Number} seconds - number of seconds to delay between each execution
|
|
1317
|
+
* @param {Function} fn - function to be called for each row of rows
|
|
1318
|
+
* @param {Array<any[]>} rows - Array where each row are arguments to be applied to fn
|
|
1319
|
+
* @returns {Promise<any>} - promise that will resolve when the last delayed function finishes
|
|
1320
|
+
* @see {@link module:array.chainFunctions|chainFunctions} - to execute methods right after each other.
|
|
1321
|
+
* @see {@link https://rxjs.dev/guide/overview|rxjs} if you would like to execute more than one at a time.
|
|
1322
|
+
*/
|
|
1323
|
+
// eslint-disable-next-line no-unused-vars
|
|
1324
|
+
module.exports.asyncWaitAndChain = (seconds, fn, rows) => {
|
|
1325
|
+
const delayedFunctions = rows.map((val) => ArrayUtils.delayedFn(fn, val));
|
|
1326
|
+
const delayedIterator = delayedFunctions.values();
|
|
1327
|
+
const answers = [];
|
|
1328
|
+
let isFirstCall = true;
|
|
1329
|
+
|
|
1330
|
+
return new Promise((resolve, reject) => {
|
|
1331
|
+
const callNext = (result) => {
|
|
1332
|
+
if (isFirstCall) {
|
|
1333
|
+
isFirstCall = false;
|
|
1334
|
+
} else {
|
|
1335
|
+
answers.push(result);
|
|
1336
|
+
}
|
|
1337
|
+
const nextVal = delayedIterator.next();
|
|
1338
|
+
const { value: delayedFunction, done } = nextVal;
|
|
1339
|
+
if (!done) {
|
|
1340
|
+
return asyncWaitThenRun(seconds, delayedFunction)
|
|
1341
|
+
.then(callNext)
|
|
1342
|
+
.catch((err) => {
|
|
1343
|
+
reject(err);
|
|
1344
|
+
});
|
|
1345
|
+
}
|
|
1346
|
+
resolve(answers);
|
|
1347
|
+
};
|
|
1348
|
+
return callNext();
|
|
1349
|
+
});
|
|
1350
|
+
};
|
package/src/chain.js
CHANGED
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
* * {@link ChainContainer#close|.close()} - gets the value of the current chain
|
|
14
14
|
* * {@link ChainContainer#chain|.chain(function)} - where it is passed the value, and returns a new Chain with that value.
|
|
15
15
|
* * {@link ChainContainer#errorHandler|.errorHandler(fn)} - custom function called if an error is ever thrown
|
|
16
|
-
* * {@link ChainContainer#debug|.debug()} - console.logs the current value, and continues the chain with that value
|
|
17
16
|
*
|
|
18
17
|
* Along with methods that can iterate on each element, assuming the value in the chain is an Array.
|
|
19
18
|
*
|
|
@@ -32,10 +31,12 @@
|
|
|
32
31
|
*
|
|
33
32
|
* There may be times you want to run side effects, or replace the value entirely. (This isn't common, but may be useful on occasion)
|
|
34
33
|
*
|
|
34
|
+
* * {@link ChainContainer#debug|.debug()} - continues with the current value, but executes a console.log first
|
|
35
35
|
* * {@link ChainContainer#execute|.execute(function)} - where it calls a function, but doesn't pass on the result.
|
|
36
36
|
* <br /> (This is useful for side-effects, like writing to files)
|
|
37
37
|
* * {@link ChainContainer#replace|.replace(value)} - replaces the value in the chain with a literal value,
|
|
38
38
|
* regardless of the previous value.
|
|
39
|
+
* * {@link ChainContainer#toArray|.toArray()} - assuming the current value is an interatable, converts it to an array.
|
|
39
40
|
*
|
|
40
41
|
* For example:
|
|
41
42
|
*
|
|
@@ -526,6 +527,25 @@ class ChainContainer {
|
|
|
526
527
|
return this.value;
|
|
527
528
|
}
|
|
528
529
|
|
|
530
|
+
/**
|
|
531
|
+
* Converts the current value to an array to be further chained.
|
|
532
|
+
* (Assumes it is iteratable);
|
|
533
|
+
*
|
|
534
|
+
* Example:
|
|
535
|
+
*
|
|
536
|
+
* ```
|
|
537
|
+
* new Chain(document.querySelectorAll('div'))
|
|
538
|
+
* .toArray()
|
|
539
|
+
* .execute((passedValue) => Array.isArray(passedValue))
|
|
540
|
+
* .close(); // [... list of div elements on the page]
|
|
541
|
+
* ```
|
|
542
|
+
*
|
|
543
|
+
* @returns {ChainContainer}
|
|
544
|
+
*/
|
|
545
|
+
toArray() {
|
|
546
|
+
return new ChainContainer(Array.from(this.value));
|
|
547
|
+
}
|
|
548
|
+
|
|
529
549
|
//-- private methods
|
|
530
550
|
|
|
531
551
|
/**
|
package/src/format.js
CHANGED
|
@@ -1068,6 +1068,12 @@ module.exports.limitLines = function limitLines(str, toLine, fromLine, lineSepar
|
|
|
1068
1068
|
: JSON.stringify(str || '', FormatUtils.mapReplacer, 2);
|
|
1069
1069
|
const cleanLine = lineSeparator || '\n';
|
|
1070
1070
|
|
|
1071
|
+
if (!toLine) {
|
|
1072
|
+
return cleanStr.split(cleanLine)
|
|
1073
|
+
.slice(fromLine || 0)
|
|
1074
|
+
.join(cleanLine);
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1071
1077
|
return cleanStr.split(cleanLine)
|
|
1072
1078
|
.slice(fromLine || 0, toLine)
|
|
1073
1079
|
.join(cleanLine);
|
package/src/object.js
CHANGED
|
@@ -52,6 +52,11 @@ const FormatUtils = require('./format');
|
|
|
52
52
|
* * Create Map of objects by key
|
|
53
53
|
* * {@link module:object.mapByProperty|mapByProperty()} -
|
|
54
54
|
* * {@link module:group.by|group(collection, accessor)}
|
|
55
|
+
* * Convert collections of objects
|
|
56
|
+
* * {@link module:object.objectCollectionFromArray|objectCollectionFromArray} - convert rows/columns 2d array to objects
|
|
57
|
+
* * {@link module:object.objectCollectionToArray} - convert objects to a rows/columns 2d array
|
|
58
|
+
* * {@link module:object.objectCollectionFromDataFrameObject} - convert tensor object with each field as 1d array of values
|
|
59
|
+
* * {@link module:object.objectCollectionToDataFrameObject} - convert objects from a tensor object
|
|
55
60
|
*
|
|
56
61
|
* @module object
|
|
57
62
|
* @exports object
|
|
@@ -1550,7 +1555,7 @@ module.exports.setPropertyDefaults = function setPropertyDefaults(targetObject,
|
|
|
1550
1555
|
* @param {Object[]} objCollection - object or multiple objects that should have properties formatted
|
|
1551
1556
|
* @param {Function} formattingFn - function to apply to all the properties specified
|
|
1552
1557
|
* @param {...any} propertiesToFormat - list of properties to apply the formatting function
|
|
1553
|
-
* @returns {Object[] - clone of objCollection with properties mapped
|
|
1558
|
+
* @returns {Object[]} - clone of objCollection with properties mapped
|
|
1554
1559
|
*/
|
|
1555
1560
|
module.exports.mapProperties = function mapProperties(objCollection, formattingFn, ...propertiesToFormat) {
|
|
1556
1561
|
const cleanCollection = !Array.isArray(objCollection)
|
|
@@ -1860,3 +1865,235 @@ module.exports.union = function union(source1, source2) {
|
|
|
1860
1865
|
|
|
1861
1866
|
return results;
|
|
1862
1867
|
};
|
|
1868
|
+
|
|
1869
|
+
/**
|
|
1870
|
+
* Converts a 2d array to a collection of objects.
|
|
1871
|
+
*
|
|
1872
|
+
* For Example:
|
|
1873
|
+
* ```
|
|
1874
|
+
* weather = [
|
|
1875
|
+
* [ 'id', 'city', 'month', 'precip' ],
|
|
1876
|
+
* [ 1, 'Seattle', 'Aug', 0.87 ],
|
|
1877
|
+
* [ 0, 'Seattle', 'Apr', 2.68 ],
|
|
1878
|
+
* [ 2, 'Seattle', 'Dec', 5.31 ]
|
|
1879
|
+
* ]
|
|
1880
|
+
*
|
|
1881
|
+
* utils.object.objectCollectionFromArray(weather);
|
|
1882
|
+
* // [
|
|
1883
|
+
* // { id: 1, city: 'Seattle', month: 'Aug', precip: 0.87 },
|
|
1884
|
+
* // { id: 0, city: 'Seattle', month: 'Apr', precip: 2.68 },
|
|
1885
|
+
* // { id: 2, city: 'Seattle', month: 'Dec', precip: 5.31 }
|
|
1886
|
+
* // ];
|
|
1887
|
+
* ```
|
|
1888
|
+
*
|
|
1889
|
+
* Note that the headers can be optionally provided separately.
|
|
1890
|
+
*
|
|
1891
|
+
* ```
|
|
1892
|
+
* weather = [
|
|
1893
|
+
* [ 1, 'Seattle', 'Aug', 0.87 ],
|
|
1894
|
+
* [ 0, 'Seattle', 'Apr', 2.68 ],
|
|
1895
|
+
* [ 2, 'Seattle', 'Dec', 5.31 ]
|
|
1896
|
+
* ];
|
|
1897
|
+
* headers = [ 'id', 'city', 'month', 'precip' ];
|
|
1898
|
+
*
|
|
1899
|
+
* utils.object.objectCollectionFromArray(weather, headers);
|
|
1900
|
+
* // [
|
|
1901
|
+
* // { id: 1, city: 'Seattle', month: 'Aug', precip: 0.87 },
|
|
1902
|
+
* // { id: 0, city: 'Seattle', month: 'Apr', precip: 2.68 },
|
|
1903
|
+
* // { id: 2, city: 'Seattle', month: 'Dec', precip: 5.31 }
|
|
1904
|
+
* // ];
|
|
1905
|
+
* ```
|
|
1906
|
+
*
|
|
1907
|
+
* @param {Array<Array>} arrayCollection - 2d collection of values
|
|
1908
|
+
* @param {String[]} [headers] - Optional set of headers to use if not present in first row 0
|
|
1909
|
+
* @returns {Object[]} - list of objects
|
|
1910
|
+
* @see {@link module:object.objectCollectionFromArray|objectCollectionFromArray}
|
|
1911
|
+
* @see {@link module:object.objectCollectionToArray|objectCollectionToArray}
|
|
1912
|
+
* @see {@link module:object.objectCollectionFromDataFrameObject|objectCollectionFromDataFrameObject}
|
|
1913
|
+
* @see {@link module:object.objectCollectionToDataFrameObject|objectCollectionToDataFrameObject}
|
|
1914
|
+
*/
|
|
1915
|
+
module.exports.objectCollectionFromArray = function objectCollectionFromArray(arrayCollection, headers = null) {
|
|
1916
|
+
let cleanHeaders;
|
|
1917
|
+
let cleanValues;
|
|
1918
|
+
|
|
1919
|
+
if (!Array.isArray(arrayCollection)) throw Error('objectCollectionFromArray: expected collection to be a 2 dimensional array');
|
|
1920
|
+
|
|
1921
|
+
if (!headers) {
|
|
1922
|
+
const [arrayCollectionHeaders, ...arrayCollectionValues] = arrayCollection;
|
|
1923
|
+
cleanHeaders = arrayCollectionHeaders;
|
|
1924
|
+
cleanValues = arrayCollectionValues;
|
|
1925
|
+
} else {
|
|
1926
|
+
cleanHeaders = headers;
|
|
1927
|
+
cleanValues = arrayCollection;
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
/* eslint-disable arrow-body-style */
|
|
1931
|
+
const newData = cleanValues.map((row) => {
|
|
1932
|
+
return cleanHeaders.reduce((result, header, index) => {
|
|
1933
|
+
return ObjectUtils.assign(result, header, row[index]);
|
|
1934
|
+
}, {});
|
|
1935
|
+
});
|
|
1936
|
+
/* eslint-enable arrow-body-style */
|
|
1937
|
+
|
|
1938
|
+
return newData;
|
|
1939
|
+
};
|
|
1940
|
+
|
|
1941
|
+
/**
|
|
1942
|
+
* Converts a 2d array to a collection of objects.
|
|
1943
|
+
*
|
|
1944
|
+
* For Example:
|
|
1945
|
+
* ```
|
|
1946
|
+
* weather = [
|
|
1947
|
+
* { id: 1, city: 'Seattle', month: 'Aug', precip: 0.87 },
|
|
1948
|
+
* { id: 0, city: 'Seattle', month: 'Apr', precip: 2.68 },
|
|
1949
|
+
* { id: 2, city: 'Seattle', month: 'Dec', precip: 5.31 }
|
|
1950
|
+
* ];
|
|
1951
|
+
*
|
|
1952
|
+
* utils.object.objectCollectionToArray(weather);
|
|
1953
|
+
* // [
|
|
1954
|
+
* // [ 'id', 'city', 'month', 'precip' ],
|
|
1955
|
+
* // [ 1, 'Seattle', 'Aug', 0.87 ],
|
|
1956
|
+
* // [ 0, 'Seattle', 'Apr', 2.68 ],
|
|
1957
|
+
* // [ 2, 'Seattle', 'Dec', 5.31 ]
|
|
1958
|
+
* // ]
|
|
1959
|
+
* ```
|
|
1960
|
+
*
|
|
1961
|
+
* @param {Array<Array>} arrayCollection - 2d collection of values
|
|
1962
|
+
* @param {String[]?} headers - Optional set of headers to use if not present in first row 0
|
|
1963
|
+
* @returns {Object[]} - list of objects
|
|
1964
|
+
* @see {@link module:object.objectCollectionFromArray|objectCollectionFromArray}
|
|
1965
|
+
* @see {@link module:object.objectCollectionToArray|objectCollectionToArray}
|
|
1966
|
+
* @see {@link module:object.objectCollectionFromDataFrameObject|objectCollectionFromDataFrameObject}
|
|
1967
|
+
* @see {@link module:object.objectCollectionToDataFrameObject|objectCollectionToDataFrameObject}
|
|
1968
|
+
*/
|
|
1969
|
+
module.exports.objectCollectionToArray = function objectCollectionToArray(objectCollection) {
|
|
1970
|
+
if (!Array.isArray(objectCollection)) throw Error('objectCollectionToArray: expected collection to be a collection of objects');
|
|
1971
|
+
|
|
1972
|
+
//-- create the result array in advance for performance
|
|
1973
|
+
// https://stackoverflow.com/questions/35578478/array-prototype-fill-with-object-passes-reference-and-not-new-instance
|
|
1974
|
+
|
|
1975
|
+
const keys = ObjectUtils.keys(objectCollection);
|
|
1976
|
+
|
|
1977
|
+
const finalResult = new Array(objectCollection.length + 1);
|
|
1978
|
+
finalResult[0] = keys;
|
|
1979
|
+
|
|
1980
|
+
for (let i = 1; i <= objectCollection.length; i += 1) {
|
|
1981
|
+
finalResult[i] = new Array(keys.length);
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
objectCollection.forEach((obj, objIndex) => {
|
|
1985
|
+
const rowResult = finalResult[objIndex + 1];
|
|
1986
|
+
keys.forEach((key, keyIndex) => {
|
|
1987
|
+
rowResult[keyIndex] = obj[key];
|
|
1988
|
+
});
|
|
1989
|
+
});
|
|
1990
|
+
return finalResult;
|
|
1991
|
+
};
|
|
1992
|
+
|
|
1993
|
+
/**
|
|
1994
|
+
* Convert a DataFrame Object into a collection of objects.
|
|
1995
|
+
*
|
|
1996
|
+
* This uses properties with 1d tensor lists
|
|
1997
|
+
* and converts them to a list of objects.
|
|
1998
|
+
*
|
|
1999
|
+
* ```
|
|
2000
|
+
* const weather = {
|
|
2001
|
+
* id: [1, 0, 2],
|
|
2002
|
+
* city: ['Seattle', 'Seattle', 'Seattle'],
|
|
2003
|
+
* month: ['Aug', 'Apr', 'Dec'],
|
|
2004
|
+
* precip: [0.87, 2.68, 5.31]
|
|
2005
|
+
* };
|
|
2006
|
+
*
|
|
2007
|
+
* ObjectUtils.objectCollectionFromDataFrameObject(weather);
|
|
2008
|
+
* // [
|
|
2009
|
+
* // { id: 1, city: 'Seattle', month: 'Aug', precip: 0.87 },
|
|
2010
|
+
* // { id: 0, city: 'Seattle', month: 'Apr', precip: 2.68 },
|
|
2011
|
+
* // { id: 2, city: 'Seattle', month: 'Dec', precip: 5.31 }
|
|
2012
|
+
* // ];
|
|
2013
|
+
* ```
|
|
2014
|
+
*
|
|
2015
|
+
* @param {Object} dataFrameObject - Object with properties holding 1d tensor arrays
|
|
2016
|
+
* @returns {Object[]} - collection of objects
|
|
2017
|
+
* @see {@link module:object.objectCollectionFromArray|objectCollectionFromArray}
|
|
2018
|
+
* @see {@link module:object.objectCollectionToArray|objectCollectionToArray}
|
|
2019
|
+
* @see {@link module:object.objectCollectionFromDataFrameObject|objectCollectionFromDataFrameObject}
|
|
2020
|
+
* @see {@link module:object.objectCollectionToDataFrameObject|objectCollectionToDataFrameObject}
|
|
2021
|
+
* @see {@link https://danfo.jsdata.org/api-reference/dataframe/creating-a-dataframe#creating-a-dataframe-from-an-object|Danfo DataFrame Objects}
|
|
2022
|
+
*/
|
|
2023
|
+
module.exports.objectCollectionFromDataFrameObject = function objectCollectionFromDataFrameObject(dataFrameObject) {
|
|
2024
|
+
if (!dataFrameObject) return [];
|
|
2025
|
+
if ((typeof dataFrameObject) !== 'object') throw Error('objectCollectionFromDataFrameObject must be passed an object with properties holding 1d tensors');
|
|
2026
|
+
|
|
2027
|
+
const fields = ObjectUtils.keys(dataFrameObject);
|
|
2028
|
+
|
|
2029
|
+
if (fields.length < 1) return [];
|
|
2030
|
+
|
|
2031
|
+
const len = fields.reduce((maxLen, field) => {
|
|
2032
|
+
const list = dataFrameObject[field];
|
|
2033
|
+
if (!Array.isArray(list)) return maxLen;
|
|
2034
|
+
|
|
2035
|
+
const listLength = list?.length || 0;
|
|
2036
|
+
return listLength > maxLen ? listLength : maxLen;
|
|
2037
|
+
}, -1);
|
|
2038
|
+
|
|
2039
|
+
// console.log(`fieldLength:${len}`);
|
|
2040
|
+
const results = new Array(len).fill(0).map((_) => ({}));
|
|
2041
|
+
|
|
2042
|
+
fields.forEach((field) => {
|
|
2043
|
+
const fieldArray = dataFrameObject[field];
|
|
2044
|
+
if (Array.isArray(fieldArray)) {
|
|
2045
|
+
fieldArray.forEach((fieldValue, rowIndex) => {
|
|
2046
|
+
results[rowIndex][field] = fieldValue;
|
|
2047
|
+
});
|
|
2048
|
+
}
|
|
2049
|
+
});
|
|
2050
|
+
|
|
2051
|
+
return results;
|
|
2052
|
+
};
|
|
2053
|
+
|
|
2054
|
+
/**
|
|
2055
|
+
* Convert a DataFrame Object into a collection of objects.
|
|
2056
|
+
*
|
|
2057
|
+
* This uses properties with 1d tensor lists
|
|
2058
|
+
* and converts them to a list of objects.
|
|
2059
|
+
*
|
|
2060
|
+
* ```
|
|
2061
|
+
* const weather = [
|
|
2062
|
+
* { id: 1, city: 'Seattle', month: 'Aug', precip: 0.87 },
|
|
2063
|
+
* { id: 0, city: 'Seattle', month: 'Apr', precip: 2.68 },
|
|
2064
|
+
* { id: 2, city: 'Seattle', month: 'Dec', precip: 5.31 }
|
|
2065
|
+
* ];
|
|
2066
|
+
*
|
|
2067
|
+
* ObjectUtils.objectCollectionToDataFrameObject(weather);
|
|
2068
|
+
* // {
|
|
2069
|
+
* // id: [1, 0, 2],
|
|
2070
|
+
* // city: ['Seattle', 'Seattle', 'Seattle'],
|
|
2071
|
+
* // month: ['Aug', 'Apr', 'Dec'],
|
|
2072
|
+
* // precip: [0.87, 2.68, 5.31]
|
|
2073
|
+
* // };
|
|
2074
|
+
* ```
|
|
2075
|
+
*
|
|
2076
|
+
* @param {Object} dataFrameObject - Object with properties holding 1d tensor arrays
|
|
2077
|
+
* @returns {Object[]} - collection of objects
|
|
2078
|
+
* @see {@link module:object.objectCollectionFromArray|objectCollectionFromArray}
|
|
2079
|
+
* @see {@link module:object.objectCollectionToArray|objectCollectionToArray}
|
|
2080
|
+
* @see {@link module:object.objectCollectionFromDataFrameObject|objectCollectionFromDataFrameObject}
|
|
2081
|
+
* @see {@link module:object.objectCollectionToDataFrameObject|objectCollectionToDataFrameObject}
|
|
2082
|
+
* @see {@link https://danfo.jsdata.org/api-reference/dataframe/creating-a-dataframe#creating-a-dataframe-from-an-object|Danfo DataFrame Objects}
|
|
2083
|
+
*/
|
|
2084
|
+
module.exports.objectCollectionToDataFrameObject = function objectCollectionToDataFrameObject(objectCollection, fields = null) {
|
|
2085
|
+
const dataFrameObject = {};
|
|
2086
|
+
|
|
2087
|
+
const cleanFields = fields || ObjectUtils.keys(objectCollection);
|
|
2088
|
+
const length = objectCollection?.length || 0;
|
|
2089
|
+
|
|
2090
|
+
cleanFields.forEach((field) => {
|
|
2091
|
+
const fieldData = new Array(length).fill(undefined);
|
|
2092
|
+
dataFrameObject[field] = fieldData;
|
|
2093
|
+
|
|
2094
|
+
objectCollection.forEach((obj, index) => {
|
|
2095
|
+
fieldData[index] = obj[field];
|
|
2096
|
+
});
|
|
2097
|
+
});
|
|
2098
|
+
return dataFrameObject;
|
|
2099
|
+
};
|