Transponieren eines 2D-Arrays in JavaScript


154

Ich habe eine Reihe von Arrays, so etwas wie:

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

Ich möchte es transponieren, um das folgende Array zu erhalten:

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

Es ist nicht schwierig, dies programmgesteuert mit Schleifen zu tun:

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

Dies scheint jedoch sperrig zu sein, und ich denke, es sollte einen einfacheren Weg geben, dies zu tun. Gibt es?


4
Können Sie garantieren, dass die beiden Dimensionen immer gleich sind? 1x1, 2x2, 3x3 usw. Wofür wird der arrayLengthParameter genau verwendet? Um sicherzustellen, dass Sie nicht über eine bestimmte Anzahl von Elementen im Array hinausgehen?
Crush

8
Das hat nichts mit JQuery zu tun, ich habe den Titel geändert.
Joe

4
Überprüfen Sie dies: stackoverflow.com/questions/4492678/… . Was Sie tun, ist eine Matrix zu transponieren
stackErr

1
Ja, transponieren. Invertieren wäre völlig anders und ich bin nicht daran interessiert. Zur Zeit.
Ckersch

3
Die Diagonale von links oben nach rechts unten bleibt unverändert, sodass eine Optimierungsmöglichkeit besteht.
S Meaden

Antworten:


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

mapRuft eine bereitgestellte callbackFunktion einmal für jedes Element in einem Array der Reihe nach auf und erstellt aus den Ergebnissen ein neues Array. callbackwird nur für Indizes des Arrays aufgerufen, denen Werte zugewiesen wurden; Es wird nicht für Indizes aufgerufen, die gelöscht wurden oder denen nie Werte zugewiesen wurden.

callbackwird mit drei Argumenten aufgerufen: dem Wert des Elements, dem Index des Elements und dem Array-Objekt, das durchlaufen wird. [Quelle]


8
Dies ist eine gute Lösung. Wenn Sie sich jedoch für die Leistung interessieren, sollten Sie die ursprüngliche Lösung von OP verwenden (mit der Fehlerbehebung zur Unterstützung von M x N-Arrays mit M! = N). Überprüfen Sie diese jsPerf
Billy McKee

3
Wenn Sie es zweimal auf demselben Array verwenden, kehrt es zum ersten Mal zurück, wenn Sie erneut 90 'drehen
Olivier Pons

3
warum array[0].mapstatt array.map?
John Vandivier

4
array[0].mapweil er so oft array.mapiterieren möchte, wie es Spalten gibt, würde er iterieren, wie viele Zeilen es gibt.
Joeycozza

2
@BillyMcKee im Jahr 2019 und Chrome 75 loopssind 45% langsamer als map. Und ja, es wird korrekt transponiert, sodass der zweite Lauf die anfängliche Matrix zurückgibt.
Ebuall


39

Sie könnten underscore.js verwenden

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

3
Das war hübsch - außerdem ist mir der Unterstrich wichtiger als jQuery.
John Strickler

oder wenn Sie eine funktionale Bibliothek verwenden, wie rambdaSie es einfach tun könnenconst transpose = apply(zip)
Guy Who Knows Stuff

1
Warum ist diese Option besser als die ausgewählte Antwort?
Alex Lenail

Diese Frage ist Jahre alt und ich bin mir nicht sicher, ob es jetzt so ist. Es ist jedoch sauberer als die Originalversion der akzeptierten Antwort . Sie werden feststellen, dass es seitdem erheblich bearbeitet wurde . Es sieht so aus, als würde ES6 verwendet, das meiner Meinung nach 2013 nicht verfügbar war, als die Frage häufig gestellt wurde.
Joe

24

kürzester Weg mit lodash/ underscoreund es6:

_.zip(...matrix)

wo matrixkönnte sein:

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

Oder ohne ES6:_.zip.apply(_, matrix)
ach

9
Schließen, aber _.unzip (Matrix) ist kürzer;)
Vigrant

1
Können Sie das näher erläutern? Ich verstehe nicht, was du hier sagst. Das kurze Snipplet soll das Problem lösen? oder ist es nur ein teil oder was?
Julix

1
Meine Güte, das ist eine kurze Lösung.
Ich habe

21

Viele gute Antworten hier! Ich habe sie zu einer Antwort zusammengefasst und einen Teil des Codes für eine modernere Syntax aktualisiert:

Einzeiler inspiriert von Fawad Ghafoor und Ó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]));
}

Funktionaler Ansatzstil mit Reduktion von Andrew Tatomyr

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

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

Vanille-Ansatz

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

Vanilla In-Place-ES6-Ansatz, inspiriert von 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]];
    }
  }
}

10

Ordentlich und rein:

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

Frühere Lösungen können zu Fehlern führen, wenn ein leeres Array bereitgestellt wird.

Hier ist es als Funktion:

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

Aktualisieren. Mit dem Spread-Operator kann es noch besser geschrieben werden:

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

2
Ich weiß nicht, dass ich dieses Update besser nennen würde. Es ist klug, klar, aber das ist ein Albtraum zu lesen.
Marie

9

Sie können dies direkt vor Ort tun, indem Sie nur einen Durchgang ausführen:

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
Wenn Ihr Array kein Quadrat ist (zum Beispiel 2x8), funktioniert dies vermutlich nicht
Olivier Pons

2
Diese Lösung ändert das ursprüngliche Array. Wenn Sie das ursprüngliche Array weiterhin benötigen, entspricht diese Lösung möglicherweise nicht Ihren Wünschen. Andere Lösungen erstellen stattdessen ein neues Array.
Alex

Weiß jemand, wie dies in der ES6-Syntax gemacht wird? Ich habe es versucht, [arr[j][j],arr[i][j]] = [arr[i][j],arr[j][j]]aber es scheint nicht zu funktionieren. Vermisse ich etwas?
Nikasv

@ Nikasv du willst wahrscheinlich [arr[j][i], arr[i][j]] = [arr[i][j], arr[j][i]]. Beachten Sie, dass Sie einige arr[j][j]Begriffe haben, die sich immer auf Zellen in der Diagonale beziehen.
Algorithmic Canary

6

Nur eine weitere Variante mit Array.map. Durch die Verwendung von Indizes können Matrizen transponiert werden, wobei 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]; 
    }); 
});

Zum Transponieren müssen die Elemente nur spaltenweise und dann zeilenweise zugeordnet werden.


5

Wenn Sie die Option haben, die Ramda JS- und ES6-Syntax zu verwenden, haben Sie folgende Möglichkeit:

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
Großartig für die Verwendung von Ramda und ES6, um dies zu lösen
Ashwin Balamohan

1
Ramda hat tatsächlich eine transpose-function jetzt.
Jacob Lauritzen

5

Ein weiterer Ansatz besteht darin, das Array von außen nach innen zu iterieren und die Matrix durch Abbildung innerer Werte zu reduzieren.

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


Tatsächlich! Sie haben die Antwort von Andrew Tatomyr optimiert! (Benchmarks arbeiten zu Ihren Gunsten !;)
Scraaappy

4

Wenn die Verwendung von RamdaJS eine Option ist, kann dies in einer Zeile erreicht werden: R.transpose(myArray)


2

Sie können dies ohne Schleifen erreichen, indem Sie Folgendes verwenden.

Es sieht sehr elegant aus und erfordert keine Abhängigkeiten wie jQuery von 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);
}

Minimiert

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

Hier ist eine Demo, die ich zusammengeschmissen habe. Beachten Sie das Fehlen von Schleifen :-)

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


Warum sollten Sie es nicht ohne Schleifen tun wollen? Ohne Schleifen ist es langsam
Downgoat

5
Ist .map nicht eine Schleife? Nur eine, die du nicht siehst? Ich meine, es geht über alle Eingaben und macht Sachen damit ...
Julix

2

ES6 1liner als:

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

so wie bei Óscar, aber wie möchten Sie es lieber im Uhrzeigersinn drehen:

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

2

Bearbeiten: Diese Antwort würde die Matrix nicht transponieren, sondern drehen. Ich habe die Frage überhaupt nicht sorgfältig gelesen: D.

Drehung im und gegen den Uhrzeigersinn:

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

Beantwortet die Frage jedoch nicht; Transponieren ist wie ... Spiegeln entlang der Diagonale (nicht rotierend)
DerMike

@DerMike danke für den Hinweis. Ich weiß nicht, warum ich diesen Fehler gemacht habe :) Aber zumindest kann ich sehen, dass es für einige andere Leute nützlich war.
Aryan Firouzian

Hier ist mein Einzeiler
Nitin Jadhav

1

Ich fand die obigen Antworten entweder schwer zu lesen oder zu ausführlich, also schreibe ich selbst eine. Und ich denke, dies ist die intuitivste Methode, um die Transponierung in der linearen Algebra zu implementieren. Sie führen keinen Werteaustausch durch , sondern fügen jedes Element an der richtigen Stelle in die neue Matrix ein:

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

Ich denke, das ist etwas besser lesbar. Es verwendet Array.fromund Logik ist identisch mit der Verwendung von verschachtelten Schleifen:

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

Wenn Sie mit Arrays ungleicher Länge arbeiten, müssen Sie diese durch arr[0].lengthetwas anderes ersetzen :

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

Eine bibliotheksfreie Implementierung in TypeScript, die für jede Matrixform funktioniert, die Ihre Arrays nicht abschneidet:

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

Einzeiler, der das angegebene Array nicht ändert.

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
Bitte posten Sie nicht nur Code als Antwort, sondern geben Sie eine Erklärung an, was Ihr Code tut und wie er das Problem der Frage löst. Antworten mit einer Erklärung sind im Allgemeinen von höherer Qualität und ziehen eher positive Stimmen an.
Mark Rotteveel

0

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


0

Ich habe keine Antwort gefunden, die mich zufriedenstellte, also habe ich selbst eine geschrieben. Ich denke, es ist leicht zu verstehen und umzusetzen und für alle Situationen geeignet.

    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

Da bisher niemand einen funktionalen rekursiven Ansatz erwähnt hat, ist dies meine Meinung. Eine Adaption von 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
}

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.