Compare two objects in an array, delete the duplicate data, and combine the different data?

Viewed 277

I am getting a list of objects in an array, some of which are duplicates. However, the condition that produces the duplicates is different.

So picture an array:

var array = [{id: 1, name: 'test', condition: 'left'},
            {id: 2, name: 'example', condition: 'left'},
            {id: 1, name: 'test', condition: 'right'},
            {id: 3, name: 'foobar', condition: 'right'}]

What I am looking for:

var solution = [{id: 1, name: 'test', condition: ['left', 'right']},
                {id: 2, name: 'example', condition: 'left'},
                {id: 3, name: 'foobar', condition: 'right'}]

I am able to delete duplicates no problem using this method:

var available = result.reduce((unique, o) => {
  if (!unique.some((obj) => obj.id === o.id && obj.name === o.name)) {
    unique.push(o);
  }
  return unique;
}, []);

But would Like to combine the condition data

3 Answers

You could collect all with an object with combined keys.

const
    array = [{ id: 1, name: 'test', condition: 'left' }, { id: 2, name: 'example', condition: 'left' }, { id: 1, name: 'test', condition: 'right' }, { id: 3, name: 'foobar', condition: 'right' }],
    keys = ['id', 'name'],
    result = Object.values(array.reduce((r, o) => {
        const key = keys.map(k => o[k]).join('|');
        if (r[key]) r[key].condition = [].concat(r[key].condition, o.condition);
        else r[key] = { ...o };
        return r;
    }, {}));

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

You can accomplish this in two steps.

  1. Group the data by the id
  2. Merge values for each group by combining the values of their properties

const array = [
  { id: 1 , name: 'test'    , condition: 'left'  },
  { id: 2 , name: 'example' , condition: 'left'  },
  { id: 1 , name: 'test'    , condition: 'right' },
  { id: 3 , name: 'foobar'  , condition: 'right' }
];

const groupBy = (key, objs) => objs.reduce((acc, obj) =>
  ({ ...acc, [obj[key]]: [...(acc[obj[key]] || []), obj] }), {});

const combine = (newVal, oldVal) =>
 oldVal != null
  ? Array.isArray(oldVal)
    ? !oldVal.includes(newVal)
      ? [ ...oldVal, newVal ]
      : oldVal
    : oldVal !== newVal
      ? [ oldVal, newVal ]
      : oldVal
  : newVal;

const merge = (objs) => objs.reduce((acc, obj) =>
  Object.keys(obj).reduce((acc1, key) =>
    ({ ...acc1, [key]: combine(obj[key], acc1[key]) }), acc), {});

const groupByAndMerge = (key, items) =>
  Object.values(groupBy(key, items)).map(merge);

console.log(groupByAndMerge('id', array));
.as-console-wrapper { top: 0; max-height: 100% !important; }

Code golf

I reduced the function to a single-line statement with a size of 259 bytes.

// Size: 259 bytes
f=(k,a)=>Object.values(a.reduce((r,i)=>({...r,[i[k]]:[...(r[i[k]]||[]),i]}),{})).map(g=>g.reduce((s,o)=>Object.keys(o).reduce((t,m)=>({...t,[m]:t[m]!=null?Array.isArray(t[m])?!t[m].includes(o[m])?[...t[m],o[m]]:t[m]:t[m]!==o[m]?[t[m],o[m]]:t[m]:o[m]}),s),{}))

console.log(f('id',[{id:1,name:'test',condition:'left'},{id:2,name:'example',condition:'left'},{id:1,name:'test',condition:'right'},{id:3,name:'foobar',condition:'right'}]));
.as-console-wrapper { top: 0; max-height: 100% !important; }

Here's a concise solution with 4 steps that only checks for unique IDs:

  1. find the unique IDs
  2. for each unique ID, find the values of that ID
  3. for each group of values with the same ID, find the unique conditions
  4. for each group of values with the same ID, create 1 output value with the unique ID, name, and conditions

let array = [{id: 1, name: 'test', condition: 'left'},
            {id: 2, name: 'example', condition: 'left'},
            {id: 1, name: 'test', condition: 'right'},
            {id: 3, name: 'foobar', condition: 'right'}];

let groupedById = [...new Set(array.map(v => v.id))]
  .map(id => {
    let valuesWithId = array.filter(v => v.id === id);
    let condition = [...new Set(valuesWithId.map(v => v.condition))];
    return { ...valuesWithId[0], condition};
  });

console.log(groupedById);

If you really have data that has values of the same ID but different names, this can be expanded to:

  1. Iterate the input array, and for each value:
  2. If it has an unseen ID & name combination, add it to the output (after transforming condition to an array).
  1. If it has a seen ID & name combination, add it's condition to its existing counterpart (if it's not present already).

let array = [{id: 1, name: 'test', condition: 'left'},
            {id: 2, name: 'example', condition: 'left'},
            {id: 1, name: 'test', condition: 'right'},
            {id: 3, name: 'foobar', condition: 'right'}];

let groupedByIdAndName = [];
array.forEach(value => {
  let duplicate = groupedByIdAndName.find(v => v.id === value.id && v.name === value.name);
  if (!duplicate)
    groupedByIdAndName.push({...value, condition: [value.condition]});   
  else if (duplicate.condition.every(c => c !== value.condition))
    duplicate.condition.push(value.condition);  
});

console.log(groupedByIdAndName);

Related