Trasposizione di un array 2D in JavaScript


154

Ho una serie di array, qualcosa del tipo:

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

Vorrei trasporlo per ottenere il seguente array:

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

Non è difficile farlo a livello di programmazione usando i 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;
}

Questo, tuttavia, sembra ingombrante e sento che dovrebbe esserci un modo più semplice per farlo. È lì?


4
Potete garantire che le due dimensioni saranno sempre le stesse? 1x1, 2x2, 3x3, ecc. A cosa arrayLengthserve esattamente il parametro? Per assicurarsi di non andare oltre un certo numero di elementi nell'array?
schiaccia

8
Questo non ha nulla a che fare con JQuery, ho cambiato il titolo.
Joe

4
Dai un'occhiata a: stackoverflow.com/questions/4492678/… . Quello che stai facendo è trasporre una matrice
stackErr

1
Sì, trasposizione. L'inversione sarebbe completamente diversa e non mi interessa. Per adesso.
Ckersch,

3
La diagonale in alto a sinistra in basso a destra è invariata, quindi esiste un'opportunità di ottimizzazione.
S Meaden,

Risposte:


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

map chiama un fornito callback funzione una volta per ogni elemento in un array, in ordine, e costruisce un nuovo array dai risultati. callbackviene invocato solo per gli indici dell'array a cui sono stati assegnati valori; non viene invocato per gli indici che sono stati eliminati o ai quali non sono mai stati assegnati valori.

callbackviene invocato con tre argomenti: il valore dell'elemento, l'indice dell'elemento e l'oggetto Array che viene attraversato. [fonte]


8
Questa è una buona soluzione Tuttavia, se ti preoccupi delle prestazioni, dovresti usare la soluzione originale di OP (con la correzione di bug per supportare le matrici M x N dove M! = N). controlla questo jsPerf
Billy McKee il

3
Se lo usi due volte sullo stesso array, ritorna alla prima intesa a ruotare di nuovo di 90 '
Olivier Pons

3
perché array[0].mapinvece di array.map?
John Vandivier,

4
array[0].mapperché vuole iterare per quante volte ci siano colonne, array.mapripeterebbe quante righe ci sono.
joeycozza,

2
@BillyMcKee nell'anno 2019 e Chrome 75 loopssono più lenti del 45% rispetto a map. E sì, traspone correttamente, quindi la seconda esecuzione restituisce la matrice iniziale.
Ebuall,


39

È possibile utilizzare underscore.js

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

3
È stato carino - inoltre, il carattere di sottolineatura è più necessario per me di quanto lo sia jQuery.
John Strickler,

o se stai usando una libreria funzionale come rambdapuoi fareconst transpose = apply(zip)
Guy Who Knows Stuff,

1
Perché questa opzione sarebbe migliore della risposta selezionata?
Alex Lenail,

Questa domanda ha anni e non sono sicuro che lo sia, ora. È, tuttavia, più pulito della versione originale della risposta accettata . Noterai che è stato modificato sostanzialmente da allora. Sembra che usi ES6, che non credo fosse disponibile nel 2013 quando la domanda è stata posta ampiamente.
Joe,

25

modo più breve con lodash/ underscoree es6:

_.zip(...matrix)

dove matrixpotrebbe essere:

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

Oppure, senza ES6:_.zip.apply(_, matrix)
ach

9
Chiudi ma _.unzip (matrice) è più corto;)
Vigrant

1
Puoi approfondire su questo? Non capisco quello che stai dicendo qui. Quel frammento breve dovrebbe risolvere il problema? o è solo una parte o cosa?
Julix,

1
mio Dio, questa è una soluzione breve. ho appena saputo dell'operatore ... l'ho usato per scomporre una stringa in una serie di lettere ... grazie per la risposta
Julix

22

Molte buone risposte qui! Li ho consolidati in una risposta e ho aggiornato parte del codice per una sintassi più moderna:

One-liners ispirati a Fawad Ghafoor e Ó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]));
}

Stile di approccio funzionale con riduzione di Andrew Tatomyr

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

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

Approccio alla vaniglia

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

Approccio ES6 sul posto alla vaniglia ispirato a 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]];
    }
  }
}

11

Pulito e puro:

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

Le soluzioni precedenti potrebbero causare errori nel caso in cui venga fornita una matrice vuota.

Ecco come una funzione:

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

Aggiornare. Può essere scritto ancora meglio con l'operatore spread:

const transpose = matrix => matrix.reduce(
    ($, row) => row.map((_, i) => [...($[i] || []), row[i]]), 
    []
)

2
Non so che chiamerei questo aggiornamento meglio. È intelligente, certo, ma è un incubo da leggere.
Marie,

9

Puoi farlo sul posto facendo solo un passaggio:

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
se il tuo array non è un quadrato (ad esempio 2x8), questo non funziona immagino
Olivier Pons

2
Questa soluzione modifica l'array originale. Se hai ancora bisogno dell'array originale, questa soluzione potrebbe non essere quella che desideri. Altre soluzioni creano invece un nuovo array.
Alex,

Qualcuno sa come questo viene fatto nella sintassi ES6? Ci ho provato [arr[j][j],arr[i][j]] = [arr[i][j],arr[j][j]]ma non sembra funzionare, mi sto perdendo qualcosa?
Nikasv,

@Nikasv probabilmente lo desideri [arr[j][i], arr[i][j]] = [arr[i][j], arr[j][i]]. Nota che hai alcuni arr[j][j]termini che faranno sempre riferimento alle celle sulla diagonale.
Algorithmic Canary,

6

Solo un'altra variante usando Array.map. L'uso degli indici consente di trasporre matrici in cui 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]; 
    }); 
});

Tutto quello che c'è da trasporre è mappare prima gli elementi colonna e poi per riga.


5

Se hai la possibilità di utilizzare la sintassi Ramda JS ed ES6, ecco un altro modo per farlo:

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
Impressionante per aver usato sia Ramda che ES6 per risolvere questo problema
Ashwin Balamohan,

1
Ramda in realtà ha una transpose-funzione ora.
Jacob Lauritzen,

5

Un altro approccio iterando l'array dall'esterno all'interno e riducendo la matrice mappando i valori interni.

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


Infatti! Hai ottimizzato la risposta di @Andrew Tatomyr! (i benchmark funzionano a tuo favore!;)
scraaappy,


2

È possibile ottenere ciò senza loop utilizzando quanto segue.

Sembra molto elegante e non richiede dipendenze come jQuery di 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);
}

minified

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

Ecco una demo che ho lanciato insieme. Notare la mancanza di anelli :-)

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


Perché non vorresti farlo senza loop? Senza loop è lento
Downgoat l'

5
.Map non è un loop? Solo uno che non vedi? Voglio dire, controlla tutti gli input e fa cose ...
Julix,

2

ES6 1liners come:

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

così come quello di Óscar, ma come preferiresti ruotarlo in senso orario:

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

2

Modifica: questa risposta non traspone la matrice, ma la ruota. Non ho letto attentamente la domanda in primo luogo: D

rotazione oraria e antioraria:

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

Non risponde alla domanda, comunque; Trasporre è come ... rispecchiarsi lungo la diagonale (non ruotare)
DerMike,

@DerMike grazie per averci segnalato. Non so perché ho fatto quell'errore :) Ma almeno posso vedere che è stato utile per altre persone.
Aryan Firouzian,

Ecco il mio codice di una riga per ruotare una matrice: stackoverflow.com/a/58668351/741251
Nitin Jadhav

1

Ho trovato le risposte di cui sopra difficili da leggere o troppo dettagliate, quindi ne scrivo una io. E penso che questo sia il modo più intuitivo per implementare la trasposizione in algebra lineare, non si fa lo scambio di valori , ma si inserisce semplicemente ogni elemento nel posto giusto nella nuova matrice:

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

Penso che questo sia leggermente più leggibile. Utilizza Array.frome la logica è identica all'utilizzo di cicli nidificati:

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

Se hai a che fare con array di lunghezza arr[0].lengthdiversa, devi sostituirlo con qualcos'altro:

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

Un'implementazione senza libreria in TypeScript che funziona con qualsiasi forma di matrice che non tronca le matrici:

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

One-liner che non cambia l'array dato.

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
Per favore, non pubblicare solo il codice come risposta, ma includi una spiegazione su cosa fa il tuo codice e su come risolve il problema della domanda. Le risposte con una spiegazione sono generalmente di qualità superiore e hanno maggiori probabilità di attrarre voti positivi.
Mark Rotteveel,

0

Non ho trovato una risposta che mi soddisfacesse, quindi ne ho scritta una anch'io, penso sia facile da capire e da attuare e adatta a tutte le situazioni.

    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

Dal momento che nessuno finora ha menzionato un approccio ricorsivo funzionale qui è la mia opinione. Un adattamento di 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
}

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.