I have two arrays, each with a years object with key valued pairs of the year and a value. I want to create an updated version of array1 with the values for each row’s years reduced by the values from the corresponding values in array2
i.e. A/Fred/2013 should be reduced from 16 by 3, to 13 and so on.
So far I have, but I’m not managing to update the array correctly
array1.forEach(r1 => {
// Get the corresponding row from array2
let array2Record = array2.find(r2 => r2.key1 === r1.key1 && r2.key2 === r1.key2);
// Go through each of the years for this row in array1
Object.entries(r1.years).forEach(keyValuePair => {
// And reduce it by the value in array2
keyValuePair.value = keyValuePair.value - array2Record.years[keyValuePair.key].value;
})
})
This is the simplified version of the data
let array1 = [
{
"key1": "A",
"key2": "Fred",
"years": {
"2013": 16,
"2014": 11,
"2015": 17
}
},
{
"key1": "A",
"key2": "Jim",
"years": {
"2013": 1,
"2014": 4,
"2015": 3
}
},
{
"key1": "B",
"key2": "Mary",
"years": {
"2013": 1,
"2014": 4,
"2015": 3
}
}
]
let array2 = [
{
"key1": "A",
"key2": "Fred",
"years": {
"2013": 3,
"2014": 2,
"2015": 7
}
},
{
"key1": "A",
"key2": "Jim",
"years": {
"2013": 9,
"2014": 3,
"2015": 1
}
},
{
"key1": "B",
"key2": "Mary",
"years": {
"2013": 8,
"2014": 3,
"2015": 6
}
}
]
3
Answers
Solution
You can do so by zipping the arrays using
map()
, then updating theyears
property value. To create the new years property value you can useObject.keys()
andreduce()
.Additional remarks on the solution
This method assumes, that both the array contain the same number of elements and that objects that should be merged are in the same position within both array (i. e. e.g. both Freds are the first elements in the array). One can also make it work without that being the case but it’s a bit more involved then (a lookup table e.g. using a
Map
would be required to do that in linear timeO(n)
. Something likefind()
would run inO(n²)
).By using the nullish coalescing operator
??
I handle the cases where a particular year might only be present in the first or second array by using a default value of0
in that case. You can remove that check if that cannot be the case with your data.In terms of runtime this runs in
O(n)
and only does a single iteration through your data and therefore is pretty much as fast as it gets.I also treated everything as immutable which is generally a good practice in order to prevent bugs. That also means the original input value has remained untouched or better unchanged. The result is (as the data currently is) a deep copy of your input.
See also
There are many possible ways to zip arrays in JavaScript e.g. using
map()
(as I did here) or generators: See this SO threadIn short, you can do it by using
reduce()
to get a new array by combining them. Then reduce the year values by matching and looping them one by one with aforEach
.It matches any records by matching
key1
andkey2
values so if you want to match more arrays, then you just need to combine them by usingconcat()
You can simply find each matching object in
array2
, and then for each year, update each count.Optional chaining
?.
and the nullish coalescing operator??
are used to handle cases where there is no matching object inarray2
, by providing an empty array{}
toObject.entries
.