Home > OS >  How to reduce a double nested array in Javascript
How to reduce a double nested array in Javascript

Time:05-10

I have an array that looks like this:

enter image description here

So as a strucutre it would be something like:

[
    [
       { classNumber: '2', status: 'A', terms: [] }, 
       { classNumber: '32', status: 'B', terms: [] }, 
       { classNumber: '44', status: 'C', terms: []  }
    ],
    [
        { classNumber: '2', status: 'B', terms: [] }, 
        { classNumber: '31', status: 'A', terms: [] }
    ],
    ....
]

This wierd array of objects happens because at some point, in our app, we are creating an array of reasons to object something using the same object.

I need to be able to merge the nested array of objects to look like this:

[
     { classNumber: '2', status: [ 'A', 'B' ], terms: [] }, 
     { classNumber: '31', status: [ 'A' ], terms: [] }, 
     { classNumber: '32', status: [ 'B' ], terms: [] }, 
     { classNumber: '44', status: [ 'C' ], terms: [] }
]

But I've been struggling with this for some days, looking for some lodash functions but still no luck...

I'm completely lost on how to achieve this. All examples look simpler, with less nested arrays. Any idea on how to merge all props for the same object key?

Thanks a lot in advance.

CodePudding user response:

From the OP's own comment ...

"For all of them I'm planning to do a generic solution. In the status example, create an array with all the status (at least for now). But first I need to be able to merge that and then I'll decide if I make that operations more complex. Does it have sense?" – Sonhja

A generic solution which handles any (flattened) array item regardless of such an item's data structure most probably has to come up with a two folded approach ...

  • a generic reducer function which reduces the flattened array's data-items by grouping any data item which for a provided key (property name) features the same value.

  • a custom implemented merge function (which gets provided as part of the reduce method's initialValue) where one can decide how a data-item's entry (which is distinct from the grouping property name) is going to be merged into the target data structure.

// reducer function which generically
// - groups any data item by a provided key's same value
// - and merges all other entries via a custom merge function.
function groupBySameKeyValueAndMergeProperties(collector, item) {
  const { merge, key, lookup, result } = collector;
  const { [key]: groupValue, ...rest } = item;

  let groupItem = lookup.get(groupValue);
  if (!groupItem) {

    groupItem = { [key]: groupValue };

    lookup.set(groupValue, groupItem);
    result.push(groupItem);
  }
  merge(groupItem, rest);

  return collector;
}

// custom, task specific merge function, according to the OP's goal.
function mergeDataItemEntries(targetItem, sourceItem) {
  Object
    .entries(sourceItem)
    .reduce((target, [key, value], idx, arr) => {

      if (target.hasOwnProperty(key)) {

        // collect value of currently processed entry.
        target[key].push(value);

      } else {

        // initial (one time) array initialization
        // in order to later collect all other values.
        target[key] = [value];
      }
      // here, maybe even a final treatment
      // for the 2 dimensional `terms` array.
      if (idx >= arr.length - 1) {

        // - flattening of the 2 dimensional `terms` array.
        // - a `Set` instance always assures unique values.
        target.terms = [...new Set(target.terms.flat())];
      }
      return target;

    }, targetItem);
}

const sampleData = [
  [{
    classNumber: '2', status: 'A', terms: ['foo', 'bar'],
  }, {
    classNumber: '32', status: 'B', terms: ['baz'],
  }, {
    classNumber: '44', status: 'C', terms: ['bizz'],
  }], [{
    classNumber: '2', status: 'B', terms: ['bar', 'baz'],
  }, {
    classNumber: '31', status: 'A', terms: ['buzz'],
  }],
];
console.log(
  'flattened, merged and sorted data items ...',
  sampleData
    .flat()
    .reduce(groupBySameKeyValueAndMergeProperties, {

      // task specific reduce configuration.
      merge: mergeDataItemEntries,
      key: 'classNumber',
      lookup: new Map,
      result: [],
    })
    .result
    .sort((a, b) =>
      Number(a.classNumber) - Number(b.classNumber)
    )
);
console.log('original data structure ...', sampleData);

console.log(
  '... Bonus .. the pure grouping result without merging and sorting ...',
  sampleData
    .flat()
    .reduce(groupBySameKeyValueAndMergeProperties, {

      merge: (_ => _),
      key: 'classNumber',
      lookup: new Map,
      result: [],

    }).result
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

CodePudding user response:

If I'm not missing something, you are looking for this:?

var merged = Array.prototype.concat.apply([], original);

like:

Array.prototype.concat.apply([], [[1,2,3],[4,5], [6]]);
// returns:
// [1, 2, 3, 4, 5, 6]

Another way:

var merged = [];
for(var i = 0; i<original.length; i  ) {
  for(var j = 0, arr = original[i]; j<arr.length; j  ) {
    merged.push(arr[j]);
  }
}
    

CodePudding user response:

I made it quick but something like this should do the job

const doubleNestedArray = [
  [{
    classNumber: '2', status: 'A', terms: ['foo', 'bar'],
  }, {
    classNumber: '32', status: 'B', terms: ['baz'],
  }, {
    classNumber: '44', status: 'C', terms: ['bizz'],
  }], [{
    classNumber: '2', status: 'B', terms: ['bar', 'baz'],
  }, {
    classNumber: '31', status: 'A', terms: ['buzz'],
  }],
];
console.log(
  _.chain(doubleNestedArray)
    .flatten()
    .groupBy(element => element.classNumber)
    .map((value) => ({
        ...value[0],
        status: value.map(element => element.status)
    }))
    .value()
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>

  • Related