【问题标题】:JS, difference in array matrix and forEach behaviorJS,数组矩阵和forEach行为的区别
【发布时间】:2018-10-12 18:46:52
【问题描述】:

我正在为我的 JS 课程做一些培训任务,我得到了一个你必须实现一个函数,该函数接受一个正整数 (n) 并返回一个如下所示的矩阵(传递了 5):

[ [ 1, 0, 0, 0, 0 ],
  [ 0, 1, 0, 0, 0 ],
  [ 0, 0, 1, 0, 0 ],
  [ 0, 0, 0, 1, 0 ],
  [ 0, 0, 0, 0, 1 ] ]

我能够使用以下代码实现该功能:

function getIdentityMatrix(n) {
  const mat = new Array(n).fill([]);
  return mat.map((row, index) => {
    row = new Array(n).fill(0);
    row[index] = 1;
    return row;
  });
}

但在执行此操作时,我发现了一个我无法解释的奇怪行为......如果我稍微修改一下代码:

function getIdentityMatrix(n) {
  const mat = new Array(n).fill(new Array(n).fill(0));
  return mat.map((row, index) => {
    row[index] = 1;
    return row;
  });
}

它返回一个像这样的矩阵:

[ [ 1, 1, 1, 1, 1 ],
  [ 1, 1, 1, 1, 1 ],
  [ 1, 1, 1, 1, 1 ],
  [ 1, 1, 1, 1, 1 ],
  [ 1, 1, 1, 1, 1 ] ]

为什么会这样?这就像 forEach 函数遍历嵌套在每一行中的所有元素,这是它不应该做的。

感谢您的建议!

【问题讨论】:

  • 问题是外部数组中的每个索引都包含对内部数组的相同引用。
  • 当你向.fill() 传递一个非基元时,数组的每个索引都将引用同一个对象。您有一个包含 5 个对同一内部数组的引用的数组。
  • 由于这不是一个完整的答案,我将其作为评论发布。一个合适的单行替换是const mat = [...Array(n).keys()].map(() => Array(n).fill(0))
  • @patrick 我更喜欢Array.from({ length: n }, (_, i1) => Array.from({ length: n }, (_, i2) => +(i1 === i2)));
  • @JonasWilms 我认为这比我的更难读,即使它确实替换了整个函数而不仅仅是错误的行。另外我刚刚又做了一个:[...Array(n).keys()].map(i => Object.assign(Array(n).fill(0), { [i]: 1 })) ;)

标签: javascript arrays foreach


【解决方案1】:

这是因为 Array 是一个引用类型。当你这样做

new Array(n).fill(new Array(n).fill(0))

首先,内部new Array(n).fill(0) 使数组大小为n,其中填充0;接下来,外部 Array(n).fill 创建一个数组,其中填充了对该内部数组的 n 个引用。

它不创建 n 个内部数组,只创建对同一个数组的 n 个引用。因此,当您更改该内部数组的元素时,外部数组中的所有引用都会反映更改,因为它们都指向同一个对象。

【讨论】:

    【解决方案2】:

    有问题的代码等价于:

    let n = 5
    let innerArr = new Array(n).fill(0)
    
    function getIdentityMatrix(n) {
      const mat = new Array(n).fill(innerArr);
      return mat.map((row, index) => {
        row[index] = 1;
        return row;
      });
    }
    console.log(getIdentityMatrix(n))

    因为您使用的是fill,所以您基本上是用对innerArr (which you can see clearly from the above console output) 的引用来填充mat 数组。

    然后你对每个 i 执行row[index] = 1 ,这会改变相同数组的相同值(在 i 索引处)

    现在你的工作示例......可以用更短的形式写成:

    const getIdentityMatrix = (n) =>
       [...Array(n)].map((row, index) => {
         row = Array(n).fill(0)
         row[index] = 1
         return row
       })
    
    console.log(getIdentityMatrix(3))

    显然 maps 覆盖了新创建的 spreaded 数组 n然后用全新的数组引用覆盖每个元素

    由于该引用是全新的,当我们从地图中 return the x 时,使用 row[index] = 1 对其进行修改会产生预期的行为。

    在一行中实现此目的的另一种方法是通过mapObject.assignObject.values,如下所示:

    const gm = (n) => [...Array(n)].map((x,i) => 
       Object.values(Object.assign(Object.assign({}, Array(n).fill(0)), {[i]:1})))
    
    console.log(gm(3))

    【讨论】:

      【解决方案3】:
      // your example is roughly equivalent to this.
      const innerArray = new Array(n).fill(0);
      const mat = new Array(n).fill(innerArray);
      
      (mat[0] === mat[1] === innerArray) === true;
      

      只有1个嵌套数组,不是n次数组。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-01-19
        • 2019-12-17
        • 1970-01-01
        • 2016-03-03
        • 1970-01-01
        • 2021-12-28
        相关资源
        最近更新 更多