Combinar / aplanar una matriz de matrices

1105

Tengo una matriz de JavaScript como:

[["$6"], ["$12"], ["$25"], ["$25"], ["$18"], ["$22"], ["$10"]]

¿Cómo haría para fusionar las matrices internas separadas en una como:

["$6", "$12", "$25", ...]
Andy
fuente
gist.github.com/Nishchit14/4c6a7349b3c778f7f97b912629a9f228 Este enlace describe ES5 y ES6 flatten
Nishchit Dhanani
18
Todas las soluciones que usan reduce+ concatson O ((N ^ 2) / 2) donde como respuesta aceptada (solo una llamada a concat) sería como máximo O (N * 2) en un navegador defectuoso y O (N) en un bueno Además, la solución de Denys está optimizada para la pregunta real y hasta 2 veces más rápido que la única concat. Para la reducegente, es divertido sentirse bien escribiendo un código pequeño, pero por ejemplo, si la matriz tuviera 1000 subarreglos de un elemento, todas las soluciones reduce + concat estarían haciendo 500500 operaciones, mientras que el concat único o el bucle simple harían 1000 operaciones.
gman
12
Operador simplemente extendido por el usuario[].concat(...array)
Oleg Dater
@gman, ¿cómo es reducir + concat solución O ((N ^ 2) / 2)? stackoverflow.com/questions/52752666/… menciona una complejidad diferente.
Usman
44
Con los últimos navegadores compatibles con ES2019 : ¿ array.flat(Infinity)dónde Infinityestá la profundidad máxima para aplanar?
Timothy Gu

Respuestas:

1860

Puede usar concatpara fusionar matrices:

var arrays = [
  ["$6"],
  ["$12"],
  ["$25"],
  ["$25"],
  ["$18"],
  ["$22"],
  ["$10"]
];
var merged = [].concat.apply([], arrays);

console.log(merged);

Usar el applymétodo de concatsolo tomará el segundo parámetro como una matriz, por lo que la última línea es idéntica a esta:

var merged2 = [].concat(["$6"], ["$12"], ["$25"], ["$25"], ["$18"], ["$22"], ["$10"]);

También existe el Array.prototype.flat()método (introducido en ES2019) que podría usar para aplanar las matrices, aunque solo está disponible en Node.js a partir de la versión 11, y no está disponible en absoluto en Internet Explorer .

const arrays = [
      ["$6"],
      ["$12"],
      ["$25"],
      ["$25"],
      ["$18"],
      ["$22"],
      ["$10"]
    ];
const merge3 = arrays.flat(1); //The depth level specifying how deep a nested array structure should be flattened. Defaults to 1.
console.log(merge3);
    

Gumbo
fuente
10
Tenga en cuenta que concatno modifica la matriz de origen, por lo que la mergedmatriz permanecerá vacía después de la llamada a concat. Mejor decir algo como:merged = merged.concat.apply(merged, arrays);
Nate
65
var merged = [].concat.apply([], arrays);parece funcionar bien para obtenerlo en una línea. editar: como ya muestra la respuesta de Nikita.
Sean
44
O Array.prototype.concat.apply([], arrays).
danhbear
30
Nota: esta respuesta solo aplana un nivel de profundidad. Para un aplanamiento recursivo, vea la respuesta de @Trindaz.
Phrogz
209
Además del comentario de @ Sean: la sintaxis de ES6 hace que esto sea muy conciso:var merged = [].concat(...arrays)
Sethi
505

Aquí hay una función corta que usa algunos de los métodos de matriz JavaScript más nuevos para aplanar una matriz n-dimensional.

function flatten(arr) {
  return arr.reduce(function (flat, toFlatten) {
    return flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten);
  }, []);
}

Uso:

flatten([[1, 2, 3], [4, 5]]); // [1, 2, 3, 4, 5]
flatten([[[1, [1.1]], 2, 3], [4, 5]]); // [1, 1.1, 2, 3, 4, 5]
Noah Freitas
fuente
17
Me gusta este enfoque. Es mucho más general y admite matrices anidadas
alfredocambera
10
¿Cuál es el perfil de uso de memoria para esta solución? Parece que crea muchas matrices intermedias durante la recursión de la cola ...
JBRWilkinson
77
@ayjay, es el valor inicial del acumulador para la función de reducción, qué mdn llama el valor inicial. En este caso, es el valor de flaten la primera llamada a la función anónima pasada reduce. Si no se especifica, entonces la primera llamada para reducevincular el primer valor fuera de la matriz flat, lo que eventualmente resultaría 1vinculado a flatambos ejemplos. 1.concatNo es una función.
Noah Freitas
19
O en una forma más corta y sexy: const flatten = (arr) => arr.reduce((flat, next) => flat.concat(next), []);
Tsvetomir Tsonev
12
Refiriéndose a las soluciones de @TsvetomirTsonev y Noah para la anidación arbitraria:const flatten = (arr) => arr.reduce((flat, next) => flat.concat(Array.isArray(next) ? flatten(next) : next), []);
Será el
314

Hay un método confusamente oculto, que construye una nueva matriz sin mutar la original:

var oldArray = [[1],[2,3],[4]];
var newArray = Array.prototype.concat.apply([], oldArray);
console.log(newArray); // [ 1, 2, 3, 4 ]

Nikita Volkov
fuente
11
En CoffeeScript esto es[].concat([[1],[2,3],[4]]...)
amoebe
8
@amoebe su respuesta da [[1],[2,3],[4]]como resultado. La solución que da @Nikita es correcta tanto para CoffeeScript como para JS.
Ryan Kennedy
55
Ahh, encontré tu error. Tienes un par adicional de corchetes en tu notación, debería ser [].concat([1],[2,3],[4],...).
Ryan Kennedy
21
Creo que también encontré tu error. El ...son de código real, no algunos puntos suspensivos.
amoebe
3
Usar una función de biblioteca no significa que haya un "desorden" menos imperativo. Solo que el desastre está escondido dentro de una biblioteca. Obviamente, usar una biblioteca es mejor que rodar su propia función, pero afirmar que esta técnica reduce el "desastre imperativo" es bastante tonto.
paldepind
204

Se puede hacer mejor con la función de reducción de JavaScript.

var arrays = [["$6"], ["$12"], ["$25"], ["$25"], ["$18"], ["$22"], ["$10"], ["$0"], ["$15"],["$3"], ["$75"], ["$5"], ["$100"], ["$7"], ["$3"], ["$75"], ["$5"]];

arrays = arrays.reduce(function(a, b){
     return a.concat(b);
}, []);

O con ES2015:

arrays = arrays.reduce((a, b) => a.concat(b), []);

js-violín

Documentos de Mozilla

usuario2668376
fuente
1
@JohnS Realmente reduce los feeds en concat un solo valor (matriz) por turno. ¿Prueba? saque reduce () y vea si funciona. reducir () aquí hay una alternativa a Function.prototype.apply ()
André Werlang
3
Hubiera usado reducir también, pero una cosa interesante que aprendí de este fragmento es que la mayoría de las veces no es necesario pasar un Valor inicial :)
José F. Romaniello
2
El problema con reduce es que la matriz no puede estar vacía, por lo que deberá agregar una validación adicional.
calbertts
44
@calbertts Simplemente pase el valor inicial []y no se necesitan más validaciones.
8
Como usa ES6, también puede usar el operador spread como literal de matriz . arrays.reduce((flatten, arr) => [...flatten, ...arr])
Putzi San
109

Hay un nuevo método nativo llamado plano para hacer esto exactamente.

(A fines de 2019, flatahora se publica en el estándar ECMA 2019, y core-js@3(la biblioteca de babel) lo incluye en su biblioteca de polyfill )

const arr1 = [1, 2, [3, 4]];
arr1.flat(); 
// [1, 2, 3, 4]

const arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat();
// [1, 2, 3, 4, [5, 6]]

// Flatten 2 levels deep
const arr3 = [2, 2, 5, [5, [5, [6]], 7]];
arr3.flat(2);
// [2, 2, 5, 5, 5, [6], 7];

// Flatten all levels
const arr4 = [2, 2, 5, [5, [5, [6]], 7]];
arr4.flat(Infinity);
// [2, 2, 5, 5, 5, 6, 7];
Alister
fuente
44
Es una pena que esto ni siquiera esté en la primera página de respuestas. Esta característica está disponible en Chrome 69 y Firefox 62 (y en el Nodo 11 para aquellos que trabajan en el backend)
Matt M.
2
-1; no, esto no es parte de ECMAScript 2018 . Todavía es solo una propuesta que no ha llegado a ninguna especificación ECMAScript.
Mark Amery el
1
Creo que ahora podemos considerar esto ... porque ahora es parte del estándar (2019) ... ¿podemos volver a visitar el rendimiento una vez?
Yuvaraj
Parece que todavía no es compatible con ningún navegador de Microsoft (al menos en el momento en que escribo este comentario)
Laurent S.
78

La mayoría de las respuestas aquí no funcionan en grandes matrices (por ejemplo, 200 000 elementos), e incluso si lo hacen, son lentas. La respuesta de polkovnikov.ph tiene el mejor rendimiento, pero no funciona para el aplanamiento profundo.

Aquí está la solución más rápida, que también funciona en matrices con múltiples niveles de anidamiento :

const flatten = function(arr, result = []) {
  for (let i = 0, length = arr.length; i < length; i++) {
    const value = arr[i];
    if (Array.isArray(value)) {
      flatten(value, result);
    } else {
      result.push(value);
    }
  }
  return result;
};

Ejemplos

Enormes matrices

flatten(Array(200000).fill([1]));

Maneja grandes matrices muy bien. En mi máquina, este código tarda unos 14 ms en ejecutarse.

Matrices anidadas

flatten(Array(2).fill(Array(2).fill(Array(2).fill([1]))));

Funciona con matrices anidadas. Este código produce [1, 1, 1, 1, 1, 1, 1, 1].

Matrices con diferentes niveles de anidamiento

flatten([1, [1], [[1]]]);

No tiene ningún problema al aplanar matrices como esta.

Michał Perłakowski
fuente
Excepto que tu enorme matriz es bastante plana. Esta solución no funcionará para matrices profundamente anidadas. Ninguna solución recursiva lo hará. De hecho, ningún navegador, pero Safari tiene TCO en este momento, por lo que ningún algoritmo recursivo funcionará bien.
nitely
@nitely Pero, ¿en qué situación del mundo real tendrías matrices con más de unos pocos niveles de anidamiento?
Michał Perłakowski
2
Por lo general, cuando la matriz se genera a partir de contenido generado por el usuario.
nitely
@ 0xcaff En Chrome no funciona en absoluto con una matriz de 200 000 elementos (obtienes RangeError: Maximum call stack size exceeded). Para una matriz de 20 000 elementos, tarda de 2 a 5 milisegundos.
Michał Perłakowski
3
¿Cuál es la complejidad de notación O de esto?
George Katsanos
58

Actualización: resultó que esta solución no funciona con matrices grandes. Si está buscando una solución mejor y más rápida, consulte esta respuesta .


function flatten(arr) {
  return [].concat(...arr)
}

Simplemente se expande arry se lo pasa como argumentos a concat(), lo que combina todas las matrices en una sola. Es equivalente a [].concat.apply([], arr).

También puede probar esto para el aplanamiento profundo:

function deepFlatten(arr) {
  return flatten(           // return shalowly flattened array
    arr.map(x=>             // with each x in array
      Array.isArray(x)      // is x an array?
        ? deepFlatten(x)    // if yes, return deeply flattened x
        : x                 // if no, return just x
    )
  )
}

Ver demo en JSBin .

Referencias para los elementos de ECMAScript 6 utilizados en esta respuesta:


Nota al margen: los métodos como find()y las funciones de flecha no son compatibles con todos los navegadores, pero eso no significa que no pueda usar estas funciones en este momento. Simplemente use Babel : transforma el código ES6 en ES5.

Michał Perłakowski
fuente
Debido a que casi todas las respuestas aquí se usan mal applyde esta manera, eliminé mis comentarios de los suyos. Todavía pienso utilizando apply/ propagan de esta manera es mal consejo, pero ya nadie le importa ...
@ LUH3417 No es así, realmente aprecio sus comentarios. Resultó que tienes razón: esta solución no funciona con grandes matrices. Publiqué otra respuesta que funciona bien incluso con matrices de 200 000 elementos.
Michał Perłakowski
Si está utilizando ES6, puede reducir aún más:const flatten = arr => [].concat(...arr)
Eruant
¿Qué quiere decir con "no funciona con matrices grandes"? ¿Que tan grande? ¿Lo que pasa?
GEMI
44
@GEMI Por ejemplo, al intentar aplanar una matriz de 500000 elementos con este método se obtiene "RangeError: se excedió el tamaño máximo de la pila de llamadas".
Michał Perłakowski
51

Puedes usar el subrayado :

var x = [[1], [2], [3, 4]];

_.flatten(x); // => [1, 2, 3, 4]
Todd Yandell
fuente
20
Whoah, ¿alguien agregó Perl a JS? :-)
JBRWilkinson
99
@JBRWilkinson ¿Tal vez JS quiere aprender un Haskell por el bien?
styfle
1+ - También puede especificar que desea una matriz plana poco profunda al especificar trueel segundo argumento .
Josh Crozier
77
En aras de la exhaustividad con respecto al comentario de @ styfle: learnyouahaskell.com
Simon A. Eugster
41

Los procedimientos genéricos significan que no tenemos que reescribir la complejidad cada vez que necesitamos utilizar un comportamiento específico.

concatMap(o flatMap) es exactamente lo que necesitamos en esta situación.

// concat :: ([a],[a]) -> [a]
const concat = (xs,ys) =>
  xs.concat (ys)

// concatMap :: (a -> [b]) -> [a] -> [b]
const concatMap = f => xs =>
  xs.map(f).reduce(concat, [])

// id :: a -> a
const id = x =>
  x

// flatten :: [[a]] -> [a]
const flatten =
  concatMap (id)

// your sample data
const data =
  [["$6"], ["$12"], ["$25"], ["$25"], ["$18"], ["$22"], ["$10"]]

console.log (flatten (data))

previsión

Y sí, lo has adivinado correctamente, solo se aplana un nivel, que es exactamente cómo debería funcionar

Imagina un conjunto de datos como este

// Player :: (String, Number) -> Player
const Player = (name,number) =>
  [ name, number ]

// team :: ( . Player) -> Team
const Team = (...players) =>
  players

// Game :: (Team, Team) -> Game
const Game = (teamA, teamB) =>
  [ teamA, teamB ]

// sample data
const teamA =
  Team (Player ('bob', 5), Player ('alice', 6))

const teamB =
  Team (Player ('ricky', 4), Player ('julian', 2))

const game =
  Game (teamA, teamB)

console.log (game)
// [ [ [ 'bob', 5 ], [ 'alice', 6 ] ],
//   [ [ 'ricky', 4 ], [ 'julian', 2 ] ] ]

Ok, ahora digamos que queremos imprimir una lista que muestre a todos los jugadores que participarán en game...

const gamePlayers = game =>
  flatten (game)

gamePlayers (game)
// => [ [ 'bob', 5 ], [ 'alice', 6 ], [ 'ricky', 4 ], [ 'julian', 2 ] ]

Si nuestro flattenprocedimiento también aplanara las matrices anidadas, terminaríamos con este resultado basura ...

const gamePlayers = game =>
  badGenericFlatten(game)

gamePlayers (game)
// => [ 'bob', 5, 'alice', 6, 'ricky', 4, 'julian', 2 ]

rodando profundo, bebé

Eso no quiere decir que a veces no quieras aplanar matrices anidadas, solo que ese no debería ser el comportamiento predeterminado.

Podemos hacer un deepFlattenprocedimiento con facilidad ...

// concat :: ([a],[a]) -> [a]
const concat = (xs,ys) =>
  xs.concat (ys)

// concatMap :: (a -> [b]) -> [a] -> [b]
const concatMap = f => xs =>
  xs.map(f).reduce(concat, [])

// id :: a -> a
const id = x =>
  x

// flatten :: [[a]] -> [a]
const flatten =
  concatMap (id)

// deepFlatten :: [[a]] -> [a]
const deepFlatten =
  concatMap (x =>
    Array.isArray (x) ? deepFlatten (x) : x)

// your sample data
const data =
  [0, [1, [2, [3, [4, 5], 6]]], [7, [8]], 9]

console.log (flatten (data))
// [ 0, 1, [ 2, [ 3, [ 4, 5 ], 6 ] ], 7, [ 8 ], 9 ]

console.log (deepFlatten (data))
// [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

Allí. Ahora tiene una herramienta para cada trabajo: una para aplastar un nivel de anidamiento flatteny otra para eliminar todo el anidamientodeepFlatten .

Tal vez puedas llamarlo obliterateo nukesi no te gusta el nombre deepFlatten.


¡No repitas dos veces!

Por supuesto, las implementaciones anteriores son inteligentes y concisas, pero el uso de un .mapseguido de un llamado a .reducesignifica que en realidad estamos haciendo más iteraciones de las necesarias

Usar un combinador de confianza al que llamo mapReduceayuda a mantener las iteraciones en un mínimo; toma una función de mapeo m :: a -> b, una función reductora r :: (b,a) ->by devuelve una nueva función reductora: este combinador está en el corazón de los transductores ; si te interesa, he escrito otras respuestas sobre ellos

// mapReduce = (a -> b, (b,a) -> b, (b,a) -> b)
const mapReduce = (m,r) =>
  (acc,x) => r (acc, m (x))

// concatMap :: (a -> [b]) -> [a] -> [b]
const concatMap = f => xs =>
  xs.reduce (mapReduce (f, concat), [])

// concat :: ([a],[a]) -> [a]
const concat = (xs,ys) =>
  xs.concat (ys)

// id :: a -> a
const id = x =>
  x

// flatten :: [[a]] -> [a]
const flatten =
  concatMap (id)
  
// deepFlatten :: [[a]] -> [a]
const deepFlatten =
  concatMap (x =>
    Array.isArray (x) ? deepFlatten (x) : x)

// your sample data
const data =
  [ [ [ 1, 2 ],
      [ 3, 4 ] ],
    [ [ 5, 6 ],
      [ 7, 8 ] ] ]

console.log (flatten (data))
// [ [ 1. 2 ], [ 3, 4 ], [ 5, 6 ], [ 7, 8 ] ]

console.log (deepFlatten (data))
// [ 1, 2, 3, 4, 5, 6, 7, 8 ]

Gracias
fuente
Con frecuencia, cuando veo sus respuestas, quiero retirar las mías, porque no tienen valor. ¡Gran respuesta! concatsí no volar la pila, única ...y applyhace (junto con matrices muy grandes). No lo vi Me siento terrible en este momento.
@ LUH3417 no deberías sentirte terrible. También proporciona buenas respuestas con explicaciones bien escritas. Todo aquí es una experiencia de aprendizaje: a menudo cometo errores y escribo malas respuestas también. Los revisito en un par de meses y pienso "¿Qué estaba pensando?" y terminan revisando completamente las cosas. Ninguna respuesta es perfecta. Todos estamos en algún punto en una curva de aprendizaje ^ _ ^
Gracias
1
Tenga en cuenta que concaten Javascript tiene un significado diferente que en Haskell. Haskell's concat( [[a]] -> [a]) se llamaría flattenen Javascript y se implementaría como foldr (++) [](Javascript: foldr(concat) ([])asumiendo funciones currificadas). Javascript concates un agregado extraño ( (++)en Haskell), que puede manejar ambos [a] -> [a] -> [a]y a -> [a] -> [a].
Supongo que un nombre mejor era flatMap, porque eso es exactamente lo que concatMapes: la bindinstancia de la listmónada. concatpMapse implementa como foldr ((++) . f) []. Traducido en Javascript: const flatMap = f => foldr(comp(concat) (f)) ([]). Por supuesto, esto es similar a su implementación sin comp.
¿Cuál es la complejidad de ese algoritmo?
George Katsanos
32

Una solución para el caso más general, cuando puede tener algunos elementos que no son de matriz en su matriz.

function flattenArrayOfArrays(a, r){
    if(!r){ r = []}
    for(var i=0; i<a.length; i++){
        if(a[i].constructor == Array){
            r.concat(flattenArrayOfArrays(a[i], r));
        }else{
            r.push(a[i]);
        }
    }
    return r;
}
Trindaz
fuente
44
Este enfoque fue muy efectivo para aplanar la forma de matriz anidada de conjuntos de resultados que obtiene de una consulta JsonPath .
kevinjansz
1
Agregado como método de matrices:Object.defineProperty(Array.prototype,'flatten',{value:function(r){for(var a=this,i=0,r=r||[];i<a.length;++i)if(a[i]!=null)a[i] instanceof Array?a[i].flatten(r):r.push(a[i]);return r}});
Phrogz
Esto se romperá si pasamos manualmente el segundo argumento. Por ejemplo, intente esto: flattenArrayOfArrays (arr, 10)o esto flattenArrayOfArrays(arr, [1,[3]]);: esos segundos argumentos se agregan a la salida.
Om Shankar
2
¡Esta respuesta no está completa! el resultado de la recursión nunca se asigna a ninguna parte, por lo que el resultado de las recursiones se pierde
r3wt
1
@ r3wt siguió adelante y agregó una solución. Lo que debe hacer es asegurarse de que la matriz actual que está manteniendo rrealmente concatene los resultados de la recursividad.
agosto
29

Para aplanar una matriz de matrices de un solo elemento, no necesita importar una biblioteca, un bucle simple es la solución más simple y más eficiente :

for (var i = 0; i < a.length; i++) {
  a[i] = a[i][0];
}

Para los votantes negativos: lea la pregunta, no haga votos negativos porque no se adapta a su problema muy diferente. Esta solución es tanto la más rápida como la más simple para la pregunta formulada.

Denys Séguret
fuente
44
Yo diría: "No busques cosas más crípticas". ^^
44
Quiero decir ... cuando veo gente aconsejando usar una biblioteca para eso ... eso es una locura ...
Denys Séguret
3
Ahh, usar una biblioteca solo para esto sería una tontería ... pero si ya está disponible.
99
@dystroy La parte condicional del bucle for no es tan legible. O solo soy yo: D
Andreas
44
Realmente no importa cuán críptico sea. Este código "aplana" esto ['foo', ['bar']]a ['f', 'bar'].
jonschlinkert
29

¿Qué pasa con el uso del reduce(callback[, initialValue])método deJavaScript 1.8

list.reduce((p,n) => p.concat(n),[]);

Haría el trabajo

rab
fuente
8
[[1], [2,3]].reduce( (a,b) => a.concat(b), [] )Es más sexy.
golopot
55
No necesitamos un segundo argumento en nuestro caso simple[[1], [2,3]].reduce( (a,b) => a.concat(b))
Umair Ahmed
29

Otra solución ECMAScript 6 en estilo funcional:

Declarar una función:

const flatten = arr => arr.reduce(
  (a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []
);

y úsalo:

flatten( [1, [2,3], [4,[5,[6]]]] ) // -> [1,2,3,4,5,6]

 const flatten = arr => arr.reduce(
         (a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []
       );


console.log( flatten([1, [2,3], [4,[5],[6,[7,8,9],10],11],[12],13]) )

Considere también una función nativa Array.prototype.flat () (propuesta para ES6) disponible en las últimas versiones de los navegadores modernos. Gracias a @ (Константин Ван) y @ (Mark Amery) lo mencionaron en los comentarios.

La flatfunción tiene un parámetro, que especifica la profundidad esperada de la anidación de la matriz, que es igual 1por defecto.

[1, 2, [3, 4]].flat();                  // -> [1, 2, 3, 4]

[1, 2, [3, 4, [5, 6]]].flat();          // -> [1, 2, 3, 4, [5, 6]]

[1, 2, [3, 4, [5, 6]]].flat(2);         // -> [1, 2, 3, 4, 5, 6]

[1, 2, [3, 4, [5, 6]]].flat(Infinity);  // -> [1, 2, 3, 4, 5, 6]

let arr = [1, 2, [3, 4]];

console.log( arr.flat() );

arr =  [1, 2, [3, 4, [5, 6]]];

console.log( arr.flat() );
console.log( arr.flat(1) );
console.log( arr.flat(2) );
console.log( arr.flat(Infinity) );

diziaq
fuente
3
Esto es bueno y ordenado, pero creo que ha hecho una sobredosis de ES6. No hay necesidad de que la función externa sea una función de flecha. Me quedaría con la función de flecha para reducir la devolución de llamada, pero aplanar debería ser una función normal.
Stephen Simpson
3
@StephenSimpson, pero ¿hay una necesidad de que la función externa sea una función no de flecha? "aplanar en sí mismo debería ser una función normal" - por "normal" quieres decir "sin flecha", pero ¿por qué? ¿Por qué usar una función de flecha en la llamada para reducir entonces? ¿Puede proporcionar su línea de razonamiento?
Gracias
@naomik Mi razonamiento es que es innecesario. Es principalmente una cuestión de estilo; Debería tener mucho más claro en mi comentario. No hay una razón importante de codificación para usar uno u otro. Sin embargo, la función es más fácil de ver y leer como sin flecha. La función interna es útil como una función de flecha, ya que es más compacta (y no se crea ningún contexto, por supuesto). Las funciones de flecha son excelentes para crear una función compacta fácil de leer y evitar esta confusión. Sin embargo, en realidad pueden hacer que sea más difícil de leer cuando una flecha no sería suficiente. ¡Sin embargo, otros pueden estar en desacuerdo!
Stephen Simpson
Obteniendo unRangeError: Maximum call stack size exceeded
Matt Westlake
@ Matt, comparta el entorno que utiliza para reproducir el error
diziaq
22
const common = arr.reduce((a, b) => [...a, ...b], [])
YairTawil
fuente
Así es como lo hago a través de mi código, pero no estoy seguro de cómo se compara la velocidad con la respuesta aceptada si tiene una matriz muy larga
Michael Aaron Wilson
14

Tenga en cuenta: cuando se usa Function.prototype.apply( [].concat.apply([], arrays)) o el operador de propagación ( [].concat(...arrays)) para aplanar una matriz, ambos pueden causar desbordamientos de la pila para matrices grandes, porque cada argumento de una función se almacena en la pila.

Aquí hay una implementación segura para la pila en un estilo funcional que sopesa los requisitos más importantes entre sí:

  • reutilización
  • legibilidad
  • concisión
  • actuación

// small, reusable auxiliary functions:

const foldl = f => acc => xs => xs.reduce(uncurry(f), acc); // aka reduce

const uncurry = f => (a, b) => f(a) (b);

const concat = xs => y => xs.concat(y);


// the actual function to flatten an array - a self-explanatory one-line:

const flatten = xs => foldl(concat) ([]) (xs);

// arbitrary array sizes (until the heap blows up :D)

const xs = [[1,2,3],[4,5,6],[7,8,9]];

console.log(flatten(xs));


// Deriving a recursive solution for deeply nested arrays is trivially now


// yet more small, reusable auxiliary functions:

const map = f => xs => xs.map(apply(f));

const apply = f => a => f(a);

const isArray = Array.isArray;


// the derived recursive function:

const flattenr = xs => flatten(map(x => isArray(x) ? flattenr(x) : x) (xs));

const ys = [1,[2,[3,[4,[5],6,],7],8],9];

console.log(flattenr(ys));

Tan pronto como se acostumbre a las funciones de flecha pequeña en forma de curry, composición de funciones y funciones de orden superior, este código se lee como prosa. La programación consiste simplemente en juntar pequeños bloques de construcción que siempre funcionan como se espera, porque no contienen ningún efecto secundario.


fuente
1
Jaja. Respeto totalmente su respuesta, aunque leer programación funcional como esta sigue siendo como leerme caracteres japoneses para mí (hablante de inglés).
Tarwin Stroh-Spijer
2
Si te encuentras implementando características del lenguaje A en el lenguaje B no como parte del proyecto con el único objetivo de hacer exactamente esto, entonces alguien en algún lugar había tomado un giro equivocado. ¿Podrías ser tú? Simplemente ir con const flatten = (arr) => arr.reduce((a, b) => a.concat(b), []);ahorra basura visual y explica a sus compañeros de equipo por qué necesita 3 funciones adicionales y algunas llamadas a funciones también.
Daerdemandt
3
@Daerdemandt Pero si lo escribe como funciones separadas, probablemente podrá reutilizarlas en otro código.
Michał Perłakowski
@ MichałPerłakowski Si necesita usarlos en varios lugares, no reinvente la rueda y elija un paquete de estos , documentado y respaldado por otras personas.
Daerdemandt
12

ES6 One Line Flatten

Ver aplanar lodash , subrayar aplanar (poco profundo true)

function flatten(arr) {
  return arr.reduce((acc, e) => acc.concat(e), []);
}

o

function flatten(arr) {
  return [].concat.apply([], arr);
}

Probado con

test('already flatted', () => {
  expect(flatten([1, 2, 3, 4, 5])).toEqual([1, 2, 3, 4, 5]);
});

test('flats first level', () => {
  expect(flatten([1, [2, [3, [4]], 5]])).toEqual([1, 2, [3, [4]], 5]);
});

ES6 One Line Deep Flatten

Ver aplanar lodash Profundo , subrayar aplanar

function flattenDeep(arr) {
  return arr.reduce((acc, e) => Array.isArray(e) ? acc.concat(flattenDeep(e)) : acc.concat(e), []);
}

Probado con

test('already flatted', () => {
  expect(flattenDeep([1, 2, 3, 4, 5])).toEqual([1, 2, 3, 4, 5]);
});

test('flats', () => {
  expect(flattenDeep([1, [2, [3, [4]], 5]])).toEqual([1, 2, 3, 4, 5]);
});
zurfyx
fuente
Su segundo ejemplo está mejor escrito Array.prototype.concat.apply([], arr)porque crea una matriz adicional solo para acceder a la concatfunción. Los tiempos de ejecución pueden o no optimizarlo cuando lo ejecutan, pero acceder a la función en el prototipo no parece más feo de lo que ya es en cualquier caso.
Mörre
11

Puede usar Array.flat()con Infinitycualquier profundidad de matriz anidada.

var arr = [ [1,2,3,4], [1,2,[1,2,3]], [1,2,3,4,5,[1,2,3,4,[1,2,3,4]]], [[1,2,3,4], [1,2,[1,2,3]], [1,2,3,4,5,[1,2,3,4,[1,2,3,4]]]] ];

let flatten = arr.flat(Infinity)

console.log(flatten)

verifique aquí la compatibilidad del navegador

Code Maniac
fuente
8

Un enfoque Haskellesque

function flatArray([x,...xs]){
  return x ? [...Array.isArray(x) ? flatArray(x) : [x], ...flatArray(xs)] : [];
}

var na = [[1,2],[3,[4,5]],[6,7,[[[8],9]]],10];
    fa = flatArray(na);
console.log(fa);

Redu
fuente
1
De acuerdo con @monners. Sin duda, la solución más elegante en todo este hilo.
Nicolás Fantone
8

Manera ES6:

const flatten = arr => arr.reduce((acc, next) => acc.concat(Array.isArray(next) ? flatten(next) : next), [])

const a = [1, [2, [3, [4, [5]]]]]
console.log(flatten(a))

Forma ES5 para la flattenfunción con el respaldo ES3 para matrices anidadas N veces:

var flatten = (function() {
  if (!!Array.prototype.reduce && !!Array.isArray) {
    return function(array) {
      return array.reduce(function(prev, next) {
        return prev.concat(Array.isArray(next) ? flatten(next) : next);
      }, []);
    };
  } else {
    return function(array) {
      var arr = [];
      var i = 0;
      var len = array.length;
      var target;

      for (; i < len; i++) {
        target = array[i];
        arr = arr.concat(
          (Object.prototype.toString.call(target) === '[object Array]') ? flatten(target) : target
        );
      }

      return arr;
    };
  }
}());

var a = [1, [2, [3, [4, [5]]]]];
console.log(flatten(a));

Artem Gavrysh
fuente
7

Si solo tiene matrices con 1 elemento de cadena:

[["$6"], ["$12"], ["$25"], ["$25"]].join(',').split(',');

Hará el trabajo. Bt que coincide específicamente con su ejemplo de código.

Florian Salihovic
fuente
3
Quien rechazó la votación, explique por qué. Estaba buscando una solución decente y de todas las soluciones que más me gustaron.
Anónimo
2
@Anónimo No lo rechacé porque técnicamente cumple con los requisitos de la pregunta, pero es probable porque esta es una solución bastante pobre que no es útil en el caso general. Teniendo en cuenta cuántas mejores soluciones hay aquí, nunca recomendaría que alguien vaya con esta, ya que se rompe en el momento en que tiene más de un elemento, o cuando no son cadenas.
Thor84no
2
Me encanta esta solución =)
Huei Tan
2
No maneja solo matrices con 1 elementos de cadena, también maneja esta matriz ['$4', ["$6"], ["$12"], ["$25"], ["$25", "$33", ['$45']]].join(',').split(',')
alucic
Descubrí este método por mi cuenta, pero sabía que ya debía haber sido documentado en alguna parte, mi búsqueda terminó aquí. El inconveniente de esta solución es que obliga a los números, booleanos, etc. a cadenas, intente [1,4, [45, 't', ['e3', 6]]].toString().split(',') ---- o ----- [1,4, [45, 't', ['e3', 6], false]].toString().split(',')
Sudhansu Choudhary
7
var arrays = [["a"], ["b", "c"]];
Array.prototype.concat.apply([], arrays);

// gives ["a", "b", "c"]

(Solo estoy escribiendo esto como una respuesta separada, basada en el comentario de @danhbear).

VasiliNovikov
fuente
7

Recomiendo una función de generador de espacio eficiente :

function* flatten(arr) {
  if (!Array.isArray(arr)) yield arr;
  else for (let el of arr) yield* flatten(el);
}

// Example:
console.log(...flatten([1,[2,[3,[4]]]])); // 1 2 3 4

Si lo desea, cree una matriz de valores aplanados de la siguiente manera:

let flattened = [...flatten([1,[2,[3,[4]]]])]; // [1, 2, 3, 4]
le_m
fuente
Me gusta este enfoque. Similar a stackoverflow.com/a/35073573/1175496 , pero usa el operador de propagación ...para iterar a través del generador.
The Red Pea
6

Prefiero transformar toda la matriz, tal cual, en una cadena, pero a diferencia de otras respuestas, lo haría usando JSON.stringifyy no el toString()método, que produce un resultado no deseado.

Con esa JSON.stringifysalida, todo lo que queda es eliminar todos los corchetes, ajustar el resultado con los corchetes de inicio y finalización una vez más, y servir el resultado con el JSON.parseque la cadena vuelve a la "vida".

  • Puede manejar matrices anidadas infinitas sin ningún costo de velocidad.
  • Puede manejar correctamente elementos de matriz que son cadenas que contienen comas.

var arr = ["abc",[[[6]]],["3,4"],"2"];

var s = "[" + JSON.stringify(arr).replace(/\[|]/g,'') +"]";
var flattened = JSON.parse(s);

console.log(flattened)

  • Solo para matrices multidimensionales de cadenas / números (no objetos)
vsync
fuente
Tu solución es incorrecta. Contendrá la coma al aplanar las matrices internas en ["345", "2", "3,4", "2"]lugar de separar cada uno de esos valores para separar los índices
pizzarob
@realseanp: entendió mal el valor de ese elemento de la matriz. Puse esa coma intencionalmente como un valor y no como una coma delimitador de matriz para enfatizar el poder de mi solución por encima de todas las demás, lo que daría como resultado "3,4".
vsync el
1
No
entendí bien
esa parece definitivamente la solución más rápida que he visto para esto; ¿Está al tanto de cualquier error @vsync (excepto el hecho de que parece un poco raro, por supuesto, tratando las matrices anidadas como cadenas: D)
George Katsanos
@GeorgeKatsanos: este método no funcionará para elementos de matriz (y elementos anidados) que no son de valor Primitivo, por ejemplo, un elemento que apunta a un elemento DOM
vsync
6

También puedes probar el nuevo Array.Flat()método. Funciona de la siguiente manera:

let arr = [["$6"], ["$12"], ["$25"], ["$25"], ["$18"], ["$22"], ["$10"]].flat()

console.log(arr);

El flat()método crea una nueva matriz con todos los elementos de la sub-matriz concatenados recursivamente hasta la capa de profundidad 1 (es decir, matrices dentro de las matrices)

Si también desea aplanar matrices tridimensionales o incluso de dimensiones superiores, simplemente llame al método plano varias veces. Por ejemplo (3 dimensiones):

let arr = [1,2,[3,4,[5,6]]].flat().flat().flat();

console.log(arr);

¡Ten cuidado!

Array.Flat()El método es relativamente nuevo. Los navegadores más antiguos como ie podrían no haber implementado el método. Si desea que el código funcione en todos los navegadores, es posible que deba transpilar su JS a una versión anterior. Verifique los documentos web de MD para ver la compatibilidad actual del navegador.

Willem van der Veen
fuente
2
para aplanar matrices de dimensiones superiores, simplemente puede llamar al método plano con Infinityargumento. Así:arr.flat(Infinity)
Mikhail, el
Esa es una muy buena adición, ¡gracias Mikhail!
Willem van der Veen
6

Usando el operador de propagación:

const input = [["$6"], ["$12"], ["$25"], ["$25"], ["$18"], ["$22"], ["$10"]];
const output = [].concat(...input);
console.log(output); // --> ["$6", "$12", "$25", "$25", "$18", "$22", "$10"]

Bagre
fuente
5

Eso no es difícil, solo itera sobre las matrices y únelas:

var result = [], input = [["$6"], ["$12"], ["$25"], ["$25"], ["$18"]];

for (var i = 0; i < input.length; ++i) {
    result = result.concat(input[i]);
}
Niko
fuente
5

¡Parece que esto parece un trabajo para RECURSION!

  • Maneja múltiples niveles de anidamiento
  • Maneja matrices vacías y parámetros no matrices
  • No tiene mutación
  • No se basa en las características modernas del navegador

Código:

var flatten = function(toFlatten) {
  var isArray = Object.prototype.toString.call(toFlatten) === '[object Array]';

  if (isArray && toFlatten.length > 0) {
    var head = toFlatten[0];
    var tail = toFlatten.slice(1);

    return flatten(head).concat(flatten(tail));
  } else {
    return [].concat(toFlatten);
  }
};

Uso:

flatten([1,[2,3],4,[[5,6],7]]);
// Result: [1, 2, 3, 4, 5, 6, 7] 
Jai
fuente
cuidado, flatten(new Array(15000).fill([1]))lanza Uncaught RangeError: Maximum call stack size exceededy congela mis devTools por 10 segundos
pietrovismara
@pietrovismara, mi prueba es de alrededor de 1 s.
Ivan Yan
5

Lo hice usando recursividad y cierres

function flatten(arr) {

  var temp = [];

  function recursiveFlatten(arr) { 
    for(var i = 0; i < arr.length; i++) {
      if(Array.isArray(arr[i])) {
        recursiveFlatten(arr[i]);
      } else {
        temp.push(arr[i]);
      }
    }
  }
  recursiveFlatten(arr);
  return temp;
}
balajivijayan
fuente
1
Simple y dulce, esta respuesta funciona mejor que la respuesta aceptada. Se aplana niveles profundamente anidados, no solo el primer nivel
Om Shankar
1
AFAIK que es un alcance léxico y no un cierre
dashambles
@dashambles es correcto: la diferencia es que si se tratara de un cierre, devolvería la función interna al exterior y cuando la función externa esté terminada, aún puede usar la función interna para acceder a su alcance. Aquí la vida útil de la función externa es más larga que la de la función interna, por lo que nunca se crea un "cierre".
Mörre
5

Estaba haciendo el tonto con los generadores ES6 el otro día y escribí esta esencia . Que contiene...

function flatten(arrayOfArrays=[]){
  function* flatgen() {
    for( let item of arrayOfArrays ) {
      if ( Array.isArray( item )) {
        yield* flatten(item)
      } else {
        yield item
      }
    }
  }

  return [...flatgen()];
}

var flatArray = flatten([[1, [4]],[2],[3]]);
console.log(flatArray);

Básicamente, estoy creando un generador que recorre la matriz de entrada original, si encuentra una matriz, utiliza el operador de rendimiento * en combinación con la recursión para aplanar continuamente las matrices internas. Si el artículo no es una matriz, solo produce el artículo individual. Luego, utilizando el operador ES6 Spread (también conocido como operador splat), aplané el generador en una nueva instancia de matriz.

No he probado el rendimiento de esto, pero creo que es un buen ejemplo simple del uso de generadores y el operador de rendimiento *.

Pero nuevamente, solo estaba haciendo el ridículo, así que estoy seguro de que hay formas más efectivas de hacer esto.

ashwell
fuente
1
Respuesta similar (usando generador y delegación de rendimiento), pero en formato PHP
The Red Pea
5

la mejor solución sin lodash

let flatten = arr => [].concat.apply([], arr.map(item => Array.isArray(item) ? flatten(item) : item))
Vlad Ankudinov
fuente