0

I'm trying to merge array2 into array1, so that empty fields stay like array1 and fields with value get overwritten at array1 from array2. But only empty fields, if it's null it should be treated as a number and overwrite a field. Please look at the two comments for a better understanding of what I mean.

What I have tried:

const array1 = [[2], [], [3,5], [], [1]]
const array2 = [[], [], [4], [], [null]]

Array.prototype.splice.apply(array1, [0, array2.length].concat(array2))
console.log(array2)

// this logs [[], [], [4], [], [null]]

// what is should log is [[2], [], [4,5], [], [null]]
Holiday
  • 871
  • 1
  • 8
  • 14
  • Why the third element `[3,5]` when "overridden" by `[4]` becomes `[4,5]`? – Balastrong May 25 '20 at 19:36
  • Why don't you simple iterate over the array2? If current index has a value overwrite array1 with the current value at the current index. (simply by: array1[i] = array2[i]) – ddobby94 May 25 '20 at 19:38

4 Answers4

2

merge by overwrite

The benefit of writing generic functions is immense. I'm using merge which was written in another post and needs no modification to support your current need -

// main.js

import { merge } from './util'

const array1 = [[2], [], [3,5], [], [1]]
const array2 = [[], [], [4], [], [null]]

const result =
  merge(array1, array2)

console.log(JSON.stringify(result))
// [[2],[],[4,5],[],[null]]

merge is helpful because it intuitively handles nested objects and arrays (and even sparse arrays!) in any imaginable shape -

// util.js
const isObject = x =>
  Object(x) === x

const mut = (o = {}, [ k, v ]) =>
  (o[k] = v, o)

const merge = (left = {}, right = {}) =>
  Object
    .entries(right)
    .map
      ( ([ k, v ]) =>
          isObject(v) && isObject(left[k])
            ? [ k, merge(left[k], v) ]
            : [ k, v ]
      )
    .reduce(mut, left)

export { merge }

Expand the snippet below to verify the results in your own browser -

// util.js

const isObject = x =>
  Object (x) === x

const mut = (o = {}, [ k, v ]) =>
  (o[k] = v, o)

const merge = (left = {}, right = {}) =>
  Object
    .entries(right)
    .map
      ( ([ k, v ]) =>
          isObject(v) && isObject(left[k])
            ? [ k, merge (left[k], v) ]
            : [ k, v ]
      )
    .reduce(mut, left)
// export { merge }

// main.js
// impor { merge } from './util'
const array1 = [[2], [], [3,5], [], [1]]
const array2 = [[], [], [4], [], [null]]

const result =
  merge(array1, array2)

console.log(JSON.stringify(result))
// [[2],[],[4,5],[],[null]]

immutable merge

Above our merge function permanently alters one of the inputs arrays, array1 in this case. Here's a variation merge that accepts any amount of objects/arrays and allows us to easily create a new array, without altering any of the inputs

const array1 = [[2], [], [3,5], [], [1]]
const array2 = [[], [], [4], [], [null]]

const result =
  merge([], array1, array2)

console.log("result:", JSON.stringify(result)) // [[2],[],[4,5],[],[null]]
console.log("array1:", JSON.stringify(array1)) // [[2],[],[3,5],[],[1]]
console.log("array2:", JSON.stringify(array2)) // [[],[],[4],[],[null]]

Here's what the revised util module could look like -

// util.js

const isArray =
  Array.isArray

const isObject = x =>
  Object(x) === x

const merge2 = (l = null, r = null) => // <- private; not exported
  isArray(l) && isArray(r)
    ? merge([], l, r)
: isObject(l) && isObject(r)
    ? merge({}, l, r)
: r

const merge = (init = {}, ...all) =>  // <- public interface
  all.reduce(replace, init)

const replace = (r = {}, o = {}) =>
{ for (const [ k, v ] of Object.entries(o))
    r[k] = merge2(r[k], v)
  return r
}

export { merge }

This was originally written for another question but it was never published. I'm glad I have a place to finally post it. Enjoy!

Expand the snippet below to verify the result in your browser -

//util.js
const isArray =
  Array.isArray

const isObject = x =>
  Object(x) === x

const merge2 = (l = null, r = null) =>
  isArray(l) && isArray(r)
    ? merge([], l, r)
: isObject(l) && isObject(r)
    ? merge({}, l, r)
: r

const merge = (init = {}, ...all) =>
  all.reduce(replace, init)

const replace = (r = {}, o = {}) =>
{ for (const [ k, v ] of Object.entries(o))
    r[k] = merge2(r[k], v)
  return r
}
// export { merge }

// main.js
// import { merge } from './util'
const array1 = [[2], [], [3,5], [], [1]]
const array2 = [[], [], [4], [], [null]]

const result =
  merge([], array1, array2)

console.log("result:", JSON.stringify(result))
// [[2],[],[4,5],[],[null]]

console.log("array1:", JSON.stringify(array1))
// [[2],[],[3,5],[],[1]]

console.log("array2:", JSON.stringify(array2))
// [[],[],[4],[],[null]]
Mulan
  • 129,518
  • 31
  • 228
  • 259
  • Wow, this is a banger, most answers don't even seem to work . Also love that you made it into a reusable function that can be imported. It works like magic and I can even understand how it does it. Thank you so much!! – Holiday May 25 '20 at 20:42
  • 2
    @Holiday you are very welcome. Nearly every answer I write on this site is coupled with a functioning code snippet that verifies the correct output. Talk is cheap and code can't lie :D – Mulan May 25 '20 at 21:04
1

You need a function that takes two arrays and merge them:

merge_arrays([3,5], [4]);
//=> [4,5]

merge_arrays([3,5], []);
//=> [3,5]

merge_arrays([3,5], [,7]);
//=> [3,7]

merge_arrays([3,5], [,7,8]);
//=> [3,7,8]

Here's one possible implementation:

const merge_arrays =
  ([xh, ...xt], [yh, ...yt], ret = []) =>
      xh === undefined && yh === undefined ? ret
    : yh === undefined                     ? merge_arrays(xt, yt, [...ret, xh])
                                           : merge_arrays(xt, yt, [...ret, yh]);

With that you can - assuming both array1 and array2 have the same length - map over array1 and apply merge_arrays to the current element and the element at the same index in array2:

const array1 = [[2], [], [3,5], [], [1]];
const array2 = [[], [], [4], [], [null]];

array1.map((arr, idx) => merge_arrays(arr, array2[idx]));
//=> [[2], [], [4,5], [], [null]]
customcommander
  • 17,580
  • 5
  • 58
  • 84
  • Thank you for the answer! It's definitely the most compressed one, but I'm in no way capable enough to understand how your code does what it does, it seems a bit too abstract for my understanding... And I try not to include things in my codebase that I will later have a tough time reading, apart from that it's amazing. Thank you! – Holiday May 25 '20 at 20:40
  • 1
    At first I was confused how `merge_arrays` was doing a deep merge, then I saw that you were using `array1.map(...)` and it makes sense now – Mulan May 25 '20 at 21:44
  • 1
    @Holiday No worries I understand ;) It's not that complicated though (YMMV): both `xh` and `yh` represent the "head" of their list. If both aren't undefined, then you haven't gone through both list yet. If the current head of the second list is undefined, you want to keep the head of the first list, otherwise use that one. Then carry on doing that with the rest of both list. Anyways @ThankYou has a good record of providing detailed and informative answers. You'll never go wrong with any of his/her answers ;) – customcommander May 26 '20 at 10:10
  • Thanks for explaining it, does make quite a lot more sense now, especially the namings of things =] – Holiday May 26 '20 at 12:54
0

You can map twice:

array1.map((arr, i) => arr.map((v, j) => j in array2[i] ? array2[i][j] : v));
Aplet123
  • 33,825
  • 1
  • 29
  • 55
0

Old style, but you can see the entire process :)

EDIT: Added a one-liner.

const array1 = [[2], [], [3,5], [], [1]];
const array2 = [[], [], [4], [], [null]];

/*
// verbose way
if (array1.length == array2.length) {
  for (var i=0; i < array1.length; i++) {
    if (array1[i].length > 0) {
      array1[i] = array2[i];
    }
  }
}
*/

// concise way
newArr = array1.map((el, i) => el.length > 0 ? array2[i] : el);

console.log(newArr);
Balastrong
  • 4,336
  • 2
  • 12
  • 31