1

Input -

[
  {color:'red', shape:'circle', size:'medium'},
  {color:'red', shape:'circle', size:'small'}
]

Output -

[
  {color:'red', shape:'circle', size:['medium','small']}
]

How can this be achieved in Javascript?

Gokkul
  • 74
  • 1
  • 1
  • 7
  • Would you want [ {color:'red', shape:'square', size:'small'}, {color:'red', shape:'circle', size:'small'} ] to output [ {color:'red', shape:['circle', 'square'], size:'small'} ] and how would you handle the case where there are three objects, and all of them match two thing in common, but not the same two things? Will it only ever be two items? – Cal Irvine May 30 '19 at 16:02
  • Yes, I am looking for an ideal solution that can handle duplicate properties in the objects. – Gokkul May 30 '19 at 16:10
  • 1
    But, then, as @Cal Irvine says, what do you expect to get as output when input is something like this: `[ {color:'red', shape:'circle', size:'medium'}, {color:'red', shape:'circle', size:'small'}, {color:'red', shape:'square', size:'small'}, {color:'blue', shape:'circle', size:'medium'}]`? How will you group the items? – Shidersz May 30 '19 at 16:15
  • 1
    Possible duplicate of [Group array items using object](https://stackoverflow.com/questions/31688459/group-array-items-using-object) – Heretic Monkey May 30 '19 at 16:28
  • In that case I would expect an output of [ {color:'red', shape:'circle', size:['medium', 'small']}, {color:'red', shape:'square', size:'small'}, {color:'blue', shape:'circle', size:'medium'}] – Gokkul May 30 '19 at 17:39

3 Answers3

1

You can create a general function which takes an array of objects and array of keys to match as its parameters.

You can do that in following steps:

  • First use reduce() on the array of objects and set accumulator to empty array []
  • Get the other props(unique props) by using filter() on Object.keys()
  • Then in each iteration find the element of the accumulator array whose all the given props matches with the current object.
  • If the element is found then use forEach() other keys and push the values to to corresponding array.
  • If element is not found then set each key to an empty array.
  • At last return the result of reduce()

const arr = [
  {color:'red', shape:'circle', size:'medium'},
  {color:'red', shape:'circle', size:'small'}
]

function groupByProps(arr,props){
  const res = arr.reduce((ac,a) => {
    let ind = ac.findIndex(b => props.every(k => a[k] === b[k]));
    let others = Object.keys(a).filter(x => !props.includes(x));
    if(ind === -1){
      ac.push({...a});
      others.forEach(x => ac[ac.length - 1][x] = []);
      ind = ac.length - 1
    }
    others.forEach(x => ac[ind][x].push(a[x]));
    return ac;
  },[])
  return res;
}

const res = groupByProps(arr,['color','shape'])
console.log(res)
Maheer Ali
  • 35,834
  • 5
  • 42
  • 73
1

If you just want to group based on color and shape, you could use reduce. Create an accumulator object with each unique combination of those 2 properties separated by a | as key. And the object you want in the output as their value. Then use Object.values() to get those objects as an array.

const input = [
  {color:'red', shape:'circle', size :'medium'},
  {color:'red', shape:'circle', size:'small'},
  {color:'blue', shape:'square', size:'small'}
];

const merged = input.reduce((acc, { color, shape, size }) => {
  const key = color + "|" + shape;
  acc[key] = acc[key] || { color, shape, size: [] };
  acc[key].size.push(size);
  return acc
}, {})

console.log(Object.values(merged))

This is what the merged/accumulator looks like:

{
  "red|circle": {
    "color": "red",
    "shape": "circle",
    "size": [
      "medium",
      "small"
    ]
  },
  "blue|square": {
    "color": "blue",
    "shape": "square",
    "size": [
      "small"
    ]
  }
}

You can make it dynamic by creating an array of keys you'd want to group by:

const input = [
      { color: 'red', shape: 'circle', size: 'medium' },
      { color: 'red', shape: 'circle', size: 'small' },
      { color: 'blue', shape: 'square', size: 'small' }
   ];
const groupKeys = ['color', 'shape'];

const merged = input.reduce((acc, o) => {
  const key = groupKeys.map(k => o[k]).join("|");
  
  if (!acc[key]) {
    acc[key] = groupKeys.reduce((r, k) => ({ ...r, [k]: o[k] }), {});
    acc[key].size = []
  }
  
  acc[key].size.push(o.size)
  return acc
}, {})

console.log(Object.values(merged))
adiga
  • 34,372
  • 9
  • 61
  • 83
1

Here is a simple groupBy function which accepts an array of objects and array of props to group on:

let data = [
  {color:'red', shape:'circle', size:'medium'},
  {color:'red', shape:'circle', size:'small'}
]

let groupBy = (arr, props) => Object.values(arr.reduce((r,c) => {
  let key = props.reduce((a,k) => `${a}${c[k]}`, '')
  let otherKeys = Object.keys(c).filter(k => !props.includes(k))
  r[key] = r[key] || {...c, ...otherKeys.reduce((a,k) => (a[k] = [], a),{})}
  otherKeys.forEach(k => r[key][k].push(c[k]))
  return r
}, {}))

console.log(groupBy(data, ['color','shape']))

The idea is to use Array.reduce and basically create a string key of the passed in props. For the other fields create an array and keep pushing values there on each iteration.

Akrion
  • 18,117
  • 1
  • 34
  • 54