Transposer un tableau 2D en JavaScript

154

J'ai un tableau de tableaux, quelque chose comme:

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

Je voudrais le transposer pour obtenir le tableau suivant:

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

Il n'est pas difficile de le faire par programmation en utilisant des boucles:

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

Ceci, cependant, semble encombrant, et j'estime qu'il devrait y avoir un moyen plus simple de le faire. Y a-t-il?

Ckersch
la source
4
Pouvez-vous garantir que les deux dimensions seront toujours les mêmes? 1x1, 2x2, 3x3, etc. À quoi arrayLengthsert exactement le paramètre? Pour vous assurer de ne pas dépasser un certain nombre d'éléments dans le tableau?
écraser le
8
Cela n'a rien à voir avec JQuery, j'ai changé le titre.
Joe le
4
Vérifiez ceci: stackoverflow.com/questions/4492678/… . Ce que vous faites est de transposer une matrice
stackErr
1
Oui, transposant. L'inversion serait complètement différente et cela ne m'intéresse pas. Pour l'instant.
ckersch
3
La diagonale en haut à gauche en bas à droite est inchangée, il y a donc une opportunité d'optimisation.
S Meaden

Réponses:

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

mapappelle une callbackfonction fournie une fois pour chaque élément d'un tableau, dans l'ordre, et construit un nouveau tableau à partir des résultats. callbackest appelée uniquement pour les index du tableau qui ont des valeurs assignées; il n'est pas appelé pour les index qui ont été supprimés ou qui n'ont jamais reçu de valeur.

callbackest appelée avec trois arguments: la valeur de l'élément, l'index de l'élément et l'objet Array parcouru. [la source]

Fawad Ghafoor
la source
8
C'est une bonne solution. Cependant, si vous vous souciez des performances, vous devez utiliser la solution originale d'OP (avec le correctif de bogue pour prendre en charge les tableaux M x N où M! = N). check this jsPerf
Billy McKee
3
Si vous l'utilisez deux fois sur le même tableau, il revient au premier au lieu de tourner à nouveau à 90 '
Olivier Pons
3
pourquoi array[0].mapau lieu de array.map?
John Vandivier
4
array[0].mapparce qu'il veut itérer autant de fois qu'il y a des colonnes, array.mapil itérerait combien de lignes il y a.
joeycozza
2
@BillyMcKee en 2019 et Chrome 75 loopssont 45% plus lents que map. Et oui, il se transpose correctement, donc la deuxième exécution renvoie la matrice initiale.
Ebuall
41

voici mon implémentation dans un navigateur moderne (sans dépendance):

transpose = m => m[0].map((x,i) => m.map(x => x[i]))
Mahdi Jadaliha
la source
Si vous l'utilisez deux fois sur le même tableau, il revient au premier au lieu de tourner à nouveau à 90 '
Olivier Pons
26
La transposition d'une matrice de transposition est la matrice originale, reportez-vous à math.nyu.edu/~neylon/linalgfall04/project1/dj/proptranspose.htm
Mahdi Jadaliha
39

Vous pouvez utiliser underscore.js

_.zip.apply(_, [[1,2,3], [1,2,3], [1,2,3]])
Joe
la source
3
C'était joli - aussi, le soulignement est plus nécessaire pour moi que jQuery.
John Strickler
ou si vous utilisez une bibliothèque fonctionnelle comme rambdavous pouvez le faireconst transpose = apply(zip)
Guy Who Knows Stuff
1
Pourquoi cette option serait-elle meilleure que la réponse choisie?
Alex Lenail
Cette question a des années et je ne suis pas sûr qu'elle le soit maintenant. C'est, cependant, plus propre que la version originale de la réponse acceptée . Vous remarquerez qu'il a été considérablement modifié depuis. On dirait qu'il utilise ES6, qui, je ne pense pas, était disponible en 2013 lorsque la question a été largement posée.
Joe
25

chemin le plus court avec lodash/ underscoreet es6:

_.zip(...matrix)

matrixpourrait être:

const matrix = [[1,2,3], [1,2,3], [1,2,3]];
Marcel
la source
Ou, sans ES6:_.zip.apply(_, matrix)
ach
9
Fermer mais _.unzip (matrice) est plus court;)
Vigrant
1
Pouvez-vous développer davantage sur ce sujet? Je ne comprends pas ce que vous dites ici. Ce court extrait est censé résoudre le problème? ou est-ce juste une partie ou quoi?
Julix
1
mon Dieu, c'est une solution courte. vient d'apprendre sur l'opérateur ... - utilisé pour décomposer une chaîne en un tableau de lettres ... merci pour la réponse
Julix
22

Beaucoup de bonnes réponses ici! Je les ai regroupés en une seule réponse et mis à jour une partie du code pour une syntaxe plus moderne:

One-liners inspirés par Fawad Ghafoor et Ó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]));
}

Style d'approche fonctionnelle avec réduire par Andrew Tatomyr

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

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

Approche vanille

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

Approche ES6 en place vanille inspirée d' 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
la source
11

Propre et pur:

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

Les solutions précédentes peuvent entraîner un échec dans le cas où un tableau vide est fourni.

Le voici en fonction:

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

Mettre à jour. Il peut être écrit encore mieux avec l'opérateur de propagation:

const transpose = matrix => matrix.reduce(
    ($, row) => row.map((_, i) => [...($[i] || []), row[i]]), 
    []
)
Andrew Tatomyr
la source
2
Je ne sais pas si j'appellerais cette mise à jour mieux. C'est malin, bien sûr, mais c'est un cauchemar à lire.
Marie le
9

Vous pouvez le faire sur place en ne faisant qu'un seul passage:

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
la source
1
si votre tableau n'est pas un carré (par exemple 2x8) cela ne fonctionne pas je suppose
Olivier Pons
2
Cette solution modifie le tableau d'origine. Si vous avez toujours besoin de la baie d'origine, cette solution n'est peut-être pas celle que vous souhaitez. D'autres solutions créent à la place un nouveau tableau.
Alex
Quelqu'un sait-il comment cela se fait dans la syntaxe ES6? J'ai essayé [arr[j][j],arr[i][j]] = [arr[i][j],arr[j][j]]mais cela ne semble pas fonctionner, est-ce que je manque quelque chose?
Nikasv le
@Nikasv vous voulez probablement [arr[j][i], arr[i][j]] = [arr[i][j], arr[j][i]]. Notez que vous avez des arr[j][j]termes qui feront toujours référence à des cellules en diagonale.
Algorithmic Canary le
6

Juste une autre variante utilisant Array.map. L'utilisation d'index permet de transposer des matrices où 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]; 
    }); 
});

Tout ce qu'il y a à transposer est de mapper les éléments d'abord par colonne, puis par ligne.

Óscar Gómez Alcañiz
la source
5

Si vous avez la possibilité d'utiliser la syntaxe Ramda JS et ES6, voici une autre façon de le faire:

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
la source
2
Génial d'avoir utilisé à la fois Ramda et ES6 pour résoudre ce problème
Ashwin Balamohan
1
Ramda a en fait une transposefonction maintenant.
Jacob Lauritzen
5

Une autre approche en itérant le tableau de l'extérieur vers l'intérieur et en réduisant la matrice en mappant les valeurs internes.

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
la source
En effet! Vous avez optimisé la réponse @Andrew Tatomyr! (les repères fonctionnent en votre faveur!;)
scraaappy
4

Si l'utilisation de RamdaJS est une option, cela peut être réalisé en une seule ligne: R.transpose(myArray)

Rafael Rozon
la source
2

Vous pouvez y parvenir sans boucles en utilisant ce qui suit.

Il a l'air très élégant et ne nécessite aucune dépendance telle que jQuery de 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);
}

Minifié

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

Voici une démo que j'ai lancée ensemble. Remarquez le manque de boucles :-)

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

M. Polywhirl
la source
Pourquoi ne voudriez-vous pas le faire sans boucles? Sans boucles, c'est lent
Downgoat
5
Le .map n'est-il pas une boucle? Juste un que vous ne voyez pas? Je veux dire qu'il passe toutes les entrées et fait des choses ...
Julix
2

ES6 1liners comme:

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

comme celui d'Óscar, mais comme vous préférez le faire tourner dans le sens des aiguilles d'une montre:

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

Edit: Cette réponse ne transposerait pas la matrice, mais la ferait pivoter. Je n'ai pas lu la question attentivement en premier lieu: D

rotation horaire et antihoraire:

    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;
    }
Aryen Firouzian
la source
Ne répond pas à la question, cependant; La transposition est comme ... la mise en miroir le long de la diagonale (sans rotation)
DerMike
@DerMike merci de l'avoir signalé. Je ne sais pas pourquoi j'ai fait cette erreur :) Mais au moins je peux voir que cela a été utile pour d'autres personnes.
Aryan Firouzian
Voici mon code one-liner pour faire pivoter une matrice: stackoverflow.com/a/58668351/741251
Nitin Jadhav
1

J'ai trouvé les réponses ci-dessus difficiles à lire ou trop verbeuses, alors j'en écris une moi-même. Et je pense que c'est le moyen le plus intuitif d'implémenter la transposition en algèbre linéaire, vous ne faites pas d' échange de valeur , mais insérez simplement chaque élément au bon endroit dans la nouvelle 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
}
Chang
la source
1

Je pense que c'est un peu plus lisible. Il utilise Array.fromet la logique est identique à l'utilisation de boucles imbriquées:

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

Si vous avez affaire à des tableaux de longueur inégale, vous devez les remplacer arr[0].lengthpar autre chose:

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
la source
1

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

piquer
la source
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
la source
0

Une implémentation sans bibliothèque dans TypeScript qui fonctionne pour toute forme de matrice qui ne tronquera pas vos tableaux:

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
la source
0

Une seule ligne qui ne change pas la matrice donnée.

a[0].map((col, i) => a.map(([...row]) => row[i]))
Offpics
la source
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
la source
3
Veuillez ne pas publier uniquement le code comme réponse, mais inclure une explication sur ce que fait votre code et comment il résout le problème de la question. Les réponses avec une explication sont généralement de meilleure qualité et plus susceptibles d'attirer des votes positifs.
Mark Rotteveel
0

Je n'ai pas trouvé de réponse qui me satisfasse, alors j'en ai rédigé une moi-même, je pense qu'elle est facile à comprendre et à mettre en œuvre et adaptée à toutes les situations.

    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
la source
0

Puisque personne n'a jusqu'à présent mentionné une approche fonctionnelle récursive, voici mon avis. Une adaptation de 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
la source