How to group nested array in Javascript?

Viewed 74

Does anyone know how to group nested arrays in Javascript like this? (can be vanilla or lodash method) (I work on Reactjs)

from

[[ 5, '103' ], [ 3, '104' ], [ 1, '105' ], [ 1, '106' ], [ 2, '107' ], [ 1, '108' ], [ 5, '109' ], [ 3, '110' ]]

to

{5: ['103','109'], 3: ['104','110'], 2: ['107'], 1: ['105','106','108']}

or

[[5, ['103','109']], [3, ['104','110']], [2, ['107']], [1, ['105','106','108']]]

Thank you

4 Answers

I'm sure we can find a couple of lodahs functions that will solve the problem together. But I think the easiest way to go about is a custom Array reducer.

const array = [[ 5, '103' ], [ 3, '104' ], [ 1, '105' ], [ 1, '106' ], [ 2, '107' ], [ 1, '108' ], [ 5, '109' ], [ 3, '110' ]];

const result = array.reduce((acc, [key, value]) => ({
    ...acc,
    [key]: [
        ...acc[key] || [],
        value,
    ]
}), {});

console.log(result)

Use _.groupBy() the _.head() (the 1st element of the array), and then map the values and extract the last parameter from each of the sub-arrays in the group.

Note: since we're grouping the array into an object, and the keys are integers, the order of the keys would be numeric ascending.

const { mapValues, groupBy, head, map, last } = _;

const array = [[ 5, '103' ], [ 3, '104' ], [ 1, '105' ], [ 1, '106' ], [ 2, '107' ], [ 1, '108' ], [ 5, '109' ], [ 3, '110' ]];

const result = mapValues(
  groupBy(array, head),
  arr => map(arr, last)
);

console.log(result);
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js" integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

Without lodash you can reduce the array to a Map, and then convert the Map's to an array using Array.from() or to an object using Object.fromEntries().

Note: A Map preserves the order of insertion, even for integer keys, and converting the Map to an array of arrays would also maintain the original order.

const array = [[ 5, '103' ], [ 3, '104' ], [ 1, '105' ], [ 1, '106' ], [ 2, '107' ], [ 1, '108' ], [ 5, '109' ], [ 3, '110' ]];

const map = array.reduce((acc, [key, val]) => {
  if(!acc.has(key)) acc.set(key, []);
  
  acc.get(key).push(val);
  
  return acc;
}, new Map());

console.log(Array.from(map)); // convert the Map to an array

console.log(Object.fromEntries(map)) // convert the Map to an object
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js" integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

If you need to group like objects, try this

function group(inarr){
  let outarr = inarr.reduce((r,a)=>{
    if(!r[a[0]]) r[a[0]] = [];
    r[a[0]].push(a[1])
    return r;
  }, {});
  console.log(outarr)
}

It can be done in vanillaJS by looping the array in forEach loop and for each cycle add the value to the object.

const toBeGrouped = [[ 5, '103' ], [ 3, '104' ], [ 1, '105' ], [ 1, '106' ], [ 2, '107' ], [ 1, '108' ], [ 5, '109' ], [ 3, '110' ]]

let grouped = {};
let k;
let value;
toBeGrouped.forEach((item) => {
  k = item[0];
  value = item[1];
  //check if the object key exists, then check is an array, then check if the current value is in the 
  if (grouped[k] && Array.isArray(grouped[k]) &&  !grouped[k].includes(value)) {
    grouped[k].push(value);
  } else {
    grouped[k] = [value];
  }
});
console.log(grouped)

Related