I want to use array reduce to group items in my array by a key.
When I use the groupByIDErr function below I get obj[item[key]] is not iterable so to get it working I use the groupByID function which had an undefined check as shown below.
Does array reduce have a better way to handle this? What I have done here works ok for the example I have provided but I find it harder to maintain when I have to reduce a more complex array.
function groupByIDErr(arr,key){
return arr.reduce((obj, item) => {
return {
...obj,
[item[key]]: [
...obj[item[key]],
item
]
}
}, {})
}
function groupByID(arr,key){
return arr.reduce((obj, item) => {
return {
...obj,
[item[key]]: obj[item[key]] !== undefined ? [
...obj[item[key]],
item
]:[item]
}
}, {})
}
const people = [
{gender:'male', name: 'Tom'},
{gender:'male', name: 'Richard'},
{gender:'male', name: 'Harry'},
{gender:'female', name: 'Sarah'},
{gender:'female', name: 'Kate'}
]
console.log(groupByID(people,'gender'))
You can use obj[item[key]] || [] in order to make it use an empty array if obj[item[key]] is undefined (since undefined is falsy).
function groupByID(arr, key) {
return arr.reduce((obj, item) => {
return {
...obj,
[item[key]]: [
...obj[item[key]] || [],
item
]
};
}, {});
}
const people = [
{ gender: 'male', name: 'Tom' },
{ gender: 'male', name: 'Richard' },
{ gender: 'male', name: 'Harry' },
{ gender: 'female', name: 'Sarah' },
{ gender: 'female', name: 'Kate' }
];
console.log(groupByID(people, 'gender'));
.as-console-wrapper { min-height: 100%; }
You need a recursion function to go through the depth of an array first and then return object value.
function recurCheck(your array,key){
//if array inside array
recurCheck(newArray,key)
//else
add your reduce function here and return
}
Related
I have this array of objects, within it I have another array of objects:
[
{
id: 1,
country: [
{
id: "5a60626f1d41c80c8d3f8a85"
},
{
id: "5a6062661d41c80c8b2f0413"
}
]
},
{
id: 2,
country: [
{
id: "5a60626f1d41c80c8d3f8a83"
},
{
id: "5a60626f1d41c80c8d3f8a84"
}
]
}
];
How to get flat array of country like this:
[
{ id: "5a60626f1d41c80c8d3f8a85" },
{ id: "5a6062661d41c80c8b2f0413" },
{ id: "5a60626f1d41c80c8d3f8a83" },
{ id: "5a60626f1d41c80c8d3f8a84" }
];
without using a forEach and a temp variable?
When I did:
(data || []).map(o=>{
return o.country.map(o2=>({id: o2.id}))
})
I got the same structure back.
Latest edit
All modern JS environments now support Array.prototype.flat and Array.prototype.flatMap
const data=[{id:1,country:[{id:"5a60626f1d41c80c8d3f8a85"},{id:"5a6062661d41c80c8b2f0413"}]},{id:2,country:[{id:"5a60626f1d41c80c8d3f8a83"},{id:"5a60626f1d41c80c8d3f8a84"}]}];
console.log(
data.flatMap(
(elem) => elem.country
)
)
Old answer
No need for any ES6 magic, you can just reduce the array by concatenating inner country arrays.
const data=[{id:1,country:[{id:"5a60626f1d41c80c8d3f8a85"},{id:"5a6062661d41c80c8b2f0413"}]},{id:2,country:[{id:"5a60626f1d41c80c8d3f8a83"},{id:"5a60626f1d41c80c8d3f8a84"}]}];
console.log(
data.reduce(
(arr, elem) => arr.concat(elem.country), []
)
)
If you want an ES6 feature (other than an arrow function), use array spread instead of the concat method:
const data=[{id:1,country:[{id:"5a60626f1d41c80c8d3f8a85"},{id:"5a6062661d41c80c8b2f0413"}]},{id:2,country:[{id:"5a60626f1d41c80c8d3f8a83"},{id:"5a60626f1d41c80c8d3f8a84"}]}];
console.log(
data.reduce(
(arr, elem) => [...arr, ...elem.country], []
)
)
Note: These suggestions would create a new array on each iteration.
For efficiency, you have to sacrifice some elegance:
const data=[{id:1,country:[{id:"5a60626f1d41c80c8d3f8a85"},{id:"5a6062661d41c80c8b2f0413"}]},{id:2,country:[{id:"5a60626f1d41c80c8d3f8a83"},{id:"5a60626f1d41c80c8d3f8a84"}]}];
console.log(
data.reduce(
(arr, elem) => {
for (const c of elem.country) {
arr.push(c);
}
return arr;
}, []
)
)
const raw = [
{
id: 1,
country: [
{
id: "5a60626f1d41c80c8d3f8a85"
},
{
id: "5a6062661d41c80c8b2f0413"
}
]
},
{
id: 2,
country: [
{
id: "5a60626f1d41c80c8d3f8a83"
},
{
id: "5a60626f1d41c80c8d3f8a84"
}
]
}
];
const countryIds = raw
.map(x => x.country)
.reduce((acc, curr) => {
return [
...acc,
...curr.map(x => x.id)
];
}, []);
console.log(countryIds)
This, works, just concat the nested arrays returned by your solution
let arr = [{ "id": 1,
"country": [{
"id": "5a60626f1d41c80c8d3f8a85",
},
{
"id": "5a6062661d41c80c8b2f0413",
}
]
},
{
"id": 2,
"country": [{
"id": "5a60626f1d41c80c8d3f8a83",
},
{
"id": "5a60626f1d41c80c8d3f8a84",
}
]
}
];
//If you want an array of country objects
console.log([].concat.apply(...(arr || []).map(o=> o.country)))
//If you can an array od country ids
console.log([].concat.apply(...(arr || []).map(o=> o.country.map(country => country.id))))
Ayush Gupta's solution will work for this case. But I would like to provide other solution.
const arr = [
{
id: 1,
country: [
{
id: '5a60626f1d41c80c8d3f8a85'
},
{
id: '5a6062661d41c80c8b2f0413'
}
]
},
{
id: 2,
country: [
{
id: '5a60626f1d41c80c8d3f8a83'
},
{
id: '5a60626f1d41c80c8d3f8a84'
}
]
}
];
const ids = arr.reduce(
(acc, {country}) => [
...acc,
...country.map(({id}) => ({
id
}))
],
[]
);
console.log(ids);
For JSON string data, it can be done during parsing too :
var ids = [], json = '[{"id":1,"country":[{"id":"5a60626f1d41c80c8d3f8a85"},{"id":"5a6062661d41c80c8b2f0413"}]},{"id":2,"country":[{"id":"5a60626f1d41c80c8d3f8a83"},{"id":"5a60626f1d41c80c8d3f8a84"}]}]';
JSON.parse(json, (k, v) => v.big && ids.push(v));
console.log( ids );
I am not sure why noone mentioned flat() (probably for large arrays, it might be less performant)
(data || []).map(o=>{
return o.country.map(o2=>({id: o2.id}))
}).flat()
I have an array of distinct objects. Some have the same date value. I'd like to group those with the same date together and return a mapped object where each date is the key and its value is an array of the object(s) with that same date. Here's the array:
const arr = [
{
date: '2/2/20',
name: 'joe',
age: 22
},
{
date: '3/3/20',
name: 'john',
age: 67
},
{
date: '2/2/20',
name: 'chuck',
age: 34
},
{
date: '3/3/20',
name: 'bob',
age: 28
},
{
date: '4/4/20',
name: 'bill',
age: 51
}
]
I know something like this can be done with map().filter(), but it's not what I'm looking for, and plus I'm trying to do this with reduce() to save on resources.
So far I have:
arr.reduce((acc, person) => {
acc[person.date] = [ person ];
return acc;
}, {});
This only returns the last match, not all the matches:
{
'2/2/20': [ { date: '2/2/20', name: 'chuck', age: 34 } ],
'3/3/20': [ { date: '3/3/20', name: 'bob', age: 28 } ],
'4/4/20': [ { date: '4/4/20', name: 'bill', age: 51 } ]
}
I've tried other aspects of this solution but I can't seem to get it right. I also know how to get the mapped object that returns the count of the number of matches, but it's still not my solution:
arr.reduce((acc, person) => {
acc[person.date] = acc[person.date] + 1 || 1
return acc
}, {})
This returns:
{ '2/2/20': 2, '3/3/20': 2, '4/4/20': 1 }
So how can I use reduce() to get an object that contains all the matches?
Initialize to an empty array if acc[person.date] doesn't exist, and push regardless:
arr.reduce((acc, person) => {
acc[person.date] = (acc[person.date] || []);
acc[person.date].push(person);
return acc;
}, {});
Destructuring one-line/spreading approach:
arr.reduce((acc, { date, ...o }) => ({ ...acc, [date]: [...(acc[date] || []), { date, ...o }), {});
Jack Bashford's answer appears to be what you are looking for. I would just add that this is a very common operation often referred to as groupBy. This is implemented in most functional helper libraries (Lodash for example). You can also implement it yourself with something like:
const groupBy = (cb, arr) =>
arr.reduce((grouped, el) => {
const key = cb(el)
return {
...grouped,
[key]: [...grouped[key] || [], el]
}
}, {})
(Note the above implementation is just an example and should not be considered a performant implementation. See Lodash groupBy implementation.
and then for your use case:
groupBy(el => el.date, arr)
which is helpful if you plan on doing similar operations.
Suppose I got this array:
const users =[
{
id:1,
name:'bob',
},
{
id:2,
name:'sally',
},
{
id:3,
name:'bob',
age:30,
}
];
And I want to use any key(in this case 'name' ) to return an object :
{
bob:[
{
id:1,
name:'bob',
},
{
id:3,
name:'bob',
age:30,
}
],
sally:[
{
id:2,
name:'sally',
}
],
}
I tried this:
const go = (A,key) =>{
return A.reduce((o, key) => ({ ...o, [key]:o }), {})
}
export default go;
But this returns:
{ '[object Object]': { '[object Object]': { '[object Object]': {} } } }
If the key is not present omit from the result. It should not mutate the original array though. How can I perform this kind of conversion?
With the approach you have, a new array is not instantiated in case the key is not yet present in the object.
This will work:
const result = users.reduce((a, v) => {
a[v.name] = a[v.name] || [];
a[v.name].push(v);
return a;
}, {});
Complete snippet wrapping this logic in a function:
const users = [{
id: 1,
name: 'bob',
}, {
id: 2,
name: 'sally',
}, {
id: 3,
name: 'bob',
age: 30,
}];
const go = (input, key) => input.reduce((a, v) => {
a[v[key]] = a[v[key]] || [];
a[v[key]].push(v);
return a;
}, {});
console.log(go(users, 'name'));
If you really want to cram it into a one-liner, this will also work, by either spreading the already existing array, or an empty one:
const result = users.reduce((a, v) => ({...a, [v.name]: [...a[v.name] || [], v]}), {});
Complete snippet wrapping this logic in a function:
const users = [{
id: 1,
name: 'bob',
}, {
id: 2,
name: 'sally',
}, {
id: 3,
name: 'bob',
age: 30,
}];
const go = (input, key) => input.reduce((a, v) => ({...a, [v[key]]: [...a[v[key]] || [], v]}), {});
console.log(go(users, 'name'));
You were close but the key attribute in this case was each value (eg: { id: 1, name: 'bob' }) so the string representation is [object Object] which is why all the keys are that. Based off what you said, you want to use key.name as the property and set it's value as [key]. (I renamed key to arr in my example since it's the array value).
So this would be something like { ...o, [arr.name]: [arr] }
Because there can be an existing value, it adds a bit of complexity which is what [...(obj[arr.name] || []), arr] is doing. It's looking up the existing value (or defaulting to an empty array) and spreading those values and adding the new value.
const users = [{
id: 1,
name: 'bob',
},
{
id: 2,
name: 'sally',
},
{
id: 3,
name: 'bob',
age: 30,
}
];
const transform = (input, keyName) => {
return input.reduce((obj, arr) => ({ ...obj,
[arr[keyName]]: [...(obj[arr[keyName]] || []), arr]
}), {})
}
console.log(transform(users, 'name'))
console.log(transform(users, 'id'))
I am trying to check if object array A includes objects from B.
let A = [
{ name: "Max" },
{ name: "Jhon" },
{ name: "Naton" },
]
let B = [
{ name: "Max" },
{ name: "Naton" },
]
So B has two objects that is in array A. How to check this ?
I am trying to achieve it with includes :
for(let entry of this.b){
if(this.a.includes(entry)){
console.log('includes');
}
}
But I get false on includes.
The method Array.includes() compare the entries of the array with the given value. Because your array entries are objects, it will not match. You have to loop at the array yourself and make the comparison.
Array.some() loops on an array and returns true if you returns true at least one. This method is useful when you want to verify something. In our example, we want to verify if the array a contains the b entry.
const a = [{
name: 'Max',
},
{
name: 'Jhon',
},
{
name: 'Naton',
},
];
const b = [{
name: 'Max',
},
{
name: 'Naton',
},
{
name: 'Daddy',
},
];
console.log(b.map(x => a.some(y => y.name === x.name)));
If I break it down :
const a = [{
name: 'Max',
},
{
name: 'Jhon',
},
{
name: 'Naton',
},
];
const b = [{
name: 'Max',
},
{
name: 'Naton',
},
{
name: 'Daddy',
},
];
// Loop on every entry of the b array
b.forEach((x) => {
// x here represent one entry
// first it will worth { name: 'Max' }, then { name: 'Naton' } ...
// for each value we are going to look at a if we can find a match
const isThereAMatch = a.some((y) => {
// y here is worth one entry of the a array
if (y.name === x.name) return true;
return false;
});
if (isThereAMatch === true) {
console.log(`We have found ${x.name} in a`);
} else {
console.log(`We have not found ${x.name} in a`);
}
});
You have to use another loop, then check the property name:
var a = [
{name: "Max"},
{name: "Jhon"},
{name: "Naton"},
];
var b = [
{name: "Max"},
{name: "Naton"},
];
for(let entry of b){
for(let entry2 of a){
if(entry2.name == entry.name){
console.log('includes', entry.name);
}
}
}
OR: You can use string version of object to check with includes():
var a = [
{name: "Max"},
{name: "Jhon"},
{name: "Naton"},
];
var b = [
{name: "Max"},
{name: "Naton"},
];
var aTemp = a.map(i => JSON.stringify(i));
var bTemp = b.map(i => JSON.stringify(i));
for(let entry of bTemp){
if(aTemp.includes(entry)){
console.log('includes', entry);
}
}
When you use Array#includes() method it will always return false because it's comparing objects which aren't equal because they aren't referencing the same object.
You should compare objects properties and not whole objects, you can do it using Array#some() method like this:
for (let entry of this.b) {
if (this.b.some(x => x.name === entry.name)) {
console.log('includes');
}
}
Demo:
A = [{
name: "Max"
},
{
name: "Jhon"
},
{
name: "Naton"
},
]
B = [{
name: "Max"
},
{
name: "Naton"
},
]
//Filter objects that exists in both arrays
let result = A.filter(el=> B.some(x => x.name === el.name));
console.log(result);
I have this array of objects, within it I have another array of objects:
[
{
id: 1,
country: [
{
id: "5a60626f1d41c80c8d3f8a85"
},
{
id: "5a6062661d41c80c8b2f0413"
}
]
},
{
id: 2,
country: [
{
id: "5a60626f1d41c80c8d3f8a83"
},
{
id: "5a60626f1d41c80c8d3f8a84"
}
]
}
];
How to get flat array of country like this:
[
{ id: "5a60626f1d41c80c8d3f8a85" },
{ id: "5a6062661d41c80c8b2f0413" },
{ id: "5a60626f1d41c80c8d3f8a83" },
{ id: "5a60626f1d41c80c8d3f8a84" }
];
without using a forEach and a temp variable?
When I did:
(data || []).map(o=>{
return o.country.map(o2=>({id: o2.id}))
})
I got the same structure back.
Latest edit
All modern JS environments now support Array.prototype.flat and Array.prototype.flatMap
const data=[{id:1,country:[{id:"5a60626f1d41c80c8d3f8a85"},{id:"5a6062661d41c80c8b2f0413"}]},{id:2,country:[{id:"5a60626f1d41c80c8d3f8a83"},{id:"5a60626f1d41c80c8d3f8a84"}]}];
console.log(
data.flatMap(
(elem) => elem.country
)
)
Old answer
No need for any ES6 magic, you can just reduce the array by concatenating inner country arrays.
const data=[{id:1,country:[{id:"5a60626f1d41c80c8d3f8a85"},{id:"5a6062661d41c80c8b2f0413"}]},{id:2,country:[{id:"5a60626f1d41c80c8d3f8a83"},{id:"5a60626f1d41c80c8d3f8a84"}]}];
console.log(
data.reduce(
(arr, elem) => arr.concat(elem.country), []
)
)
If you want an ES6 feature (other than an arrow function), use array spread instead of the concat method:
const data=[{id:1,country:[{id:"5a60626f1d41c80c8d3f8a85"},{id:"5a6062661d41c80c8b2f0413"}]},{id:2,country:[{id:"5a60626f1d41c80c8d3f8a83"},{id:"5a60626f1d41c80c8d3f8a84"}]}];
console.log(
data.reduce(
(arr, elem) => [...arr, ...elem.country], []
)
)
Note: These suggestions would create a new array on each iteration.
For efficiency, you have to sacrifice some elegance:
const data=[{id:1,country:[{id:"5a60626f1d41c80c8d3f8a85"},{id:"5a6062661d41c80c8b2f0413"}]},{id:2,country:[{id:"5a60626f1d41c80c8d3f8a83"},{id:"5a60626f1d41c80c8d3f8a84"}]}];
console.log(
data.reduce(
(arr, elem) => {
for (const c of elem.country) {
arr.push(c);
}
return arr;
}, []
)
)
const raw = [
{
id: 1,
country: [
{
id: "5a60626f1d41c80c8d3f8a85"
},
{
id: "5a6062661d41c80c8b2f0413"
}
]
},
{
id: 2,
country: [
{
id: "5a60626f1d41c80c8d3f8a83"
},
{
id: "5a60626f1d41c80c8d3f8a84"
}
]
}
];
const countryIds = raw
.map(x => x.country)
.reduce((acc, curr) => {
return [
...acc,
...curr.map(x => x.id)
];
}, []);
console.log(countryIds)
This, works, just concat the nested arrays returned by your solution
let arr = [{ "id": 1,
"country": [{
"id": "5a60626f1d41c80c8d3f8a85",
},
{
"id": "5a6062661d41c80c8b2f0413",
}
]
},
{
"id": 2,
"country": [{
"id": "5a60626f1d41c80c8d3f8a83",
},
{
"id": "5a60626f1d41c80c8d3f8a84",
}
]
}
];
//If you want an array of country objects
console.log([].concat.apply(...(arr || []).map(o=> o.country)))
//If you can an array od country ids
console.log([].concat.apply(...(arr || []).map(o=> o.country.map(country => country.id))))
Ayush Gupta's solution will work for this case. But I would like to provide other solution.
const arr = [
{
id: 1,
country: [
{
id: '5a60626f1d41c80c8d3f8a85'
},
{
id: '5a6062661d41c80c8b2f0413'
}
]
},
{
id: 2,
country: [
{
id: '5a60626f1d41c80c8d3f8a83'
},
{
id: '5a60626f1d41c80c8d3f8a84'
}
]
}
];
const ids = arr.reduce(
(acc, {country}) => [
...acc,
...country.map(({id}) => ({
id
}))
],
[]
);
console.log(ids);
For JSON string data, it can be done during parsing too :
var ids = [], json = '[{"id":1,"country":[{"id":"5a60626f1d41c80c8d3f8a85"},{"id":"5a6062661d41c80c8b2f0413"}]},{"id":2,"country":[{"id":"5a60626f1d41c80c8d3f8a83"},{"id":"5a60626f1d41c80c8d3f8a84"}]}]';
JSON.parse(json, (k, v) => v.big && ids.push(v));
console.log( ids );
I am not sure why noone mentioned flat() (probably for large arrays, it might be less performant)
(data || []).map(o=>{
return o.country.map(o2=>({id: o2.id}))
}).flat()