This question already has answers here:
How can I group an array of objects by key?
(32 answers)
Merge JavaScript objects in array with same key
(15 answers)
Closed 7 months ago.
I have an array of objects each with a nested array and a non-unique ID. I would like to concatenate the objects subarrays where the ID's match.
With the following input:
inputArr = [
{id: 1,
days: ["2022-09-05",
"2022-09-06",
]
},
{id: 2,
days: ["2022-10-05",
"2022-10-06"]
},
{id: 1,
days: ["2022-09-05",
"2022-09-06",
"2022-09-07",
"2022-09-08"]
},
{id: 2,
days: ["2022-10-05",
"2022-10-08"]
},
]
My desired output is as follows:
outputArr = [
{id: 1,
days: ["2022-09-05",
"2022-09-06",
"2022-09-07",
"2022-09-08"
]
},
{id: 2,
days: ["2022-10-05",
"2022-10-06",
"2022-10-08"]
},
]
Ideally I would like to do this without the use of for loops and instead use a map, filter, reduce strategy. I have tried a couple of variations but am having trouble with the nesting. Thanks in advance for the help! Its greatly appreciated.
Not an elegant solution but something like this could work:
let inputArr = [
{ id: 1, days: ["2022-09-05", "2022-09-06"] },
{ id: 2, days: ["2022-10-05", "2022-10-06"] },
{ id: 1, days: ["2022-09-05", "2022-09-06", "2022-09-07", "2022-09-08"] },
{ id: 2, days: ["2022-10-05", "2022-10-08"] },
];
let outputObj = {};
inputArr.forEach(
(item) =>
(outputObj[item.id] = outputObj[item.id]
? [...outputObj[item.id], ...item.days]
: item.days)
);
let outputArr = Object.entries(outputObj).map((item) => ({
id: parseInt(item[0]),
days: Array.from(new Set(item[1])),
}));
Output:
[
{
id: 1,
days: [ '2022-09-05', '2022-09-06', '2022-09-07', '2022-09-08' ]
},
{ id: 2, days: [ '2022-10-05', '2022-10-06', '2022-10-08' ] }
]
My approach using reduce
const inputArr = [
{ id: 1, days: ["2022-09-05", "2022-09-06"] },
{ id: 2, days: ["2022-10-05", "2022-10-06"] },
{ id: 1, days: ["2022-09-05", "2022-09-06", "2022-09-07", "2022-09-08"] },
{ id: 2, days: ["2022-10-05", "2022-10-08"] },
];
const outputArr = inputArr.reduce((accArr, currObj) => {
let ind = accArr.findIndex((obj) => obj.id == currObj.id);
if (ind < 0) {
accArr.push({ ...currObj, days: [...new Set(currObj.days)].slice().sort() });
} else {
accArr[ind].days = [...new Set([...accArr[ind].days, ...currObj.days])].slice().sort();
}
return accArr;
}, []);
console.log(JSON.stringify(outputArr, null, 4));
Result
[
{
"id": 1,
"days": [
"2022-09-05",
"2022-09-06",
"2022-09-07",
"2022-09-08"
]
},
{
"id": 2,
"days": [
"2022-10-05",
"2022-10-06",
"2022-10-08"
]
}
]
Note: [...new Set(array)] to get only unique values from that array.
Related
Given the following Array of Objects:
[{
"id": 1,
"name": "random_name1",
"published_at": "2021-01-16T08:52:24.408Z",
},
{
"id": 2,
"name": "random_name2",
"published_at": "2022-02-16T08:52:24.408Z",
},
{
"id": 3,
"name": "random_name3",
"published_at": "2020-04-16T08:52:24.408Z",
},
{
"id": 4,
"name": "random_name4",
"published_at": "2020-04-16T08:52:24.408Z",
},
{
"id": 5,
"name": "random_name5",
"published_at": "2022-05-16T08:52:24.408Z",
}
]
I need to group the items in one array of nested objects (descending) by Year and Month, result should be:
[
{
year: '2022',
months: [
{
month: '5',
items: [
{
id: '5',
name: 'random_name5'
}
]
},
{
month: '2',
items: [
{
id: '2',
name: 'random_name2'
}
]
}
]
},
{
year: '2021',
months: [
{
month: '1',
items: [
{
id: '1',
name: 'random_name1'
}
]
},
{
month: '2',
items: [
{
id: '2',
name: 'random_name2'
}
]
}
]
},
{
year: '2020',
months: [
{
month: '4',
items: [
{
id: '3',
name: 'random_name3'
},
{
id: '4',
name: 'random_name4'
}
]
}
]
}
];
I have tried the following:
items = [...new Set(items.map((item) => parseInt(item.published_at.split('-')[0])))].map((year) => [
{
year: year,
months: [
...new Set(
items
.filter((item) => parseInt(item.published_at.split('-')[0]) === year)
.map((item) => parseInt(item.published_at.split('-')[1]))
)
].map((month) => [
{
month: month,
items: items.filter(
(item) => parseInt(item.published_at.split('-')[0]) === year && parseInt(item.published_at.split('-')[1]) === month
)
}
])
}
]);
return items
The problem with the above solution, is that it will create a two dimensional array like so (months being two dimensional too):
[
[ { year: 2022, months: [Array] } ],
[ { year: 2021, months: [Array] } ],
[ { year: 2020, months: [Array] } ],
[ { year: 2019, months: [Array] } ],
[ { year: 2018, months: [Array] } ]
]
How to fix this?
If you get a unique list of year-months you can use this to map your object
const items = [{ "id": 1,"name": "random_name1","published_at": "2021-01-16T08:52:24.408Z", },
{ "id": 2, "name": "random_name2", "published_at": "2022-02-16T08:52:24.408Z",},
{ "id": 3, "name": "random_name3","published_at": "2020-04-16T08:52:24.408Z",},
{"id": 4, "name": "random_name4", "published_at": "2020-04-16T08:52:24.408Z",},
{ "id": 5, "name": "random_name5", "published_at": "2022-05-16T08:52:24.408Z",}]
let uniqueYearMonths = [... new Set(items.map(x => x.published_at.substring(0,7)))];
let results = [... new Set(items.map(x => x.published_at.substring(0,4)))]
.map(year => ({
year: year,
months: uniqueYearMonths
.filter(ym => ym.startsWith(year))
.map(ym => ({
month: ym.substring(5,7),
items: items
.filter(item => item.published_at.startsWith(ym))
.map(item => ({
id: item.id,
name: item.name
}))
}))
}));
console.log(results);
Given you array as data, you could do something with array methods like map and reduce.
Like this:
const groupedByYear = data.map((e) => ({ ...e, published_at: new Date(e.published_at) }))
.reduce((acc, e) => {
const year = e.published_at.getFullYear();
const month = e.published_at.getMonth() + 1;
if (!acc[year]) acc[year] = { year };
if (!acc[year][month]) acc[year][month] = [];
acc[year][month] = e;
return acc;
}, {})
const result = Object.values(groupedByYear).reduce((acc, e) => {
const { year, ...months } = e;
acc.push({ year: year, months: months });
return acc;
}, [])
This is an example and is probably not the best way to do this. It is only intended to show you a path of data transformations.
First data.map to be able to do operations on dates. Then a reduce to group data (here using an object). Then creating an array from the object values to match the output you want.
Compared to a solution like you showed, there is the advantage that you limit the number of times that you iterate over the array. It is always a good idea to avoid iterating to much time on an array for better performance.
I have an array of arrays, which contain objects, would like to get the value of a certain key and return it as a big array, have tried a nested map but it returns multiple array's rather than a single array.
const items = [
{
id: 1,
sub_items: [
{
id: 1
},
{
id: 2
},
{
id: 3
}
]
},
{
id: 2,
sub_items: [
{
id: 4
},
{
id: 5
},
{
id: 6
}
]
}
]
const subItemIDs = items.map( (item) =>
item.sub_items.map( (subItem) => subItem.id )
)
console.log(subItemIDs);
Expected output
[1, 2, 3, 4, 5, 6]
Actual output
[ [1,2,3], [4,5,6] ]
You can use arrays.flat(). I can provide more specific code once output is mentioned in the question
const arr1 = [0, 1, 2, [3, 4]];
console.log(arr1.flat());
// expected output: [0, 1, 2, 3, 4]
const arr2 = [0, 1, 2, [[[3, 4]]]];
console.log(arr2.flat(2));
// expected output: [0, 1, 2, [3, 4]]
You could take Array#flatMap to get a flat array from nested arrays.
const
items = [{ id: 1, sub_items: [{ id: 1 }, { id: 2 }, { id: 3 }] }, { id: 2, sub_items: [{ id: 4 }, { id: 5 }, { id: 6 }] }],
subItemIDs = items.flatMap(({ sub_items }) => sub_items.map(({ id }) => id));
console.log(subItemIDs);
Achieved this with:
const items = [
{
id: 1,
sub_items: [
{
id: 1
},
{
id: 2
},
{
id: 3
}
]
},
{
id: 2,
sub_items: [
{
id: 4
},
{
id: 5
},
{
id: 6
}
]
}
]
const subItemIDs = [].concat(...items.map( (item) =>
item.sub_items.map( (subItem) => subItem.id )
))
console.log(subItemIDs);
Sometimes, the obvious is the easiest:
Given a data structure that looks like this
const items = [
{ id: 1, sub_items: [ { id: 1 }, { id: 2 }, { id: 3 }, ] },
{ id: 2, sub_items: [ { id: 4 }, { id: 5 }, { id: 6 }, ] },
];
A trivial function like this
function extract_item_ids( items ) {
const ids = [];
for ( const item of items ) {
for ( const {id} of sub_items ) {
ids.push(id);
}
}
return ids;
}
should do the trick. If you want to collect the ids from a tree of any depth, it's just as easy:
function extract_item_ids( items ) {
const ids = [];
const pending = items;
while ( pending.length > 0 ) {
const item = pending.pop();
ids.push(item.id);
pending.push(...( item.sub_items || [] ) );
}
return ids;
}
And collecting the set of discrete item IDs is no more difficult:
If you want to collect the ids from a tree of any depth, it's just as easy:
function extract_item_ids( items ) {
const ids = new Set();
const pending = [...items];
while ( pending.length > 0 ) {
const item = pending.pop();
ids.add(item.id);
pending.push(...( item.sub_items || [] ) );
}
return Array.from(ids);
}
As is the case with most things JavaScript, you have several options. Some are more efficient than others, others have a certain stylistic purity, others might better speak to your fancy. Here are a few:
Array.flat
With array flat you can take your original code and have the JS Engine flatten the array down to a one-dimensional array. Simply append .flat() onto the end of your map.
const items = [
{ id: 1, sub_items: [ { id: 1 }, { id: 2 }, { id: 3 }, ] },
{ id: 2, sub_items: [ { id: 4 }, { id: 5 }, { id: 6 }, ] },
];
const subItemIds = items.map( (item) =>
item.sub_items.map( (subItem) => subItem.id )
).flat()
console.log(subItemIds);
Array.reduce
Another method is to use reduce to iterate over the object and build an accumulation array using Array.reduce. In the example below, when pushing onto the array, the spread operator (...) is used to break the array into elements.
const items = [
{ id: 1, sub_items: [ { id: 1 }, { id: 2 }, { id: 3 }, ] },
{ id: 2, sub_items: [ { id: 4 }, { id: 5 }, { id: 6 }, ] },
];
const subItemIds = items.reduce((arr,item) => (
arr.push(...item.sub_items.map((subItem) => subItem.id)), arr
),[])
console.log(subItemIds);
Other
Other answers here make use of custom functions or Array.flatMap, which should be explored as they could lead to more readable and efficient code, depending on the program's needs.
I want to add new values on my array in javascript. The array is like that:
[
0: { Id: 0,
Name: "First",
Time: "2020-06-08T11:12:03.003" },
1: { Id: 1,
Name: "Second",
Time: "2020-06-08T11:12:03.003" }
]
This is a simple array, my array is bigger and more values. I want to add new values like that:
[
0: { Id: 0,
Name: "First",
Time: "2020-06-08T11:12:03.003",
SecondTime: "2020-06-08T11:12:03.003" },
1: { Id: 1,
Name: "Second",
Time: "2020-06-08T11:12:03.003",
SecondTime: "2020-06-08T11:12:03.003" }
]
I know there are a lot of similar questions but i couldn't find any solution to my problem.
using foreach
const data = [
{ Id: 0,
Name: "First",
Time: "2020-06-08T11:12:03.003" },
{ Id: 1,
Name: "Second",
Time: "2020-06-08T11:12:03.003" }
]
data.forEach(i=> i.SecondTime = i.Time);
console.log(data)
Using reduce:
const data = [
{ Id: 0,
Name: "First",
Time: "2020-06-08T11:12:03.003" },
{ Id: 1,
Name: "Second",
Time: "2020-06-08T11:12:03.003" }
]
var res = data.reduce((acc, elem)=>{
elem.Secondtime = elem.Time;
return acc = [...acc, elem];
},[]);
console.log(res)
let blah = {
0: { Id: 0,
Name: "First",
Time: "2020-06-08T11:12:03.003"},
1: { Id: 1,
Name: "Second",
Time: "2020-06-08T11:12:03.003" }
};
Object.keys(blah).forEach((ele)=>{
blah[ele].SecondTime = "2020-06-08T11:12:03.003"
});
console.log(blah);
Hope this helps
In an alternative to foreach and reduce answers, you can use map. Has you can see here, for changing data, the map has a little better performance over foreach loops or reduce fucntions.
Follows a full working example:
const originalArray = [
{ 0: { Id: 0,
Name: "First",
Time: "2020-06-08T11:12:03.003" }},
{ 1: { Id: 1,
Name: "Second",
Time: "2020-06-08T11:12:03.003" }}
]
const transformedArray = originalArray.map((element, index) => {
element[index].SecondTime = new Date();
return element;
});
console.log(transformedArray);
I have the following data and I want to return an array (of objects) of years that are distinct.
I tried the following function but I'm getting an array within an array.
const data = [{
id: 1,
name: "test1",
years: [{
id: 1,
name: "year1"
}, {
id: 2,
name: "year2"
}]
},
{
id: 2,
name: "test2",
years: [{
id: 1,
name: "year1"
}]
},
]
let years = data.map((s) => {
return s.years
})
let distinctYears = Array.from(new Set(years.map(c => c.id))).map(id => {
return {
id: id,
name: years.find(c => c.id === id).name,
}
})
console.log(distinctYears);
desired outcome:
[
{id: 1, name: "year1"},
{id: 2, name: "year2"}
]
Since s.years() is an array, and data.map() returns an array of the results, years is necessarily an array of arrays.
Instead of using .map(), use .reduce() to concatenate them.
const data = [{
id: 1,
name: "test1",
years: [{
id: 1,
name: "year1"
}, {
id: 2,
name: "year2"
}]
},
{
id: 2,
name: "test2",
years: [{
id: 1,
name: "year1"
}]
},
];
const years = data.reduce((a, {
years
}) => a.concat(years), []);
let distinctYears = Array.from(new Set(years.map(c => c.id))).map(id => {
return {
id: id,
name: years.find(c => c.id === id).name,
}
});
console.log(distinctYears);
There's so many ways you can go about doing this. Here's one, it's not a one-liner but its broken down to parts to help us understand whats going on.
Your dataset:
let data =
[
{
id: 1,
name: "test1",
years: [{id: 1, name: "year1"}, {id: 2, name: "year2"} ]
},
{
id: 2,
name: "test2",
years: [{id: 1, name: "year1"} ]
},
]
Use .flatMap() to create a one-level array with all items:
let allItems = data.flatMap((item) => {
return item.years.map((year) => {
return year
})
})
Getting distinct items:
let distinct = []
allItems.forEach((item) => {
let matchingItem = distinct.find((match) => match.id == item.id && match.name == item.name)
if(!matchingItem){
distinct.push(item)
}
})
In Practice:
let data = [{
id: 1,
name: "test1",
years: [{
id: 1,
name: "year1"
}, {
id: 2,
name: "year2"
}]
},
{
id: 2,
name: "test2",
years: [{
id: 1,
name: "year1"
}]
},
]
let allItems = data.flatMap((item) => {
return item.years.map((year) => {
return year
})
})
let distinct = []
allItems.forEach((item) => {
let matchingItem = distinct.find((match) => match.id == item.id && match.name == item.name)
if (!matchingItem) {
distinct.push(item)
}
})
console.log(distinct)
Instead of explaining the problem in words, I've just made a quick visual representation below.
Say I have the following array:
let arr1 = [
{
id: 1,
someKey: someValue
},
{
id: 2,
someKey: someValue
},
]
and another array:
let arr2 = [
{
id: 1,
numberOfItems: 10
},
{
id: 2,
numberOfItems: 20
},
]
How would I create the following array?
let result = [
{
id: 1,
someKey: someValue,
numberOfItems: 10
},
{
id: 2,
someKey: someValue,
numberOfItems: 10
},
]
So as you can see, both arrays have the same id value. I want to take numberOfItems: 10 from the second array and place it into the first array under the same id.
Note: the two ids are completely different, have different properties and length. The only similarity is the id
You could first create a map with id as key and then combine the objects This would solve the problem in O(n):
let arr1 = [
{
id: 1,
someKey: 3
},
{
id: 2,
someKey: 6
},
];
let arr2 = [
{
id: 1,
numberOfItems: 10
},
{
id: 2,
numberOfItems: 20
},
];
let arr2Map = arr2.reduce((acc, curr) => {
acc[curr.id] = curr
return acc;
}, {});
let combined = arr1.map(d => Object.assign(d, arr2Map[d.id]));
console.log(combined);
let arr1 = [
{
id: 1,
someKey: 3
},
{
id: 2,
someKey: 6
},
];
let arr2 = [
{
id: 1,
numberOfItems: 10
},
{
id: 2,
numberOfItems: 20
},
];
let idToNumberOfItem = {};
arr2.forEach(({id, numberOfItems})=> idToNumberOfItem[id]=numberOfItems)
arr1.forEach((item)=> item['numberOfItems'] = idToNumberOfItem[item.id])
console.log(arr1);