Replace array with another array by splice & push without touching non-unique values

319 views Asked by At

I have two arrays:

var current_items = [{hash: 1}, {hash: 2}, {hash: 3}];

var next_items    = [{hash: 3}, {hash: 4}, {hash: 5}];

The first array represents currently rendered items, the other represents the next items to render (simplified of course - the real objects contain a lot more information).

I need to replace the current_items array with the new items just by pushing/splicing. The problem is that I can not just override the current_items with the new items: All current items that have the same hash as an item in next_items must be kept as they are (if the object is changed/overridden the item would be rendered again unnecessarily).

I guess the first step would be to remove/splice all items in current_items that are not contained in next_items: current_items would become [{hash: 3}] (keep hash 3 because it is also contained in next_items).

Then remove all items from next_items that are already contained in current_items, so next_items becomes [{hash: 4}, {hash: 5}]

and finally concat the remaining next_items with the current_items

current_items.push.apply(current_items, next_items); 

which results in [{hash: 3}, {hash: 4}, {hash: 5}]

My current solution looks something like this:

var current_items = [{hash: 1}, {hash: 2}, {hash: 3}];
var next_items    = [{hash: 3}, {hash: 4}, {hash: 5}];

// splice old items
var i = current_items.length, j, found;
while (i--) {
    found = false;
    for (j = 0; j < next_items.length; j++) {
        if (current_items[i]['hash'] === next_items[j]['hash']) {
            found = true;
            break;
        }
    }
    !found && current_items.splice(i, 1);
}

// get unique new items
var unique_new_items = [];
for (i = 0; i < next_items.length; i++) {
    found = false;
    for (j = 0; j < current_items.length; j++) {
        if (next_items[i]['hash'] === current_items[j]['hash']) {
            found = true;
            break;
        }
    }
    !found && unique_new_items.push(next_items[i]);
}

current_items.push.apply(current_items, unique_new_items); 
// [{hash: 3}, {hash: 4}, {hash: 5}]

Is there an easier/cleaner/shorter way to do that?

4

There are 4 answers

1
RomanPerekhrest On

The solution using Array.filter(), Array.concat(), Array.splice(),JSON.stringify() and JSON.parse() functions:

var current_items = [{hash: 1}, {hash: 2}, {hash: 3}],
    next_items    = [{hash: 3}, {hash: 4}, {hash: 5}],
    next_items_stringified = next_items.map(JSON.stringify);

current_items = current_items.filter(function (o) {
  var pos = this.indexOf(JSON.stringify(o));
  return pos !== -1 && this.splice(pos, 1);
}, next_items_stringified).concat(next_items_stringified.map(JSON.parse));

console.log(current_items);

0
Nina Scholz On

You could use two hash table for the hashes and splice accordingly. Later push not included object to current_items.

var current_items = [{ hash: 1 }, { hash: 2 }, { hash: 3, keep: true /* flag for identify this object */ }],
    next_items = [{ hash: 3 }, { hash: 4 }, { hash: 5 }],
    hashNext = Object.create(null),
    hashCurrent = Object.create(null);

next_items.forEach(function (a) {
    hashNext[a.hash] = true;
});

current_items.reduceRight(function (_, a, i, aa) {
    hashCurrent[a.hash] = true;
    if (!hashNext[a.hash]) {
        aa.splice(i, 1);
    }
}, 0);

next_items.forEach(function (a) {
    if (!hashCurrent[a.hash]) {
        current_items.push(a);
    }
});

console.log(current_items);
.as-console-wrapper { max-height: 100% !important; top: 0; }

0
Redu On

You might do as follows;

var current_items = [{hash: 1}, {hash: 2}, {hash: 3}, {hash: 4}],
       next_items = [{hash: 3}, {hash: 4}, {hash: 5}, {hash: 6}],
           result = current_items.filter(h => next_items.some(g => g.hash === h.hash))
                                 .reduce((p,c,i,a) => i ? p.concat(next_items.filter(n => p.every(e => e.hash !== n.hash)))
                                                        : p.concat(a, next_items.filter(n => a.every(e => e.hash !== n.hash))),[]);
console.log(result);

And another explicit way of doing this job would be;

var current_items = [{hash: 1}, {hash: 2}, {hash: 3}, {hash: 4}],
       next_items = [{hash: 3}, {hash: 4}, {hash: 5}, {hash: 6}],
            union = current_items.filter(c => next_items.some(n => c.hash === n.hash)),
       separation = next_items.filter(n => !union.some(u => n.hash === u.hash));
           result = union.concat(separation);
console.log(result);

0
AudioBubble On

You could do the following, as long as your arrays are sorted based on the hash (check twice, I'm not 100% sure that it works with every configuration).

var curr = [{hash:1},{hash:2},{hash:3}];
var next = [{hash:3},{hash:4},{hash:5}];

var i = 0;
while (next.length > 0 || i < curr.length) {
  if (next.length == 0) {
    curr.pop();
  } else if (curr.length == i) {
    curr.push(next.shift()), i++;
  } else if (curr[i].hash > next[0].hash) {
    curr.splice(i++, 0, next.shift());
  } else if (curr[i].hash < next[0].hash) {
    curr.splice(i, 1);
  } else {
    next.shift(), i++;
  }
}

console.log("[", curr.map(x => x.hash).join(" "), "]");
console.log("[", next.map(x => x.hash).join(" "), "]");

Though I would do something simpler :

var curr = [{hash:1},{hash:2},{hash:3}];
var next = [{hash:3},{hash:4},{hash:5}];

function cookUpdate (curr, next) {
  var i = 0, j = 0, midd = [];
  while (j < next.length) {
    if (i == curr.length) {
      midd.push(next[j]), j++;
    } else if (curr[i].hash == next[j].hash) {
      midd.push(curr[i]), i++, j++;
    } else if (curr[i].hash > next[0].hash) {
      midd.push(next[j]), j++;
    } else {
      i++;
    }
  }
  return midd;
}

curr = cookUpdate(curr, next);
next = [];

console.log("[", curr.map(x => x.hash).join(" "), "]");
console.log("[", next.map(x => x.hash).join(" "), "]");