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 (...)
Related
I have an object with objects and i want to display a ranking of names which occurs the most.
like
John
Maria
Josh
this is my object:
let data = {
"1111": {
firstName: "john",
lastName: "doe",
},
"1112": {
firstName: "john",
lastName: "doe",
},
"1113": {
firstName: "maria",
lastName: "dee",
},
"1114": {
firstName: "john",
lastName: "doe",
},
"1115": {
firstName: "maria",
lastName: "dee",
},
"1116": {
firstName: "josh",
lastName: "kek",
},
"1117": {
firstName: "maria",
lastName: "dee",
},
"1118": {
firstName: "nick",
lastName: "smith"
}
}
i tried things with a for in loop but i dont got it right... On stackoverflow i just examples with arrays and array methods but i need this for an object.
for (let key in data) {
let firstFind = data[key]["firstName"]
let firstCounter = 0
if(data[key]["firstName"] = firstFind) {
firstCounter++
} else {
let secondFind = data[key]["firstname"]
let secondCounter = 0
if(data[key]["firstName"] = secondFind) {
secondCounter++
}
//...reproduce until there is no new name
//this is where my limit is
}
}
The Code should loop trough all objects, count the names, and create a ranking of which name occurs the most. See example above
You can essentially use Object.entries() in-order to get an iterable array of key value pairs and then do something like this
const data = {} // insert your object here
const dupes = [];
for (const [key, value] of Object.entries(data)) {
const elem = dupes.find(x => x.firstName === value.firstName); // if there is an element in the array
if (elem) elem.count = elem.count + 1; // increase count by 1
else {
const dat = { firstName: value.firstName, lastName: value.lastName, count: 1 };
dupes.push(dat); // initiating a new element with count 1
}
}
const sortArr = dupes.sort((a, b) => b.count - a.count);
console.log(sortArr[0]) // returns the element with highest count.
// in your case this would be the output
// {firstName: 'john', lastName: 'doe', count: 3}
// update to get the list in order
const lbArr = [];
sortArr.forEach(x => lbArr.push(`${x.firstName} ${x.lastName} ${x.count}`))
console.log(lbArr.join('\n'))
P.S you can use Object.values() as well, i used Object.entries() in-case you want to assign the key to the dupes array.
Here's another method, which results in a readable object that considers ties. I will comment the code to make it readable. Here is the output from your data:
{ "first": [
{"name": "john doe", "count": 3},
{"name": "maria dee", "count": 3}
],
"second": [
{"name": "josh kek", "count": 1},
{ "name": "nick smith", "count": 1}
],
"third": []
}
let data = {
"1111": { firstName: "john",lastName: "doe" },
"1112": { firstName: "john",lastName: "doe" },
"1113": { firstName: "maria",lastName: "dee" },
"1114": { firstName: "john",lastName: "doe" },
"1115": { firstName: "maria",lastName: "dee" },
"1116": { firstName: "josh",lastName: "kek" },
"1117": { firstName: "maria",lastName: "dee" },
"1118": { firstName: "nick",lastName: "smith" }
}
let ranking =
// we want to iterate through the {firstName:...} in each object so we use Object.values()
// but we can't iterate objects like this so we wrap it in Object.entries, which turns it into an array
Object.entries(Object.values(data)
// this first reduce function does the counting and sets up an object of {name: count}
.reduce((b, {firstName,lastName}) => {
let n = firstName + ' ' + lastName;
// setting up a counter and using the whole name as a key (there may be more than one JOhn)
b[n] = b.hasOwnProperty(n) ? b[n] + 1 : 1;
return b
}, {}))
// wrapped in Objct.entries, this is an array which we can sort
.sort(([, a], [, b]) => b - a)
// the last reduce transfers the sorted {name: count} object into first second and third arrays
.reduce((b, a) => {
let v = { name: a[0], count: a[1]},
placed = false;
// we iterate through first, second and third and fill them in
['first', 'second', 'third'].forEach(p => {
if (!placed && ((b[p].length > 0 && b[p][0].count === a[1]) || b[p].length === 0)) {
b[p].push(v);
placed = true;
}
});
return b
}, {first: [], second: [], third: [] });
console.log(ranking)
// all that can be optimized to this:
Object.entries(Object.values(data).reduce((b, {firstName,lastName}) => {
let n = firstName + ' ' + lastName; b[n] = b.hasOwnProperty(n) ? b[n] + 1 : 1; return b;
}, {})).sort(([, a], [, b]) => b - a).reduce((b, a) => {
let v = { name: a[0], count: a[1]}, placed = false;
['first', 'second', 'third'].forEach(p => {
if (!placed && ((b[p].length > 0 && b[p][0].count === a[1]) || b[p].length === 0)) {
b[p].push(v); placed = true;
}}); return b;
}, {first: [], second: [], third: [] });
A fairly simple approach is to gather the counts into an object using reduce, take the entries of that object, and sort these by the counts:
const namesByCount = (xs) =>
Object.entries (Object .values (xs) .reduce (
(a, {firstName}) => ((a [firstName] = (a [firstName] || 0) + 1), a),
{}
)) .sort (([, a], [, b]) => b - a) //.map (([a]) => a)
let data = {1111: {firstName: "john", lastName: "doe"}, 1112: {firstName: "john", lastName: "doe"}, 1113: {firstName: "maria", lastName: "dee"}, 1114: {firstName: "john", lastName: "doe"}, 1115: {firstName: "maria", lastName: "dee"}, 1116: {firstName: "josh", lastName: "kek"}, 1117: {firstName: "maria", lastName: "dee"}, 1118: {firstName: "nick", lastName: "smith"}}
console .log (namesByCount (data))
.as-console-wrapper {max-height: 100% !important; top: 0}
If you wanted just the names, you could uncomment the .map call.
There is a bit of trickiness in the use of the comma operator. I prefer to work with expressions rather than statements. But if it bothers you, you could do this instead:
const namesByCount = (xs) =>
Object.entries (Object .values (xs) .reduce (
(a, {firstName}) => {
a [firstName] = a [firstName] || 0
a [firstName] += 1
return a
},
{}
)) .sort (([, a], [, b]) => b - a) //.map (([a]) => a)
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)
I have an array like this:
[
{
0 : {
id: 'somevalue',
name: 'John Doe',
age: '20'
}
}
...
]
I would like to modify the array, for example to set the key to the id attribute like this:
[
{
somevalue : {
name: 'John Doe',
age: '20'
}
}
]
What would be the best way to achieve this. Thanks for your time.
You could destructure the object and take the wanted key out of the object. Then return new object with wanted value.
var array = [{ 0 : { id: 'somevalue', name: 'John Doe', age: '20' } }],
key = 'id',
result = array.map(({ 0: { [key]: k, ...o } }) => ({ [k]: o }));
console.log(result);
Assuming there is only one nested object, you can use the function map as follow:
let arr = [
{
0 : {
id: 'somevalue',
name: 'John Doe',
age: '20'
}
}
];
let result = arr.map(o => {
let [{id, ...rest}] = Object.values(o);
return {[id]: rest};
});
console.log(result);
I guess you have just more then 1 Object. You can try it like this.
let data = [
{
0 : {
id: 'somevalue',
name: 'John Doe',
age: '20'
}
}
]
let result = data.map((el, index) => {
let id = el[index].id;
delete el[index].id;
return { [id]: {...el[index]}}
})
console.log(result);
You can try this if the key is always '0' in the given object
const arr = [
{
0 : {
id: 'somevalue',
name: 'John Doe',
age: '20'
}
}
]
arr.map(item => {
const value = {...item[0]};
const key = value.id;
delete value.id;
return {
[key]: value
}
})
You can use map and reduce functions to achieve the desired output.
This solution will work even if you have more than one nested objects and if you have more properties in your nested objects, i.e. properties other than name and age
const arr = [
{
0: { id: "somevalue", name: "John Doe", age: "20" },
1: { id: "somevalue 2", name: "John Doe", age: "20", gender: 'male' }
},
{
0: { id: "somevalue 3", name: "John Doe", age: "20" },
1: { id: "somevalue 4", name: "John Doe", age: "20", gender: 'male' },
2: { id: "somevalue 5", name: "John Doe", age: "20" }
}
];
const res = arr.map((obj) => {
const v = Object.values(obj);
return v.reduce((acc, curr) => {
const {id, ...restProps } = curr;
acc[curr.id] = restProps;
return acc;
}, {});
});
console.log(res);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Here is my sample response:
var jsonData1 = [{
firstName: "Sam",
age: "10"
}, {
firstName: "John",
age: "11"
}, {
firstName: "Jack",
age: "12"
}, {
firstName: "Pam",
age: "13"
}, {
firstName: "",
age: "14"
}, {
firstName: "Mitch",
age: ""
}];
All I want is, wherever I have a blank string in any field, to show it as zero. I want to do it using plain JS, no lodash, no jQuery.
Assuming the given data structure, I'd approach it like this:
loop through the elements in the array with Array.forEach()
loop through the properties in the element with Object.keys(person).forEach()
check for emptystring and replace
Like this:
jsonData1.forEach(person => {
Object.keys(person).forEach(key => {
if (person[key] === "") person[key] = "-";
});
});
You can write this in one line of code:
jsonData1.forEach(o => Object.keys(o).forEach(key => o[key] === '' && (o[key] = '-')))
Using forEach loop
var jsonData1 = [
{
firstName: "Sam",
age: "10"
},
{
firstName: "John",
age: "11"
},
{
firstName: "Jack",
age: "12"
},
{
firstName: "Pam",
age: "13"
},
{
firstName: "",
age: "14"
},
{
firstName: "Mitch",
age: ""
}
];
jsonData1.forEach((e)=>{Object.keys(e).forEach((x)=>e[x]==""?e[x]=0:false)})
console.log(jsonData1)
That should do the trick for nested objects:
const convertObj = obj => {
Object.keys(obj).forEach(key => {
if (obj[key] === '') {
obj[key] = 0
} else if (obj[key] instanceof Object) {
convertObj(obj[key])
}
})
return obj
}
Sample test:
const jsonData1 = [
{
firstName: "Mitch",
age: "",
nested_test: [
{
foo: 'bar',
age: ''
}
],
nested_object: {
foo: 'bar',
age: ''
}
}
]
Result:
[
{
"firstName": "Mitch",
"age": 0,
"nested_array": [
{
"foo": "bar",
"age": 0
}
],
"nested_object": {
"foo": "bar",
"age": 0
}
}
]
As you do have an array, you can simply iterate over it:
for (var i=0;i<jsonData1.length;i++) {
if (jsonData1[i].age==="")
jsonData1[i].age = 0;
if (jsonData1[i].firstName==="")
jsonData1[i].firstName = "Unknown";
}
You can use for...of
The for...of statement creates a loop iterating over iterable objects, including: built-in String, Array, Array-like objects (e.g., arguments or NodeList), TypedArray, Map, Set, and user-defined iterables.
var jsonData1 = [{
firstName: "Sam",
age: "10"}, {
firstName: "John",
age: "11"},{
firstName: "Jack",
age: "12"},{
firstName: "Pam",
age: "13"},{
firstName: "",
age: "14"},{
firstName: "Mitch",
age: ""
}];
for(var p of jsonData1){
if(p.firstName.trim()=="") p.firstName="0";
if(p.age.trim()=="") p.age="0"
}
console.log(jsonData1);
Try this
let newData = jsonData1.forEach(function(data) {
for(ans in data) {
If(data[ans] === "") data[ans] = 0;
}
});
jsonData1 = jsonData1.map(item=>{
for(let key in item){
if(item[key].length === 0) item[key] = 0;
}
return item;
})
With Simple Loop
for(let i = 0; i<data.length;i++){
let keys = Object.keys(data[i]); //returns an array of keys of object
for(let key = 0;key<keys.length;key++){
let keyName = keys[key];
if(data[i][keyName].length === 0) data[i][keyName] = 0
}
}
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);