Unisci / appiattisci una matrice di array


1105

Ho un array JavaScript come:

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

Come farei per fondere le matrici interne separate in una come:

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

gist.github.com/Nishchit14/4c6a7349b3c778f7f97b912629a9f228 Questo link descrive l'appiattimento ES5 ed ES6
Nishchit Dhanani

18
Tutte le soluzioni che usano reduce+ concatsono O ((N ^ 2) / 2) dove come risposta accettata (solo una chiamata a concat) sarebbe al massimo O (N * 2) su un browser non valido e O (N) su un buono Anche la soluzione Denys è ottimizzata per la domanda attuale e fino a 2 volte più veloce della singola concat. Per la reducegente è divertente sentirsi a proprio agio nel scrivere codice minuscolo, ma per esempio se l'array avesse 1000 un elemento subarray tutte le soluzioni di riduzione + concat farebbero 500500 operazioni mentre come il singolo concat o il ciclo semplice farebbero 1000 operazioni.
Gman,

12
Semplicemente operatore di diffusione utente[].concat(...array)
Oleg Dater

@gman come si riduce + concat soluzione O ((N ^ 2) / 2)? stackoverflow.com/questions/52752666/… menziona una complessità diversa.
Usman,

4
Con gli ultimi browser che supportano ES2019 : array.flat(Infinity)dov'è Infinityla massima profondità da appiattire.
Timothy Gu,

Risposte:


1860

È possibile utilizzare concatper unire le matrici:

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

console.log(merged);

Utilizzando il applymetodo di concatprenderai semplicemente il secondo parametro come un array, quindi l'ultima riga è identica a questa:

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

Esiste anche il Array.prototype.flat()metodo (introdotto in ES2019) che è possibile utilizzare per appiattire le matrici, sebbene sia disponibile solo in Node.js a partire dalla versione 11 e non in 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
Si noti che concatnon modifica l'array di origine, quindi l' mergedarray rimarrà vuoto dopo la chiamata a concat. Meglio dire qualcosa del genere:merged = merged.concat.apply(merged, arrays);
Nate

65
var merged = [].concat.apply([], arrays);sembra funzionare bene per farlo su una riga. modifica: come già mostra la risposta di Nikita.
Sean,

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

30
Nota: questa risposta appiattisce solo un livello in profondità. Per un appiattimento ricorsivo, vedere la risposta di @Trindaz.
Phrogz,

209
A seguito del commento di @ Sean: la sintassi ES6 rende questo conciso:var merged = [].concat(...arrays)
Sethi

505

Ecco una breve funzione che utilizza alcuni dei più recenti metodi di array JavaScript per appiattire un array n-dimensionale.

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
Mi piace questo approccio. È molto più generale e supporta array nidificati
alfredocambera,

10
Qual è il profilo di utilizzo della memoria per questa soluzione? Sembra che crei molte matrici intermedie durante la ricorsione della coda ....
JBRWilkinson,

7
@ayjay, è il valore iniziale dell'accumulatore per la funzione di riduzione, ciò che mdn chiama il valore iniziale. In questo caso è il valore di flatnella prima chiamata alla funzione anonima passata a reduce. Se non è specificato, la prima chiamata reducelega il primo valore fuori della matrice flat, che sarebbe poi risultato 1essere legati ad flatentrambi gli esempi. 1.concatnon è una funzione.
Noah Freitas,

19
O in una forma più breve e più sexy: const flatten = (arr) => arr.reduce((flat, next) => flat.concat(next), []);
Tsvetomir Tsonev

12
Riffing sulle soluzioni di @TsvetomirTsonev e Noah per la nidificazione arbitraria:const flatten = (arr) => arr.reduce((flat, next) => flat.concat(Array.isArray(next) ? flatten(next) : next), []);
Will

314

Esiste un metodo confusamente nascosto, che costruisce un nuovo array senza mutare quello originale:

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


11
In CoffeeScript questo è[].concat([[1],[2,3],[4]]...)
amoebe il

8
@amoebe [[1],[2,3],[4]]come risultato la tua risposta . La soluzione fornita da @Nikita è corretta per CoffeeScript e JS.
Ryan Kennedy,

5
Ahh, ho trovato il tuo errore. Hai una coppia extra di parentesi quadre nella tua notazione, dovrebbe essere [].concat([1],[2,3],[4],...).
Ryan Kennedy,

21
Penso di aver trovato anche il tuo errore. Il ...sono il codice vero e proprio, non alcuni puntini puntini di sospensione.
amebe,

3
L'uso di una funzione di libreria non significa che ci sia un "disordine" meno imperativo. Solo che il casino è nascosto all'interno di una libreria. Ovviamente usare una libreria è meglio che implementare la propria funzione, ma affermare che questa tecnica riduce il "caos imperativo" è piuttosto sciocco.
paldepind

204

Può essere fatto meglio con la funzione di riduzione 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);
}, []);

Oppure, con ES2015:

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

js-violino

Documenti Mozilla


1
@JohnS Riduci effettivamente i feed concat un singolo valore (array) per turno. Prova? togliere ridurre () e vedere se funziona. ridurre () ecco un'alternativa a Function.prototype.apply ()
André Werlang,

3
Avrei usato anche ridurre, ma una cosa interessante che ho imparato da questo frammento è che il più delle volte non è necessario passare un valore iniziale :)
José F. Romaniello,

2
Il problema con ridurre è che l'array non può essere vuoto, quindi sarà necessario aggiungere una convalida aggiuntiva.
Calbertts,

4
@calbertts Basta passare il valore iniziale []e non sono necessarie ulteriori convalide.

8
Dato che usi ES6, puoi anche usare l'operatore spread come array letterale . arrays.reduce((flatten, arr) => [...flatten, ...arr])
Putzi San

109

C'è un nuovo metodo nativo chiamato flat per fare esattamente questo.

(A partire dalla fine del 2019, flatè ora pubblicato nello standard ECMA 2019 e core-js@3(la biblioteca di babel) lo include nella loro libreria 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
È un peccato che non sia nemmeno nella prima pagina di risposte. Questa funzione è disponibile in Chrome 69 e Firefox 62 (e Nodo 11 per coloro che lavorano nel back-end)
Matt M.


2
-1; no, questo non fa parte di ECMAScript 2018 . È ancora solo una proposta che non è arrivata a nessuna specifica ECMAScript.
Mark Amery,

1
Penso che ora possiamo considerare questo .. perché ora fa parte dello standard (2019) .. possiamo rivisitare la parte delle prestazioni di questo una volta?
Yuvaraj,

Sembra che non sia ancora supportato da nessun browser Microsoft (almeno al momento in cui scrivo questo commento)
Laurent S.

78

La maggior parte delle risposte qui non funziona su matrici enormi (ad esempio 200000 elementi) e, anche se lo fanno, sono lente. La risposta di polkovnikov.ph ha le migliori prestazioni, ma non funziona per l'appiattimento profondo.

Ecco la soluzione più veloce, che funziona anche su array con più livelli di annidamento :

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

Esempi

Matrici enormi

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

Gestisce enormi array proprio bene. Sulla mia macchina questo codice impiega circa 14 ms per essere eseguito.

Matrici nidificate

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

Funziona con array nidificati. Questo codice produce [1, 1, 1, 1, 1, 1, 1, 1].

Matrici con diversi livelli di nidificazione

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

Non ha problemi con appiattimenti di array come questo.


Solo che il tuo enorme array è piuttosto piatto. Questa soluzione non funziona con array nidificati in profondità. Nessuna soluzione ricorsiva lo farà. In realtà nessun browser, ma Safari ha TCO in questo momento, quindi nessun algoritmo ricorsivo funzionerà bene.
nettamente il

@nitely Ma in quale situazione del mondo reale avresti array con più di qualche livello di annidamento?
Michał Perłakowski,

2
Di solito, quando l'array viene generato dal contenuto generato dall'utente.
nettamente il

@ 0xcaff In Chrome non funziona affatto con un array di 200000 elementi (ottieni RangeError: Maximum call stack size exceeded). Per un array di 20.000 elementi sono necessari 2-5 millisecondi.
Michał Perłakowski,

3
qual è la complessità della notazione O di questo?
George Katsanos,

58

Aggiornamento: si è scoperto che questa soluzione non funziona con array di grandi dimensioni. Se stai cercando una soluzione migliore e più veloce, dai un'occhiata a questa risposta .


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

Si espande arre lo passa semplicemente come argomenti a concat(), che unisce tutti gli array in uno solo. È equivalente a [].concat.apply([], arr).

Puoi anche provare questo per l'appiattimento profondo:

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

Guarda la demo su JSBin .

Riferimenti per ECMAScript 6 elementi utilizzati in questa risposta:


Nota a margine: i metodi come find()e le funzioni freccia non sono supportati da tutti i browser, ma ciò non significa che al momento non è possibile utilizzare queste funzionalità. Usa Babel : trasforma il codice ES6 in ES5.


Poiché quasi tutte le risposte qui vengono utilizzate applyin questo modo, ho rimosso i miei commenti dai tuoi. Penso ancora che usare apply/ diffondere in questo modo sia un cattivo consiglio, ma poiché a nessuno importa ...

@ LUH3417 Non è così, apprezzo molto i tuoi commenti. Si è scoperto che hai ragione: questa soluzione in effetti non funziona con array di grandi dimensioni. Ho pubblicato un'altra risposta che funziona benissimo anche con array di 200000 elementi.
Michał Perłakowski,

Se si utilizza ES6, è possibile const flatten = arr => [].concat(...arr)
ridurre

Cosa intendi con "non funziona con array di grandi dimensioni"? Quanto largo? Che succede?
GEMI

4
@GEMI Ad esempio, il tentativo di appiattire un array di 500000 elementi utilizzando questo metodo fornisce "RangeError: dimensione massima dello stack di chiamate superata".
Michał Perłakowski,


41

Le procedure generiche indicano che non è necessario riscrivere la complessità ogni volta che è necessario utilizzare un comportamento specifico.

concatMap (o flatMap ) è esattamente ciò di cui abbiamo bisogno in questa situazione.

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

lungimiranza

E sì, l'hai indovinato correttamente, si appiattisce solo un livello, ed è esattamente come dovrebbe funzionare

Immagina alcuni set di dati come questo

// 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, ora diciamo che vogliamo stampare un elenco che mostra tutti i giocatori a cui parteciperanno game ...

const gamePlayers = game =>
  flatten (game)

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

Se nostro flatten procedura appiattisse le matrici nidificate, finiremmo con questo risultato spazzatura ...

const gamePlayers = game =>
  badGenericFlatten(game)

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

rotolando in profondità, piccola

Questo non vuol dire che a volte non si desidera appiattire anche le matrici nidificate - solo che non dovrebbe essere il comportamento predefinito.

Possiamo fare una deepFlattenprocedura con facilità ...

// 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à. Ora hai uno strumento per ogni lavoro: uno per schiacciare un livello di annidamento,flatten e uno per cancellare tutto l'annidamento deepFlatten.

Forse puoi chiamarlo obliterateo nukese non ti piace il nome deepFlatten.


Non ripetere due volte!

Naturalmente le implementazioni di cui sopra sono intelligenti e concise, ma usando a .map seguito da una chiamata .reducesignifica che stiamo effettivamente facendo più iterazioni del necessario

Usando un fidato combinatore che sto chiamando mapReduceaiuta a mantenere le iterazioni su un minimo; prende una funzione di mappatura m :: a -> b, una funzione di riduzione r :: (b,a) ->be restituisce una nuova funzione di riduzione: questo combinatore è al centro dei trasduttori ; se sei interessato, ho scritto altre risposte su di loro

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


Spesso, quando vedo le tue risposte, desidero ritirare le mie, perché sono diventate inutili. Bella risposta! concatstesso non fa esplodere la pila, solo ...e lo applyfa (insieme a matrici molto grandi). Non l'ho visto Mi sento davvero male adesso.

@ LUH3417 non dovresti sentirti terribile. Fornisci anche buone risposte con spiegazioni ben scritte. Tutto qui è un'esperienza di apprendimento - spesso commetto errori e scrivo anche risposte sbagliate. Li rivisito tra un paio di mesi e penso "wtf stavo pensando?" e finiscono per rivedere completamente le cose. Nessuna risposta è perfetta. Siamo tutti ad un certo punto su una curva di apprendimento ^ _ ^
Grazie

1
Si noti che concatin Javascript ha un significato diverso rispetto a Haskell. Haskell concat( [[a]] -> [a]) sarebbe chiamato flattenin Javascript ed è implementato come foldr (++) [](Javascript: foldr(concat) ([])assumendo funzioni al curry). Javascript concatè un'appendice strana ( (++)in Haskell), che può gestire sia [a] -> [a] -> [a]e che a -> [a] -> [a].

Immagino che un nome migliore fosse flatMap, perché è esattamente quello che concatMapè: l' bindistanza della listmonade. concatpMapè implementato come foldr ((++) . f) []. Tradotto in Javascript: const flatMap = f => foldr(comp(concat) (f)) ([]). Questo è ovviamente simile all'implementazione senza comp.

qual è la complessità di quell'algoritmo?
George Katsanos,

32

Una soluzione per il caso più generale, quando potresti avere alcuni elementi non array nel tuo array.

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
Questo approccio è stato molto efficace nell'appiattimento della forma di array nidificata di set di risultati ottenuti da una query JsonPath .
kevinjansz,

1
Aggiunto come metodo di array: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

Questo si interromperà se passiamo manualmente al secondo argomento. Ad esempio, prova questo:flattenArrayOfArrays (arr, 10) o questo flattenArrayOfArrays(arr, [1,[3]]);: questi secondi argomenti vengono aggiunti all'output.
Om Shankar,

2
questa risposta non è completa! il risultato della ricorsione non viene mai assegnato da nessuna parte, quindi il risultato della ricorsione viene perso
r3wt

1
@ r3wt è andato avanti e ha aggiunto una correzione. Quello che devi fare è assicurarti che l'array corrente che stai mantenendo, rconcatenerà effettivamente i risultati della ricorsione.
agosto

29

Per appiattire una matrice di matrici a singolo elemento, non è necessario importare una libreria, un semplice ciclo è il più semplice ed efficiente soluzione :

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

Ai votanti: leggi la domanda, non votare perché non si adatta al tuo problema molto diverso. Questa soluzione è sia la più veloce sia la più semplice per la domanda posta.


4
Direi "Non cercare cose più criptiche". ^^

4
Voglio dire ... quando vedo persone che consigliano di usare una biblioteca per quello ... è pazzesco ...
Denys Séguret,

3
Ah, usare una libreria solo per questo sarebbe sciocco ... ma se è già disponibile.

9
@dystroy La parte condizionale del ciclo for non è così leggibile. O sono solo io: D
Andreas,

4
Non importa quanto sia criptico. Questo codice "appiattisce" questo ['foo', ['bar']]a ['f', 'bar'].
jonschlinkert,

29

Che ne dici di usare il reduce(callback[, initialValue])metodo diJavaScript 1.8

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

Farebbe il lavoro.


8
[[1], [2,3]].reduce( (a,b) => a.concat(b), [] )è più sexy.
Golopot

5
Non è necessario un secondo argomento nel nostro caso semplice[[1], [2,3]].reduce( (a,b) => a.concat(b))
Umair Ahmed il

29

Un'altra soluzione ECMAScript 6 in stile funzionale:

Dichiara una funzione:

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

e usalo:

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

Considera anche una funzione nativa Array.prototype.flat () (proposta per ES6) disponibile nelle ultime versioni dei browser moderni.Grazie a @ (Константин Ван) e @ (Mark Amery) lo hanno menzionato nei commenti.

La flatfunzione ha un parametro, che specifica la profondità prevista dell'annidamento dell'array, che è uguale 1per impostazione predefinita.

[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
Questo è bello e pulito ma penso che tu abbia fatto un sovradosaggio di ES6. Non è necessario che la funzione esterna sia una funzione freccia. Attaccherei con la funzione freccia per ridurre la richiamata, ma l'appiattimento dovrebbe essere una funzione normale.
Stephen Simpson,

3
@StephenSimpson ma è necessario che la funzione esterna sia una funzione non stretta? "appiattirsi dovrebbe essere una funzione normale" - per "normale" intendi "non freccia", ma perché? Perché utilizzare una funzione freccia nella chiamata per ridurla? Potete fornire il vostro ragionamento?
Grazie

@naomik Il mio ragionamento è che non è necessario. È principalmente una questione di stile; Dovrei avere molto più chiaro nel mio commento. Non esiste un motivo di codifica importante per utilizzare l'uno o l'altro. Tuttavia, la funzione è più facile da vedere e leggere come non freccia. La funzione interna è utile come funzione freccia in quanto è più compatta (e nessun contesto creato ovviamente). Le funzioni della freccia sono grandi per creare la funzione compatta di facile lettura ed evitare questa confusione. Tuttavia, possono effettivamente rendere più difficile la lettura quando una non freccia sarebbe sufficiente. Altri potrebbero non essere d'accordo!
Stephen Simpson,

Ottenere unRangeError: Maximum call stack size exceeded
Matt Westlake

@Matt, ti preghiamo di condividere lo sviluppo che usi per riprodurre l'errore
diziaq

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

Ecco come lo faccio attraverso il mio codice ma non sono sicuro di come la velocità possa essere paragonata alla risposta accettata se hai un array molto lungo
Michael Aaron Wilson,

14

Nota: quando Function.prototype.apply( [].concat.apply([], arrays)) o l'operatore di diffusione ([].concat(...arrays) ) viene utilizzato per appiattire un array, entrambi possono causare overflow dello stack per array di grandi dimensioni, poiché ogni argomento di una funzione viene archiviato nello stack.

Ecco un'implementazione sicura dello stack in stile funzionale che soppesa i requisiti più importanti l'uno rispetto all'altro:

  • riutilizzabilità
  • leggibilità
  • concisione
  • prestazione

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

Non appena ti abitui alle piccole funzioni a forma di freccia in forma curry, composizione delle funzioni e funzioni di ordine superiore, questo codice si legge come in prosa. La programmazione quindi consiste semplicemente nel mettere insieme piccoli blocchi che funzionano sempre come previsto, perché non contengono effetti collaterali.


1
Haha. Rispetta totalmente la tua risposta, anche se leggere una programmazione funzionale come questa è ancora come leggere un personaggio giapponese per carattere (madrelingua inglese).
Tarwin Stroh-Spijer,

2
Se ti accorgi di implementare funzionalità della lingua A nella lingua B non come parte del progetto con l'unico obiettivo di fare esattamente questo, qualcuno da qualche parte ha preso una svolta sbagliata. Potresti essere tu? Basta andare con const flatten = (arr) => arr.reduce((a, b) => a.concat(b), []);salva la spazzatura visiva e la spiegazione ai tuoi compagni di squadra perché hai bisogno di 3 funzioni extra e anche alcune chiamate di funzione.
Daerdemandt,

3
@Daerdemandt Ma se lo scrivi come funzioni separate, probabilmente sarai in grado di riutilizzarli in altri codici.
Michał Perłakowski,

@ MichałPerłakowski Se devi usarli in più punti, non reinventare la ruota e scegliere un pacchetto tra questi - documentato e supportato da altre persone.
Daerdemandt,

12

ES6 One Line Appiattire

Vedi lodash appiattire , sottolineare appiattire (superficiale true)

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

o

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

Testato 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

Vedi lodash flatten Deep , underscore appiattire

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

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

Il tuo secondo esempio è scritto meglio Array.prototype.concat.apply([], arr)perché crei un array extra solo per accedere alla concatfunzione. I runtime possono o meno ottimizzarlo quando lo eseguono, ma l'accesso alla funzione sul prototipo non sembra più brutto di quanto non lo sia già in ogni caso.
Mörre,

11

È possibile utilizzare Array.flat()con Infinityper qualsiasi profondità dell'array nidificato.

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)

controlla qui per la compatibilità del browser


8

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


1
Accetto con @monners. Giù le mani sulla soluzione più elegante in tutto questo thread.
Nicolás Fantone,

8

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

Modo ES5 per la flattenfunzione con fallback ES3 per array nidificati N-volte:

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 si dispone solo di matrici con 1 elemento stringa:

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

farà il lavoro. Bt che corrisponde specificamente al tuo esempio di codice.


3
Chiunque abbia votato, spiega perché. Ero alla ricerca di una soluzione decente e di tutte le soluzioni che mi sono piaciute di più di questa.
Anonimo il

2
@Anonimamente non l'ho votato a fondo dato che tecnicamente soddisfa i requisiti della domanda, ma è probabile perché questa è una soluzione piuttosto scadente che non è utile nel caso generale. Considerando quante soluzioni migliori ci sono qui, non consiglierei mai a nessuno di scegliere questo perché si rompe nel momento in cui hai più di un elemento o quando non sono stringhe.
Thor84no,

2
Adoro questa soluzione =)
Huei Tan

2
Non gestisce solo array con 1 elementi stringa, ma gestisce anche questo array ['$4', ["$6"], ["$12"], ["$25"], ["$25", "$33", ['$45']]].join(',').split(',')
alucico

Ho scoperto questo metodo da solo, ma sapevo che doveva essere già stato documentato da qualche parte, la mia ricerca è finita qui. Lo svantaggio di questa soluzione è che costringe numeri, booleani ecc. Alle stringhe, prova [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"]

(Sto solo scrivendo questo come una risposta separata, basata sul commento di @danhbear.)


7

Raccomando una funzione di generatore di spazio ridotto :

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 lo si desidera, creare una matrice di valori appiattiti come segue:

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

Mi piace questo approccio. Simile a stackoverflow.com/a/35073573/1175496 , ma utilizza l'operatore di diffusione ...per scorrere il generatore.
The Red Pea,

6

Preferirei trasformare l'intero array, così com'è, in una stringa, ma a differenza di altre risposte, lo farei usando JSON.stringifye non usando iltoString() metodo, che produce un risultato indesiderato.

Con JSON.stringifyquell'output, tutto ciò che rimane è rimuovere tutte le parentesi, avvolgere nuovamente il risultato con parentesi iniziali e finali e servire il risultato con JSON.parsecui riporta la stringa in "vita".

  • Può gestire array nidificati infiniti senza costi di velocità.
  • Può gestire correttamente gli elementi dell'array che sono stringhe contenenti virgole.

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

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

console.log(flattened)

  • Solo per array multidimensionali di stringhe / numeri (non oggetti)

La tua soluzione non è corretta ["345", "2", "3,4", "2"]
Conterrà

@realseanp - hai frainteso il valore di quell'elemento Array. Ho messo intenzionalmente quella virgola come valore e non come virgola delimitatore di array per enfatizzare il potere della mia soluzione su tutte le altre, che sarebbe uscita "3,4".
vsync,

1
Ho frainteso
pizzarob il

sembra sicuramente la soluzione più veloce che abbia mai visto per questo; sei a conoscenza di eventuali insidie ​​@vsync (tranne per il fatto che sembra un po 'confuso ovviamente - trattando gli array nidificati come stringhe: D)
George Katsanos,

@GeorgeKatsanos - Questo metodo non funzionerà per gli elementi dell'array (e gli oggetti nidificati) che non hanno un valore di Primitive, ad esempio un elemento che punta a un elemento DOM
vsync,

6

Puoi anche provare il nuovo Array.Flat()metodo. Funziona nel modo seguente:

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

console.log(arr);

Il flat() metodo crea un nuovo array con tutti gli elementi del sub-array concatenati in esso ricorsivamente fino al livello 1 di profondità (ovvero array all'interno di array)

Se si desidera appiattire anche array tridimensionali o anche di dimensioni superiori, è sufficiente chiamare il metodo flat più volte. Ad esempio (3 dimensioni):

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

console.log(arr);

Stai attento!

Array.Flat()il metodo è relativamente nuovo. I browser meno recenti come ie potrebbero non aver implementato il metodo. Se vuoi che il tuo codice funzioni su tutti i browser, potresti dover compilare il tuo JS in una versione precedente. Controlla i documenti Web MD per la compatibilità del browser corrente.


2
per matrici di dimensioni superiori piatte è possibile semplicemente chiamare il metodo flat con Infinityargomento. In questo modo:arr.flat(Infinity)
Mikhail il

È una bella aggiunta, grazie Mikhail!
Willem van der Veen,

6

Utilizzando l'operatore 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

Non è difficile, basta scorrere le matrici e unirle:

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

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

5

Sembra che questo sembra un lavoro per RECURSION!

  • Gestisce più livelli di annidamento
  • Gestisce array vuoti e parametri non array
  • Non ha mutazione
  • Non si basa sulle moderne funzionalità del browser

Codice:

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] 

attento, flatten(new Array(15000).fill([1]))getta Uncaught RangeError: Maximum call stack size exceedede congela il mio devTools per 10 secondi
pietrovismara,

@pietrovismara, il mio test è di circa 1 secondo.
Ivan Yan,

5

L'ho fatto usando ricorsione e chiusure

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
Semplice e dolce, questa risposta funziona meglio della risposta accettata. Appiattisce i livelli profondamente annidati, non solo al primo livello
Om Shankar,

1
AFAIK che è scoping lessicale e non una chiusura
dashambles

@dashambles è corretto - la differenza è che se si trattasse di una chiusura, si restituirebbe la funzione interna all'esterno e al termine della funzione esterna è comunque possibile utilizzare la funzione interna per accedere al suo ambito. Qui la durata della funzione esterna è più lunga di quella della funzione interna, pertanto non viene mai creata una "chiusura".
Mörre,

5

L' altro giorno stavo scherzando con i generatori ES6 e ho scritto questo riassunto . Che 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);

Fondamentalmente sto creando un generatore che si sovrappone all'array di input originale, se trova un array utilizza l' operatore yield * in combinazione con la ricorsione per appiattire continuamente gli array interni. Se l'articolo non è un array, produce solo il singolo articolo. Quindi utilizzando l' operatore ES6 Spread (noto anche come operatore splat) appiattisco il generatore in una nuova istanza di array.

Non ho testato le prestazioni di questo, ma immagino che sia un semplice esempio di utilizzo dei generatori e dell'operatore yield *.

Ma di nuovo, stavo solo scherzando, quindi sono sicuro che ci sono modi più performanti per farlo.


1
Risposta simile (utilizzando generatore e delega di rendimento), ma in formato PHP
The Red Pea,

5

solo la migliore soluzione senza lodash

let flatten = arr => [].concat.apply([], arr.map(item => Array.isArray(item) ? flatten(item) : item))
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.