Транспонирование 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.