How to distinct an object based off multiple attributes - javascript

I am looking for a way to distinct an array of objects, the method needs to distinct by two attributes for instance,
let arr = [
{
name: "George",
surname: "Hendricks"
},
{
name: "George",
surname: "Marques"
},
{
name: "George",
surname: "Hendricks"
}
]
Once filtered should only return an array of 2 objects, George Hendricks and George Marques As they are unique. Currently I can only filter with ES6 Set like so
let uniArr = [...(new Set(arr))]
How can I accomplish my task as fast as possible (working with big data)

If the property values are really strings, you can combine them to make a unique key, and then build a new array using an object (or Set) to track the ones you've already seen. The advantage to using an object or Set is that you don't have to re-scan the array every time to find out if an entry is unique. Lookup time on them is typically much better (even dramatically better) than a linear search.
Here's an example with an object:
let arr = [
{
name: "George",
surname: "Hendricks"
},
{
name: "George",
surname: "Marques"
},
{
name: "George",
surname: "Hendricks"
},
];
let seen = Object.create(null);
let filtered = arr.filter(entry => {
const key = entry.name + "\u0000" + entry.surname;
// ^---- a string you know won't be in either name or surname
const keep = !seen[key];
if (keep) {
seen[key] = true;
}
return keep;
});
console.log(filtered);
Or with a Set:
let arr = [
{
name: "George",
surname: "Hendricks"
},
{
name: "George",
surname: "Marques"
},
{
name: "George",
surname: "Hendricks"
},
];
let seen = new Set();
let filtered = arr.filter(entry => {
const key = entry.name + "\u0000" + entry.surname;
// ^---- a string you know won't be in either name or surname
const keep = !seen.has(key);
if (keep) {
seen.add(key);
}
return keep;
});
console.log(filtered);

You can use Array.filter() method to filter the array, by searching over the couple name and surname.
This is how should be your code:
var filtered = arr.filter((person, index, selfArray) =>
index === selfArray.findIndex((p) => (
p.name === person.name && p.surname === person.surname
))
);
Demo:
let arr = [{
name: "George",
surname: "Hendricks"
},
{
name: "George",
surname: "Marques"
},
{
name: "George",
surname: "Hendricks"
},
];
var filtered = arr.filter((person, index, selfArray) =>
index === selfArray.findIndex((p) => (
p.name === person.name && p.surname === person.surname
))
);
console.log(filtered);

Related

Compare objects within an array and create a new array without duplicates

I've an array of objects:
[
{ name: "John", age: "34" },
{ name: "Ace", age: "14" },
{ name: "John", age: "45" },
{ name: "Harry", age: "11" },
]
I want to compare the objects within an array by name. If the duplicate name exists, I should compare the age and only keep the higher age object.
The expected output should be:
[
{ name: "Ace", age: "14" },
{ name: "John", age: "45" },
{ name: "Harry", age: "11" },
]
I am new to javascript/typescript and couldn't find any optimal solution for this problem. I hope, I was able to explain my problem clearly.
Thanks.
The next provided approach uses reduce and creates in a first step just an index/map of items of highest age which are each unique by name. Thus one could use the temporary state of the programmatically built result as lookup for already existing named items.
Within a second step one would retrieve the array of unique named items of highest age by passing such an index to Object.values.
function collectHighestAgeItemOfSameName(result, item) {
const { name, age } = item;
if (
!(name in result) ||
Number(result[name].age) < Number(age)
) {
result[name] = item;
}
return result;
}
const sampleData = [{
name: "John",
age: "34"
}, {
name: "Ace",
age: "14"
}, {
name: "Harry",
age: "9"
}, {
name: "John",
age: "45"
}, {
name: "Harry",
age: "11"
}, {
name: "Ace",
age: "13"
}];
console.log(
'reduced index of unique person items of highest age ...',
sampleData
.reduce(collectHighestAgeItemOfSameName, {})
)
console.log(
'array of unique person items of highest age ...',
Object
.values(
sampleData
.reduce(collectHighestAgeItemOfSameName, {})
)
)
.as-console-wrapper { min-height: 100%!important; top: 0; }
Maybe something like that
const obj = [{ name: "John", age: "34" }, { name: "Ace", age: "14" }, { name: "John", age: "45" }, { name: "Harry", age: "11" }];
const objCopy = JSON.parse(JSON.stringify(obj))
const res = objCopy.reduce((acc, obj) => {
const personExist = acc.find(({ name }) => name === obj.name);
if (personExist) {
if (parseInt(obj.age, 10) > parseInt(personExist.age, 10)) {
personExist.age = obj.age;
}
} else {
acc.push(obj);
}
return acc;
}, []);
console.log({ res });
.as-console-wrapper { min-height: 100%!important; top: 0; }
try this
var objArr=...your json object;
var maxValueGroup = "name";
var maxValueName = "age";
console.log(JSON.stringify(newArr(objArr,maxValueGroup, maxValueName)));
newArr
var newArr = function (objArr,maxValueGroup, maxValueName) {
var arr = groupBy(objArr, maxValueGroup);
var newArr = [];
$.each(arr, function (key) {
var maxVal = 0;
var maxValItem;
arr[key].forEach((element) => {
if (element[maxValueName] > maxVal) {
maxVal = element[maxValueName];
maxValItem = element;
}
});
newArr.push(maxValItem);
});
return newArr;
};
groupby
var groupBy = function (xs, key) {
return xs.reduce(function (rv, x) {
(rv[x[key]] = rv[x[key]] || []).push(x);
return rv;
}, {});
};
This works basically the same as #PeterSeliger's fine and upvote-worthy answer, except it uses a Map object which is nice because Map.set returns the Map object, allowing you to return it as the accumulator for the next iteration of the reduce function.
const data = [{name: "John", age: "34"}, {name: "Ace", age: "14"}, {name: "John", age: "45"}, {name: "Harry", age: "11"}];
const res = [...data.reduce(
(acc, val) =>
+(acc.get(val.name)?.age ?? -1) >= +val.age ?
acc :
acc.set(val.name, val),
new Map()
).values()];
console.log(JSON.stringify( res ));
.as-console-wrapper { min-height: 100%!important; top: 0; }
Other references:
Unary plus (+)
Optional chaining (?.)
Nullish coalescing operator (??)
Conditional (ternary) operator
Array.prototype.reduce()
Spread syntax (...)

How do I convert an array of objects into an object (dynamically) in JavaScript [duplicate]

This question already has answers here:
Convert array of objects with same property to one object with array values
(6 answers)
Closed 2 months ago.
I have an array of objects that looks like this:
arr = [
{name: "john", age: 23},
{name: "mary", age: 40},
{name: "zack", age: 17}
]
I am trying to convert it into something like this:
{
name: ["john", "mary", "zack"],
age: ['23', 40, 17]
}
i have tried the following
arr.map(item => item.name)
arr.map(item => item.age)
return {names, ages}
and it works fine but this assumes that you already know, beforehand, the keys of the objects you're converting.
I want to be able to load the object keys and corresponding array of values dynamically. Assuming i don't know that the objects in our example array have "name" and "age" as keys.
You could reduce the array and the entries of the object and collect the values in the group of the keys.
const
data = [{ name: "john", age: 23 }, { name: "mary", age: 40 }, { name: "zack", age: 17 }],
result = data.reduce((r, o) => Object.entries(o).reduce((t, [k, v]) => {
if (!t[k]) t[k] = [];
t[k].push(v);
return t;
}, r), {});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could get the key of the first element and then map through it. With each, get its corresponded values
const arr = [
{ name: "john", age: 23, gender: "male" },
{ name: "mary", age: 40, gender: "female" },
{ name: "zack", age: 17, gender: "male" },
]
const res = Object.keys(arr[0]).reduce((acc, el) => {
const values = arr.map((item) => item[el])
return { ...acc, [el]: values }
}, {})
console.log(res)
Assuming that each object in your list has the same keys you could get the keys of the first object
const keys = Object.keys(arr[0])
and then map through the keys with your above approach
const returnObj = {}
keys.forEach(key => {
returnObj[key] = arr.map(item => item[key])
})
return returnObj
You can use Object.entries for the mapping.
var arr = [
{name: "john", age: 23},
{name: "mary", age: 40},
{name: "zack", age: 17}
];
var entries = arr.map((item) => Object.entries(item));
var result = {};
entries.forEach(entry => {
entry.forEach(item => {
if (result[item[0]] && result[item[0]].length > 0) {
result[item[0]].push(item[1]);
} else {
result[item[0]] = [item[1]];
}
});
});
console.log(result);
You can make use of Array.reduce and Object.keys.
let arr = [
{name: "john", age: 23},
{name: "mary", age: 40},
{name: "zack", age: 17}
]
const formatData = (data) => {
return data.reduce((res, obj) => {
Object.keys(obj).map(d => {
res[d] = [...(res[d] ||[]), obj[d]]
})
return res;
}, {})
}
console.log(formatData(arr))
You can do this with Ramda
import { mergeWith, concat } from “Ramda”
const mergeConcat = mergeWith(concat)
mergeConcat(arr)

Array.filter() to remove duplicate Ojects

I would like to fuse Array.filter() function to remove duplicate objects
I am able to achieve in the case of string or integer arrays. But I am not able to achieve the same with array of objects as in the second case of names
const names = ['John', 'Paul', 'George', 'Ringo', 'John'];
let x = names => names.filter((v, i, arr) => arr.indexOf(v) === i);
console.log(x(names)); //[ 'John', 'Paul', 'George', 'Ringo' ]
const names = [
{ name: "John" },
{ name: "Paul" },
{ name: "George" },
{ name: "Ringo" },
{ name: "John" } ];
// returns the same original array
Could you please help?
Using Array#reduce() and a Map accumulator then spread the values() of the Map into array
const names = [
{ name: "John" },
{ name: "Paul" },
{ name: "George" },
{ name: "Ringo" },
{ name: "John" } ];
const unique = [... names.reduce((a,c)=>(a.set(c.name,c)),new Map).values()]
console.log(unique)
Use Array.reduce and Object.values
Iterate over the array and create an object with key as name and value as object from array. In case of objects with same name, the value will be overwritten in resultant object. Finally use Object.values to collect all the unique objects.
const names = [{ name: "John" },{ name: "Paul" },{ name: "George" },{ name: "Ringo" },{ name: "John" } ];
let result = Object.values(names.reduce((a,c) => Object.assign(a, {[c.name]:c}),{}));
console.log(result);
For tweaking - Plunker
const names = [
{ name: "John" },
{ name: "Paul" },
{ name: "George" },
{ name: "Ringo" },
{ name: "John" }
];
/* unique => Filter: Remove all duplicate items from an array. Works with plain objects as well, since we stringify each array item.
* #type public Function
* #name unique
* #return Function( item )
* #notes
*/
const unique = () => {
const seen = {};
return item => {
const json = JSON.stringify( item );
return seen.hasOwnProperty( json )
? false
: ( seen[ json ] = true );
};
};
const result = names.filter( unique() );
console.log( result );
You could use lodash's _uniqBy for this:
const names = [
{ name: "John" },
{ name: "Paul" },
{ name: "George" },
{ name: "Ringo" },
{ name: "John" } ];
const result = _uniqBy(names, 'name');
This can be done with the help of Sets as well
var names = [{ name: "John" },{ name: "Paul" },{ name: "George" },{ name: "Ringo" },{ name: "John" } ];
var result = Array.from(
names.reduce((s, d) => s.add(d.name), new Set)
, d => ({ name: d })
)
console.log(result)
Keith had a great suggestion to use findIndex with filter instead of indexOf. Object literals are always unique references, so we cannot compare them. We can however compare the name keys between the objects. We can do this with the aforementioned functions.
const names = [
{ name: "John" },
{ name: "Paul" },
{ name: "George" },
{ name: "Ringo" },
{ name: "John" }
];
console.log(names.filter(({name1}, i, a) => {
return i == a.findIndex(({name2}) => {
return name1 == name2;
});
});
const names = ['John', 'Paul', 'George', 'Ringo', 'John'];
function removeDups(names) {
let unique = {};
names.forEach(function(i) {
if(!unique[i]) {
unique[i] = true;
}
});
return Object.keys(unique);
}
removeDups(names); //'John', 'Paul', 'George', 'Ringo'

Group an array by multiple fields

I have an api response that looks like this:
people = [
{
name: 'John',
surname: 'Doe',
pet: {
type: 'CAT',
name: 'whiskers',
age: 1
}
},
{
name: 'John',
surname: 'Doe',
pet: {
type: 'DOG',
name: 'Dexter',
age: 4
}
},
{
name: 'Jane',
surname: 'Doe',
pet: {
type: 'CAT',
name: 'Fluffy',
age: 10
}
},
{
name: 'Jane',
surname: 'Doe',
pet: {
type: 'CAT',
name: 'Dennis',
age: 3
}
}
]
I would like to translate it so that it looks like this (there are only ever two types of pet):
people = [
{
“name”: “John”,
“surname”: “Doe”,
“cats”: [
{
“name”: “whiskers”,
“age”: 1
}
],
“dogs”: [
{
“name”: “Dexter”,
“age”: 4
}
]
},
{
“name”: “Jane”,
“surname”: “Doe”,
“cats”: [
{
“name”: “Fluffy”,
“age”: 10
},
{
“name”: “Dennis”,
“age”: 3
}
]
}
]
I'm using angular 5. I need to be able to show a table similar to:
<table>
<thead>
<tr>
<th>Name</th>
<th>Surname</th>
<th>Cats</th>
<th>Dogs</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let person of people">
<td>{{person.name}}</td>
<td>{{person.surname}}</td>
<td>
<ul>
<li *ngFor="let cat of person.cats">name: {{cat.name}}, age: {{cat.age}} years</li>
</ul>
</td>
<td>
<ul>
<li *ngFor="let dog of person.dogs">name: {{dog.name}}, age: {{dog.age}} years</li>
</ul>
</td>
</tr>
</tbody>
</table>
I tried looping over building up a map as I went but I struggled to convert this back from a map to an array at the end. Also I was hoping there was a cleaner solution that I had missed:
const peopleMap = new Map;
this.people.forEach(person => {
const key = person.name + '_' + person.surname;
if (peopleMap[key]) {
if (person.pet.type === 'CAT') {
peopleMap[key].cats.push(new Pet(person.pet.name, person.pet.age));
} else {
peopleMap[key].dogs.push(new Pet(person.pet.name, person.pet.age));
}
} else {
let cats: [Pet];
let dogs: [Pet];
if (person.pet.type === 'CAT') {
cats.push(new Pet(person.pet.name, person.pet.age));
} else {
dogs.push(new Pet(person.pet.name, person.pet.age));
}
peopleMap[key] = new Person(person.name, person.surname, cats, dogs);
}
});
Ideally I will be allowed to change the api so that this logic sits server side. In the meantime I would love to know how to do this.
I would approach this by reducing the array ([see Array.prototype.reduce][1]
[1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce )
Basically, create an aggregate results array as you iterate through each (current) object in the "people" data. Check to see if an object with the person key exists. If they do, add the new pet to the appropriate pet array. Else, create a new person with the current pet.
Be sure to initialize the reduce function with an empty array (second argument when calling groupByPerson).
let people = [{name: 'John',surname: 'Doe',pet: {type: 'CAT',name: 'whiskers',age: 1}},{name: 'John',surname: 'Doe',pet: {type: 'DOG', name: 'Dexter',age: 4}},{name: 'Jane',surname: 'Doe',pet: {type: 'CAT',name: 'Fluffy',age: 10}},{name: 'Jane',surname: 'Doe',pet: {type: 'CAT',name: 'Dennis',age: 3}}];
function groupByPerson(data) {
return data.reduce( function (results, current) {
const key = current.name + "_" + current.surname;
let matched = results.filter(r => r.key === key)[0];
if (matched) {
let petArray = (current.pet.type === "CAT") ? matched.cats : matched.dogs;
petArray.push({name:current.pet.name, age:current.pet.age});
}
else {
let newPerson = {
key: key,
name: current.name,
surname: current.surname,
cats: [],
dogs: []
}
let petArray = (current.pet.type === "CAT") ? newPerson.cats : newPerson.dogs;
petArray.push({name:current.pet.name, age:current.pet.age});
results.push(newPerson);
}
return results;
}, []); // initialize with empty array
}
const results = groupByPerson(people);
You can use reduce to summarize your data to an object. Use Object.values to convert the object into an array
let people = [{name: 'John',surname: 'Doe',pet: {type: 'CAT',name: 'whiskers',age: 1}},{name: 'John',surname: 'Doe',pet: {type: 'DOG', name: 'Dexter',age: 4}},{name: 'Jane',surname: 'Doe',pet: {type: 'CAT',name: 'Fluffy',age: 10}},{name: 'Jane',surname: 'Doe',pet: {type: 'CAT',name: 'Dennis',age: 3}}];
let result = Object.values(people.reduce((c, {pet,...u}) => {
let k = u.name + u.surname; //Make a key using name and last name
let {type,...p} = pet; //Deconstrct pet object
type = type.toLowerCase(); //Make type lower case
c[k] = c[k] || u; //Initiate if name does not exist
c[k][type] = c[k][type] || []; //Initiate if pet does not exist
c[k][type].push(p); //Add the pet
return c;
}, {}));
console.log(result);
Fixing the answer to group:
First we create array with desired object structure
var newPeople = people.map(function(item){
var newPeople = {};
newPeople.name = item.name;
newPeople.surname = item.surname;
var type = item.pet.type;
newPeople[type] = [];
var petDetails = {
name: item.pet.name,
age: item.pet.age
}
newPeople[type].push(petDetails);
return newPeople;
});
/*Output: people = [{name: "John", surname: "Doe", CAT: Array(1)},
{name: "John", surname: "Doe", DOG: Array(1)},
{name: "Jane", surname: "Doe", CAT: Array(1)},
{name: "Jane", surname: "Doe", CAT: Array(1)}]*/
Now we will group them with below loop:
for (var i = 0; i < newPeople.length; i++) {
for(var j = i+1; j < newPeople.length;j++){
var item = newPeople[i];
var nextItem = newPeople[j];
if(item.name === nextItem.name && item.surname === nextItem.surname) {
var firstItemKeys = Object.keys(item);
var nextItemKeys = Object.keys(nextItem);
if(firstItemKeys[2] === nextItemKeys[2]) {
item[firstItemKeys[2]].push(nextItem[nextItemKeys[2]][0]);
} else {
if (Array.isArray(item[nextItemKeys[2]])) {
item[nextItemKeys[2]].push(nextItem[nextItemKeys[2]][0]);
} else {
item[nextItemKeys[2]] = [];
item[nextItemKeys[2]].push(nextItem[nextItemKeys[2]][0]);
}
}
newPeople.splice(j,1);
j--
}
}
}
The output is as expected:
newPeople = [{name: "John", surname: "Doe", CAT: Array(1), DOG: Array(1)},
{name: "Jane", surname: "Doe", CAT: Array(2)}]

How would I retrieve a property's value by knowing another property's value in an object?

I would like to not use jQuery if possible. What I'm looking for:
var obj = [
{
name: "Sam",
Job: "Developer"
},
{
name: "Mike",
Job: "Brother"
}
];
var testing = obj[name == sam].job // testing equals "Developer"
You are looking for Array.filter:
var obj = [
{
name: "Sam",
Job: "Developer"
},
{
name: "Mike",
Job: "Brother"
}
]
var testing = obj
.filter(function(person) {
return person.name == 'Sam'
}).map(function(person) {
return person.Job
})
console.log(testing)
or if you know that you're interested in the first one:
var obj = [
{
name: "Sam",
Job: "Developer"
},
{
name: "Mike",
Job: "Brother"
}
]
var testing = obj
.filter(function(person) {
return person.name == 'Sam'
}) var obj = [
{
name: "Sam",
Job: "Developer"
},
{
name: "Mike",
Job: "Brother"
}
]
var testing = obj
.filter(function(person) {
return person.name == 'Sam'
})
console.log(testing[0] && testing[0].Job)
and last but not least: If you know you have exactly one match, you can just do:
var testing = obj.filter(function(p) { return p.name == 'Sam'; })[0].Job
So, you can't do it as simply as you have written, but you can filter and find what you're looking for. I've made it a generic function to make life easier for reusability:
function getArrObjPropertyValue(arr, searchProperty, searchCompare, returnProperty) {
return arr.filter(function(item) {
return item[searchProperty] === searchCompare;
})[0][returnProperty];
}
console.log(getArrObjPropertyValue(obj, 'name', 'Sam', 'Job')); // Developer
Make the comparison for the object's key separately from assigning the value like this:
var people = [
{
name: "Sam",
Job: "Developer"
},
{
name: "Mike",
Job: "Brother"
}
];
var testing = null;
for (var person in people) {
if (people[person].name === 'Sam') { testing = people[person].job; }
}

Categories

Resources