Here is ES6 code to do that:
var res = Array.from([...a,...b].reduce ( (hash, v) =>
!hash.has(v.title) || hash.get(v.title).date < v.date ? hash.set(v.title, v) : hash
, new Map()), v => v[1]);
var a = [{
"title": "title1",
"date": "2010-08-20T15:51:58"
}, {
"title": "title2",
"date": "2015-09-20T16:45:21"
}]
var b = [{
"title": "title1",
"date": "2015-08-20T15:51:58"
}, {
"title": "title2",
"date": "2015-09-20T16:45:21"
}]
var res = Array.from([...a,...b].reduce ( (hash, v) =>
!hash.has(v.title) || hash.get(v.title).date < v.date ? hash.set(v.title, v) : hash
, new Map()), v => v[1]);
console.log(res);
Explanation
First the input arrays are concatenated together into one new array with the spread operator:
[...a,...b]
Then an empty Map
is created and passed as last argument to reduce
:
new Map()
The reduce
method calls the arrow function for each element in the concatenated array. The arrow function also receives the above mentioned map as argument (as hash
).
The arrow function must return a value. That value is then passed again to subsequent call of this function (for the next element), and so we always return the map, which grows in each function call. It is, as it were, passed from one call to the next. In the last call the returned map becomes the return value of .reduce()
.
The arrow function itself checks if the current element's title is not yet in the map:
!hash.has(v.title)
If it is in the map already, then the next expression is also evaluated; it checks whether the date in the map entry is before the current element's date.
hash.get(v.title).date < date
If either of the above conditions is true (not in map, or with smaller date), then the map entry is (re)created with the current element as value.
? hash.set(v.title, v)
This set
also returns the whole map after setting. Otherwise the map is returned unchanged:
: hash
The result of reduce()
is thus a map, keyed by titles. This is really the result you need, but it is in Map
format. To get it back to a normal array, the Array.from
method is called on it. This changes the Map
values into an array of key-value pairs (sub-arrays with 2 elements). Since we are only interested in the values, we apply a function to it:
v => v[1]
This replaces every pair with only the second value. This function is passed as second argument to Array.from
which applies it to every pair.
Some remarks
This assumes that your dates are in ISO format, like in your sample: in that case the string comparison gives the correct result for determining whether one date precedes another.
The result will include also the objects that only occur in one of the two input arrays
This is easily extendible to three input arrays: just add a third one like this: [...a,...b,...c]
This runs in O(n) time, where n is the total number of objects present in the input arrays. This is because most JavaScript engines implement Map
access operations like .has
, .get
and .put
with O(1) time.