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

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

ckersch
источник
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
4

Если использование RamdaJS является опцией, это может быть достигнуто в одной строке: R.transpose(myArray)

Рафаэль Розон
источник
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())
ysle
источник
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);

Салман А
источник
1

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

панк
источник
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)
}
millsp
источник
0

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

a[0].map((col, i) => a.map(([...row]) => row[i]))
Offpics
источник
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
}

Redu
источник