This question already has answers here:
Most efficient method to groupby on an array of objects
(58 answers)
Closed 3 years ago.
I have an array of objects that looks like this:
let stuff = [
{
"id": "48202847",
"name": "Doe"
},
{
"id": "17508",
"name": "Marie"
},
{
"id": "175796",
"name": "Robert"
},
{
"id": "175796",
"name": "Ronald"
},
]
What I want to get is a dictionary looking something like this:
{
"D": [{"id": "48202847", "name": "Doe"}],
"M": [{"id": "17508", "name": "Marie"}],
"R": [{"id": "175796", "name": "Robert"}, {"id": "175796", "name": "Ronald"}]
}
Notice how all the people whose name starts with "R" are listed under one key.
This is my function that creates a dictionary with the person's name as the key:
const byId = (array) =>
array.reduce((obj, item) => {
obj[item.name] = item
return obj
}, {})
But this obviously doesn't do what I want it to. I do have some ideas of how to make this possible, but they are extremely legacy and I would love to know how to do this right.
Any help is appreciated!
You need the first character, uppercase and an array for collecting the objects.
const byId = array =>
array.reduce((obj, item) => {
var key = item.name[0].toUpperCase(); // take first character, uppercase
obj[key] = obj[key] || []; // create array if not exists
obj[key].push(item); // push item
return obj
}, {});
let stuff = [{ id: "48202847", name: "Doe" }, { id: "17508", name: "Marie" }, { id: "175796", name: "Robert" }, { id: "175796", name: "Ronald" }],
result = byId(stuff)
console.log(result);
Here's a solution based on Set, map, reduce and filter:
let stuff = [{"id": "48202847","name": "Doe"},{"id": "17508","name": "Marie"},{"id": "175796","name": "Robert"},{"id": "175796","name": "Ronald"}];
let result = [...new Set(stuff.map(x => x.name[0]))]
.reduce((acc, val) => {
return acc = { ...acc,
[val]: stuff.filter(x => x.name.startsWith(val))
}
}, {});
console.log(result);
Great solution Nina! Could be made a little cleaner by utilizing the spread operator.
const byId = (array) =>
array.reduce((obj, item) => {
var key = item.name[0].toUpperCase();
return {
...obj,
[key]: obj[key] ? [...obj[key], item] : [item],
}
}, {});
Related
I have a array as follows:
data = [
{
"id":1
"name":"london"
},
{
"id":2
"name":"paris"
},
{
"id":3
"name":"london"
},
{
"id":4
"name":"paris"
},
{
"id":5
"name":"australia"
},
{
"id":6
"name":"newzearland"
}
]
At runtime this array can have n number of elements. I want to group this array with respect to name attribute. All the elements with same name should be moved to a separate array. I don't know the what value can name have in advance. This is coming at runtime. For example, from above array I want final output as follows:
output:
newArray1 = [
{
"id":1
"name":"london"
},
{
"id":3
"name":"london"
}
]
newArray2 = [
{
"id":2
"name":"paris"
},
{
"id":4
"name":"paris"
}
]
newArray3 = [
{
"id":5
"name":"australia"
}
]
newArray4 = [
{
"id":6
"name":"newzearland"
}
]
How can I do that?
As Teemu has already pointed out in a comment, creating new variables to store the data is not ideal. You would have no way of knowing how many groups you've created and using variables that you can't be sure exist is not the best way to write code. Fortunately, JavaScript has objects, which can store data like this in a much cleaner way. Here's the code I've come up with:
function groupBy(arr, key) {
let res = {}
for (let element of arr) {
if (res.hasOwnProperty(element[key])) {
res[element[key]].push(element)
} else {
res[element[key]] = [element]
}
}
return res
}
This code is not the best, most efficient code ever, but it is written to be easier to understand for someone still learning. This code loops over every element in your data and checks whether our result already contains an array for elements with that name. If there's already an array for elements with that name, the current element is added to it. If there isn't one, a new one is created with the current element inside it. To do exactly what you want, you'd call this function with groupBy(data, "name") and assign it to a new variable like groupedData (THIS DOES NOT MODIFY THE DATA, IT RETURNS A NEW OBJECT OF GROUPED DATA) .
Start by getting all the unique .names, then map them to the original array filtered by each .name:
const data = [{
"id": 1, "name": "london"
},
{
"id": 2, "name": "paris"
},
{
"id": 3, "name": "london"
},
{
"id": 4, "name": "paris"
},
{
"id": 5, "name": "australia"
},
{
"id": 6, "name": "newzearland"
}
];
const newData = [...new Set(data
//Get all names in an array
.map(({name}) => name))]
//For each name filter original array by name
.map(n => data.filter(({name}) => n === name));
console.log( newData );
//OUTPUT: [newArray1, newArray2, .....]
You can get the expected result with grouping by key approach.
const data = [{"id":1,"name":"london"},{"id":2,"name":"paris"},{"id":3,"name":"london"},{"id":4,"name":"paris"},{"id":5,"name":"australia"},{"id":6,"name":"newzearland"}];
const result = Object.values(data.reduce((acc, obj) =>
({ ...acc, [obj.name]: [...(acc[obj.name] ?? []), obj] }), {}));
console.log(result);
const [newArray1, newArray2, newArray3, newArray4, ...rest] = result;
console.log('newArray1:', newArray1);
console.log('newArray2:', newArray2);
console.log('newArray3:', newArray3);
console.log('newArray4:', newArray4);
.as-console-wrapper{min-height: 100%!important; top: 0}
I am new to javascript. I have an id, name and time that I am trying to get from my data and for each name I am trying to loop through the data and call a function from each name. Any help would be much appreciated. Thank you so much!
This is what I have done
const data = [
[
{
"id": "14hyzdrdsquo",
"name": "Ronald",
"time": '12pm',
},
],
[
{
"id": "1f496w43b8yi",
"name": "Jack",
"time": '1am',
},
],
]
const getData = (id, name, time) => {
const ids = [] // desired ['14hyzdrdsquo','1f496w43b8yi']
const names = []// desired ['Ronald','Jack']
const times = []// desired ['12pm','1am']
ids.push(id) // should have each id in this array
names.push(name) // should have each name in this array
times.push(time) // should have each time in this array
}
var id = Math.random().toString(16).slice(2)
data.map(j => j.map(i => getData(id, i.name, i.time)))
Using a for...of loop, you can loop through your array and group on the keys of each object. Since you have arrays in your outer array, you can use another for loop to loop over those and get each object. Then you can use a for...in loop to over the keys in your object. For each key, you can check if it exists within grouped, and if it does, concatenate the object's value to the grouped array. If it doesn't exist, you can create a new element, and push the value. Once you have grouped everything, you can use destructuring to pull out the array values from your object into variables:
const data = [[{ "id": "14hyzdrdsquo", "name": "Ronald", "time": '12pm', }, ], [{ "id": "1f496w43b8yi", "name": "Jack", "time": '1am', }, ],];
const grouped = {};
for(const arr of data) {
for(const obj of arr) {
for(const key in obj) {
grouped[key] = (grouped[key] || []).concat(obj[key]);
}
}
}
const {id, name, time} = grouped;
console.log(id);
console.log(name);
console.log(time);
The above concept can be achieved with .reduce() as well, where the grouped array gets built by using the accumulator argument of the reduce method, and each object is iterated using Object.entries() with .forEach():
const data = [[{ "id": "14hyzdrdsquo", "name": "Ronald", "time": '12pm', }, ], [{ "id": "1f496w43b8yi", "name": "Jack", "time": '1am', }, ],];
const grouped = data.reduce((acc, arr) => {
arr.forEach(obj => {
Object.entries(obj).forEach(([key, val]) => {
acc[key] = [...(acc[key] || []), val];
});
})
return acc;
}, {});
const {id, name, time} = grouped;
console.log(id);
console.log(name);
console.log(time);
Lastly, if you're happy with doing multiple iterations through your array, you can use .flatMap() to iterate through your array, and for each array, .map() over the objects inside of that. For each object you can extract the key using destructuring assignment. The result of the inner map is then flattened into the outer resulting array due to .flatMap():
const data = [[{ "id": "14hyzdrdsquo", "name": "Ronald", "time": '12pm', }, ], [{ "id": "1f496w43b8yi", "name": "Jack", "time": '1am', }, ],];
const id = data.flatMap(arr => arr.map(({id}) => id));
const name = data.flatMap(arr => arr.map(({name}) => name));
const time = data.flatMap(arr => arr.map(({time}) => time));
console.log(id);
console.log(name);
console.log(time);
You could destructure the double nested array and take the entries of the object and push the values to the same named properties with 's'.
Ath the end destructure the objct for single arrays.
const
data = [[{ id: "14hyzdrdsquo", name: "Ronald", time: '12pm' }], [{ id: "1f496w43b8yi", name: "Jack", time: '1am' }]],
{ ids, names, times } = data.reduce((r, [o]) => {
Object.entries(o).forEach(([k, v]) => (r[k + 's'] ??= []).push(v));
return r;
}, {});
console.log(ids);
console.log(names);
console.log(times);
The problems is you put
const ids = [] // desired ['14hyzdrdsquo','1f496w43b8yi']
const names = []// desired ['Ronald','Jack']
const times = []// desired ['12pm','1am']
inside getData function. That's mean you created new one arrays time when call getData
and for you solution better use .forEach instead of .map.
const data = [
[
{
"id": "14hyzdrdsquo",
"name": "Ronald",
"time": '12pm',
},
],
[
{
"id": "1f496w43b8yi",
"name": "Jack",
"time": '1am',
},
],
]
const ids = [] // desired ['14hyzdrdsquo','1f496w43b8yi']
const names = []// desired ['Ronald','Jack']
const times = []// desired ['12pm','1am']
const getData = (id, name, time) => {
ids.push(id) // should have each id in this array
names.push(name) // should have each name in this array
times.push(time) // should have each time in this array
}
var id = Math.random().toString(16).slice(2)
data.forEach(j => j.forEach(i => getData(id, i.name, i.time)))
console.log(ids)
console.log(names)
console.log(times)
You may do that using reduce inside each loop so you can have lower complexity.
const data = [
[
{
id: '14hyzdrdsquo',
name: 'Ronald',
time: '12pm',
},
{
id: '14hyzdrdsquo',
name: 'Ronald',
time: '12pm',
},
],
[
{
id: '1f496w43b8yi',
name: 'Jack',
time: '1am',
},
{
id: '1f496w43b8yi',
name: 'Jack',
time: '1am',
},
],
];
let ids = [];
let names = [];
let times = [];
data.forEach((elem) => {
const obj = elem.reduce(
(acc, curr, i) => {
acc.ids.push(curr.id);
acc.names.push(curr.name);
acc.times.push(curr.time);
return acc;
},
{
ids: [],
names: [],
times: [],
}
);
ids = [...ids, ...obj.ids];
names = [...names, ...obj.names];
times = [...times, ...obj.times];
});
console.log({
ids,
names,
times,
});
I have a string value and an object obj, want to convert value to array then find it in obj by value, and get name but it return undefined, what I have missed?
let value = '3,4';
let obj = {
"DistrictData": [{
"id": 3,
"name": 'blah'
}, {
"id": 4,
"name": 'oops'
}]
}
let res = value.split(',').map((v, i) => obj.DistrictData.find(o => o.id === v))
console.log(res)
You need to find with a number value, because split returns an array of strings. Then map the name as well.
let value = '3,4',
obj = { DistrictData: [{ id: 3, name: 'blah' }, { id: 4, name: 'oops' }] },
res = value
.split(',')
.map((v, i) => obj.DistrictData.find(o => o.id === +v))
.map(o => o.name);
console.log(res);
you can normalize array
to like
let obj = { "DistrictData": {"3":{ "id": 3,"name": 'blah'}, "4":{"id": 4,"name": 'oops'}}
then you can filter on name
normalizr
The split array contains string value and within find you are comparing string with number so either convert string to number or use == to ignore checking type. And finally get the name property from the object.
let value = '3,4';
let obj = {
"DistrictData": [{
"id": 3,
"name": 'blah'
}, {
"id": 4,
"name": 'oops'
}]
}
let res = value.split(',').map((v, i) => (bj.DistrictData.find(o => o.id == v).name)
console.log(res)
Refer : Which equals operator (== vs ===) should be used in JavaScript comparisons?
I have an array of data. Some of the key in the array are same. I would like to create a new array based on the key and add the other data.
This is my array
var myObjOne = [
{
"name":"John",
"id":1,
"car":"maruti"
},
{
"name":"John",
"id":2,
"car":"wolks"
},
{
"name":"John",
"id":3,
"car":"bmw"
},
{
"name":"Peter",
"id":4,
"car":"alto"
},
{
"name":"Peter",
"id":5,
"car":"swift"
}
];
I would like to convert the array in to the below format.
var myObj = [
{
"name":"John",
"items": [
{ "id":1, "car":"maruti" },
{ "id":2, "car":"wolks" },
{ "id":3, "car":"bmw" }
]},
{
"name":"Peter",
"items": [
{ "id":4, "car":"alto" },
{ "id":5, "car":"swift" },
]
}
];
I am working on a node environment.
You can create an object using Array#reduce first which maps name with items, and then create the final array by looping over the intermediate map using a for...of loop:
var source = [{"name":"John","id":1,"car":"maruti"},{"name":"John","id":2,"car":"wolks"},{"name":"John","id":3,"car":"bmw"},{"name":"Peter","id":4,"cars":"alto"},{"name":"Peter","id":5,"cars":"swift"}];
const map = source.reduce((acc, {name, ...obj}) => {
if (!acc[name]) {
acc[name] = [];
}
acc[name].push(obj);
return acc;
}, {});
const result = [];
for (let[name, items] of Object.entries(map)) {
result.push({name, items});
}
console.log(result);
Array.reduce is at rescue.This method accepts an accumulator and current
item. Check in the accumulator if there exist an object where the value of name property is John or Peter
var myObjOne = [{
"name": "John",
"id": 1,
"car": "maruti"
},
{
"name": "John",
"id": 2,
"car": "wolks"
},
{
"name": "John",
"id": 3,
"car": "bmw"
},
{
"name": "Peter",
"id": 4,
"car": "alto"
},
{
"name": "Peter",
"id": 5,
"car": "swift"
}
];
var newObj = myObjOne.reduce(function(acc, curr, currIndex) {
// using findIndex to check if there exist an object
// where the value of the name property is John, Peter
// if it exist it will return the index else it will return -1
let ifNameExist = acc.findIndex(function(item) {
return item.name === curr.name;
})
// if -1 then create a object with name and item property and push
// it to the accumulator
if (ifNameExist === -1) {
let nameObj = {};
nameObj.name = curr.name;
nameObj.items = [];
nameObj.items.push({
id: curr.id,
car: curr.car
})
acc.push(nameObj)
} else {
// if such an object already exist then just update the item array
acc[ifNameExist].items.push({
id: curr.id,
car: curr.car
})
}
return acc;
}, []);
console.log(newObj)
Use .reduce to group by name, and use .find inside the reducer to find if the matching name has already been added:
const input=[{"name":"John","id":1,"car":"maruti"},{"name":"John","id":2,"car":"wolks"},{"name":"John","id":3,"car":"bmw"},{"name":"Peter","id":4,"cars":"alto"},{"name":"Peter","id":5,"cars":"swift"}]
const output = input.reduce((a, { name, ...item }) => {
const foundNameObj = a.find(nameObj => nameObj.name === name);
if (foundNameObj) foundNameObj.items.push(item);
else a.push({ name, items: [item] });
return a;
}, []);
console.log(output);
I have following Plunkr which works perfectly.
https://plnkr.co/edit/WDjoEK7bAVpKSJbAmB9D?p=preview
It uses the _.differenceWith() function of lodash, in order two save all array values, which are not contained by the two arrays.
var result = _.differenceWith(data, test, _.isEqual);
Now I have two problems:
1.) In our project we use an older Lodash Version where the function differenceWith is not implemented
2.) I only need to compare one value of the array. This currently compares the complete objects. I only need to compare the id property.
This will find the objects in arr1 that are not in arr2 based on the id attribute.
var arr1 = [ { "id": "1" }, { "id": "2" }, { "id": "3" } ];
var arr2 = [ { "id": "1" }, { "id": "2" } ];
var result = arr1.filter(o1 => arr2.filter(o2 => o2.id === o1.id).length === 0);
console.log(result);
Note that this example does not require lodash.
If you want to use a different comparison instead of id, you can change the o2.id === o1.id part to a different property.
Here is a more generic solution:
var arr1 = [ { "name": "a" }, { "name": "b" }, { "name": "c" } ];
var arr2 = [ { "name": "a" }, { "name": "c" } ];
function differenceWith(a1, a2, prop) {
return a1.filter(o1 => a2.filter(o2 => o2[prop] === o1[prop]).length === 0);
}
var result = differenceWith(arr1, arr2, 'name');
console.log(result);