One approach would be to go through them simultaneously. If they aren't the same value at a given index, do a look ahead on them both. If one has the same value in the look ahead as the other in the current position, it was probably an insertion on that one.
For example, looking through your examples above:
s1 = [1, 4, 11, 9, 11]
s2 = [1, 3, 4, 9, 11]
Index 0, both are the same. Index 1, they are different. Looking at index 2 of each, you see that s2
has the same value as the current index of s1
, so s2
likely had an insertion, so add that before the value of s1
. After that, line them back up again (so look at s2
one ahead`.
const s1 = [1, 4, 11, 9, 11];
const s2 = [1, 3, 4, 9, 11];
const s3 = [1, 3, 4, 11, 9, 11]; // for comparison
function merge(a, b) {
let iA = 0;
let iB = 0;
let result = [];
let abort = 0;
while (abort < 100 && iA < a.length && iB < b.length) {
if (a[iA] === b[iB]) {
result.push(a[iA]);
iA++;
iB++;
} else if (a[iA+1] === b[iB]) { // a had an insert
result.push(a[iA]);
iA++;
} else if (a[iA] === b[iB+1]) { // b had an insert
result.push(b[iB]);
iB++;
} else {
// extra stuff here
}
abort++; // this is just so no infinite loop in partially defined loop
}
// Catch values at the end
if (iA < a.length) {
result = result.concat(a.slice(iA));
} else if (iB < b.length) {
result = result.concat(b.slice(iB));
}
return result;
}
console.log(merge(s1, s2));
This code works for your particular example. Notice there is an extra else though. If you got there, it means an insertion happened in one, but either they both got an insertion at the same place, or one got multiple insertions. You'd have to loop through both and determine how to resolve, but the basic approach would be the same: loop through until you find where things line up again, and then take the ones that don't match before incrementing the iterators appropriately.
I just re-read your question a bit closer and realized that you know that only one operation is happening on each before the merge. That makes things quite a bit easier, since you don't have to loop for an arbitrary distance, you just have to go out two at most.
If you know they won't be in the same location, then you can just get rid of the else in my above code, because it'll never happen.
If they might be in the same position, since you know you only have two changes, if neither of the values match (and you know each only received one change), then both received a change at the same point. In that case, just take both. If they need a particular order in this case, you'll have to define some logic. In general though, probably doesn't matter, so just take either first.
function merge(a, b) {
let iA = 0;
let iB = 0;
let result = [];
while (iA < a.length && iB < b.length) {
if (a[iA] === b[iB]) {
result.push(a[iA]);
iA++;
iB++;
} else if (a[iA+1] === b[iB]) { // a had an insert
result.push(a[iA]);
iA++;
} else if (a[iA] === b[iB+1]) { // b had an insert
result.push(b[iB]);
iB++;
} else { // both received a change, take both
result.push(a[iA]);
result.push(b[iB]);
iA++;
iB++;
}
}
// Catch values at the end
if (iA < a.length) {
result = result.concat(a.slice(iA));
} else if (iB < b.length) {
result = result.concat(b.slice(iB));
}
return result;
}
console.log(merge([1, 4, 11, 9, 11], [1, 3, 4, 9, 11])); // different places
console.log(merge([1, 3, 4, 9, 11], [1, 2, 4, 9, 11])); // same places
console.log(merge([1, 4, 9, 11, 13], [1, 3, 4, 9, 11]));