Transposing 2D-array dalam JavaScript

154

Saya punya array array, seperti:

[
    [1,2,3],
    [1,2,3],
    [1,2,3],
]

Saya ingin memindahkannya untuk mendapatkan array berikut:

[
    [1,1,1],
    [2,2,2],
    [3,3,3],
]

Tidak sulit untuk melakukan pemrograman menggunakan loop:

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

Namun, ini kelihatannya besar, dan saya merasa harus ada cara yang lebih mudah untuk melakukannya. Disana?

ckersch
sumber
4
Bisakah Anda menjamin bahwa kedua dimensi akan selalu sama? 1x1, 2x2, 3x3, dll. Apa arrayLengthparameter yang digunakan untuk tepatnya? Untuk memastikan bahwa Anda tidak melampaui sejumlah elemen dalam array?
naksir
8
Ini tidak ada hubungannya dengan JQuery, saya mengubah judulnya.
Joe
4
Lihat ini: stackoverflow.com/questions/4492678/… . Apa yang Anda lakukan adalah mentransposisi sebuah matriks
stackErr
1
Ya, transposing. Pembalikan akan sangat berbeda dan saya tidak tertarik. Untuk sekarang.
ckersch
3
Diagonal kiri atas ke kanan bawah tidak berubah sehingga ada peluang pengoptimalan.
S Meaden

Jawaban:

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

mapmemanggil callbackfungsi yang disediakan satu kali untuk setiap elemen dalam sebuah array, secara berurutan, dan membangun sebuah array baru dari hasil. callbackdipanggil hanya untuk indeks array yang telah menetapkan nilai; itu tidak dipanggil untuk indeks yang telah dihapus atau yang tidak pernah diberi nilai.

callbackdipanggil dengan tiga argumen: nilai elemen, indeks elemen, dan objek Array yang dilalui. [sumber]

Fawad Ghafoor
sumber
8
Ini solusi yang bagus. Namun, jika Anda peduli dengan kinerja, Anda harus menggunakan solusi asli OP (dengan perbaikan bug untuk mendukung array M x N di mana M! = N). periksa jsPerf ini
Billy McKee
3
Jika Anda menggunakannya dua kali pada array yang sama, ia kembali ke yang pertama sebelum memutar 90 'lagi
Olivier Pons
3
mengapa array[0].mapbukannya array.map?
John Vandivier
4
array[0].mapkarena dia ingin mengulangi berapa kali pun ada kolom, array.mapakan mengulangi berapa banyak baris yang ada.
joeycozza
2
@BillyMcKee pada tahun 2019 dan Chrome 75 loopslebih lambat 45% dari map. Dan ya, transposnya dengan benar, sehingga proses kedua mengembalikan matriks awal.
Ebuall
41

inilah implementasi saya di browser modern (tanpa ketergantungan):

transpose = m => m[0].map((x,i) => m.map(x => x[i]))
Mahdi Jadaliha
sumber
Jika Anda menggunakannya dua kali pada array yang sama, ia kembali ke yang pertama sebelum memutar 90 'lagi
Olivier Pons
26
Transpos dari matriks transpos adalah matriks asli, merujuk ke math.nyu.edu/~neylon/linalgfall04/project1/dj/proptranspose.htm
Mahdi Jadaliha
39

Anda dapat menggunakan underscore.js

_.zip.apply(_, [[1,2,3], [1,2,3], [1,2,3]])
Joe
sumber
3
Itu cukup - juga, garis bawah lebih penting bagi saya daripada jQuery.
John Strickler
atau jika Anda menggunakan perpustakaan fungsional seperti rambdayang bisa Anda lakukanconst transpose = apply(zip)
Guy Who Knows Stuff
1
Mengapa opsi ini lebih baik daripada jawaban yang dipilih?
Alex Lenail
Pertanyaan ini sudah berumur bertahun-tahun dan saya tidak yakin sekarang. Namun, ini lebih bersih daripada versi asli dari jawaban yang diterima . Anda akan melihat bahwa itu telah diedit secara substansial sejak itu. Sepertinya itu menggunakan ES6, yang saya pikir tidak tersedia pada 2013 ketika pertanyaan diajukan secara luas.
Joe
25

cara terpendek dengan lodash/ underscoredan es6:

_.zip(...matrix)

dimana matrixbisa:

const matrix = [[1,2,3], [1,2,3], [1,2,3]];
marcel
sumber
Atau, tanpa ES6:_.zip.apply(_, matrix)
ach
9
Tutup tetapi _.unzip (matriks) lebih pendek;)
Vigrant
1
Dapatkah Anda memperluas ini? Saya tidak mengerti apa yang Anda katakan di sini. Potongan pendek itu seharusnya bisa menyelesaikan masalah? atau hanya sebagian atau apa?
Julix
1
astaga, itu solusi singkat. baru belajar tentang ... operator - menggunakannya untuk memecah string menjadi array surat ... terima kasih atas jawabannya
Julix
22

Banyak jawaban bagus di sini! Saya menggabungkannya menjadi satu jawaban dan memperbarui beberapa kode untuk sintaksis yang lebih modern:

One-liner terinspirasi oleh Fawad Ghafoor dan Óscar Gómez Alcañiz

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

Gaya pendekatan fungsional dengan mengurangi oleh Andrew Tatomyr

function transpose(matrix) {
  return matrix.reduce((prev, next) => next.map((item, i) =>
    (prev[i] || []).concat(next[i])
  ), []);
}

Lodash / Underscore oleh 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]])
}

Pendekatan vanilla

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

Pendekatan ES6 vanilla in-place terinspirasi oleh Emanuel 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]];
    }
  }
}
Yangshun Tay
sumber
11

Rapi dan murni:

[[0, 1], [2, 3], [4, 5]].reduce((prev, next) => next.map((item, i) =>
    (prev[i] || []).concat(next[i])
), []); // [[0, 2, 4], [1, 3, 5]]

Solusi sebelumnya dapat menyebabkan kegagalan jika array kosong disediakan.

Ini dia sebagai fungsi:

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

Memperbarui. Itu dapat ditulis lebih baik dengan operator spread:

const transpose = matrix => matrix.reduce(
    ($, row) => row.map((_, i) => [...($[i] || []), row[i]]), 
    []
)
Andrew Tatomyr
sumber
2
Saya tidak tahu bahwa saya akan menyebut pembaruan itu lebih baik. Memang pintar, tapi itu mimpi buruk untuk dibaca.
Marie
9

Anda dapat melakukannya di tempat dengan hanya melakukan satu pass:

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;
    }
  }
}
Emanuel Saringan
sumber
1
jika array Anda bukan persegi (misalnya 2x8) ini tidak berfungsi saya kira
Olivier Pons
2
Solusi ini mengubah array asli. Jika Anda masih membutuhkan array asli maka solusi ini mungkin bukan yang Anda inginkan. Solusi lain malah membuat array baru.
Alex
Adakah yang tahu bagaimana ini dilakukan dalam sintaks ES6? Saya mencoba [arr[j][j],arr[i][j]] = [arr[i][j],arr[j][j]]tetapi tampaknya tidak berhasil, apakah saya kehilangan sesuatu?
Nikasv
@ Nikik Anda mungkin ingin [arr[j][i], arr[i][j]] = [arr[i][j], arr[j][i]]. Perhatikan bahwa Anda memiliki beberapa arr[j][j]istilah yang akan selalu merujuk ke sel pada diagonal.
Algoritma Canary
6

Hanya menggunakan variasi lain Array.map. Menggunakan indeks memungkinkan untuk mengubah posisi matriks di mana 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]; 
    }); 
});

Yang perlu dilakukan transposing adalah memetakan elemen-elemen kolom terlebih dahulu, lalu dengan baris.

Óscar Gómez Alcañiz
sumber
5

Jika Anda memiliki opsi untuk menggunakan sintaks Ramda JS dan ES6, maka inilah cara lain untuk melakukannya:

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>

Kevin Le - Khnle
sumber
2
Kedahsyatan karena menggunakan Ramda dan ES6 untuk menyelesaikan ini
Ashwin Balamohan
1
Ramda sebenarnya memiliki transposefungsi sekarang.
Jacob Lauritzen
5

Pendekatan lain dengan iterasi array dari luar ke dalam dan mengurangi matriks dengan memetakan nilai-nilai dalam.

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

Nina Scholz
sumber
Memang! Anda telah mengoptimalkan jawaban @Andrew Tatomyr! (tolok ukur bekerja sesuai keinginan Anda
!;
4

Jika menggunakan RamdaJS adalah opsi, ini dapat dicapai dalam satu baris: R.transpose(myArray)

Rafael Rozon
sumber
2

Anda dapat mencapai ini tanpa loop dengan menggunakan yang berikut ini.

Terlihat sangat elegan dan tidak memerlukan dependensi seperti jQuery of 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);
}

Diperkecil

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

Ini demo yang saya lempar bersama. Perhatikan kurangnya loop :-)

// 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>

Tuan Polywhirl
sumber
Mengapa Anda tidak ingin melakukannya tanpa loop? Tanpa loop itu lambat
Downgoat
5
Bukankah .map loop? Hanya satu yang tidak Anda lihat? Maksud saya itu akan setiap input dan melakukan hal-hal untuk itu ...
Julix
2

ES6 1liners sebagai:

let invert = a => a[0].map((col, c) => a.map((row, r) => a[r][c]))

begitu sama dengan Óscar, tetapi seperti yang Anda inginkan putar searah jarum jam:

let rotate = a => a[0].map((col, c) => a.map((row, r) => a[r][c]).reverse())
kamu
sumber
2

Sunting: Jawaban ini tidak akan mengubah posisi matriks, tetapi memutarnya. Saya tidak membaca pertanyaan dengan seksama sejak awal: D

rotasi searah jarum jam dan berlawanan arah jarum jam:

    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;
    }
Arya Firouzian
sumber
Namun, tidak menjawab pertanyaan; Transposing seperti ... mirroring sepanjang diagonal (tidak berputar)
DerMike
@DerMike terima kasih telah menunjukkan. Saya tidak tahu mengapa saya melakukan kesalahan itu :) Tapi setidaknya saya bisa melihatnya bermanfaat bagi sebagian orang.
Aryan Firouzian
Berikut ini adalah kode satu-liner untuk memutar matriks: stackoverflow.com/a/58668351/741251
Nitin Jadhav
1

Saya menemukan jawaban di atas sulit dibaca atau terlalu bertele-tele, jadi saya menulis sendiri. Dan saya pikir ini adalah cara paling intuitif untuk mengimplementasikan transpos dalam aljabar linier, Anda tidak melakukan pertukaran nilai , tetapi cukup masukkan setiap elemen ke tempat yang tepat dalam matriks baru:

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
}
Chang
sumber
1

Saya pikir ini sedikit lebih mudah dibaca. Ini menggunakan Array.fromdan logika identik dengan menggunakan loop bersarang:

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

Jika Anda berurusan dengan array dengan panjang yang tidak sama, Anda perlu mengganti arr[0].lengthdengan yang lain:

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

Salman A
sumber
1

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

pank
sumber
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;
}
Samuel Reid
sumber
0

Implementasi bebas pustaka di TypeScript yang bekerja untuk bentuk matriks apa pun yang tidak akan memotong array Anda:

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
sumber
0

Satu-liner yang tidak mengubah array yang diberikan.

a[0].map((col, i) => a.map(([...row]) => row[i]))
Offpics
sumber
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]));
}
Ondrej Machala
sumber
3
Tolong jangan posting kode hanya sebagai jawaban, tetapi sertakan penjelasan apa yang kode Anda lakukan dan bagaimana memecahkan masalah pertanyaan. Jawaban dengan penjelasan umumnya berkualitas lebih tinggi dan lebih cenderung menarik upvotes.
Mark Rotteveel
0

Saya tidak menemukan jawaban yang memuaskan saya, jadi saya menulis sendiri, saya pikir mudah dimengerti dan diterapkan dan cocok untuk semua situasi.

    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;
    }
Chuan Sun
sumber
0

Karena tidak ada yang sejauh ini menyebutkan pendekatan rekursif fungsional di sini yang saya ambil. Adaptasi dari Haskell 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
sumber