Using nested forEach loops to push and object to an array

Viewed 11165

I'd love an explanation as to why the result of this function don't match my expectation.

const numbers = [ 1, 2 ]
const objects = [{f: 'a'}, {f: 'b'}]

async function(domain) {
  let matrix = []
  objects.forEach((object) => {
    numbers.forEach((number) => {
      object.number = number
      matrix.push(object)
    })
  }) 
  return matrix
}()

Actual

The result, once the promise is resolved returns:

[ 
  { f: 'a', number: 2 },
  { f: 'a', number: 2 },
  { f: 'b', number: 2 },
  { f: 'b', number: 2 } 
] 

Expected

But, my expectation is that it would return:

[ 
  { f: 'a', number: 1 },
  { f: 'a', number: 2 },
  { f: 'b', number: 1 },
  { f: 'b', number: 2 } 
] 

One of the things that puzzles me the most is that if I console.log the value of object just before I push, it logs out each of the objects in my expected result.

3 Answers

Because objects are passed by reference and you are modifying original object, instead you can push new object.

const numbers = [ 1, 2 ]
const objects = [{f: 'a'}, {f: 'b'}]

let matrix = []
objects.forEach((object) => {
  numbers.forEach((number) => {
    matrix.push({...object, number})
  })
})

console.log(matrix)

Use Array.flatMap() to iterate the objects, with a nested Array.map() to iterate the numbers and return an array of new objects with the relevant number. The flatMap will flatten the arrays produced by the map to a single array:

const numbers = [ 1, 2 ]
const objects = [{f: 'a'}, {f: 'b'}]

const matrix = objects.flatMap(o => // map the objects and flatten
  numbers.map( // map the numbers
    number => ({ ...o, number }) // return a new object with the number
  )
);

console.log(matrix);

Old answer:

Use nested Array#map to iterate the objects and the numbers, and Object#assign to create new objects that include the relevant number. Flatten resulting nested arrays by using the spread syntax in Array#concat:

const numbers = [ 1, 2 ]
const objects = [{f: 'a'}, {f: 'b'}]

const matrix = [].concat(... // flatten the nested arrays
  objects.map((o) => // map the objects
    numbers.map( // map the numbers
      (number) => Object.assign({}, o, { number }) // return a new object with the number
    )
  ));

console.log(matrix);

@Nenad is correct. You can also do it with reduce and map.

const numbers = [ 1, 2 ];
const objects = [{f: 'a'}, {f: 'b'}];

const matrix = numbers.reduce((acc, nxt) => {
 const numberedObjs = objects.map(obj => ({...obj, number:nxt}));
 acc.push(...numberedObjs);
 return acc;
}, []);

Related