【问题标题】:Transposing a 2D-array in JavaScript在 JavaScript 中转置二维数组
【发布时间】:2013-06-29 23:56:42
【问题描述】:

我有一个数组数组,类似于:

[
    [1,2,3],
    [1,2,3],
    [1,2,3],
]

我想转置得到如下数组:

[
    [1,1,1],
    [2,2,2],
    [3,3,3],
]

使用循环以编程方式做到这一点并不难:

function transposeArray(array, arrayLength){
    var newArray = [];
    for(var i = 0; i < array.length; i++){
        newArray.push([]);
    };

    for(var i = 0; i < array.length; i++){
        for(var j = 0; j < arrayLength; j++){
            newArray[j].push(array[i][j]);
        };
    };

    return newArray;
}

然而,这似乎很笨重,我觉得应该有一种更简单的方法来做到这一点。有吗?

【问题讨论】:

  • 你能保证这两个维度永远是一样的吗? 1x1、2x2、3x3 等。arrayLength 参数究竟用于什么?确保不超出数组中一定数量的元素?
  • 这个和JQuery无关,我改了标题。
  • 看看这个:stackoverflow.com/questions/4492678/…。你正在做的是转置矩阵
  • 是的,移调。反转将完全不同,我对此不感兴趣。现在。
  • 左上角到右下角对角线没有改变,所以有优化机会。

标签: javascript arrays matrix transpose


【解决方案1】:
array[0].map((_, colIndex) => array.map(row => row[colIndex]));

map 按顺序为数组中的每个元素调用一次提供的callback 函数,并根据结果构造一个新数组。 callback 仅对具有赋值的数组索引调用;它不会为已删除或从未被赋值的索引调用。

callback 使用三个参数调用:元素的值、元素的索引和被遍历的 Array 对象。 [source]

【讨论】:

  • 这是一个很好的解决方案。但是,如果您关心性能,您应该使用 OP 的原始解决方案(带有错误修复以支持 M x N 数组,其中 M!= N)。 check this jsPerf
  • 如果你在同一个数组上使用两次,它会回到第一次而不是再次旋转 90'
  • 为什么是array[0].map 而不是array.map
  • array[0].map 因为他想迭代多少次有列,array.map 会迭代有多少行。
  • @BillyMcKee 在 2019 年和 Chrome 75 loopsmap 慢 45%。是的,它正确转置,所以第二次运行返回初始矩阵。
【解决方案2】:

这是我在现代浏览器中的实现(不依赖):

transpose = m => m[0].map((x,i) => m.map(x => x[i]))

【讨论】:

【解决方案3】:

这里有很多好的答案!我将它们合并为一个答案,并更新了一些代码以获得更现代的语法:

Fawad GhafoorÓscar Gómez Alcañiz 启发的单行代码

function transpose(matrix) {
  return matrix[0].map((col, i) => matrix.map(row => row[i]));
}

function transpose(matrix) {
  return matrix[0].map((col, c) => matrix.map((row, r) => matrix[r][c]));
}

带有reduce by Andrew Tatomyr的函数式方法风格

function transpose(matrix) {
  return matrix.reduce((prev, next) => next.map((item, i) =>
    (prev[i] || []).concat(next[i])
  ), []);
}

Lodash/下划线marcel

function tranpose(matrix) {
  return _.zip(...matrix);
}

// Without spread operator.
function transpose(matrix) {
  return _.zip.apply(_, [[1,2,3], [1,2,3], [1,2,3]])
}

Vigrant 提供的更简单的 Lodash/Underscore 解决方案

_.unzip(matrix);

普通方法

function transpose(matrix) {
  const rows = matrix.length, cols = matrix[0].length;
  const grid = [];
  for (let j = 0; j < cols; j++) {
    grid[j] = Array(rows);
  }
  for (let i = 0; i < rows; i++) {
    for (let j = 0; j < cols; j++) {
      grid[j][i] = matrix[i][j];
    }
  }
  return grid;
}

Emanuel Saringan启发的原版原位ES6方法

function transpose(matrix) {
  for (var i = 0; i < matrix.length; i++) {
    for (var j = 0; j < i; j++) {
      const temp = matrix[i][j];
      matrix[i][j] = matrix[j][i];
      matrix[j][i] = temp;
    }
  }
}

// Using destructing
function transpose(matrix) {
  for (var i = 0; i < matrix.length; i++) {
    for (var j = 0; j < i; j++) {
      [matrix[i][j], matrix[j][i]] = [matrix[j][i], matrix[i][j]];
    }
  }
}

【讨论】:

  • 我认为@vigrant 的回答(只是在评论中)应该包含在这里 - _.unzip 已经做到了
  • @Andy 随时更新答案。任何人都可以编辑它:)
【解决方案4】:

你可以使用underscore.js

_.zip.apply(_, [[1,2,3], [1,2,3], [1,2,3]])

【讨论】:

  • 这很漂亮——而且,下划线对我来说比 jQuery 更重要。
  • 或者如果你使用像rambda这样的函数库,你可以做const transpose = apply(zip)
  • 为什么这个选项比选择的答案更好?
  • 这个问题已经存在多年了,现在我不确定它是否存在。不过,它比original version of the accepted answer 更干净。你会注意到它一直是edited substantially。看起来它使用的是 ES6,我认为 2013 年这个问题被广泛提出时还没有。
【解决方案5】:

lodash/underscorees6 的最短路径:

_.zip(...matrix)

matrix 可能在哪里:

const matrix = [[1,2,3], [1,2,3], [1,2,3]];

【讨论】:

  • 或者,没有 ES6:_.zip.apply(_, matrix)
  • 关闭但 _.unzip(matrix) 更短;)
  • 你能扩展一下吗?我不明白你在这里说什么。那个简短的snipplet应该可以解决问题吗?还是只是一部分还是什么?
  • 天哪,这是一个简短的解决方案。刚刚了解了 ... 运算符 - 使用它将字符串分解为字母数组...感谢您的回复
【解决方案6】:

干净整洁:

[[0, 1], [2, 3], [4, 5]].reduce((prev, next) => next.map((item, i) =>
    (prev[i] || []).concat(next[i])
), []); // [[0, 2, 4], [1, 3, 5]]

如果提供了一个空数组,以前的解决方案可能会导致失败。

这是一个函数:

function transpose(array) {
    return array.reduce((prev, next) => next.map((item, i) =>
        (prev[i] || []).concat(next[i])
    ), []);
}

console.log(transpose([[0, 1], [2, 3], [4, 5]]));

更新。 使用扩展运算符可以写得更好:

const transpose = matrix => matrix.reduce(
    ($, row) => row.map((_, i) => [...($[i] || []), row[i]]), 
    []
)

【讨论】:

  • 我不知道我会更好地调用该更新。当然,它很聪明,但读起来简直就是一场噩梦。
【解决方案7】:

你可以通过只做一次就地完成:

function transpose(arr,arrLen) {
  for (var i = 0; i < arrLen; i++) {
    for (var j = 0; j <i; j++) {
      //swap element[i,j] and element[j,i]
      var temp = arr[i][j];
      arr[i][j] = arr[j][i];
      arr[j][i] = temp;
    }
  }
}

【讨论】:

  • 如果您的数组不是正方形(例如 2x8),我猜这不起作用
  • 此解决方案更改了原始数组。如果您仍然需要原始数组,那么此解决方案可能不是您想要的。其他解决方案改为创建一个新数组。
  • 有谁知道这是如何在 ES6 语法中完成的?我试过[arr[j][j],arr[i][j]] = [arr[i][j],arr[j][j]],但它似乎不起作用,我错过了什么吗?
  • @Nikasv 你可能想要[arr[j][i], arr[i][j]] = [arr[i][j], arr[j][i]]。请注意,您有一些 arr[j][j] 术语总是指对角线上的单元格。
【解决方案8】:

使用Array.map 的另一种变体。使用索引可以转置矩阵,其中M != N:

// Get just the first row to iterate columns first
var t = matrix[0].map(function (col, c) {
    // For each column, iterate all rows
    return matrix.map(function (row, r) { 
        return matrix[r][c]; 
    }); 
});

所有的转置都是先映射元素列,然后按行。

【讨论】:

    【解决方案9】:

    另一种方法是从外到内迭代数组并通过映射内部值来减少矩阵。

    const
        transpose = array => array.reduce((r, a) => a.map((v, i) => [...(r[i] || []), v]), []),
        matrix = [[1, 2, 3], [1, 2, 3], [1, 2, 3]];
    
    console.log(transpose(matrix));

    【讨论】:

    • 确实!您已经优化了@Andrew Tatomyr 的答案! (基准对您有利!;)
    【解决方案10】:

    如果您可以选择使用 Ramda JS 和 ES6 语法,那么这里有另一种方法:

    const transpose = a => R.map(c => R.map(r => r[c], a), R.keys(a[0]));
    
    console.log(transpose([
      [1, 2, 3, 4],
      [5, 6, 7, 8],
      [9, 10, 11, 12]
    ])); // =>  [[1,5,9],[2,6,10],[3,7,11],[4,8,12]]
    &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.22.1/ramda.min.js"&gt;&lt;/script&gt;

    【讨论】:

    • 使用 Ramda 和 ES6 来解决这个问题真是太棒了
    • Ramda 现在实际上有一个transpose-function
    【解决方案11】:

    如果使用RamdaJS 是一个选项,这可以在一行中实现:R.transpose(myArray)

    【讨论】:

      【解决方案12】:

      您可以通过以下方式在不使用循环的情况下实现此目的。

      它看起来非常优雅,并且不需要任何依赖,例如jQueryUnderscore.js

      function transpose(matrix) {  
          return zeroFill(getMatrixWidth(matrix)).map(function(r, i) {
              return zeroFill(matrix.length).map(function(c, j) {
                  return matrix[j][i];
              });
          });
      }
      
      function getMatrixWidth(matrix) {
          return matrix.reduce(function (result, row) {
              return Math.max(result, row.length);
          }, 0);
      }
      
      function zeroFill(n) {
          return new Array(n+1).join('0').split('').map(Number);
      }
      

      缩小

      function transpose(m){return zeroFill(m.reduce(function(m,r){return Math.max(m,r.length)},0)).map(function(r,i){return zeroFill(m.length).map(function(c,j){return m[j][i]})})}function zeroFill(n){return new Array(n+1).join("0").split("").map(Number)}
      

      这是我拼凑的一个演示。注意缺少循环:-)

      // Create a 5 row, by 9 column matrix.
      var m = CoordinateMatrix(5, 9);
      
      // Make the matrix an irregular shape.
      m[2] = m[2].slice(0, 5);
      m[4].pop();
      
      // Transpose and print the matrix.
      println(formatMatrix(transpose(m)));
      
      function Matrix(rows, cols, defaultVal) {
          return AbstractMatrix(rows, cols, function(r, i) {
              return arrayFill(cols, defaultVal);
          });
      }
      function ZeroMatrix(rows, cols) {
          return AbstractMatrix(rows, cols, function(r, i) {
              return zeroFill(cols);
          });
      }
      function CoordinateMatrix(rows, cols) {
          return AbstractMatrix(rows, cols, function(r, i) {
              return zeroFill(cols).map(function(c, j) {
                  return [i, j];
              });
          });
      }
      function AbstractMatrix(rows, cols, rowFn) {
          return zeroFill(rows).map(function(r, i) {
              return rowFn(r, i);
          });
      }
      /** Matrix functions. */
      function formatMatrix(matrix) {
          return matrix.reduce(function (result, row) {
              return result + row.join('\t') + '\n';
          }, '');
      }
      function copy(matrix) {  
          return zeroFill(matrix.length).map(function(r, i) {
              return zeroFill(getMatrixWidth(matrix)).map(function(c, j) {
                  return matrix[i][j];
              });
          });
      }
      function transpose(matrix) {  
          return zeroFill(getMatrixWidth(matrix)).map(function(r, i) {
              return zeroFill(matrix.length).map(function(c, j) {
                  return matrix[j][i];
              });
          });
      }
      function getMatrixWidth(matrix) {
          return matrix.reduce(function (result, row) {
              return Math.max(result, row.length);
          }, 0);
      }
      /** Array fill functions. */
      function zeroFill(n) {
        return new Array(n+1).join('0').split('').map(Number);
      }
      function arrayFill(n, defaultValue) {
          return zeroFill(n).map(function(value) {
              return defaultValue || value;
          });
      }
      /** Print functions. */
      function print(str) {
          str = Array.isArray(str) ? str.join(' ') : str;
          return document.getElementById('out').innerHTML += str || '';
      }
      function println(str) {
          print.call(null, [].slice.call(arguments, 0).concat(['<br />']));
      }
      #out {
          white-space: pre;
      }
      &lt;div id="out"&gt;&lt;/div&gt;

      【讨论】:

      • 为什么你不想在没有循环的情况下这样做?没有循环会很慢
      • .map 不是循环吗?只有一个你没看到?我的意思是它会遍历每一个输入并对其进行处理......
      【解决方案13】:

      ES6 1liner 为:

      let invert = a => a[0].map((col, c) => a.map((row, r) => a[r][c]))
      

      和 Óscar 的一样,但你宁愿顺时针旋转它:

      let rotate = a => a[0].map((col, c) => a.map((row, r) => a[r][c]).reverse())
      

      【讨论】:

        【解决方案14】:

        我发现上面的答案要么难以阅读,要么太冗长,所以我自己写了一个。而且我认为这是在线性代数中实现转置的最直观的方式,你不做值交换,而只是将每个元素插入到新矩阵中的正确位置:

        function transpose(matrix) {
          const rows = matrix.length
          const cols = matrix[0].length
        
          let grid = []
          for (let col = 0; col < cols; col++) {
            grid[col] = []
          }
          for (let row = 0; row < rows; row++) {
            for (let col = 0; col < cols; col++) {
              grid[col][row] = matrix[row][col]
            }
          }
          return grid
        }
        

        【讨论】:

          【解决方案15】:

          编辑:这个答案不会转置矩阵,而是旋转它。我一开始没有仔细阅读这个问题:D

          顺时针和逆时针旋转:

              function rotateCounterClockwise(a){
                  var n=a.length;
                  for (var i=0; i<n/2; i++) {
                      for (var j=i; j<n-i-1; j++) {
                          var tmp=a[i][j];
                          a[i][j]=a[j][n-i-1];
                          a[j][n-i-1]=a[n-i-1][n-j-1];
                          a[n-i-1][n-j-1]=a[n-j-1][i];
                          a[n-j-1][i]=tmp;
                      }
                  }
                  return a;
              }
          
              function rotateClockwise(a) {
                  var n=a.length;
                  for (var i=0; i<n/2; i++) {
                      for (var j=i; j<n-i-1; j++) {
                          var tmp=a[i][j];
                          a[i][j]=a[n-j-1][i];
                          a[n-j-1][i]=a[n-i-1][n-j-1];
                          a[n-i-1][n-j-1]=a[j][n-i-1];
                          a[j][n-i-1]=tmp;
                      }
                  }
                  return a;
              }
          

          【讨论】:

          • 虽然没有回答这个问题;转置就像......沿对角线镜像(不旋转)
          • @DerMike 感谢您的指出。我不知道我为什么会犯这个错误 :) 但至少我可以看到它对其他人有用。
          • 这是我用于旋转矩阵的单行代码:stackoverflow.com/a/58668351/741251
          【解决方案16】:

          const transpose = array => array[0].map((r, i) => array.map(c => c[i]));
          console.log(transpose([[2, 3, 4], [5, 6, 7]]));

          【讨论】:

            【解决方案17】:

            Spread 语法不应用作 push 的替代品,仅应在您不想改变现有数组时使用。

            算法: 对于每一列,只需检查该列是否在结果矩阵中有一行,如果已经有一行,则只需 push 元素,否则创建一个新行数组,然后 push

            因此,与上述许多其他解决方案不同,此解决方案不会一次又一次地创建新数组,而是推送到同一个数组上。

            另外,请花点时间欣赏Nullish Coalescing Operator 的使用。

            const 
              transpose = arr => arr.reduce((m, r) => (r.forEach((v, i) => (m[i] ??= [], m[i].push(v))), m), []),
              matrix = [[1, 2, 3], [1, 2, 3], [1, 2, 3]]
            
            console.log(transpose(matrix))

            【讨论】:

              【解决方案18】:

              我认为这更具可读性。它使用Array.from,逻辑与使用嵌套循环相同:

              var arr = [
                [1, 2, 3, 4],
                [1, 2, 3, 4],
                [1, 2, 3, 4]
              ];
              
              /*
               * arr[0].length = 4 = number of result rows
               * arr.length = 3 = number of result cols
               */
              
              var result = Array.from({ length: arr[0].length }, function(x, row) {
                return Array.from({ length: arr.length }, function(x, col) {
                  return arr[col][row];
                });
              });
              
              console.log(result);

              如果您正在处理长度不等的数组,则需要将 arr[0].length 替换为其他内容:

              var arr = [
                [1, 2],
                [1, 2, 3],
                [1, 2, 3, 4]
              ];
              
              /*
               * arr[0].length = 4 = number of result rows
               * arr.length = 3 = number of result cols
               */
              
              var result = Array.from({ length: arr.reduce(function(max, item) { return item.length > max ? item.length : max; }, 0) }, function(x, row) {
                return Array.from({ length: arr.length }, function(x, col) {
                  return arr[col][row];
                });
              });
              
              console.log(result);

              【讨论】:

              • 对于不等长数组的结果中的undefined 呢?
              • @Bhuwan (a) OP 没有提到长度不等的数组 (b) 再次阅读我的答案......第二部分。
              • 在发表评论之前,我已经阅读了您的回答。我正在讨论数组中收到的undefined 值...第二部分。
              • @bhuwan 发布[[1,2], [4, 5, 6]] 的预期结果,我会更新。我检查的所有其他答案都有类似的问题。
              【解决方案19】:

              由于到目前为止没有人提到函数式递归方法,所以我认为这是我的看法。改编自 Haskell 的 Data.List.transpose

              var transpose = as => as.length ? as[0].length ? [as.reduce((rs, a) => a.length ? (rs.push(a[0]), rs) :
                  rs, []
                ), ...transpose(as.map(a => a.slice(1)))] :
                transpose(as.slice(1)) :
                [],
                mtx = [
                  [1],
                  [1, 2],
                  [1, 2, 3]
                ];
              
              console.log(transpose(mtx))
              .as-console-wrapper {
                max-height: 100% !important
              }

              【讨论】:

              • 我找到了这个 u => n ...r [ e.a( d? a , b : l ) e
              【解决方案20】:

              不改变给定数组的单线。

              a[0].map((col, i) => a.map(([...row]) => row[i]))
              

              【讨论】:

                【解决方案21】:

                我没有找到一个让我满意的答案,所以我自己写了一个,我认为它易于理解和实施,适合所有情况。

                    transposeArray: function (mat) {
                        let newMat = [];
                        for (let j = 0; j < mat[0].length; j++) {  // j are columns
                            let temp = [];
                            for (let i = 0; i < mat.length; i++) {  // i are rows
                                temp.push(mat[i][j]);  // so temp will be the j(th) column in mat
                            }
                            newMat.push(temp);  // then just push every column in newMat
                        }
                        return newMat;
                    }
                

                【讨论】:

                  【解决方案22】:

                  这个,不仅是一个超级高效的解决方案,而且是一个非常短的解决方案。

                  算法时间复杂度:O(n log n)

                  const matrix = [
                     [1,1,1,1],
                     [2,2,2,2],
                     [3,3,3,3],
                     [4,4,4,4]
                  ];
                  
                  matrix.every((r, i, a) => (
                     r.every((_, j) => (
                        j = a.length-j-1,
                        [ r[j], a[j][i] ] = [ a[j][i], r[j] ],
                        i < j-1
                     )), 
                     i < length-2
                  ));
                  
                  console.log(matrix);
                  /*
                  Prints:
                  [
                     [1,2,3,4],
                     [1,2,3,4],
                     [1,2,3,4],
                     [1,2,3,4]
                  ]
                  */
                  

                  上面的示例将只进行 6 次迭代。
                  对于更大的矩阵,比如 100x100,它将进行 4,900 次迭代,这比此处提供的任何其他解决方案快 51%。

                  原理很简单,你只迭代矩阵的上对角线,因为对角线永远不会改变,下对角线与上半线一起切换,所以没有理由迭代它以及。这样,您可以节省大量运行时间,尤其是在大型矩阵中。

                  【讨论】:

                    【解决方案23】:

                    在此处添加 TS 版本。

                    const transpose = <T>(m: Array<Array<T>>): Array<Array<T>> => m[0].map((_, i) => m.map(x => x[i]));
                    

                    【讨论】:

                      【解决方案24】:
                      function invertArray(array,arrayWidth,arrayHeight) {
                        var newArray = [];
                        for (x=0;x<arrayWidth;x++) {
                          newArray[x] = [];
                          for (y=0;y<arrayHeight;y++) {
                              newArray[x][y] = array[y][x];
                          }
                        }
                        return newArray;
                      }
                      

                      【讨论】:

                        【解决方案25】:
                        reverseValues(values) {
                                let maxLength = values.reduce((acc, val) => Math.max(val.length, acc), 0);
                                return [...Array(maxLength)].map((val, index) => values.map((v) => v[index]));
                        }
                        

                        【讨论】:

                        • 请不要只发布代码作为答案,而是要说明您的代码的作用以及它如何解决问题的问题。带有解释的答案通常质量更高,更有可能吸引投票。
                        猜你喜欢
                        • 2010-11-29
                        • 2021-07-20
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2020-12-23
                        • 1970-01-01
                        相关资源
                        最近更新 更多