Транспонирование 2D-массива в JavaScript


155

У меня есть массив массивов, что-то вроде:

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

Это, однако, кажется громоздким, и я чувствую, что должен быть более простой способ сделать это. Здесь?


4
Можете ли вы гарантировать, что эти два измерения всегда будут одинаковыми? 1x1, 2x2, 3x3 и т. Д. Для чего arrayLengthконкретно используется параметр? Чтобы вы не выходили за пределы определенного количества элементов в массиве?
— раздавить

8
Это не имеет ничего общего с JQuery, я изменил название.
— Джо

4
Проверьте это: stackoverflow.com/questions/4492678/… . То, что вы делаете, это транспонирование матрицы
— stackErr

1
Да, транспонирование. Инвертирование было бы совершенно другим, и я не заинтересован в этом. На данный момент.
— ckersch

3
Диагональ вверху слева направо остается неизменной, поэтому есть возможность оптимизации.
— S Meaden

Ответы:


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

mapвызывает предоставленную callbackфункцию один раз для каждого элемента в массиве по порядку и создает новый массив из результатов. callbackвызывается только для индексов массива, которым присвоены значения; он не вызывается для индексов, которые были удалены или которым никогда не присваивались значения.

callbackвызывается с тремя аргументами: значением элемента, индексом элемента и объектом Array, по которому осуществляется обход. [источник]


8
Это хорошее решение. Однако, если вы заботитесь о производительности, вам следует использовать оригинальное решение OP (с исправлением ошибки для поддержки массивов M x N, где M! = N). проверить это jsPerf
— Билли Макки

3
Если вы дважды используете его в одном и том же массиве, он возвращается к первому целому числу повторения поворота на 90 '
— Оливье Понс

4
почему array[0].mapвместо array.map?
— Джон Vandivier

4
array[0].mapпотому что он хочет повторять, сколько бы раз ни было столбцов, array.mapбудет повторять, сколько строк.
— Joeycozza

2
@BillyMcKee в 2019 году и Chrome 75 loopsна 45% медленнее, чем map. И да, он транспонируется правильно, поэтому второй прогон возвращает исходную матрицу.
— Ebuall

41

Вот моя реализация в современном браузере (без зависимости):

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

Если вы дважды используете его в одном и том же массиве, он возвращается к первому целому числу повторения поворота на 90 '
— Оливье Понс

26
Транспонирование матрицы транспонирования - это исходная матрица, см. Math.nyu.edu/~neylon/linalgfall04/project1/dj/proptranspose.htm
— Махди Джадалиха,

39

Вы можете использовать underscore.js

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

3
Это было довольно - к тому же, подчеркивание мне более необходимо, чем jQuery.
— Джон Стриклер

или если вы используете функциональную библиотеку, как rambdaвы можете просто сделатьconst transpose = apply(zip)
— Guy Who Knows Stuff

1
Почему этот вариант будет лучше, чем выбранный ответ?
— Алекс Ленаил

Этому вопросу уже лет, и я не уверен, что это сейчас. Это, однако, чище, чем оригинальная версия принятого ответа . Вы заметите, что он был отредактирован в значительной степени с тех пор. Похоже, что он использует ES6, который, я не думаю, был доступен в 2013 году, когда широко задавался вопрос.
— Джо

25

кратчайший путь с lodash/ underscoreи es6:

_.zip(...matrix)

где matrixможет быть:

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

Или без ES6:_.zip.apply(_, matrix)
— ACH

9
Закрыть, но _.unzip (матрица) короче;)
— Vigrant

1
Вы можете расширить это? Я не понимаю, что вы здесь говорите. Этот короткий отрывок должен решить проблему? или это просто часть или как?
— Julix

1
Боже мой, это короткое решение. только что узнал об операторе ... - использовал его для разбиения строки на массив букв ... спасибо за ответ
— Julix

22

Здесь много хороших ответов! Я объединил их в один ответ и обновил часть кода для более современного синтаксиса:

Однострочники, вдохновленные Фавадом Гафуром и Оскаром Гомесом Альканьисом

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

Стиль функционального подхода с редуктором. Андрей Татомир

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

Ванильный подход

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, вдохновленный Эмануэлем Саринганом

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

11

Аккуратный и чистый:

[[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]]), 
    []
)

2
Я не знаю, что я бы назвал это обновление лучше. Это умно, конечно, но это кошмар для чтения.
— Мари

9

Вы можете сделать это на месте, сделав только один проход:

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
если ваш массив не является квадратом (например, 2x8), это не работает, я думаю,
— Оливье Понс

2
Это решение изменяет исходный массив. Если вам все еще нужен исходный массив, тогда это решение может быть не тем, что вам нужно. Другие решения вместо этого создают новый массив.
— Алекс

Кто-нибудь знает, как это делается в синтаксисе 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]термины, которые всегда относятся к ячейкам по диагонали.
— Алгоритмическая канарейка

6

Просто еще один вариант использования 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]; 
    }); 
});

Все, что нужно для транспонирования - это сопоставление элементов сначала по столбцам, а затем по строкам.


5

Если у вас есть возможность использовать синтаксис 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>


2
Потрясающе для использования как Ramda, так и ES6, чтобы решить эту проблему
— Эшвин Баламохан

1
Ramda на самом деле имеет transpose-функции в настоящее время.
— Джейкоб Лауритцен

5

Другой подход - перебирать массив снаружи внутрь и уменьшать матрицу, отображая внутренние значения.

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! (тесты работают на вашу пользу!;)
— scraaappy


2

Вы можете достичь этого без петель, используя следующее.

Это выглядит очень элегантно и не требует никаких зависимостей, таких как jQuery из 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);
}

Минимизированный

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>


Почему бы вам не захотеть сделать это без петель? Без петель это медленно
— Downgoat

5
Разве .map не цикл? Всего лишь одного, которого ты не видишь? Я имею в виду, что он проходит через все входы и делает что-то для этого ...
— Julix

2

ES6 1 лайнеры как:

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

2

Изменить: этот ответ не будет транспонировать матрицу, но повернуть ее. Я не внимательно прочитал вопрос: 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

@DerMike спасибо за указание. Я не знаю, почему я сделал эту ошибку :) Но, по крайней мере, я вижу, что это было полезно для некоторых других людей.
— Ариан Фирузян

Вот мой однострочный код для вращения матрицы: stackoverflow.com/a/58668351/741251
— Нитин Джадхав,

1

Мне показалось, что приведенные выше ответы либо трудно читаются, либо слишком многословны, поэтому я пишу один сам. И я думаю, что это наиболее интуитивный способ реализации транспонирования в линейной алгебре, вы не делаете обмен значениями , а просто вставляете каждый элемент в нужное место в новой матрице:

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

Я думаю, что это немного более читабельно. Он использует 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);



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

Безбиблиотечная реализация в TypeScript, которая работает для любой формы матрицы, которая не усекает ваши массивы:

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

Однострочник, который не меняет заданный массив.

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
Пожалуйста, не публикуйте только код в качестве ответа, но включайте объяснение того, что делает ваш код и как он решает проблему вопроса. Ответы с объяснением, как правило, более высокого качества и, скорее всего, привлекут положительные отзывы.
— Марк Роттвил

0

Я не нашел ответ, который меня удовлетворил, поэтому я написал его сам, думаю, его легко понять, реализовать и он подходит для всех ситуаций.

    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

Поскольку никто до сих пор не упомянул функционально-рекурсивный подход, вот мое мнение. Адаптация Хаскелла 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
}

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.