I want to remove duplicates in an array of objects, depending on object attributes.
Simplified example:
Assuming you have an array like:
[
{
name: 'alice',
something: 123
},
{
name: 'alice',
something: 321
},
{
name: 'bob',
something: 213
}
]
I want to remove objects, wich have the same value for name, but I want to decide which object to remove with some custom calculation (e.g. keep the object with bigger value for something).
I was able to adapt the accepted answer in find duplicate values in a JavaScript array, but that does not work so well with more than 2 duplicates.
You can try with reduce and object, set properties base on your condition.
Then convert it to array by Object.values.
var arr = [
{
name: 'alice',
something: 123
},
{
name: 'alice',
something: 321
},
{
name: 'bob',
something: 213
}
];
var res = arr.reduce( (acc,b) => {
if ((acc[b.name] && acc[b.name].something < b.something) || !acc[b.name]) {
acc[b.name] = b;
}
return acc;
}, {});
var newArr = Object.values(res);
console.log(newArr);
You could use a hash table as reference to the same name objects.
var array = [{ name: 'alice',something: 123 }, { name: 'alice', something: 321 }, { name: 'bob', something: 213 }],
result = array.reduce(function (hash) {
return function (r, a) {
if (!(a.name in hash)) {
hash[a.name] = r.push(a) - 1;
return r;
}
if (r[hash[a.name]].something < a.something) {
r[hash[a.name]] = a;
}
return r;
};
}(Object.create(null)), []);
console.log(result)
Related
I have to create match condition based on an array my array will look like below
var groupData={
A:[
{rollnum: 1, name:'Arya', age:15},
{rollnum: 2, name:'Aryan', age:15}
],
B:[
{rollnum:11, name:'Biba', age:15},
{rollnum:12, name:'Bimisha', age:15}
]
}
I am looping using for loop. How can reduce the loops. Can any one suggest me a proper way for this
Object.values(groupData).flat().forEach((rowitem)=>{
query={};
Object.keys(rowitem).forEach(eachField=>{
query[eachField]["$in"].push(rowitem[eachField])
});
fullarray[Object.keys(groupData)]=matchQuery;
})
I need an output (fullarray) like below
{
'A':{
rollnum:{'$in':[1,2]},
name: {'$in':['Arya', 'Aryan']},
age: {'$in':[15]}
},
'B':{
rollnum:{'$in':[11,12]},
name: {'$in':['Biba', 'Bimisha']},
age: {'$in':[15]}
}
}
Here 'A' 'B' is not coming correctly
Don't use Object.values() since that discards the A and B keys.
Use nested loops, one loop for the properties in the object, and a nested loop for the arrays.
You need to create the nested objects and arrays before you can add to them.
var groupData = { A:
[ { rollnum: 1,
name: 'Arya',
age:15},
{ rollnum: 2,
name: 'Aryan',
age:15}, ],
B:
[ { rollnum: 11,
name: 'Biba',
age:15},
{ rollnum: 12,
name: 'Bimisha',
age:15} ] }
result = {};
Object.entries(groupData).forEach(([key, arr]) => {
if (!result[key]) {
result[key] = {};
}
cur = result[key];
arr.forEach(obj => {
Object.entries(obj).forEach(([key2, val]) => {
if (!cur[key2]) {
cur[key2] = {
"$in": []
};
}
cur[key2]["$in"].push(val);
});
});
});
console.log(result);
I am trying to play with the Reduce function in JavaScript. What I am trying to achieve can be easily done via the filter function but I am trying to do it via Reduce.
I am trying to retrieve all the people where the age is greater or equal to 18 years and store the result in an array
var people = [
{ name: "John", age: 16 },
{ name: "Thomas", age: 20 },
{ name: "Smith", age: 18 },
{ name: "Jessy", age: 17 },
];
var arr = [];
var output = people.reduce(function(arr,ppl){
if(ppl.age >= 18)
return(arr.push(ppl));
},arr);
console.log(output);
However when I run this snippet, I get an error that says "TypeError: Cannot read property 'push' of undefined". I am not sure where do I need to define the arr (array where I need to store the output)
try this:
you should return arr,not return arr.push(ppl) because [].push(3) return 1(new length of [] ) not [3].And reduce works with accumulator which in this case the accumulator is arr.So,you should return arr not return (arr.push(ppl));
var people = [{
name: "John",
age: 16
},
{
name: "Thomas",
age: 20
},
{
name: "Smith",
age: 18
},
{
name: "Jessy",
age: 17
},
];
var arr = [];
var output = people.reduce(function(arr, ppl) {
if (ppl.age >= 18)
(arr.push(ppl));
return arr;
}, arr);
console.log(output);
return(arr.push(ppl));
As arr.push returns the new length of the array, it will return a number. So at the next iteration arr will be a number, and you cant push to that. So you need to pass on the array:
const output = people.reduce((arr, person) => person.age > 17 ? arr.concat(person): arr, []);
That works as arr.concat returns an array.
You can use array.concat to push to an array and return it, and put that in a ternary operator:
let people=[{name:"John",age:16},{name:"Thomas",age:20},{name:"Smith",age:18},{name:"Jessy",age:17}]
let output = people.reduce((arr,ppl) => {
return ppl.age >= 18 ? arr.concat(ppl) : arr
},[])
console.log(output)
I'm learning JS. Supposing I have the below array of objects:
var family = [
{
name: "Mike",
age: 10
},
{
name: "Matt"
age: 13
},
{
name: "Nancy",
age: 15
},
{
name: "Adam",
age: 22
},
{
name: "Jenny",
age: 85
},
{
name: "Nancy",
age: 2
},
{
name: "Carl",
age: 40
}
];
Notice that Nancy is showing up twice (changing only the age). Supposing I want to output only unique names. How do I output the above array of objects, without duplicates? ES6 answers more than welcome.
Related (couldn't find a good way for usage on objects):
Remove Duplicates from JavaScript Array
Easiest way to find duplicate values in a JavaScript array
EDIT Here's what I tried. It works well with strings but I can't figure how to make it work with objects:
family.reduce((a, b) => {
if (a.indexOf(b) < 0 ) {
a.push(b);
}
return a;
},[]);
You could use a Set in combination with Array#map and a spread operator ... in a single line.
Map returns an array with all names, which are going into the set initializer and then all values of the set are returned in an array.
var family = [{ name: "Mike", age: 10 }, { name: "Matt", age: 13 }, { name: "Nancy", age: 15 }, { name: "Adam", age: 22 }, { name: "Jenny", age: 85 }, { name: "Nancy", age: 2 }, { name: "Carl", age: 40 }],
unique = [...new Set(family.map(a => a.name))];
console.log(unique);
For filtering and return only unique names, you can use Array#filter with Set.
var family = [{ name: "Mike", age: 10 }, { name: "Matt", age: 13 }, { name: "Nancy", age: 15 }, { name: "Adam", age: 22 }, { name: "Jenny", age: 85 }, { name: "Nancy", age: 2 }, { name: "Carl", age: 40 }],
unique = family.filter((set => f => !set.has(f.name) && set.add(f.name))(new Set));
console.log(unique);
The Solution
Store occurrences of name external to the loop in an object, and filter if there's been a previous occurrence.
https://jsfiddle.net/nputptbb/2/
var occurrences = {}
var filteredFamily = family.filter(function(x) {
if (occurrences[x.name]) {
return false;
}
occurrences[x.name] = true;
return true;
})
you can also generalize this solution to a function
function filterByProperty(array, propertyName) {
var occurrences = {}
return array.filter(function(x) {
var property = x[propertyName]
if (occurrences[property]) {
return false;
}
occurrences[property]] = true;
return true;
})
}
and use it like
var filteredFamily = filterByProperty(family, 'name')
Explanation
Don't compare objects using indexOf, which only uses the === operator between objects. The reason why your current answer doesn't work is because === in JS does not compare the objects deeply, but instead compares the references. What I mean by that you can see in the following code:
var a = { x: 1 }
var b = { x: 1 }
console.log(a === b) // false
console.log(a === a) // true
Equality will tell you if you found the same exact object, but not if you found an object with the same contents.
In this case, you can compare your object on name since it should be a unique key. So obj.name === obj.name instead of obj === obj. Moreover another problem with your code that affects its runtime and not its function is that you use an indexOf inside of your reduce. indexOf is O(n), which makes the complexity of your algorithm O(n^2). Thus, it's better to use an object, which has O(1) lookup.
This will work fine.
const result = [1, 2, 2, 3, 3, 3, 3].reduce((x, y) => x.includes(y) ? x : [...x, y], []);
console.log(result);
With the code you mentioned, you can try:
family.filter((item, index, array) => {
return array.map((mapItem) => mapItem['name']).indexOf(item['name']) === index
})
Or you can have a generic function to make it work for other array of objects as well:
function printUniqueResults (arrayOfObj, key) {
return arrayOfObj.filter((item, index, array) => {
return array.map((mapItem) => mapItem[key]).indexOf(item[key]) === index
})
}
and then just use printUniqueResults(family, 'name')
(FIDDLE)
I just thought of 2 simple ways for Lodash users
Given this array:
let family = [
{
name: "Mike",
age: 10
},
{
name: "Matt",
age: 13
},
{
name: "Nancy",
age: 15
},
{
name: "Adam",
age: 22
},
{
name: "Jenny",
age: 85
},
{
name: "Nancy",
age: 2
},
{
name: "Carl",
age: 40
}
]
1. Find duplicates:
let duplicatesArr = _.difference(family, _.uniqBy(family, 'name'), 'name')
// duplicatesArr:
// [{
// name: "Nancy",
// age: 2
// }]
2 Find if there are duplicates, for validation purpose:
let uniqArr = _.uniqBy(family, 'name')
if (uniqArr.length === family.length) {
// No duplicates
}
if (uniqArr.length !== family.length) {
// Has duplicates
}
Since most of the answers won't have a good performance, i thought i share my take on this:
const arrayWithDuplicateData = [{ id: 5, name: 'Facebook'}, { id: 3, name: 'Twitter' }, { id: 5, name: 'Facebook' }];
const uniqueObj = {};
arrayWithDuplicateData.forEach(i => {
uniqueObj[i.id] = i;
});
const arrayWithoutDuplicates = Object.values(uniqueObj);
We're leveraging the fact that keys are unique within objects. That means the last duplication item inside the first array, will win over its predecessors. If we'd want to change that, we could flip the array before iterating over it.
Also we're not bound to use only one property of our object for identifying duplications.
const arrayWithDuplicateData = [{ id: 5, name: 'Facebook'}, { id: 3, name: 'Twitter' }, { id: 5, name: 'Facebook' }];
const uniqueObj = {};
arrayWithDuplicateData.forEach(item => {
uniqueObj[`${item.id}_${item.name}`] = item;
});
const arrayWithoutDuplicates = Object.values(uniqueObj);
Or we could simply add a check, if the uniqueObj already holds a key and if yes, not overwrite it.
Overall this way is not very costly in terms of performance and served me well so far.
I would probably set up some kind of object. Since you've said ECMAScript 6, you have access to Set, but since you want to compare values on your objects, it will take a little more work than that.
An example might look something like this (removed namespace pattern for clarity):
var setOfValues = new Set();
var items = [];
function add(item, valueGetter) {
var value = valueGetter(item);
if (setOfValues.has(value))
return;
setOfValues.add(value);
items.push(item);
}
function addMany(items, valueGetter) {
items.forEach(item => add(item, valueGetter));
}
Use it like this:
var family = [
...
];
addMany(family, item => item.name);
// items will now contain the unique items
Explanation: you need to pull a value from each object as it's added and decide if it has already been added yet, based on the value you get. It requires a value getter, which is a function that given an item, returns a value (item => item.name). Then, you only add items whose values haven't already been seen.
A class implementation:
// Prevents duplicate objects from being added
class ObjectSet {
constructor(key) {
this.key = key;
this.items = [];
this.set = new Set();
}
add(item) {
if (this.set.has(item[this.key])) return;
this.set.add(item[this.key]);
this.items.push(item);
}
addMany(items) {
items.forEach(item => this.add(item));
}
}
var mySet = new ObjectSet('name');
mySet.addMany(family);
console.log(mySet.items);
Given an array of objects like this:
objects = [
{ id: 'aaaa', description: 'foo' },
{ id: 'bbbb', description: 'bar' },
{ id: 'cccc', description: 'baz' }
];
And an array of strings like this:
order = [ 'bbbb', 'aaaa', 'cccc' ];
How would I sort the first array so that the id attribute matches the order of the second array?
Try this:
objects.sort(function(a, b){
return order.indexOf(a.id) - order.indexOf(b.id)
});
Assuming the variables are like you declared them in the question, this should return:
[
{ id: 'bbbb', description: 'bar' },
{ id: 'aaaa', description: 'foo' },
{ id: 'cccc', description: 'baz' }
];
(It actually modifies the objects variable)
You need a way to translate the string into the position in the array, i.e. an index-of function for an array.
There is one in newer browsers, but to be backwards compatible you need to add it if it's not there:
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function(str) {
var i;
for (i = 0; i < this.length; i++) if (this[i] == str) return i;
return -1;
}
}
Now you can sort the array by turning the string into an index:
objects.sort(function(x,y){ return order.indexOf(x.id) - order.indexOf(y.id); });
Demo: http://jsfiddle.net/Guffa/u3CQW/
Use a mapping object for (almost) constant access time:
/* Create a mapping object `orderIndex`:
{
"bbbb": 0,
"aaaa": 1,
"cccc": 2
}
*/
const orderIndex = {}
order.forEach((value, index) => orderIndex[value] = index);
// Sort
objects.sort((a, b) => orderIndex[a.id] - orderIndex[b.id]);
// data
const objects = [
{ id: 'aaaa', description: 'foo' },
{ id: 'bbbb', description: 'bar' },
{ id: 'cccc', description: 'baz' }
];
const order = [ 'bbbb', 'aaaa', 'cccc' ];
/* Create a mapping object `orderIndex`:
{
"bbbb": 0,
"aaaa": 1,
"cccc": 2
}
*/
const orderIndex = {}
order.forEach((value, index) => orderIndex[value] = index);
// Sort
objects.sort((a, b) => orderIndex[a.id] - orderIndex[b.id]);
// Log
console.log('orderIndex:', orderIndex);
console.log('objects:', objects);
Have data that has this kind of structure:
$input = [ { animal: 'cat', name: 'Rocky', value: 1 },
{ animal: 'cat', name: 'Spot', value: 2 },
{ animal: 'dog', name: 'Spot', value: 3 } ];
Need fastest possible method for converting to this format:
$output = { animal: [ 'cat', 'dog' ],
name: [ 'Rocky', 'Spot' ],
value: [ 1, 2, 3 ] };
The output should have keys equal to each of the keys in each object from the input. And the output values should be arrays with the sorted unique values. I found a few ways to do it using nested loops, but slower than I would like. With 30,000 elements to the input array with 8 keys for each of the objects, the best I have been able to do is 300ms in Chrome. Would like to get down to 100ms. Is there any faster method using a map or reduce?
Yet another way for modern browsers:
$input.reduce(function(acc, obj) {
Object.keys(obj).forEach(function(k) {
acc[k] = (acc[k] || []).concat(obj[k])
})
return acc
},{})
Here's one way.
$input = [ { animal: 'cat', name: 'Rocky', value: 1 },
{ animal: 'cat', name: 'Spot', value: 2 },
{ animal: 'dog', name: 'Spot', value: 3 } ];
$output = {animal:{},name:{},value:{}};
$input.forEach(function(v,i) {
$output.animal[v.animal] = 1;
$output.name[v.name] = 1;
$output.value[v.value] = 1;
});
$output.animal = Object.keys($output.animal);
$output.name = Object.keys($output.name);
$output.value = Object.keys($output.value);
It prevents having to test each Array every time. You can performance compare to see if it helps.
live example: http://jsfiddle.net/TJVtj/1/
If you don't want to hardcode the keys, you can make the solution generic.
var keys = Object.keys($input[0]),
$output = {};
keys.forEach(function(v) {
$output[v] = {};
});
$input.forEach(function(v) {
keys.forEach(function(vv) {
$output[vv][v[vv]] = 1;
});
});
keys.forEach(function(v) {
$output[v] = Object.keys($output[v]);
});
live example: http://jsfiddle.net/TJVtj/2/
Warning. All the values will be strings since they're fetched as object keys.
function inArray(needle, haystack) {
var length = haystack.length;
for(var i = 0; i < length; i++) {
if(haystack[i] == needle) return true;
}
return false;
}
Above function is used to check duplicates
$output={};
for(i=0; i< $input.length; i++)
{
if(!$output.animal) $output.animal=[];
if(!$output.name) $output.name=[];
if(!$output.value) $output.value=[];
var ani=$input[i];
if(ani.animal && !inArray(ani.animal, $output.animal)) $output.animal.push(ani.animal);
if(ani.name && !inArray(ani.name, $output.name)) $output.name.push(ani.name);
if(ani.value) $output.value.push(ani.value);
}
DEMO.
// If you don't know the objects all have the same keys you need to look at each one-
var output= {},
input= [{
animal:'cat', name:'Rocky', value:1
},{
animal:'cat', name:'Spot', value:2
},{
animal:'dog', name:'Spot', value:3
}];
input.forEach(function(itm){
for(var p in itm){
if(itm.hasOwnProperty(p)){
if(!output[p]) output[p]= [];
if(output[p].indexOf(itm[p])== -1) output[p].push(itm[p]);
}
}
});
Run.expose(output)// nonstandard object to string method
// returned value: (String)
{
animal:[
'cat',
'dog'
],
name:[
'Rocky',
'Spot'
],
value:[
1,
2,
3
]
}
Try Underscore, it's magnificent with this kind of tasks)