Esszed
Esszed

Reputation: 607

Find duplicate objects in array and return new array of object with number of duplicates as a new property

so I have an array of objects that goes for example like this:

let arrayOfObjects = [
  {
    Name: "Apple",
    Type: "Fruit"
  },
  {
    Name: "Carrot",
    Type: "Vegetable"
  },
  {
    Name: "Carrot",
    Type: "Vegetable"
  },
  {
    Name: "Carrot",
    Type: "Vegetable"
  },
  {
    Name: "Apple",
    Type: "Fruit"
  },
  {
    Name: "Apple",
    Type: "Fruit"
  },
  {
    Name: "Carrot",
    Type: "Vegetable"
  }
];

And I need to go through it, find duplicates and reuturn only one object for each duplicate but with number of duplicates as a new parameter.

So like this:

let newArrayOfObjects = [
  {
    Name: "Apple",
    Type: "Fruit",
    times: 3,
  },
  {
    Name: "Carrot",
    Type: "Vegetable",
    times: 4,
  }
];

I can count duplicate objects based on one parameter but I don't know how to do based on whole object. What is the best way to solve this?

Upvotes: 5

Views: 12080

Answers (6)

Juan Pablo
Juan Pablo

Reputation: 408

I had a look at these answers and think I have a shorter way.

If you have your array

let arrayOfObjects = [
  {
    Name: "Apple",
    Type: "Fruit"
  },
  {
    Name: "Carrot",
    Type: "Vegetable"
  },
  {
    Name: "Carrot",
    Type: "Vegetable"
  },
  {
    Name: "Carrot",
    Type: "Vegetable"
  },
  {
    Name: "Apple",
    Type: "Fruit"
  },
  {
    Name: "Apple",
    Type: "Fruit"
  },
  {
    Name: "Carrot",
    Type: "Vegetable"
  }
];

You could create a a function called hasDuplicates(array) which creates a Set and compares the length of that Set to the length of the input array.

  function hasDuplicates(array) {
    return (new Set(array)).size !== array.length;
  }

Finally, call the hasDuplicates(), but use the map function to concatenate the both values as this function won't work for an array of objects, just an array of strings or integers.

const dups = hasDuplicates(arrayOfObjects.map(v => v.Name + v.Type))

Upvotes: 0

Goran Stoyanov
Goran Stoyanov

Reputation: 2311

You can do it with a combination of .map on the original array and searching through the items of the newly formed array with .find.

    let arrayOfObjects = [
    {
      Name: "Apple",
      Type: "Fruit"
    },
    {
      Name: "Carrot",
      Type: "Vegetable"
    },
    {
      Name: "Carrot",
      Type: "Vegetable"
    },
    {
      Name: "Carrot",
      Type: "Vegetable"
    },
    {
      Name: "Apple",
      Type: "Fruit"
    },
    {
      Name: "Apple",
      Type: "Fruit"
    },
    {
      Name: "Carrot",
      Type: "Vegetable"
    }
  ];

const resultArray = [];

arrayOfObjects.map(item => {
    //for each item in arrayOfObjects check if the object exists in the resulting array
    if(resultArray.find(object => {
        if(object.Name === item.Name && object.Type === item.Type) {
            //if the object exists iterate times
            object.times++;
            return true;
            //if it does not return false
        } else {
            return false;
        }
    })){
    } else {
        //if the object does not exists push it to the resulting array and set the times count to 1
        item.times = 1;
        resultArray.push(item);
    }
})

console.log(resultArray)

UPDATE:

It's better to use .reduce instead of .map as the latter is primarily used to apply a certain function on each element of an array and get an array of function execution results.

Array .reduce() method, on the other hand, is executing a function (callback) on each item too but it's passing the result from one element array to another. So we can use an empty array as a start and fill it with an object or iterate .times property in case the current element is existing in the accumulating array passed to each callback.

Here's an example:

let arrayOfObjects = [
  {
    Name: "Apple",
    Type: "Fruit"
  },
  {
    Name: "Carrot",
    Type: "Vegetable"
  },
  {
    Name: "Carrot",
    Type: "Vegetable"
  },
  {
    Name: "Carrot",
    Type: "Vegetable"
  },
  {
    Name: "Apple",
    Type: "Fruit"
  },
  {
    Name: "Apple",
    Type: "Fruit"
  },
  {
    Name: "Carrot",
    Type: "Vegetable"
  }
];


const newArrayOfObjects = 
  arrayOfObjects.reduce((accumulator, object) => {
    if(objectFound = accumulator.find(arrItem => arrItem.Name === object.Name && arrItem.Type === object.Type)) {
        objectFound.times++;
    } else {
        object.times = 1;
        accumulator.push(object);
    }
    return accumulator;
  }, []);

console.log(newArrayOfObjects);

Upvotes: 10

mike510a
mike510a

Reputation: 2168

Just do a mapping operation with a nested filter operation to get the number of times duplicate objects appear. Easy.

let arrayOfObjects = [{
    Name: "Apple",
    Type: "Fruit"
}, {
    Name: "Carrot",
    Type: "Vegetable"
}, {
    Name: "Carrot",
    Type: "Vegetable"
}, {
    Name: "Carrot",
    Type: "Vegetable"
}, {
    Name: "Apple",
    Type: "Fruit"
}, {
    Name: "Apple",
    Type: "Fruit"
}, {
    Name: "Carrot",
    Type: "Vegetable"
}];
var returnedArray = [];

function countNumberIn(obj, array) {
    return array.filter(function(object) {
        return ((object.Name == obj.Name) && (object.Type == obj.Type))
    }).length;
}
arrayOfObjects.map(function(obj) {
    obj.count = countNumberIn(obj, arrayOfObjects);
    return obj;
}).forEach(function(obj) {
    if (countNumberIn(obj, returnedArray) == 0) {
        returnedArray.push(obj);
    }
});
console.log(returnedArray);

Upvotes: 0

NegativeFriction
NegativeFriction

Reputation: 576

I think you'd be best suited by creating a helper object. A helper object will initially be empty, but will become populated slowly by what you're reading through. I'm going to assume that the keys in your array are consistent.

const keys = ["Name","Type"]
var counterObj = {}
let keyForCounterObj
arrayOfObjects.forEach((obj)=>{
    keyForCounterObj = ''
    keys.forEach((key)=>{
        keyForCounterObj += String(obj[key])
}
if(counterObj[keyForCounterObj]){
    counterObj[keyForCounterObj].times ++
}else{
    counterObj[keyForCounterObj] = {
        ...obj,
        times:1
}}}

Let's break that down, because I understand that it might be a little bit confusing if you've never seen this setup before.

We're looping through each object in the array, and we're constructing a key based on all of the values that this object is storing. For example, arrayOfObjects[0] will create a key of "AppleFruit." (I'm using the String() method just in case this is being applied to an object with only integer or floating point values, as those are invalid to create a key in javaScript. It isn't necessary for your specific question)

Once we have that key, we check to see if it exists in our counterObject. If it does not exist, then we define it. We set the "times" attribute to 1, because we just created this object; it wouldn't exist unless we had just found it.

If the object does already exist, then we just increment the "times" attribute. At the end, we have an object that looks like this:

counterObj = {
    AppleFruit: {
        Name:"Apple",
        Type:"Fruit",
        times:3,
    },
    CarrotVegetable:{
        Name:"Carrot",
        Type:"Vegetable",
        times:4,
    }
}

Okay, so now we have an object of objects. Let's turn that into an array!

let newArrayOfObjects = []
const counterObjKeys = Object.keys(counterObj)
counterObjKeys.forEach((key)=>{
    newArrayOfObjects.push(counterObj[key])
}

This will return the final value in the format that you specified!

Upvotes: 3

StepUp
StepUp

Reputation: 38094

It can be done using reduce method:

const arrayOfObjects = [
  {
    Name: "Apple", Type: "Fruit"
  },
  {
    Name: "Carrot", Type: "Vegetable"
  },
  {
    Name: "Carrot", Type: "Vegetable"
  },
  {
    Name: "Carrot", Type: "Vegetable"
  },
  {
    Name: "Apple",  Type: "Fruit"
  },
  {
    Name: "Apple",  Type: "Fruit"
  },
  {
    Name: "Carrot", Type: "Vegetable"
  }
];


const result = arrayOfObjects.reduce((a, {Name, Type}) => {
  a[Name] = a[Name] || {Name, Type, times: 0};
  a[Name].times += 1;
  return a;
}, {})

console.log(Object.values(result));

UPDATE:

If you want to find duplicated by Name and Type keys, then you can do:

const arrayOfObjects = [
  {
    Name: "Apple", Type: "Super Meal"
  },
  {
    Name: "Carrot", Type: "Vegetable"
  },
  {
    Name: "Carrot", Type: "Vegetable"
  },
  {
    Name: "Carrot", Type: "Vegetable"
  },
  {
    Name: "Apple",  Type: "Fruit"
  },
  {
    Name: "Apple",  Type: "Fruit"
  },
  {
    Name: "Carrot", Type: "Vegetable"
  }
];

const result = [...arrayOfObjects.reduce((r, o) => {

  const key = o.Name + '-' + o.Type;

  const item = r.get(key) || Object.assign({}, o, {
times: 0
  });

  item.times += 1;

  return r.set(key, item);
}, new Map).values()];

console.log(Object.values(result));

Upvotes: 1

user6702203
user6702203

Reputation:

You can loop through your array of objects while using an object to hold how many times a certain "Name" appears. Then loop through new array of objects and assign the times key from the object that was holding the counter.

let arrayOfObjects = [
  {Name: "Apple", Type: "Fruit"},
  {Name: "Carrot", Type: "Vegetable"},
  {Name: "Carrot", Type: "Vegetable"},
  {Name: "Carrot", Type: "Vegetable"},
  {Name: "Apple", Type: "Fruit"},
  {Name: "Apple", Type: "Fruit"},
  {Name: "Carrot", Type: "Vegetable"}
];
let newArrayOfObjects = [];
// object that holds how many times a "Name" value appears.
var uniqueNames = {};
// loop through array of objects
for (var i = 0; i < arrayOfObjects.length; i++) {
  // if uniqueNames key does not exist
  if (!uniqueNames[arrayOfObjects[i].Name]) {
    // push this object into new array
    newArrayOfObjects.push(arrayOfObjects[i]);
  }
  // use this as a counter for each "Name" value
  uniqueNames[arrayOfObjects[i].Name] = ((uniqueNames[arrayOfObjects[i].Name] || 0) + 1);
}
// loop through new array of objects, and add the "times" key to it
for (var j = 0; j < newArrayOfObjects.length; j++) {
  newArrayOfObjects[j].times = uniqueNames[newArrayOfObjects[j].Name];
}
console.log(newArrayOfObjects);

Upvotes: 1

Related Questions