0

I'm trying to merge two arrays of objects by checking if the titles are the same, and if they are, then checking for which entry is newer, and discarding the older one. I have found a lot of solutions for discarding true duplicates, but how can I do this in a way where I can decided which to keep based on dates?

const a = [{
  "title": "title1",
  "date": "2010-08-20T15:51:58"
}, {
  "title": "title2",
  "date": "2015-09-20T16:45:21"
}]

const b = [{
  "title": "title1",
  "date": "2015-08-20T15:51:58"
}, {
  "title": "title2",
  "date": "2015-09-20T16:45:21"
}]

Thanks for any tips you can provide!

uladzimir
  • 5,639
  • 6
  • 31
  • 50
Slbox
  • 10,957
  • 15
  • 54
  • 106

4 Answers4

3

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.

Community
  • 1
  • 1
trincot
  • 317,000
  • 35
  • 244
  • 286
  • Thanks a lot! I have to admit, I have only a small idea of how this works, but it seems definitely the most elegant of the suggested solutions. My actual task has 5 fields to merge, which I didn't mention for the sake of simplicity (stupidly.) What changes would I need to make to the above to accommodate merging of additional fields. (Date would still be the only field comparisons would be run on) – Slbox Sep 25 '16 at 20:15
  • 1
    The solution doesn't need any changes for objects with more fields (this is true also for all other solutions posted at this time). Did you try? – trincot Sep 25 '16 at 20:26
  • I did. I suspected (and now you've all but confirmed) that I had a bug somewhere else in my code, but I figured I should probably ask about the piece of code I don't quite understand before embarking on the search. Thanks very much for your help! – Slbox Sep 25 '16 at 20:34
  • 1
    I added some explanation to my answer. Hope it is of help. – trincot Sep 25 '16 at 20:39
  • Thanks a lot, that's really helpful and way above and beyond! If I could, I'd award you the answer twice. – Slbox Sep 26 '16 at 03:13
0
a.forEach(function(elem1,count1){
   b.forEach(function(elem2,count2){
   //loop trough each a frouvh each b
     if(elem1.title==elem2.title){
       var date1=new Date(elem1.date);
       var date2=new Date(elem2.date);
       if(date1.getTime()>date2.getTime()){
            //elem from a is older
            //delete elem from a
         }else{
            //elem from b is older
         }
       }
    });
   });
Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
0

You can use for loop, new Date().getTime()

var a = [{
"title": "title1",
"date": "2010-08-20T15:51:59"
}, {
"title": "title2",
"date": "2015-09-20T16:45:22"
}];

var b = [{
"title": "title1",
"date": "2015-08-20T15:51:58"
}, {
"title": "title2",
"date": "2015-09-20T16:45:21"
}];

var res = [];
for (var i = 0; i < a.length; i++) {
  var curr = a[i];
  for (var n = 0; n < b.length; n++) {
    if (curr.title === b[n].title) {
      if (new Date(curr.date).getTime() > new Date(b[n].date).getTime()) {
        res.push(curr)
      } else {
        res.push(b[n])
      }
    }
  }
}

console.log(res);
guest271314
  • 1
  • 15
  • 104
  • 177
0
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"
}]

function assign(a, b) {
  return a.reduce((acc, itemA) => {
    const {title: titleA, date: dateA} = itemA
    const itemB = b.find(({title: titleB}) => titleA == titleB) 

    if (itemB) {
      if (new Date(dateA) - new Date(itemB.date) >= 0) {
        acc.push(itemA)
      } else {
        acc.push(itemB)
      }
    }

    return acc
  }, [])
}
uladzimir
  • 5,639
  • 6
  • 31
  • 50