【问题标题】:Inexplicable behavior of a for-loop after a reduce in Javascript function在 Javascript 函数中减少后 for 循环的莫名行为
【发布时间】:2021-09-20 14:04:44
【问题描述】:

问题:

  • 函数内部的 For 循环返回一个莫名其妙的结果(第一张图片,在第 150 行以黄色突出显示)
  • 在函数外部复制粘贴相同的 for 循环会返回预期结果(第二张图片,在第 164 行以紫色突出显示)
  • 我不是在寻找有关算法问题方法的反馈 - 只是想了解上述内容

上下文:

  • rock-paper-scissors 算法问题,返回一个嵌套数组数组,表示可能的手牌排列,基于作为参数传入的回合数
  • 函数是递归的,以“n”轮作为参数,[[]] 作为默认参数
  • 第 136 - 142 行:因为可能的总排列是 3 ^ n,所以它采用默认参数并将每个元素复制 3 次(newOutput)
  • 第 146 - 149 行:for 循环通过 newOutput,将“rock”添加到第一个,“paper”添加到第二个,“scissors”添加到第三个嵌套数组,依此类推

问题

第 136 行元素的重复影响了第 146 行 for 循环的结果,但为什么呢?莫名其妙,因为执行线程已经移到第 146 行了。


1st picture

2nd picture

代码如下:

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]]

【问题讨论】:

  • 您图片中的代码有newOutput[i].push(…),而您发布为文本的代码没有。
  • 你从来没有使用过moves 数组,这些动作应该如何进入结果?

标签: javascript algorithm for-loop recursion reduce


【解决方案1】:

发生这种情况是因为您将 SAME 元素(对象)输出 [0] n 次推送到累加器中,创建了它的 n 个克隆,后来成为newOutput。 由于它们代表相同的实例,因此对其中一个执行的每个操作都会对其他实例产生副作用。


如何解决?

你可以通过使用扩展运算符来解决这个问题,它将数组的值复制到一个新的数组中。

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

一些不错的阅读:https://javascript.info/object-copy

【讨论】:

  • 这非常有用 - 很高兴知道数组中的元素的行为与通过引用传递的对象一样。谢谢
【解决方案2】:

因为这样:

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

这不是创建 curr 数组的副本,它只是将相同的引用推送 3 次。所有这 3 个元素都引用(或指向)相同的底层数组。你所拥有的是这样的:

  [  ref_0,   ref_1,   ref_3  ]

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

当您尝试通过其中一个引用修改指向数组的内容时,所有其他“看到”更改,因为它都是同一个数组:

  [  ref_0,   ref_1,   ref_3  ]

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

下面的可运行 sn-p 演示了这一点。

请注意,您可以将每个引用替换为其他内容就可以了;只有当您修改它们指向的数组的 contents 时,问题才会变得明显。换句话说,您可以这样做:

  newOutput[0] = "something else"

  [  ref_0,   ref_1,   ref_3  ]

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

如果您想要副本,则必须使用扩展运算符明确地制作它们。假设output中的内部对象总是一个数组,你可以这样复制:

acc.push([...curr])

如果您没有令人信服的理由使用 reduce,您可以这样做:

// [...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>

【讨论】:

  • 太棒了。就是这样。感谢您及时详细的回复
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-18
  • 2017-07-22
  • 2021-04-23
相关资源
最近更新 更多