Mesclar / nivelar uma matriz de matrizes


1105

Eu tenho uma matriz JavaScript como:

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

Como eu iria mesclar as matrizes internas separadas em uma como:

["$6", "$12", "$25", ...]


18
Todas as soluções que usam reduce+ concatsão O ((N ^ 2) / 2), onde como resposta aceita (apenas uma chamada para concat) seria no máximo O (N * 2) em um navegador ruim e O (N) em um um bom. A solução Denys também é otimizada para a pergunta real e até 2x mais rápida que a única concat. Para as reducepessoas, é divertido sentir-se bem ao escrever códigos minúsculos, mas, por exemplo, se o array tivesse 1000 subarrays de um elemento, todas as soluções reduz + concat estariam executando operações 500500, onde o concat único ou o loop simples faria 1000 operações.
gman

12
Basta operador de usuário propagação[].concat(...array)
Oleg Dater

@ gman como é reduzir + concat solução O ((N ^ 2) / 2)? stackoverflow.com/questions/52752666/… menciona uma complexidade diferente.
Usman

4
Com os navegadores mais recentes compatíveis com o ES2019 : array.flat(Infinity)onde Infinityestá a profundidade máxima a nivelar.
Timothy Gu

Respostas:


1860

Você pode usar concatpara mesclar matrizes:

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

console.log(merged);

Usando o applymétodo de concat, apenas o segundo parâmetro será usado como uma matriz; portanto, a última linha é idêntica a esta:

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

Há também o Array.prototype.flat()método (introduzido no ES2019) que você pode usar para nivelar as matrizes, embora ele esteja disponível apenas no Node.js, começando na versão 11, e nem no 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);
    


10
Observe que concatnão modifica a matriz de origem; portanto, a mergedmatriz permanecerá vazia após a chamada para concat. Melhor dizer algo como:merged = merged.concat.apply(merged, arrays);
Nate

65
var merged = [].concat.apply([], arrays);parece funcionar bem para colocá-lo em uma linha. editar: como a resposta de Nikita já mostra.
21313 Sean

44
Or Array.prototype.concat.apply([], arrays).
danhbear

30
Nota: esta resposta apenas nivela um nível profundamente. Para um nivelamento recursivo, consulte a resposta do @Trindaz.
Phrogz

209
Para além @ o comentário de Sean: sintaxe ES6 faz este super concisa:var merged = [].concat(...arrays)
Sethi

505

Aqui está uma função curta que usa alguns dos métodos mais recentes de matriz JavaScript para nivelar uma 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]

17
Eu gosto dessa abordagem. É muito mais geral e suporta matrizes aninhadas
alfredocambera

10
Qual é o perfil de uso de memória para esta solução? Parece que ele cria um monte de matrizes intermediárias durante a recursão de cauda ....
JBRWilkinson

7
@ayjay, é o valor inicial do acumulador para a função de redução, o que mdn chama de valor inicial. Nesse caso, é o valor da flatprimeira chamada para a função anônima passada para reduce. Se não for especificado, a primeira chamada a reducevincular o primeiro valor da matriz a flat, o que acabaria resultando em 1vinculação flatnos dois exemplos. 1.concatnão é uma função.
Noah Freitas

19
Ou em uma forma mais curta e mais sexy: const flatten = (arr) => arr.reduce((flat, next) => flat.concat(next), []);
Tsvetomir Tsonev 08/08/16

12
Riffing nas soluções de @TsvetomirTsonev e Noah para aninhamento arbitrário:const flatten = (arr) => arr.reduce((flat, next) => flat.concat(Array.isArray(next) ? flatten(next) : next), []);
Will

314

Existe um método confuso, oculto, que constrói uma nova matriz sem alterar a original:

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


11
No CoffeeScript, isto é[].concat([[1],[2,3],[4]]...)
amoebe

8
@amoebe sua resposta dá [[1],[2,3],[4]]como resultado. A solução que o @Nikita fornece está correta para o CoffeeScript e para o JS.
Ryan Kennedy

5
Ahh, encontrei seu erro. Você tem um par extra de colchetes em sua notação, deve ser [].concat([1],[2,3],[4],...).
Ryan Kennedy

21
Também acho que encontrei o seu erro. O ...são código real, não algumas reticências.
23714 amebe

3
Usar uma função de biblioteca não significa que exista uma "bagunça" menos imperativa. Só que a bagunça está escondida dentro de uma biblioteca. Obviamente, usar uma biblioteca é melhor do que rolar sua própria função, mas afirmar que essa técnica reduz a "bagunça imperativa" é muito tolo.
paldepind

204

Pode ser feito melhor com a função de redução 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);
}, []);

Ou, com o ES2015:

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

js-fiddle

Documentos do Mozilla


1
@JohnS Na verdade, reduza feeds para concat um único valor (array) por turno. Prova? retire () e veja se funciona. reduzir () aqui é uma alternativa para Function.prototype.apply ()
André Werlang

3
Eu teria usado reduzir tão bem, mas uma coisa interessante que eu aprendi a partir deste trecho é que na maioria das vezes você não precisa passar por um initialValue :)
José F. Romaniello

2
O problema com o reduzir é que a matriz não pode estar vazia, portanto, você precisará adicionar uma validação extra.
9789 Calvints #

4
@calbertts Basta passar o valor inicial []e nenhuma validação adicional é necessária.

8
Como você usa o ES6, também pode usar o operador de propagação como literal de matriz . arrays.reduce((flatten, arr) => [...flatten, ...arr])
Putzi San

109

Há um novo método nativo chamado flat para fazer exatamente isso.

(A partir do final de 2019, flatagora é publicado no padrão ECMA 2019 e core-js@3(a biblioteca de babel) inclui-o em sua 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];

4
É uma pena que isso não esteja na primeira página de respostas. Esse recurso está disponível no Chrome 69 e Firefox 62 (e no Nó 11 para quem trabalha no back-end)
Matt M.


2
-1; não, isso não faz parte do ECMAScript 2018 . Ainda é apenas uma proposta que não chegou a nenhuma especificação do ECMAScript.
Mark Amery

1
Acho que agora podemos considerar isso .. porque agora faz parte do padrão (2019) .. podemos revisitar a parte de desempenho disso uma vez?
Yuvaraj

Parece que ainda não é suportado por qualquer navegador Microsoft embora (pelo menos no momento em que escrevo este comentário)
Laurent S.

78

A maioria das respostas aqui não funciona em matrizes enormes (por exemplo, 200.000 elementos) e, mesmo se o fizerem, são lentas. A resposta de polkovnikov.ph tem o melhor desempenho, mas não funciona para achatamento profundo.

Aqui está a solução mais rápida, que também funciona em matrizes com vários níveis de aninhamento :

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

Exemplos

Matrizes enormes

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

Ele lida com matrizes enormes muito bem. Na minha máquina, esse código leva cerca de 14 ms para ser executado.

Matrizes aninhadas

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

Funciona com matrizes aninhadas. Este código produz [1, 1, 1, 1, 1, 1, 1, 1].

Matrizes com diferentes níveis de aninhamento

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

Ele não tem nenhum problema ao nivelar matrizes como esta.


Exceto que sua enorme variedade é bastante plana. Essa solução não funcionará para matrizes profundamente aninhadas. Nenhuma solução recursiva será. De fato, nenhum navegador, exceto o Safari, tem TCO no momento, portanto, nenhum algoritmo recursivo terá um bom desempenho.
nitely

@nitely Mas em que situação do mundo real você teria matrizes com mais do que alguns níveis de aninhamento?
Michał Perłakowski

2
Geralmente, quando a matriz é gerada a partir do conteúdo gerado pelo usuário.
nitely

@ 0xcaff No Chrome, ele não funciona com uma matriz de 200.000 elementos (você entende RangeError: Maximum call stack size exceeded). Para uma matriz de 20.000 elementos, leva de 2 a 5 milissegundos.
Michał Perłakowski

3
qual é a complexidade dessa notação?
George Katsanos

58

Atualização: verificou-se que esta solução não funciona com matrizes grandes. Se você está procurando uma solução melhor e mais rápida, confira esta resposta .


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

É simplesmente expande arre passa como argumentos para concat(), que mescla todas as matrizes em uma. É equivalente a [].concat.apply([], arr).

Você também pode tentar isso para achatamento 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
    )
  )
}

Veja a demonstração no JSBin .

Referências para os elementos do ECMAScript 6 usados ​​nesta resposta:


Nota lateral: métodos como find()e funções de seta não são compatíveis com todos os navegadores, mas isso não significa que você não possa usar esses recursos no momento. Basta usar o Babel - ele transforma o código ES6 em ES5.


Como quase todas as respostas aqui são mal utilizadas applydessa maneira, removi meus comentários dos seus. Eu ainda acho que usando apply/ espalhar desta maneira é ruim aconselhar, mas desde que ninguém se importa ...

@ LUH3417 Não é assim, eu realmente aprecio seus comentários. Aconteceu que você está certo - essa solução realmente não funciona com matrizes grandes. Postei outra resposta que funciona bem mesmo com matrizes de 200.000 elementos.
Michał Perłakowski

Se você estiver usando o ES6, poderá const flatten = arr => [].concat(...arr)
reduzir

O que você quer dizer com "não funciona com matrizes grandes"? Quão largo? O que acontece?
GEMI

4
@ GEMI Por exemplo, tentar achatar uma matriz de 500000 elementos usando este método fornece "RangeError: tamanho máximo da pilha de chamadas excedido".
Michał Perłakowski

51

Você pode usar o sublinhado :

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

_.flatten(x); // => [1, 2, 3, 4]

20
Whoah, alguém adicionou Perl ao JS? :-)
JBRWilkinson

9
@JBRWilkinson Talvez JS queira aprender um Haskell para um grande bem !?
styfle

1+ - Você também pode especificar que deseja uma matriz rasa achatada especificando trueo segundo argumento .
Josh Crozier

7
Por uma questão de exaustividade sobre @ comentário de styfle: learnyouahaskell.com
Simon A. Eugster

41

Procedimentos genéricos significam que não precisamos reescrever a complexidade toda vez que precisamos utilizar um comportamento específico.

concatMap(ou flatMap) é exatamente o que precisamos nessa situação.

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

previsão

E sim, você adivinhou corretamente, apenas nivela um nível, que é exatamente como deve funcionar

Imagine alguns conjuntos de dados 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, agora digamos que queremos imprimir uma lista que mostre todos os jogadores que participarão game

const gamePlayers = game =>
  flatten (game)

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

Se nosso flattenprocedimento também nivelar matrizes aninhadas, acabaremos com esse resultado de lixo ...

const gamePlayers = game =>
  badGenericFlatten(game)

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

rolando fundo, baby

Isso não quer dizer que às vezes você também não queira achatar matrizes aninhadas - apenas esse não deve ser o comportamento padrão.

Podemos fazer um deepFlattenprocedimento com facilidade…

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

Lá. Agora você tem uma ferramenta para cada trabalho - uma para esmagar um nível de aninhamento flattene outra para obliterar todos os aninhamentos deepFlatten.

Talvez você possa ligar obliterateou nukese não gostar do nome deepFlatten.


Não itere duas vezes!

É claro que as implementações acima são inteligentes e concisas, mas usar um .mapseguido de uma chamada para .reducesignifica que estamos realmente fazendo mais iterações do que o necessário

Usar um combinador confiável que estou chamando mapReduceajuda a manter as iterações em um mínimo; requer uma função de mapeamento m :: a -> b, uma função redutora r :: (b,a) ->be retorna uma nova função redutora - esse combinador está no coração dos transdutores ; se você estiver interessado, eu escrevi outras respostas sobre eles

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


Freqüentemente, quando vejo suas respostas, quero retirar as minhas, porque elas se tornaram inúteis. Ótima resposta! concatsi não explodir a pilha, única ...e applyfaz (juntamente com matrizes muito grandes). Eu não vi. Eu me sinto terrível agora.

@ LUH3417 você não deve se sentir terrível. Você também fornece boas respostas com explicações bem escritas. Tudo aqui é uma experiência de aprendizado - muitas vezes eu cometo erros e escrevo respostas ruins também. Eu os revisito em alguns meses e penso "o que eu estava pensando?" e acabamos revisando completamente as coisas. Nenhuma resposta é perfeita. Estamos todos em algum momento de uma curva de aprendizado ^ _ ^
Obrigado

1
Observe que concatem Javascript tem um significado diferente do que em Haskell. O Haskell's concat( [[a]] -> [a]) seria chamado flattenem Javascript e é implementado como foldr (++) [](Javascript: foldr(concat) ([])assumindo funções ao curry). Javascript concaté um anexo estranho ( (++)em Haskell), que pode lidar com ambos [a] -> [a] -> [a]e a -> [a] -> [a].

Acho que um nome melhor era flatMap, porque é exatamente isso concatMap: a bindinstância da listmônada. concatpMapé implementado como foldr ((++) . f) []. Traduzido em Javascript: const flatMap = f => foldr(comp(concat) (f)) ([]). Naturalmente, isso é semelhante à sua implementação sem comp.

qual é a complexidade desse algoritmo?
George Katsanos

32

Uma solução para o caso mais geral, quando você pode ter alguns elementos que não são da 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;
}

4
Essa abordagem foi muito eficaz no nivelamento da forma de matriz aninhada de conjuntos de resultados obtidos de uma consulta JsonPath .
kevinjansz

1
Adicionado como um método de matrizes: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 21/02

Isso será interrompido se passarmos manualmente no segundo argumento. Por exemplo, tente o seguinte: flattenArrayOfArrays (arr, 10)ou isto flattenArrayOfArrays(arr, [1,[3]]);- esses segundos argumentos são adicionados à saída.
Om Shankar

2
esta resposta não está completa! o resultado da recursão não é atribuído qualquer lugar, então resultado de recursions são perdidos
r3wt

1
O @ r3wt foi em frente e adicionou uma correção. O que você precisa fazer é garantir que a matriz atual que você está mantendo, rconcatenará os resultados da recursão.
agosto

29

Para nivelar uma matriz de matrizes de elemento único, você não precisa importar uma biblioteca, um loop simples é a solução mais simples e mais eficiente :

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

Para votantes negativos: leia a pergunta, não diminua o voto porque não se adequa ao seu problema muito diferente. Esta solução é a mais rápida e a mais simples para a pergunta feita.


4
Eu dizia: "Não procure coisas mais enigmáticas". ^^

4
Quero dizer ... quando vejo pessoas aconselhando a usar uma biblioteca para isso ... isso é loucura ...
Denys Séguret

3
Ahh, usar uma biblioteca apenas para isso seria bobagem ... mas se já estiver disponível.

9
@dystroy A parte condicional do loop for não é tão legível. Ou sou apenas eu: D
Andreas

4
Realmente não importa o quão enigmático é. Este código "achata" isso ['foo', ['bar']]para ['f', 'bar'].
Json

29

Que tal usar o reduce(callback[, initialValue])método deJavaScript 1.8

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

Faria o trabalho.


8
[[1], [2,3]].reduce( (a,b) => a.concat(b), [] )é mais sexy.
golopot 16/07/19

5
Não precisa de segundo argumento no nosso caso simples[[1], [2,3]].reduce( (a,b) => a.concat(b))
Umair Ahmed

29

Outra solução do ECMAScript 6 em estilo funcional:

Declare uma função:

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

e use-o:

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 também uma função nativa Array.prototype.flat () (proposta para ES6) disponível nas últimas versões dos navegadores modernos. Obrigado a @ (Константин Ван) e @ (Mark Amery) o mencionaram nos comentários.

A flatfunção possui um parâmetro, especificando a profundidade esperada do aninhamento de matriz, que é igual 1por padrão.

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


3
Isso é legal e arrumado, mas acho que você fez uma overdose do ES6. Não há necessidade de a função externa ser uma função de seta. Eu ficaria com a função de seta para reduzir o retorno de chamada, mas achatar-se deveria ser uma função normal.
Stephen Simpson

3
@StephenSimpson, mas é necessário que a função externa seja uma função não -estreita? "achatar-se deveria ser uma função normal" - por "normal" você quer dizer "não flecha", mas por quê? Por que usar uma função de seta na chamada para reduzir então? Você pode fornecer sua linha de raciocínio?
Obrigado

@naomik Meu raciocínio é que é desnecessário. É principalmente uma questão de estilo; Eu deveria ter muito mais claro no meu comentário. Não há nenhum motivo importante de codificação para usar um ou outro. No entanto, é mais fácil ver e ler a função como não flecha. A função interna é útil como uma função de seta, pois é mais compacta (e nenhum contexto criado, é claro). As funções de seta são ótimas para criar uma função compacta de fácil leitura e evitar essa confusão. No entanto, eles podem realmente tornar mais difícil ler quando uma não-seta seria suficiente. Outros podem discordar!
Stephen Simpson

Obtendo umRangeError: Maximum call stack size exceeded
Matt Westlake

@ Matt, por favor, compartilhe o ambiente que você usa para reproduzir o erro
disseq

22
const common = arr.reduce((a, b) => [...a, ...b], [])

É assim que faço em todo o meu código, mas não tenho certeza de como a velocidade se compara à resposta aceita se você tiver uma matriz muito longa.
Michael Aaron Wilson

14

Observe: Quando Function.prototype.apply( [].concat.apply([], arrays)) ou o operador de dispersão ( [].concat(...arrays)) é usado para achatar uma matriz, ambos podem causar estouros de pilha para matrizes grandes, porque todos os argumentos de uma função são armazenados na pilha.

Aqui está uma implementação segura para a pilha em estilo funcional que avalia os requisitos mais importantes entre si:

  • reutilização
  • legibilidade
  • concisão
  • desempenho

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

Assim que você se acostumar com pequenas funções de seta na forma de caril, composição de funções e funções de ordem superior, esse código será exibido como prosa. A programação consiste apenas em reunir pequenos blocos de construção que sempre funcionam como esperado, porque não contêm efeitos colaterais.


1
Haha Respeite totalmente sua resposta, embora ler uma programação funcional como essa ainda seja como ler caracteres japoneses por caractere para mim (falante de inglês).
Tarwin Stroh-Spijer 3/11

2
Se você se encontra implementando recursos da linguagem A na linguagem B, não como parte do projeto, com o único objetivo de fazer exatamente isso, então alguém em algum lugar tomou a direção errada. Poderia ser você? Basta seguir const flatten = (arr) => arr.reduce((a, b) => a.concat(b), []);você economiza lixo visual e explicações para seus colegas de equipe por que você precisa de 3 funções extras e algumas chamadas de função também.
Daerdemandt

3
@Daerdemandt Mas se você o escrever como funções separadas, provavelmente poderá reutilizá-las em outro código.
Michał Perłakowski

@ MichałPerłakowski Se você precisar usá-los em vários lugares, não reinvente a roda e escolha um pacote desses - documentado e suportado por outras pessoas.
Daerdemandt

12

Achatar uma linha ES6

Consulte achatamento de lodash , achatamento de sublinhado (raso true)

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

ou

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

Testado com

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

Nivelamento profundo de uma linha ES6

Veja lodash flattenDeep , sublinhado flatten

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

Testado com

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

Seu segundo exemplo é melhor escrito, Array.prototype.concat.apply([], arr)porque você cria uma matriz extra apenas para acessar a concatfunção. Os tempos de execução podem ou não otimizá-lo quando executados, mas o acesso à função no protótipo não parece mais feio do que isso já é em qualquer caso.
Morre

11

Você pode usar Array.flat()com Infinityqualquer profundidade da matriz aninhada.

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 aqui a compatibilidade do navegador


8

Uma abordagem haskellesca

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


1
Concorde com @monners. Sem dúvida, a solução mais elegante em todo esse segmento.
Nicolás Fantone

8

ES6 maneira:

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

Maneira ES5 de flattenfunção com fallback ES3 para matrizes aninhadas N-times:

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


7

Se você tiver apenas matrizes com 1 elemento de sequência:

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

fará o trabalho. Bt que corresponde especificamente ao seu exemplo de código.


3
Quem votou, por favor, explique o porquê. Eu estava procurando uma solução decente e de todas as soluções que eu mais gostei.
Anonymous

2
@ Anonymous Não diminuí a votação, pois ela atende tecnicamente aos requisitos da pergunta, mas é provável que seja uma solução muito ruim que não é útil no caso geral. Considerando quantas soluções melhores existem aqui, eu nunca recomendaria que alguém aceitasse essa solução, pois isso quebra o momento em que você tem mais de um elemento ou quando não são strings.
Thor84no

2
Eu amo essa solução =)
Huei Tan

2
Ele não lida apenas com matrizes com 1 elemento de string, mas também com essa matriz ['$4', ["$6"], ["$12"], ["$25"], ["$25", "$33", ['$45']]].join(',').split(',')
alucic

Descobri esse método sozinho, mas sabia que ele já deveria ter sido documentado em algum lugar, minha pesquisa terminou aqui. A desvantagem com esta soluo, que coage números, booleanos, etc, para cordas, tentar [1,4, [45, 't', ['e3', 6]]].toString().split(',') ---- ----- ou [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"]

(Estou apenas escrevendo isso como uma resposta separada, com base no comentário de @danhbear.)


7

Eu recomendo uma função de gerador com economia de espaço :

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

Se desejar, crie uma matriz de valores nivelados da seguinte maneira:

let flattened = [...flatten([1,[2,[3,[4]]]])]; // [1, 2, 3, 4]

Eu gosto dessa abordagem. Semelhante ao stackoverflow.com/a/35073573/1175496 , mas usa o operador de propagação ...para percorrer o gerador.
The Red Pea

6

Prefiro transformar toda a matriz, como está, em uma string, mas, ao contrário de outras respostas, faria isso usando JSON.stringifye não usando otoString() método, que produz um resultado indesejado.

Com essa JSON.stringifysaída, tudo o que resta é remover todos os colchetes, envolver o resultado com os colchetes de início e final mais uma vez e servir o resultado com o JSON.parsequal a string volta à "vida".

  • Pode lidar com matrizes aninhadas infinitas sem custos de velocidade.
  • Pode lidar corretamente com itens de matriz que são cadeias contendo vírgulas.

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

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

console.log(flattened)

  • Somente para matriz multidimensional de cadeias / números (não objetos)

Sua solução está incorreta. Ele irá conter a vírgula quando achatamento séries internas ["345", "2", "3,4", "2"]em vez de separar cada um desses valores para separar índices
pizzarob

@realseanp - você não entendeu o valor desse item da matriz. Intencionalmente, coloquei essa vírgula como um valor e não como uma vírgula delimitadora de matriz para enfatizar o poder da minha solução acima de todas as outras, o que seria gerado "3,4".
vsync

1
Eu entendi
errado

essa parece definitivamente a solução mais rápida que eu já vi para isso; você está ciente de qualquer armadilhas @vsync (exceto o fato de que parece um hacky claro bit - tratamento de matrizes aninhadas como cordas: D)
George Katsanos

@GeorgeKatsanos - Este método não trabalho para itens de matriz (e itens aninhados) que não são de valor primitivo, por exemplo, um item que aponta para um elemento DOM
vsync

6

Você também pode tentar o novo Array.Flat()método. Funciona da seguinte maneira:

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

console.log(arr);

O flat()método cria uma nova matriz com todos os elementos da sub-matriz concatenados nela recursivamente até a 1 camada de profundidade (ou seja, matrizes dentro de matrizes)

Se você também deseja achatar matrizes tridimensionais ou dimensionais mais altas, basta chamar o método flat várias vezes. Por exemplo (3 dimensões):

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

console.log(arr);

Seja cuidadoso!

Array.Flat()O método é relativamente novo. Navegadores mais antigos, como por exemplo, podem não ter implementado o método. Se você deseja que o código funcione em todos os navegadores, pode ser necessário transpilar seu JS para uma versão mais antiga. Verifique os documentos da web do MD para obter a compatibilidade atual do navegador.


2
para arrumar matrizes dimensionais mais altas, basta chamar o método flat com Infinityargumento Assim:arr.flat(Infinity)
Mikhail

Essa é uma adição muito legal, obrigado Mikhail!
Willem van der Veen

6

Usando o operador spread:

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


5

Isso não é difícil, basta percorrer as matrizes e mesclá-las:

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

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

5

Parece que isso parece um trabalho para RECURSION!

  • Lida com vários níveis de aninhamento
  • Lida com matrizes vazias e sem parâmetros de matriz
  • Não tem mutação
  • Não depende dos recursos modernos do 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] 

cuidadoso, flatten(new Array(15000).fill([1]))lances Uncaught RangeError: Maximum call stack size exceedede congelado meus DevTools por 10 segundos
pietrovismara

@pietrovismara, meu teste é de cerca de 1s.
Ivan Yan

5

Eu fiz isso usando recursão e fechamentos

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

1
Simples e doce, esta resposta funciona melhor que a resposta aceita. Aplaina profundamente aninhados níveis para, não apenas o primeiro nível
Om Shankar

1
AFAIK que é escopo lexical e não um fecho
dashambles

@dashambles está correto - a diferença é que, se fosse um fechamento, você retornaria a função interna para o exterior e, quando a função externa estiver concluída, você ainda poderá usar a função interna para acessar seu escopo. Aqui, a vida útil da função externa é mais longa que a da função interna, portanto, um "fechamento" nunca é criado.
Morre

5

Eu estava brincando com os ES6 Generators outro dia e escrevi essa essência . Que contém ...

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

Basicamente, estou criando um gerador que faz um loop na matriz de entrada original; se encontrar uma matriz, ele usa o operador yield * em combinação com a recursão para nivelar continuamente as matrizes internas. Se o item não for uma matriz, apenas produzirá o único item. Em seguida, usando o operador ES6 Spread (também conhecido como operador splat), aplico o gerador em uma nova instância de array.

Não testei o desempenho disso, mas acho que é um bom exemplo simples de uso de geradores e do operador yield *.

Mas, novamente, eu estava apenas brincando, então tenho certeza de que existem maneiras mais eficazes de fazer isso.


1
Resposta semelhante (usando o gerador e a delegação de rendimento), mas no formato PHP
The Red Pea

5

apenas a melhor solução sem lodash

let flatten = arr => [].concat.apply([], arr.map(item => Array.isArray(item) ? flatten(item) : item))
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.