'Group by array with key with another array of values

I have the following object:

var series = [{
    name: 'Series 1',
    data: [1400, 999, 450],
    tag: "Tag 1"
}, {
    name: 'Series 2',
    data: [355, 188, 99],
    tag: "Tag 1"
}, {
    name: 'Series 3',
    data: [205, 488, 104],
    tag: "Tag 2"
}];

What I'm trying to do is to perform a groupBy function to that array so for example, calling orderBy("tag") should return the following output:

[{
    name: "Series 1 / Series 2",
    data: [1755, 1187, 549], // => Sum: Series1[0] + Series2[0], Series1[1] + Series2[1], etc...
    tag: "Tag 1"
},  {
    name "Series 3",
    data: [205, 488, 104],
    tag: "Tag 2"
}]

At the moment this is what I've so far:

var seriesArray = [{
            name: 'Series 1',
            data: [1400, 999, 450],
            tag: "Tag 1"
        }, {
            name: 'Series 2',
            data: [355, 188, 99],
            tag: "Tag 1"
        }, {
            name: 'Series 3',
            data: [205, 488, 104],
            tag: "Tag 2"
        }];

const groupBy = (key) => seriesArray.reduce((total, currentValue) => {
  const newTotal = total;
  if (
    total.length &&
    total[total.length - 1][key] === currentValue[key]
  )
    newTotal[total.length - 1] = {
      ...total[total.length - 1],
      ...currentValue,
      data: parseInt(total[total.length - 1].data[0]) + parseInt(currentValue.data[0]),
    };
  else newTotal[total.length] = currentValue;
  return newTotal;
}, []);

console.log(groupBy('tag'));

As you can see it seems to work the orderBy("tag") but I'm doing the sum operation explicit, the main problem with this is that data[] could have X elements.



Solution 1:[1]

Good challenge, this occurs to me with findIndex

const groupBy = (key) => seriesArray.reduce((total, currentValue) => {
  index = total.findIndex(x => x[key] === currentValue[key])
  if (index >= 0) {
    total[index].data = total[index].data.map(function (num, idx) {
      return num + parseInt(currentValue.data[idx]);
    }); 
    total[index].name += " / " + currentValue.name;
  } else {
    total.push(currentValue);
  }
  return total;
}, []);

Solution 2:[2]

I think the logic in your reduce callback function is not going to give you the result you want. I'll explain what that function is doing.

  1. First iteration total is an empty array as this is the initial value so the conditions is false and the else block is executed and adding the first element of the array to the new array
  2. Second iteration total now has one element which has the same tag as the current element so if statement is true and code is executed which would override the first element, which is why name becomes Serie 2 instead of Serie 1 and data is a number instead of an array with the value equals to the sum of current.data[0] + total[0].data[0] = 1755
  3. Third iteration if statement does not match and else block is executed adding the third element to total array. Also note that using total.length -1 is not covering the case when the second item doesn't actually match if you try to switch the order between serie 2 and serie 3 you will not get serie 1 and serie 2 grouped by tag.

What you want to do

var seriesArray = [{
    name: 'Series 1',
    data: [1400, 999, 450],
    tag: "Tag 1"
  },
  {
    name: 'Series 3',
    data: [1400, 999, 450],
    tag: "Tag 2"
  }, {
    name: 'Series 2',
    data: [355, 188, 99],
    tag: "Tag 1"
  },
];

const isMatchingArray = (array1, array2) => {
  if (array1.length !== array2.length) {
    return false;
  }
  for (let i = 0; i < array1.length; i++) {
    if (array1[i] !== array2[i]) {
      return false;
    }
  }
  return true;
}

const groupBy = (key, array) => array.reduce((total, currentValue) => {
  const newTotal = total;
  if (total.length) {
    const matchingItemIndex = total.findIndex(element => {
      if (typeof element[key] === 'string') {
        return element[key] === currentValue[key];
      } else if (Array.isArray(element[key])) {
        return isMatchingArray(element[key], currentValue[key]);
      }
    });
    if (matchingItemIndex !== -1) {
      newTotal[matchingItemIndex] = {
        ...total[matchingItemIndex],
        ...currentValue,
        name: total[matchingItemIndex].name + ' / ' + currentValue.name,
        data: currentValue.data.map((dataItem, index) => parseInt(dataItem + total[matchingItemIndex].data[index])),
      };
    } else {
      newTotal.push(currentValue)
    }
  } else {
    newTotal.push(currentValue)
  };
  return newTotal;
}, []);

console.log(groupBy('tag', seriesArray))
console.log(groupBy('data', seriesArray));

Solution 3:[3]

const series = [
  {
    name: 'Series 1',
    data: [1400, 999, 450],
    tag: 'Tag 1',
  },
  {
    name: 'Series 2',
    data: [355, 188, 99],
    tag: 'Tag 1',
  },
  {
    name: 'Series 3',
    data: [205, 488, 104],
    tag: 'Tag 2',
  },
];
const groupBykeyAndSum = (key, series) => {
  const grouped = series.reduce((acc, serie) => {
    const group = acc.find(g => g.tag === serie[key]);
    if (group) {
      group.name += ` / ${serie.name}`;
      group.data = group.data.map((d, i) => d + serie.data[i]);
    } else {
      acc.push({
        name: serie.name,
        data: serie.data,
        tag: serie[key],
      });
    }
    return acc;
  }, []);
  return grouped;
};
console.log(groupBykeyAndSum('tag', series));

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Alex Alvarado
Solution 2 Neveen Atik
Solution 3 Parvesh Kumar