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 CHANGED
@@ -74,7 +74,11 @@ Give it a try here:
74
74
  [![Binder:what can I do with this](https://mybinder.org/badge_logo.svg)](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
@@ -1,3 +1,3 @@
1
1
  # syntax=docker/dockerfile:1
2
2
 
3
- FROM darkbluestudios/jupyter-ijavascript-utils:binder_1.45.0
3
+ FROM darkbluestudios/jupyter-ijavascript-utils:binder_1.47.0
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
  ![Screenshot of example notebook](docResources/img/mainExampleNotebook.png)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jupyter-ijavascript-utils",
3
- "version": "1.45.0",
3
+ "version": "1.47.0",
4
4
  "description": "Utilities for working with iJavaScript - a Jupyter Kernel",
5
5
  "homepage": "https://jupyter-ijavascript-utils.onrender.com/",
6
6
  "license": "MIT",
@@ -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
- const record = this.#data[rowIndex];
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 [[...results.headers], ...results.data];
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
+ };