Search code examples
javascriptarraysmultidimensional-arrayconditional-statementssum

Sum multiple items in 2D array based on condition (javascript)


I have a 2D array:

[ [ 1, 2, 43, 5 ],
  [ 1, 5, 12, 1 ],
  [ 2, 3, 6, 77 ],
  [ 2, 1, 48, 94 ],
  [ 3, 3, 15, 85 ],
  [ 3, 7, 97, 86 ],
  [ 4, 0, 9, 54 ],
  [ 4, 1, 83, 16 ]]

This is the result I'm after:

[ [ 1, 7, 55, 6 ],
  [ 2, 4, 54, 171 ],
  [ 3, 10, 112, 171 ],
  [ 4, 1, 92, 70 ]]

Match the first index within each array, and sum the other indexes.

My first thought was to use reduce and findIndex to find the index of the first item, add item to array if not found, otherwise sum the values.

But I really don't know what I'm doing.

array.reduce((acc,e) => 
      { let index = acc.findIndex(inner => inner[0] === e[0]);
      (index === -1) ? [...acc,e] : (acc[index][1] += e[1]) && (acc[index][2] += e[2]) && (acc[index][3] += e[3]);
      return acc},[]);

Help please!

EDIT: I'm learning! A simple change to my attempt and it worked, thanks for pointing out my error @mykaf and @trincot.

Change made to update accumulator [...acc,e] to acc.push(e)

  const input = [ [ 1, 2, 43, 5 ],
              [ 1, 5, 12, 1 ],
              [ 2, 3, 6, 77 ],
              [ 2, 1, 48, 94 ],
              [ 3, 3, 15, 85 ],
              [ 3, 7, 97, 86 ],
              [ 4, 0, 9, 54 ],
              [ 4, 1, 83, 16 ]];

  const result = input.reduce((acc,e) => 
      { let index = acc.findIndex(inner => inner[0] === e[0]);
      (index === -1) ? acc.push(e) : (acc[index][1] += e[1]) && (acc[index][2] += e[2]) && (acc[index][3] += e[3]);
      return acc;
      },[]);

  console.log(result);

Read on for some excellent/better answers to the problem.


Solution

  • Using reduce is a good way to do it, but in your attempt you never update the accumulator acc: the callback always returns the same, unmutated acc array.

    I would however suggest using a Map or a plain object as your accumulator, so that you don't need to scan an array with findIndex, but can immediately look up by key. Then when the reducing is complete, you can extract the values from the result.

    Here is how that would work:

    const arr = [ [ 1, 2, 43, 5 ],
                  [ 1, 5, 12, 1 ],
                  [ 2, 3, 6, 77 ],
                  [ 2, 1, 48, 94 ],
                  [ 3, 3, 15, 85 ],
                  [ 3, 7, 97, 86 ],
                  [ 4, 0, 9, 54 ],
                  [ 4, 1, 83, 16 ]];
    
    const result = Object.values(
        arr.reduce((acc, [key, ...rest]) => {
            if (!acc[key]) acc[key] = [key, ...rest];
            else rest.forEach((val, i) => acc[key][i+1] += val);
            return acc;
        }, {})
    );
    console.log(result);

    If you want to optimize for speed, then you're better off with plain old for loops, and to support any data type for the key column, you would use a Map:

    const arr = [ [ 1, 2, 43, 5 ],
                  [ 1, 5, 12, 1 ],
                  [ 2, 3, 6, 77 ],
                  [ 2, 1, 48, 94 ],
                  [ 3, 3, 15, 85 ],
                  [ 3, 7, 97, 86 ],
                  [ 4, 0, 9, 54 ],
                  [ 4, 1, 83, 16 ]];
    
    const acc = new Map;
    for (let i = 0; i < arr.length; i++) {
        const key = arr[i][0];
        let sub = acc.get(key);
        if (!sub) acc.set(key, arr[i]);
        else {
            const add = arr[i];
            for (let j = 1; j < sub.length; j++) {
                sub[j] += add[j];
            }
        }
    }
        
    const result = [...acc.values()];
    console.log(result);

    This faster version mutates the input array. If you don't want that, you'll need to spend some processing time to create a new sub array by doing a spread like acc.set(key, [...arr[i]]);