Chuyển một mảng 2D trong JavaScript


154

Tôi đã có một mảng các mảng, đại loại như:

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

Tôi muốn hoán đổi nó để có được mảng sau:

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

Không khó để lập trình bằng cách sử dụng các vòng lặp:

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;
}

Điều này, tuy nhiên, có vẻ cồng kềnh, và tôi cảm thấy nên có một cách dễ dàng hơn để làm điều đó. Lanhung?


4
Bạn có thể đảm bảo rằng hai chiều sẽ luôn giống nhau không? 1x1, 2x2, 3x3, v.v ... arrayLengthThông số được sử dụng cho chính xác là gì? Để đảm bảo rằng bạn không vượt quá một số phần tử nhất định trong mảng?
nghiền nát

8
Điều này không liên quan gì đến JQuery, tôi đã thay đổi tiêu đề.
Joe

4
Hãy xem cái này: stackoverflow.com/questions/4492678/ . Những gì bạn đang làm là hoán chuyển một ma trận
stackErr

1
Vâng, hoán vị. Đảo ngược sẽ hoàn toàn khác và tôi không quan tâm đến nó. Để bây giờ.
ckersch

3
Đường chéo từ trái sang phải dưới không thay đổi nên có cơ hội tối ưu hóa.
S Meaden

Câu trả lời:


205
array[0].map((_, colIndex) => array.map(row => row[colIndex]));

mapgọi một callbackhàm được cung cấp một lần cho mỗi phần tử trong một mảng, theo thứ tự và xây dựng một mảng mới từ các kết quả. callbackchỉ được gọi cho các chỉ mục của mảng có các giá trị được gán; nó không được gọi cho các chỉ mục đã bị xóa hoặc chưa bao giờ được gán giá trị.

callbackđược gọi với ba đối số: giá trị của phần tử, chỉ mục của phần tử và đối tượng Array được duyệt qua. [nguồn]


8
Đây là một giải pháp tốt. Tuy nhiên, nếu bạn quan tâm đến hiệu suất, bạn nên sử dụng giải pháp ban đầu của OP (w / sửa lỗi để hỗ trợ mảng M x N trong đó M! = N). kiểm tra jsPerf này
Billy McKee

3
Nếu bạn sử dụng nó hai lần trên cùng một mảng, nó sẽ quay trở lại lần đầu tiên quay 90 'một lần nữa
Olivier Pons

3
Tại sao array[0].mapthay vì array.map?
John Vandivier

4
array[0].mapbởi vì anh ta muốn lặp đi lặp lại nhiều lần rằng có các cột, array.mapsẽ lặp lại có bao nhiêu hàng.
joeycozza

2
@BillyMcKee vào năm 2019 và Chrome 75 loopschậm hơn 45% so với map. Và vâng, nó chuyển đổi chính xác, vì vậy lần chạy thứ hai trả về ma trận ban đầu.
Ebuall


39

Bạn có thể sử dụng underscore.js

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

3
Điều đó thật tuyệt - cũng vậy, gạch dưới là cần thiết đối với tôi hơn jQuery.
John Strickler

hoặc nếu bạn đang sử dụng một thư viện chức năng như rambdabạn có thể làmconst transpose = apply(zip)
Guy Who Knows Stuff

1
Tại sao tùy chọn này sẽ tốt hơn câu trả lời được chọn?
Alex Lenail

Câu hỏi này đã cũ và tôi không chắc là nó bây giờ. Tuy nhiên, nó sạch hơn phiên bản gốc của câu trả lời được chấp nhận . Bạn sẽ nhận thấy rằng nó đã được chỉnh sửa đáng kể từ đó. Có vẻ như nó sử dụng ES6, mà tôi không nghĩ là có sẵn vào năm 2013 khi câu hỏi được hỏi rộng rãi.
Joe

24

con đường ngắn nhất với lodash/ underscorees6:

_.zip(...matrix)

nơi matrixcó thể là:

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

Hoặc, không có ES6:_.zip.apply(_, matrix)
ach

9
Đóng nhưng _.unzip (ma trận) ngắn hơn;)
Vigrant 18/03/2016

1
Bạn có thể mở rộng về điều này? Tôi không hiểu những gì bạn nói ở đây. Đó là snipplet ngắn được cho là để giải quyết vấn đề? hoặc nó chỉ là một phần hoặc những gì?
Julix

1
Chúa ơi, đó là một giải pháp ngắn. vừa tìm hiểu về toán tử ... - đã sử dụng nó để chia một chuỗi thành một mảng các chữ cái ... cảm ơn vì đã trả lời
Julix

21

Nhiều câu trả lời tốt ở đây! Tôi hợp nhất chúng thành một câu trả lời và cập nhật một số mã cho cú pháp hiện đại hơn:

Một lớp lót lấy cảm hứng từ 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]));
}

Phong cách tiếp cận chức năng với giảm bởi Andrew Tatomyr

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

Lodash / gạch dưới bởi 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]])
}

Cách tiếp cận vani

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;
}

Cách tiếp cận ES6 tại chỗ của Vanilla lấy cảm hứng từ Emanuel Saringan

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

10

Gọn gàng và tinh khiết:

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

Các giải pháp trước đây có thể dẫn đến thất bại trong trường hợp một mảng trống được cung cấp.

Đây là một chức năng:

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

Cập nhật. Nó có thể được viết thậm chí tốt hơn với toán tử trải rộng:

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

2
Tôi không biết rằng tôi sẽ gọi bản cập nhật đó tốt hơn. Đó là thông minh, chắc chắn, nhưng đó là một cơn ác mộng để đọc.
Marie

9

Bạn có thể thực hiện tại chỗ bằng cách chỉ thực hiện một lần:

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;
    }
  }
}

1
nếu mảng của bạn không phải là hình vuông (ví dụ 2x8) thì điều này không hoạt động tôi đoán
Olivier Pons

2
Giải pháp này thay đổi mảng ban đầu. Nếu bạn vẫn cần mảng ban đầu thì giải pháp này có thể không phải là điều bạn muốn. Các giải pháp khác tạo ra một mảng mới thay thế.
Alex

Có ai biết làm thế nào điều này được thực hiện trong cú pháp ES6? Tôi đã thử [arr[j][j],arr[i][j]] = [arr[i][j],arr[j][j]]nhưng nó dường như không hoạt động, tôi có thiếu thứ gì không?
Nikasv

@Nikasv bạn có thể muốn [arr[j][i], arr[i][j]] = [arr[i][j], arr[j][i]]. Lưu ý rằng bạn có một số arr[j][j]thuật ngữ sẽ luôn đề cập đến các ô trên đường chéo.
Thuật toán Canary

6

Chỉ cần một biến thể khác bằng cách sử dụng Array.map. Sử dụng các chỉ mục cho phép hoán vị ma trận trong đó 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]; 
    }); 
});

Tất cả chỉ là hoán vị là ánh xạ các phần tử cột trước, sau đó theo hàng.


5

Nếu bạn có một tùy chọn sử dụng cú pháp Ramda JS và ES6, thì đây là một cách khác để làm điều đó:

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]]
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.22.1/ramda.min.js"></script>


2
Thật tuyệt vời khi sử dụng cả Ramda và ES6 để giải quyết vấn đề này
Ashwin Balamohan

1
Ramda thực sự có một transposechức năng bây giờ.
Jacob Lauritzen

5

Một cách tiếp cận khác bằng cách lặp lại mảng từ ngoài vào trong và giảm ma trận bằng cách ánh xạ các giá trị bên trong.

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));


Thật! Bạn đã tối ưu hóa câu trả lời @Andrew Tatomyr! (điểm chuẩn hoạt động theo ý bạn!)
scraaappy

4

Nếu sử dụng RamdaJS là một tùy chọn, điều này có thể đạt được trong một dòng: R.transpose(myArray)


2

Bạn có thể đạt được điều này mà không cần vòng lặp bằng cách sử dụng sau đây.

Nó trông rất thanh lịch và nó không yêu cầu bất kỳ sự phụ thuộc nào như jQuery của Underscore.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);
}

Giảm thiểu

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)}

Đây là một bản demo tôi đã ném cùng nhau. Lưu ý thiếu các vòng lặp :-)

// 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;
}
<div id="out"></div>


Tại sao bạn không muốn làm điều đó mà không có vòng lặp? Không có vòng lặp, nó chậm
Downgoat

5
Không phải là .map một vòng lặp? Chỉ là một trong những bạn không nhìn thấy? Ý tôi là nó sẽ vượt qua mọi đầu vào và làm mọi thứ với nó ...
Julix

2

ES6 1liners như:

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

cũng giống như của Óscar, nhưng bạn muốn xoay nó theo chiều kim đồng hồ:

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

2

Chỉnh sửa: Câu trả lời này sẽ không hoán đổi ma trận, mà xoay nó. Tôi đã không đọc kỹ câu hỏi ngay từ đầu: D

xoay theo chiều kim đồng hồ và ngược chiều kim đồng hồ:

    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;
    }

Không trả lời câu hỏi, mặc dù; Chuyển đổi giống như ... phản chiếu dọc theo đường chéo (không quay)
DerMike

@DerMike cảm ơn bạn đã chỉ ra. Tôi không biết tại sao tôi lại mắc lỗi đó :) Nhưng ít nhất tôi có thể thấy nó hữu ích cho một số người khác.
Aryan Firouzian

Đây là mã một lớp của tôi để xoay một ma trận: stackoverflow.com/a/58668351/741251
Nitin Jadhav

1

Tôi thấy các câu trả lời ở trên khó đọc hoặc quá dài dòng, vì vậy tôi tự viết một câu. Và tôi nghĩ rằng đây là cách trực quan nhất để thực hiện chuyển vị trong đại số tuyến tính, bạn không thực hiện trao đổi giá trị , mà chỉ cần chèn từng phần tử vào đúng vị trí trong ma trận mới:

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
}

1

Tôi nghĩ rằng điều này là dễ đọc hơn một chút. Nó sử dụng Array.fromvà logic giống hệt với việc sử dụng các vòng lặp lồng nhau:

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);

Nếu bạn đang xử lý các mảng có độ dài không bằng nhau, bạn cần thay thế arr[0].lengthbằng một thứ khác:

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);


0
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;
}

0

Việc triển khai không có thư viện trong TypeScript hoạt động cho bất kỳ hình dạng ma trận nào sẽ không cắt bớt mảng của bạn:

const rotate2dArray = <T>(array2d: T[][]) => {
    const rotated2d: T[][] = []

    return array2d.reduce((acc, array1d, index2d) => {
        array1d.forEach((value, index1d) => {
            if (!acc[index1d]) acc[index1d] = []

            acc[index1d][index2d] = value
        })

        return acc
    }, rotated2d)
}

0

Một lớp lót không thay đổi mảng đã cho.

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

0
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]));
}

3
Vui lòng không chỉ đăng mã dưới dạng câu trả lời, nhưng bao gồm phần giải thích mã của bạn làm gì và cách giải quyết vấn đề của câu hỏi. Câu trả lời với một lời giải thích thường có chất lượng cao hơn và có nhiều khả năng thu hút upvote.
Mark Rotteveel

0

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


0

Tôi đã không tìm thấy một câu trả lời làm tôi hài lòng, vì vậy tôi đã tự viết một câu, tôi nghĩ nó dễ hiểu và dễ thực hiện và phù hợp với mọi tình huống.

    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;
    }

0

Vì cho đến nay không ai đề cập đến một cách tiếp cận đệ quy chức năng ở đây là của tôi. Một bản chuyển thể của 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
}

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.