Транспонирование 2D-массива в JavaScript
у меня есть массив массивов, что-то вроде:
[
[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;
}
Это, однако, кажется громоздким, и я чувствую, что там должен быть более простой способ сделать это. Есть ли?
16 ответов:
array[0].map((col, i) => array.map(row => row[i]));
map
звонит, предусмотренногоcallback
функция один раз для каждого элемента в массиве, по порядку, и создает новый массив из результатов.callback
вызывается только для индексов массива, которым присвоены значения; он не вызывается для индексов, которые были удалены или которым никогда не присваивались значения.
callback
вызывается с тремя аргументами: значение элемента, индекс элемента массива, объекта пройденный. [источник]
вы могли бы использовать подчеркивания.js
_.zip.apply(_, [[1,2,3], [1,2,3], [1,2,3]])
вот моя реализация в современном браузере (без зависимостей):
transpose = m => m[0].map((x,i) => m.map(x => x[i]))
самый короткий путь с
lodash
/underscore
иes6
:_.zip(...matrix)
здесь
matrix
можно:const matrix = [[1,2,3], [1,2,3], [1,2,3]];
вы можете сделать это на месте, делая лишь один проход:
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; } } }
аккуратный и чистый:
[[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]]));
обновление. Еще лучше это можно написать с помощью оператора spread:
const transpose = matrix => matrix.reduce(($, row) => row.map((_, i) => [...($[i] || []), row[i]]), [] )
еще один вариант с использованием
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]; }); });
все, что нужно транспонировать, это сопоставление элементов столбца-сначала, а затем по строке.
много хороших ответов здесь! Я объединил их в один ответ и обновил часть кода для более современного синтаксиса:
однострочные вдохновленные Фавад Гафур и Оскар Гомес Альканьис
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])); }
функциональный стиль подхода с уменьшением на Андрей Tatomyr
function transpose(matrix) { return matrix.reduce((prev, next) => next.map((item, i) => (prev[i] || []).concat(next[i]) ), []); }
Lodash / подчеркивание by Марсель
function tranpose(matrix) { return _.zip(...matrix); } // Without spread operator. function transpose(matrix) { return _.zip.apply(_, [[1,2,3], [1,2,3], [1,2,3]]) }
Ванильное подход
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; }
Ваниль на месте ES6 подход вдохновлен Эмануэль 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]]; } } }
Если у вас есть возможность использовать синтаксис 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]]
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.22.1/ramda.min.js"></script>
Edit: этот ответ не будет транспонировать матрицу, а повернет ее. Я не читал вопрос тщательно в первую очередь :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; }
при использовании RamdaJS вариант, это может быть достигнуто в одну строку:
R.transpose(myArray)
вы можете достичь этого без петель, используя следующее.
он выглядит очень элегантно и не требует каких-либо зависимостей, таких как jQuery of подчеркивания.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; }
<div id="out"></div>
ES6 1liners as:
let invert = a => a[0].map((col, c) => a.map((row, r) => a[r][c]))
так же как Оскар, но как бы вы скорее повернули его по часовой стрелке:
let rotate = a => a[0].map((col, c) => a.map((row, r) => a[r][c]).reverse())
Я обнаружил, что приведенные выше ответы либо трудно читать, либо слишком многословны, поэтому я пишу сам. И я думаю, что это самый интуитивный способ реализовать транспонирование в линейной алгебре, вы не делаете курса, но просто вставить каждый элемент в нужное место в новой матрице:
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 }
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; }
Я думаю, что это немного более читабельным. Он использует
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);