Transposición de una matriz 2D en JavaScript

154

Tengo una serie de matrices, algo así como:

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

Me gustaría transponerlo para obtener la siguiente matriz:

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

No es difícil hacerlo mediante programación usando bucles:

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

Esto, sin embargo, parece voluminoso, y siento que debería haber una manera más fácil de hacerlo. ¿Esta ahí?

ckersch
fuente
44
¿Pueden garantizar que las dos dimensiones siempre serán las mismas? 1x1, 2x2, 3x3, etc. ¿Para qué se arrayLengthusa exactamente el parámetro? ¿Para asegurarse de no ir más allá de un cierto número de elementos en la matriz?
aplastar
8
Esto no tiene nada que ver con JQuery, cambié el título.
Joe
44
Mira esto: stackoverflow.com/questions/4492678/… . Lo que está haciendo es la transposición de una matriz
stackErr
1
Sí, transponiendo. La inversión sería completamente diferente y no me interesa. Por ahora.
ckersch
3
La diagonal superior izquierda a inferior derecha no cambia, por lo que hay una oportunidad de optimización.
S Meaden

Respuestas:

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

mapllama a una callbackfunción proporcionada una vez para cada elemento de una matriz, en orden, y construye una nueva matriz a partir de los resultados. callbackse invoca solo para índices de la matriz que tienen valores asignados; no se invoca para índices que se han eliminado o a los que nunca se les han asignado valores.

callbackse invoca con tres argumentos: el valor del elemento, el índice del elemento y el objeto Array que se atraviesa. [fuente]

Fawad Ghafoor
fuente
8
Esta es una buena solución. Sin embargo, si le importa el rendimiento, debe usar la solución original de OP (con la corrección de errores para admitir matrices M x N donde M! = N). mira esto jsPerf
Billy McKee
3
Si lo usa dos veces en la misma matriz, vuelve a la primera en lugar de girar 90 'nuevamente
Olivier Pons
3
¿Por qué en array[0].maplugar de array.map?
John Vandivier
44
array[0].mapporque quiere iterar sin importar cuántas veces haya columnas, array.mapiterará cuántas filas hay.
joeycozza
2
@BillyMcKee en el año 2019 y Chrome 75 loopsson un 45% más lentos que map. Y sí, se transpone correctamente, por lo que la segunda ejecución devuelve la matriz inicial.
Ebuall
41

Aquí está mi implementación en el navegador moderno (sin dependencia):

transpose = m => m[0].map((x,i) => m.map(x => x[i]))
Mahdi Jadaliha
fuente
Si lo usa dos veces en la misma matriz, vuelve a la primera en lugar de girar 90 'nuevamente
Olivier Pons
26
La transposición de una matriz de transposición es la matriz original, consulte math.nyu.edu/~neylon/linalgfall04/project1/dj/proptranspose.htm
Mahdi Jadaliha
39

Podrías usar underscore.js

_.zip.apply(_, [[1,2,3], [1,2,3], [1,2,3]])
Joe
fuente
3
Eso fue bonito, además, el subrayado es más necesario para mí que jQuery.
John Strickler
o si está utilizando una biblioteca funcional como rambdapuede hacerloconst transpose = apply(zip)
Guy Who Knows Stuff
1
¿Por qué esta opción sería mejor que la respuesta seleccionada?
Alex Lenail
Esta pregunta tiene años y no estoy seguro de que lo sea, ahora. Sin embargo, es más limpio que la versión original de la respuesta aceptada . Notarás que ha sido editado sustancialmente desde entonces. Parece que usa ES6, que no creo que estuviera disponible en 2013 cuando se hizo la pregunta ampliamente.
Joe
25

camino más corto con lodash/ underscorey es6:

_.zip(...matrix)

donde matrixpodría estar:

const matrix = [[1,2,3], [1,2,3], [1,2,3]];
marcel
fuente
O, sin ES6:_.zip.apply(_, matrix)
ach
9
Cerrar pero _.unzip (matriz) es más corto;)
Vigrant
1
¿Puedes ampliar esto? No entiendo lo que estás diciendo aquí. ¿Se supone que ese pequeño snipplet resuelve el problema? o es solo una parte o qué?
Julix
1
Dios mío, esa es una solución corta. acabo de enterarme del ... operador: lo usé para dividir una cadena en una serie de letras ... gracias por la respuesta
Julix
22

Muchas buenas respuestas aquí! Los consolidé en una respuesta y actualicé parte del código para una sintaxis más moderna:

One-liners inspirados en Fawad Ghafoor y Ó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]));
}

Estilo de enfoque funcional con reducción por Andrew Tatomyr

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

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

Enfoque de vainilla

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

Enfoque de vainilla en el lugar ES6 inspirado en 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
fuente
11

Aseado y 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]]

Las soluciones anteriores pueden provocar fallas en caso de que se proporcione una matriz vacía.

Aquí está como una función:

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

Actualizar. Se puede escribir aún mejor con el operador de propagación:

const transpose = matrix => matrix.reduce(
    ($, row) => row.map((_, i) => [...($[i] || []), row[i]]), 
    []
)
Andrew Tatomyr
fuente
2
No sé si llamaría a esa actualización mejor. Es inteligente, claro, pero es una pesadilla de leer.
Marie
9

Puede hacerlo en el lugar haciendo solo un pase:

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
fuente
1
si su matriz no es un cuadrado (por ejemplo, 2x8), esto no funciona, supongo
Olivier Pons
2
Esta solución cambia la matriz original. Si aún necesita la matriz original, entonces esta solución podría no ser la que desea. Otras soluciones crean una nueva matriz en su lugar.
Alex
¿Alguien sabe cómo se hace esto en la sintaxis de ES6? Lo intenté [arr[j][j],arr[i][j]] = [arr[i][j],arr[j][j]]pero no parece funcionar, ¿me estoy perdiendo algo?
Nikasv
@Nikasv que probablemente quieras [arr[j][i], arr[i][j]] = [arr[i][j], arr[j][i]]. Tenga en cuenta que tiene algunos arr[j][j]términos que siempre se referirán a las celdas en diagonal.
Algorithmic Canary
6

Solo otra variación usando Array.map. El uso de índices permite transponer matrices donde 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]; 
    }); 
});

Todo lo que hay que hacer para transponer es mapear los elementos primero en columna, y luego por fila.

Óscar Gómez Alcañiz
fuente
5

Si tiene la opción de usar la sintaxis de Ramda JS y ES6, esta es otra forma de hacerlo:

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
fuente
2
Impresionante por usar tanto Ramda como ES6 para resolver esto
Ashwin Balamohan
1
Ramda en realidad tiene una transposefunción ahora.
Jacob Lauritzen
5

Otro enfoque al iterar la matriz de afuera hacia adentro y reducir la matriz al mapear los valores internos.

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
fuente
¡En efecto! ¡Has optimizado la respuesta de @Andrew Tatomyr! (¡los puntos de referencia funcionan a su favor!)
scraaappy
4

Si usar RamdaJS es una opción, esto se puede lograr en una línea: R.transpose(myArray)

Rafael Rozon
fuente
2

Puede lograr esto sin bucles utilizando lo siguiente.

Se ve muy elegante y no requiere dependencias como 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);
}

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

Aquí hay una demostración que hice. Observe la falta de bucles :-)

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

Mr. Polywhirl
fuente
¿Por qué no querrías hacerlo sin bucles? Sin bucles es lento
Downgoat
55
¿No es .map un bucle? ¿Solo uno que no ves? Quiero decir que está revisando cada una de las entradas y le hace cosas ...
Julix
2

ES6 1liners como:

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

igual que el de Óscar, pero como prefieres rotarlo en sentido horario:

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

Editar: esta respuesta no transpondrá la matriz, sino que la rotará. No leí la pregunta cuidadosamente en primer lugar: D

rotación en sentido horario y antihorario:

    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;
    }
Aria Firouzian
fuente
Sin embargo, no responde la pregunta; La transposición es como ... reflejar a lo largo de la diagonal (no girando)
DerMike
@DerMike gracias por señalar. No sé por qué cometí ese error :) Pero al menos puedo ver que ha sido útil para otras personas.
Aryan Firouzian
Aquí está mi código de una línea para rotar una matriz: stackoverflow.com/a/58668351/741251
Nitin Jadhav
1

Encontré las respuestas anteriores difíciles de leer o demasiado detalladas, así que escribo una. Y creo que esta es la forma más intuitiva de implementar la transposición en álgebra lineal, no se hace intercambio de valores , sino que simplemente se inserta cada elemento en el lugar correcto en la nueva matriz:

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
fuente
1

Creo que esto es un poco más legible. Utiliza Array.fromy la lógica es idéntica al uso de bucles anidados:

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 se trata de matrices de longitud desigual, debe reemplazar arr[0].lengthpor otra cosa:

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
fuente
1

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

pank
fuente
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
fuente
0

Una implementación sin biblioteca en TypeScript que funciona para cualquier forma de matriz que no truncará sus matrices:

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

Una línea que no cambia la matriz dada.

a[0].map((col, i) => a.map(([...row]) => row[i]))
Offpics
fuente
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
fuente
3
No publique solo el código como respuesta, sino que incluya una explicación de lo que hace su código y cómo resuelve el problema de la pregunta. Las respuestas con una explicación son generalmente de mayor calidad y es más probable que atraigan votos positivos.
Mark Rotteveel
0

No encontré una respuesta que me satisficiera, así que escribí una, creo que es fácil de entender e implementar y adecuada para todas las situaciones.

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

Como nadie hasta ahora mencionó un enfoque recursivo funcional aquí es mi opinión. Una adaptación de Haskell's 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
fuente