Home > Back-end >  Group by array with key with another array of values
Group by array with key with another array of values

Time:02-17

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.

CodePudding user response:

Currently your approach assumes that the last item that was added to your total array has the same key that you're trying to group on. This is all good if the data is sorted by key (ie: all "Tag 1"s come first), but can be an issue if its not. I would suggest that instead, you first build an object/Map that is keyed by tag. That way you can use the object to find your accumulated "Tag 1", "Tag 2", etc... objects. Once you've reduced it to an object/Map, you can grab the values (which are the accumulated objects) and put them into an array. To handle the different data array lengths, you can use .map() on your accumulated data array (ie: seen.data) and then add each number with the corresponding number from the current object's data array using the index argument i:

const 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 = (arr, key) => Array.from(arr.reduce((map, currentObj) => {
  const groupOnKey = currentObj[key];
  const seen = map.get(groupOnKey);
  return map.set(groupOnKey, seen ? {
    ...seen,
    name: seen.name   " / "   currentObj.name,
    data: seen.data.map((num, i) => num   currentObj.data[i], 0)
  } : currentObj);
}, new Map()).values());

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

CodePudding user response:

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;
}, []);

CodePudding user response:

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));

CodePudding user response:

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));

  • Related