0

Problem:

  • For-loop inside of a function is returning an inexplicable result (1st picture, highlighted in yellow on line 150)
  • Same for-loop copy-pasted outside of the function returns the expected result (2nd pic, highlighted in purple on line 164)
  • I am not looking for feedback on the approach to the algo problem - only trying to understand the above

Context:

  • rock-paper-scissors algo problem, to return an array of nested arrays representing the permutations of hands possible, based on number of rounds played passed in as argument
  • function is recursive, with 'n' rounds to play as argument, [[]] as default parameter
  • lines 136 - 142 : because total permutations possible is 3 ^ n, it takes the default parameter and duplicates each element 3 times (newOutput)
  • lines 146 - 149 : for-loop through newOutput, adding 'rock' to first, 'paper' to second, 'scissors' to third nested array, so on

Question

The duplication of elements in line 136 is impacting the result of for-loop at 146, but why? It's inexplicable since the thread of execution has already moved onto line 146.


1st picture

2nd picture

code below:

function rockPaperScissors(num, output = [[]]) {

  // when num is 0, return output
  if (num === 0) return output;
  const moves = ['rock', 'paper', 'scissors']

  // take output parameter, and duplicate each of the existing nested array 3 times 
  const newOutput = output.reduce((acc, curr) => {
    let i = 1;
    while (i <= 3) {
      acc.push(curr);
      i++
    }
    return acc;
  }, [])

  // iterate through newOutput, push to each nested array rock first, then paper, then scissors, so on 
  for (let i = 0; i < newOutput.length; i++) {
    newOutput[i].push(moves[i%3]);
  }

  return rockPaperScissors(num - 1, newOutput);
}

rockPaperScissors(1)
// should return [[rock], [paper], [scissors]]
// instead, returns [[rock, paper, scissors], [rock, paper, scissors], [rock, paper, scissors]]

2
  • The code in your images has newOutput[i].push(…), the code you posted as text doesn't. Commented Jul 10, 2021 at 17:17
  • You're never using the moves array, how are the moves supposed to get into the result? Commented Jul 10, 2021 at 17:26

2 Answers 2

1

This happens because you're pushing the SAME element (Object) output[0] n times into the accumulator, creating n clones of it, which later becomes the newOutput. Since those represent the SAME instance, every operation executed on one of them, will have a side effect on the others.


How to solve it?

You could solve it, by using a spread operator, which copies the value of an array into a new one.

...
      acc.push([...curr]);
...

Some good reading: https://javascript.info/object-copy

Sign up to request clarification or add additional context in comments.

1 Comment

this is super helpful - really good to know that elements within an array acts same as passed by reference like the object it is. thank you
1

It's because of this:

  while (i <= 3) {
    acc.push(curr);   // these are not copies, it's all the same array
    i++
  }

This is not creating copies of the curr array, it just pushes the same reference 3 times. All of those 3 elements refer (or point to) the same underlying array. What you have is something like this:

  [  ref_0,   ref_1,   ref_3  ]

      |         |        |
      |         |        |
       `---------`--------`--------------> []

When you try to modify the contents of the pointed-to array through one of those references, all of the other "see" the change, because it's all the same array:

  [  ref_0,   ref_1,   ref_3  ]

      |         |        |
      |         |        |
       `---------`--------`--------------> ['rock', 'paper', 'scissors']

The runnable snippet below demonstrates this.

Note that you can replace each reference with something else just fine; the problem only becomes apparent when you modify the contents of the array they point to. In other words, you can do this:

  newOutput[0] = "something else"

  [  ref_0,   ref_1,   ref_3  ]

      |         |        |
      |         |        |
      |          `--------`--------------> ['rock', 'paper', 'scissors']
      | 
       `---------> "something else"

If you want copies, you have to make them explicitly, by using the spread operator. Assuming the inner object in output is always an array, you can copy it like this:

acc.push([...curr])

If you don't have a compelling reason to use reduce, you can do this instead:

// [...Array(3).keys()]  produces the range [0, 1, 2]
// Use that with .map to create your copies.

const newOutput = [...Array(3).keys()].map(i => [...output[0]]);

const output = [['Array Content']];

const newOutput = output.reduce((acc, curr) => {
  let i = 1;
  while (i <= 3) {
    acc.push(curr);   // these are not copies, it's all the same array
    i++
  }
  return acc;
}, []);


displayInDiv('output-div-1', newOutput);
// shows: [["Array Content"],["Array Content"],["Array Content"]]


// Since all elements refer to the same underlying array, 
// modifying its contents through one of them affects all of them
output[0][0] = 'Modified Content';

displayInDiv('output-div-2', newOutput);
// shows: [["Modified Content"],["Modified Content"],["Modified Content"]]

displayInDiv('output-div-3', newOutput[0] === newOutput[1]);
displayInDiv('output-div-4', newOutput[1] === newOutput[2]);
// shows true in both cases
// it means that it's all the same object


function displayInDiv(divId, objectToShow) {
  const div = document.getElementById(divId);
  div.innerText = JSON.stringify(objectToShow);
}
div {
  margin-top: 4px;
}

.description {
  margin-top: 16px;
}

.output {
  color: gray;
  font-family: 'Courier New', monospace;
}
<div class="description">Result of reduce: </div>
<div id="output-div-1" class="output"></div>

<div class="description">After modification of array content: </div>
<div id="output-div-2" class="output"></div>

<div class="description">newOutput[0] === newOutput[1]: </div>
<div id="output-div-3" class="output"></div>

<div class="description">newOutput[1] === newOutput[2]: </div>
<div id="output-div-4" class="output"></div>

1 Comment

amazing. this was it. thank you for the prompt and detailed response

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.