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


154

У мене є масив масивів, на кшталт:

[
    [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викликається трьома аргументами: значенням елемента, індексом елемента та об'єктом масиву, що пересувається. [джерело]


8
Це хороше рішення. Однак, якщо ви дбаєте про продуктивність, вам слід скористатися оригінальним рішенням OP (без виправлення помилок для підтримки масивів M x N, де M! = N). перевірити це jsPerf
Біллі Маккі

3
Якщо ви використовуєте його двічі в одному масиві, він повертається до першої черги, що повертається на 90 '
Олів'є Понс

3
чому array[0].mapзамість array.map?
Джон Вандів'є

4
array[0].mapоскільки він хоче повторити, скільки стовпців є, array.mapповторить, скільки рядків є.
joeycozza

2
@BillyMcKee в 2019 році і Chrome 75 loopsна 45% повільніше , ніж map. І так, він транспонує правильно, тому другий запуск повертає початкову матрицю.
Ебуал

41

ось моя реалізація в сучасному браузері (без залежності):

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

Якщо ви використовуєте його двічі в одному масиві, він повертається до першої черги, що повертається на 90 '
Олів'є Понс

26
Транспонування матриці транспозиції є вихідна матриця, см math.nyu.edu/~neylon/linalgfall04/project1/dj/proptranspose.htm
Махді Jadaliha

39

Ви можете використовувати underscore.js

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

3
Це було досить - також, підкреслення мені більше потрібне, ніж jQuery.
Джон Стріклер

або якщо ви використовуєте функціональну бібліотеку, як rambdaви можете просто робитиconst transpose = apply(zip)
Хлопець, хто знає речі,

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 (матриця) коротший;)
Жорсткий

1
Чи можете ви розширити це? Я не розумію, що ви тут говорите. Цей короткий фрагмент повинен вирішити проблему? чи це просто частина чи що?
Джулікс

1
мій боже, це коротке рішення. щойно дізнався про ... оператора - використовував його для розбиття рядка на масив літер ... дякую за відповідь
Джулікс

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

Лодаш / Підкреслення за марселем

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! (Бенчмарки працюють на вашу користь
!;


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 1liners як:

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 дякую, що вказали. Я не знаю, чому я допустив цю помилку :) Але принаймні я можу побачити, що це було корисно для інших людей.
Арій Фірузіян

Ось мій
Нітін

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.