Ein Array von Arrays zusammenführen / reduzieren


1104

Ich habe ein JavaScript-Array wie:

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

Wie würde ich vorgehen, um die einzelnen inneren Arrays zu einem zusammenzuführen:

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

gist.github.com/Nishchit14/4c6a7349b3c778f7f97b912629a9f228 Dieser Link beschreibt die Abflachung von ES5 und ES6
Nishchit Dhanani

17
Alle Lösungen, die reduce+ verwenden, concatsind O ((N ^ 2) / 2), wobei als akzeptierte Antwort (nur ein Aufruf an concat) höchstens O (N * 2) in einem schlechten Browser und O (N) in a gut. Auch Denys Lösung ist für die eigentliche Frage optimiert und bis zu 2x schneller als die einzelne concat. Für die reduceLeute macht es Spaß, sich beim Schreiben von winzigem Code cool zu fühlen, aber wenn das Array beispielsweise 1000 Subarrays mit einem Element hätte, würden alle Reduce + Concat-Lösungen 500500 Operationen ausführen, während die einzelne Concat- oder einfache Schleife 1000 Operationen ausführen würde.
Gman

12
Einfach User Spread Operator[].concat(...array)
Oleg Dater

@gman wie ist redu + concat Lösung O ((N ^ 2) / 2)? stackoverflow.com/questions/52752666/… erwähnt eine andere Komplexität.
Usman

4
Mit den neuesten Browsern, die ES2019 unterstützen : array.flat(Infinity)Wo Infinityist die maximale Tiefe zum Abflachen?
Timothy Gu

Antworten:


1860

Sie können concatArrays zusammenführen:

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

console.log(merged);

Bei Verwendung der applyMethode von concatwird nur der zweite Parameter als Array verwendet, sodass die letzte Zeile mit dieser identisch ist:

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

Es gibt auch die Array.prototype.flat()in ES2019 eingeführte Methode, mit der Sie die Arrays reduzieren können, obwohl sie nur in Node.js ab Version 11 und überhaupt nicht in Internet Explorer verfügbar ist .

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
Beachten Sie, dass das concatQuellarray nicht geändert wird, sodass das mergedArray nach dem Aufruf von leer bleibt concat. Besser etwas zu sagen wie:merged = merged.concat.apply(merged, arrays);
Nate

65
var merged = [].concat.apply([], arrays);scheint gut zu funktionieren, um es in eine Zeile zu bekommen. edit: wie Nikitas Antwort schon zeigt.
Sean

44
Oder Array.prototype.concat.apply([], arrays).
Danhbear

30
Hinweis: Diese Antwort glättet nur eine Ebene tiefer. Eine rekursive Abflachung finden Sie in der Antwort von @Trindaz.
Phrogz

209
Weiter zu @ Seans Kommentar: Die ES6-Syntax macht dies super prägnant:var merged = [].concat(...arrays)
Sethi

505

Hier ist eine kurze Funktion, die einige der neueren JavaScript-Array-Methoden verwendet, um ein n-dimensionales Array zu reduzieren.

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

Verwendungszweck:

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
Ich mag diesen Ansatz. Es ist viel allgemeiner und unterstützt verschachtelte Arrays
Alfredocambera

10
Wie lautet das Speichernutzungsprofil für diese Lösung? Sieht so aus, als würde es während der Schwanzrekursion viele Zwischenarrays
erzeugen

7
@ayjay, es ist der Startakkumulatorwert für die Reduktionsfunktion, was mdn den Anfangswert nennt. In diesem Fall ist dies der Wert von flatbeim ersten Aufruf der an übergebenen anonymen Funktion reduce. Wenn es nicht angegeben wird, reducebindet der erste Aufruf zum Binden den ersten Wert aus dem Array an flat, was schließlich dazu führen würde, 1dass er flatin beiden Beispielen gebunden wird . 1.concatist keine Funktion.
Noah Freitas

19
Oder in einer kürzeren, sexier Form: const flatten = (arr) => arr.reduce((flat, next) => flat.concat(next), []);
Tsvetomir Tsonev

12
Riffing auf @TsvetomirTsonev und Noahs Lösungen für willkürliches Verschachteln:const flatten = (arr) => arr.reduce((flat, next) => flat.concat(Array.isArray(next) ? flatten(next) : next), []);
Will

314

Es gibt eine verwirrend versteckte Methode, die ein neues Array erstellt, ohne das ursprüngliche zu mutieren:

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


11
In CoffeeScript ist dies[].concat([[1],[2,3],[4]]...)
Amöbe

8
@amoebe Ihre Antwort gibt [[1],[2,3],[4]]als Ergebnis. Die von @Nikita angebotene Lösung ist sowohl für CoffeeScript als auch für JS korrekt.
Ryan Kennedy

5
Ahh, ich habe deinen Fehler gefunden. Sie haben ein zusätzliches Paar eckige Klammern in Ihrer Notation, sollte sein [].concat([1],[2,3],[4],...).
Ryan Kennedy

21
Ich glaube, ich habe auch Ihren Fehler gefunden. Das ...ist tatsächlicher Code, nicht einig Auslassungspunkte.
Amöbe

3
Die Verwendung einer Bibliotheksfunktion bedeutet nicht, dass es weniger zwingendes "Durcheinander" gibt. Nur dass das Chaos in einer Bibliothek versteckt ist. Natürlich ist die Verwendung einer Bibliothek besser als das Rollen Ihrer eigenen Funktion, aber zu behaupten, dass diese Technik das "zwingende Durcheinander" reduziert, ist ziemlich dumm.
Paldepind

204

Dies kann am besten mit der Javascript-Reduktionsfunktion erfolgen.

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

Oder mit ES2015:

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

js-Geige

Mozilla-Dokumente


1
@JohnS Reduziere tatsächlich die Feeds auf concat um einen einzelnen (Array-) Wert pro Runde. Beweis? Nehmen Sie redu () heraus und prüfen Sie, ob es funktioniert. reduzieren () hier ist eine Alternative zu Function.prototype.apply ()
André Werlang

3
Ich hätte auch Reduzieren verwendet, aber eine interessante Sache, die ich aus diesem Snippet gelernt habe, ist, dass Sie die meiste Zeit keinen Anfangswert übergeben müssen :)
José F. Romaniello

2
Das Problem bei der Reduzierung besteht darin, dass das Array nicht leer sein kann, sodass Sie eine zusätzliche Validierung hinzufügen müssen.
Calbertts

4
@calbertts Übergeben Sie einfach den Anfangswert []und es sind keine weiteren Validierungen erforderlich.

8
Da Sie ES6 verwenden, können Sie den Spread-Operator auch als Array-Literal verwenden . arrays.reduce((flatten, arr) => [...flatten, ...arr])
Putzi San

109

Es gibt eine neue native Methode namens flat , um dies genau zu tun.

(Ab Ende 2019 flatist es jetzt im ECMA 2019-Standard veröffentlicht und core-js@3(Babels Bibliothek) enthält es in ihrer Polyfill- Bibliothek. )

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
Es ist eine Schande, dass dies nicht einmal auf der ersten Seite der Antworten steht. Diese Funktion ist in Chrome 69 und Firefox 62 (und Knoten 11 für diejenigen, die im Backend arbeiten) verfügbar
Matt M.


2
-1; Nein, dies ist nicht Teil von ECMAScript 2018 . Es ist immer noch nur ein Vorschlag, der es zu keiner ECMAScript-Spezifikation geschafft hat.
Mark Amery

1
Ich denke, jetzt können wir das in Betracht ziehen. Weil es jetzt Teil des Standards (2019) ist. Können wir den Leistungsteil davon einmal wiederholen?
Yuvaraj

Es scheint, dass es noch von keinem Microsoft-Browser unterstützt wird (zumindest zu dem Zeitpunkt, als ich diesen Kommentar schreibe)
Laurent S.

78

Die meisten Antworten hier funktionieren nicht auf großen Arrays (z. B. 200 000 Elemente), und selbst wenn dies der Fall ist, sind sie langsam. Die Antwort von polkovnikov.ph hat die beste Leistung, funktioniert jedoch nicht bei tiefer Abflachung.

Hier ist die schnellste Lösung, die auch auf Arrays mit mehreren Verschachtelungsebenen funktioniert :

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

Beispiele

Riesige Arrays

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

Es handhabt große Arrays ganz gut. Auf meinem Computer dauert die Ausführung dieses Codes ca. 14 ms.

Verschachtelte Arrays

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

Es funktioniert mit verschachtelten Arrays. Dieser Code erzeugt [1, 1, 1, 1, 1, 1, 1, 1].

Arrays mit unterschiedlichen Verschachtelungsebenen

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

Es gibt keine Probleme mit dem Reduzieren von Arrays wie diesem.


Nur dass Ihr riesiges Array ziemlich flach ist. Diese Lösung funktioniert nicht für tief verschachtelte Arrays. Keine rekursive Lösung wird. Tatsächlich hat derzeit kein Browser außer Safari TCO, sodass kein rekursiver Algorithmus eine gute Leistung erbringt.
Endlich

@nitely Aber in welcher realen Situation hätten Sie Arrays mit mehr als ein paar Verschachtelungsebenen?
Michał Perłakowski

2
Normalerweise, wenn das Array aus benutzergenerierten Inhalten generiert wird.
Endlich

@ 0xcaff In Chrome funktioniert es überhaupt nicht mit einem Array mit 200.000 Elementen (Sie erhalten RangeError: Maximum call stack size exceeded). Für ein Array mit 20 000 Elementen dauert es 2-5 Millisekunden.
Michał Perłakowski

3
Was ist die Komplexität der O-Notation?
George Katsanos

58

Update: Es stellte sich heraus, dass diese Lösung nicht mit großen Arrays funktioniert. Wenn Sie nach einer besseren und schnelleren Lösung suchen, lesen Sie diese Antwort .


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

Is erweitert einfach arrund übergibt es als Argumente an concat(), wodurch alle Arrays zu einem zusammengeführt werden. Es ist gleichbedeutend mit [].concat.apply([], arr).

Sie können dies auch für eine tiefe Abflachung versuchen:

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

Siehe Demo auf JSBin .

Referenzen für ECMAScript 6-Elemente, die in dieser Antwort verwendet werden:


Randnotiz: Methoden wie find()und Pfeilfunktionen werden nicht von allen Browsern unterstützt, dies bedeutet jedoch nicht, dass Sie diese Funktionen derzeit nicht verwenden können. Verwenden Sie einfach Babel - es wandelt ES6-Code in ES5 um.


Da fast alle Antworten hier applyauf diese Weise missbraucht werden, habe ich meine Kommentare aus Ihren entfernt. Ich denke immer noch, dass die Verwendung apply/ Verbreitung auf diese Weise ein schlechter Rat ist, aber da es niemanden interessiert ...

@ LUH3417 Es ist nicht so, ich schätze deine Kommentare sehr. Es stellte sich heraus, dass Sie Recht haben - diese Lösung funktioniert in der Tat nicht mit großen Arrays. Ich habe eine weitere Antwort gepostet , die auch mit Arrays mit 200 000 Elementen gut funktioniert.
Michał Perłakowski

Wenn Sie ES6 verwenden, können Sie weiter reduzieren auf:const flatten = arr => [].concat(...arr)
Eruant

Was meinst du mit "funktioniert nicht mit großen Arrays"? Wie groß? Was geschieht?
GEMI

4
@GEMI Wenn Sie beispielsweise versuchen, ein Array mit 500000 Elementen mit dieser Methode zu reduzieren, wird "RangeError: Maximale Aufrufstapelgröße überschritten" angezeigt.
Michał Perłakowski

51

Sie können Unterstrich verwenden :

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

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

20
Whoah, hat jemand Perl zu JS hinzugefügt? :-)
JBRWilkinson

9
@JBRWilkinson Vielleicht möchte JS dir ein Haskell zum Wohle des Guten beibringen!?
Styfle

1+ - Sie können auch angeben, dass Sie ein flach abgeflachtes Array möchten, indem Sie truefür das zweite Argument angeben .
Josh Crozier

7
Der Vollständigkeit halber bezüglich des Kommentars von @ styfle: learnyouahaskell.com
Simon A. Eugster

41

Generische Prozeduren bedeuten, dass wir die Komplexität nicht jedes Mal neu schreiben müssen, wenn wir ein bestimmtes Verhalten verwenden müssen.

concatMap(oder flatMap) ist genau das, was wir in dieser Situation brauchen.

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

Voraussicht

Und ja, Sie haben es richtig erraten, es glättet nur eine Ebene, genau so sollte es funktionieren

Stellen Sie sich einen solchen Datensatz vor

// 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, jetzt sagen wir, wir möchten eine Liste drucken, in der alle Spieler aufgeführt sind, die teilnehmen werden game.

const gamePlayers = game =>
  flatten (game)

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

Wenn unsere flattenProzedur auch verschachtelte Arrays reduzieren würde, würden wir dieses Müllergebnis erhalten ...

const gamePlayers = game =>
  badGenericFlatten(game)

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

rollt tief, Baby

Das heißt nicht, dass Sie manchmal auch nicht verschachtelte Arrays reduzieren möchten - nur das sollte nicht das Standardverhalten sein.

Wir können deepFlattenmit Leichtigkeit ein Verfahren durchführen ...

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

Dort. Jetzt haben Sie für jeden Job ein Werkzeug - eines zum Quetschen einer Verschachtelungsebene flattenund eines zum Löschen aller Verschachtelungen deepFlatten.

Vielleicht kannst du es nennen obliterateoder nukewenn dir der Name nicht gefällt deepFlatten.


Nicht zweimal iterieren!

Natürlich sind die oben genannten Implementierungen clever und prägnant, aber .mapwenn .reducewir einen gefolgt von einem Aufruf von verwenden, bedeutet dies, dass wir tatsächlich mehr Iterationen als nötig durchführen

Die Verwendung eines vertrauenswürdigen Kombinators, den ich anrufe, mapReducehilft dabei, die Iterationen auf ein Minimum zu beschränken. Es übernimmt eine Abbildungsfunktion m :: a -> b, eine Reduktionsfunktion r :: (b,a) ->bund gibt eine neue Reduktionsfunktion zurück - dieser Kombinator ist das Herzstück der Wandler . Wenn Sie interessiert sind, habe ich andere Antworten darüber geschrieben

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


Wenn ich Ihre Antworten sehe, möchte ich meine häufig zurückziehen, weil sie wertlos geworden sind. Gute Antwort! concatselbst sprengt nicht den Stapel, nur ...und applytut (zusammen mit sehr großer Arrays). Ich habe es nicht gesehen. Ich fühle mich gerade schrecklich.

@ LUH3417 du solltest dich nicht schrecklich fühlen. Sie geben auch gute Antworten mit gut geschriebenen Erklärungen. Alles hier ist eine Lernerfahrung - ich mache oft Fehler und schreibe auch schlechte Antworten. Ich besuche sie in ein paar Monaten noch einmal und denke "wtf habe ich gedacht?" und am Ende die Dinge komplett überarbeiten. Keine Antwort ist perfekt. Wir sind alle irgendwann auf einer Lernkurve ^ _ ^
Danke

1
Bitte beachten Sie, dass concatJavascript eine andere Bedeutung hat als in Haskell. Haskells concat( [[a]] -> [a]) würde flattenin Javascript aufgerufen und ist implementiert als foldr (++) [](Javascript: foldr(concat) ([])Annahme von Curry-Funktionen). Javascript concatist ein seltsamer Anhang ( (++)in Haskell), der sowohl [a] -> [a] -> [a]als auch verarbeiten kann a -> [a] -> [a].

Ich denke, ein besserer Name wäre flatMap, denn genau das concatMapist: Die bindInstanz der listMonade. concatpMapwird implementiert als foldr ((++) . f) []. Übersetzt in Javascript : const flatMap = f => foldr(comp(concat) (f)) ([]). Dies ähnelt natürlich Ihrer Implementierung ohne comp.

Was ist die Komplexität dieses Algorithmus?
George Katsanos

32

Eine Lösung für den allgemeineren Fall, wenn Sie möglicherweise einige Nicht-Array-Elemente in Ihrem Array haben.

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
Dieser Ansatz war sehr effektiv beim Reduzieren der verschachtelten Array-Form von Ergebnismengen, die Sie aus einer JsonPath-Abfrage erhalten .
Kevinjansz

1
Als Methode für Arrays 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}});
hinzugefügt

Dies wird unterbrochen, wenn wir das zweite Argument manuell übergeben. Versuchen Sie beispielsweise Folgendes: flattenArrayOfArrays (arr, 10)oder this flattenArrayOfArrays(arr, [1,[3]]);- Diese zweiten Argumente werden der Ausgabe hinzugefügt.
Om Shankar

2
Diese Antwort ist nicht vollständig! Das Ergebnis der Rekursion wird nirgendwo zugewiesen, so dass das Ergebnis der Rekursionen verloren geht
r3wt

1
@ r3wt ging voran und fügte einen Fix hinzu. Sie müssen lediglich sicherstellen, dass das aktuelle Array, das Sie verwalten, rdie Ergebnisse der Rekursion tatsächlich verkettet.
August

29

Um ein Array von Einzelelement-Arrays zu reduzieren, müssen Sie keine Bibliothek importieren. Eine einfache Schleife ist sowohl die einfachste als auch die effizienteste Lösung:

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

Für Downvoter: Bitte lesen Sie die Frage, stimmen Sie nicht ab, da sie nicht zu Ihrem ganz anderen Problem passt. Diese Lösung ist sowohl die schnellste als auch die einfachste für die gestellte Frage.


4
Ich würde sagen: "Suche nicht nach kryptischeren Dingen." ^^

4
Ich meine ... wenn ich Leute sehe, die raten, eine Bibliothek dafür zu benutzen ... ist das verrückt ...
Denys Séguret

3
Ahh, eine Bibliothek nur dafür zu benutzen wäre dumm ... aber wenn sie bereits verfügbar ist.

9
@dystroy Der bedingte Teil der for-Schleife ist nicht so lesbar. Oder bin es nur ich: D
Andreas

4
Es spielt keine Rolle, wie kryptisch es ist. Dieser Code "glättet" dies ['foo', ['bar']]auf ['f', 'bar'].
Jonschlinkert

29

Was ist mit der reduce(callback[, initialValue])Methode vonJavaScript 1.8

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

Würde den Job machen.


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

5
Kein zweites Argument in unserem Fall einfach[[1], [2,3]].reduce( (a,b) => a.concat(b))
Umair Ahmed

29

Eine weitere ECMAScript 6-Lösung im funktionalen Stil:

Deklarieren Sie eine Funktion:

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

und benutze es:

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

Betrachten Sie auch eine native Funktion Array.prototype.flat () (Vorschlag für ES6), die in den letzten Versionen moderner Browser verfügbar ist. Dank an @ (Константин Ван) und @ (Mark Amery) in den Kommentaren erwähnt.

Die flatFunktion verfügt über einen Parameter, der die erwartete Tiefe der Array-Verschachtelung angibt, die 1standardmäßig gleich ist.

[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
Das ist schön und ordentlich, aber ich denke, Sie haben eine ES6-Überdosis gemacht. Es ist nicht erforderlich, dass die äußere Funktion eine Pfeilfunktion ist. Ich würde mich an die Pfeilfunktion für den Rückruf reduzieren halten, aber das Abflachen selbst sollte eine normale Funktion sein.
Stephen Simpson

3
@StephenSimpson, aber muss die äußere Funktion eine Nicht- Pfeil-Funktion sein? "Abflachen selbst sollte eine normale Funktion sein" - mit "normal" meinen Sie "Nichtpfeil", aber warum? Warum eine Pfeilfunktion im Aufruf verwenden, um dann zu reduzieren? Können Sie Ihre Argumentation angeben?
Vielen Dank, dass Sie

@naomik Meine Argumentation ist, dass es unnötig ist. Es ist hauptsächlich eine Frage des Stils; Ich sollte in meinem Kommentar viel klarer haben. Es gibt keinen wichtigen Codierungsgrund für die Verwendung des einen oder anderen. Die Funktion ist jedoch leichter als Nichtpfeil zu sehen und zu lesen. Die innere Funktion ist als Pfeilfunktion nützlich, da sie kompakter ist (und natürlich kein Kontext erstellt wird). Pfeilfunktionen eignen sich hervorragend, um kompakte, leicht lesbare Funktionen zu erstellen und diese Verwirrung zu vermeiden. Sie können jedoch das Lesen erschweren, wenn ein Nichtpfeil ausreichen würde. Andere mögen jedoch anderer Meinung sein!
Stephen Simpson

Immer einRangeError: Maximum call stack size exceeded
Matt Westlake

@ Matt, bitte teilen Sie die Umgebung, die Sie verwenden, um den Fehler zu reproduzieren
diziaq

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

So mache ich das in meinem Code, aber ich bin mir nicht sicher, wie die Geschwindigkeit mit der akzeptierten Antwort verglichen wird, wenn Sie ein sehr langes Array haben
Michael Aaron Wilson

14

Bitte beachten Sie: Wenn Function.prototype.apply( [].concat.apply([], arrays)) oder der Spread-Operator ( [].concat(...arrays)) verwendet werden, um ein Array zu reduzieren, können beide Stapelüberläufe für große Arrays verursachen, da jedes Argument einer Funktion auf dem Stapel gespeichert ist.

Hier ist eine stapelsichere Implementierung im funktionalen Stil, die die wichtigsten Anforderungen gegeneinander abwägt:

  • Wiederverwendbarkeit
  • Lesbarkeit
  • Prägnanz
  • Performance

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

Sobald Sie sich an kleine Pfeilfunktionen in Curryform, Funktionszusammensetzung und Funktionen höherer Ordnung gewöhnt haben, liest sich dieser Code wie Prosa. Die Programmierung besteht dann lediglich darin, kleine Bausteine ​​zusammenzustellen, die immer wie erwartet funktionieren, da sie keine Nebenwirkungen enthalten.


1
Haha. Respektieren Sie Ihre Antwort voll und ganz, obwohl das Lesen einer solchen funktionalen Programmierung für mich (englischer Sprecher) immer noch wie das Lesen von Japanisch Zeichen für Zeichen ist.
Tarwin Stroh-Spijer

2
Wenn Sie feststellen, dass Sie Funktionen von Sprache A in Sprache B implementieren, die nicht Teil des Projekts sind, mit dem einzigen Ziel, genau dies zu tun, hat irgendwo jemand eine falsche Wendung eingeschlagen. Könntest du das sein? const flatten = (arr) => arr.reduce((a, b) => a.concat(b), []);Wenn Sie nur mit gehen, sparen Sie visuellen Müll und erklären Ihren Teamkollegen, warum Sie 3 zusätzliche Funktionen und einige Funktionsaufrufe benötigen .
Daerdemandt

3
@Daerdemandt Aber wenn Sie es als separate Funktionen schreiben, können Sie sie wahrscheinlich in anderem Code wiederverwenden.
Michał Perłakowski

@ MichałPerłakowski Wenn Sie sie an mehreren Stellen verwenden müssen, erfinden Sie das Rad nicht neu und wählen Sie ein Paket aus diesen - dokumentiert und unterstützt von anderen Personen.
Daerdemandt

12

ES6 One Line Flatten

Siehe Lodash-Abflachung , Unterstrich-Abflachung (flach true)

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

oder

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

Getestet mit

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

Siehe lodash flattenDeep , unterstreichen Sie flatten

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

Getestet mit

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

Ihr zweites Beispiel ist besser geschrieben, Array.prototype.concat.apply([], arr)weil Sie ein zusätzliches Array erstellen, um zur concatFunktion zu gelangen . Runtimes können es möglicherweise optimieren oder nicht, wenn sie es ausführen, aber der Zugriff auf die Funktion des Prototyps sieht nicht hässlicher aus, als dies in jedem Fall bereits der Fall ist.
Mörre

11

Sie können Array.flat()mit Infinityfür jede Tiefe des verschachtelten Arrays verwenden.

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)

Check hier für die Browser - Kompatibilität


8

Ein haskellesker Ansatz

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
Stimmen Sie mit @monners überein. Zweifellos die eleganteste Lösung in diesem ganzen Thread.
Nicolás Fantone

8

ES6 Weg:

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

ES5- flattenFunktionsweise mit ES3-Fallback für N-mal verschachtelte Arrays:

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

Wenn Sie nur Arrays mit 1 Zeichenfolgenelement haben:

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

wird den Job machen. Bt, das speziell zu Ihrem Codebeispiel passt.


3
Wer auch immer gewählt hat, erklärt bitte warum. Ich war auf der Suche nach einer anständigen Lösung und von allen Lösungen hat mir diese am besten gefallen.
Anonym

2
@Anonymous Ich habe es nicht abgelehnt, da es technisch den Anforderungen der Frage entspricht, aber es ist wahrscheinlich, weil dies eine ziemlich schlechte Lösung ist, die im allgemeinen Fall nicht nützlich ist. In Anbetracht der Anzahl der besseren Lösungen, die es hier gibt, würde ich niemals jemandem empfehlen, sich für diese zu entscheiden, da dies in dem Moment unterbrochen wird, in dem Sie mehr als ein Element haben oder wenn es sich nicht um Zeichenfolgen handelt.
Thor84no

2
Ich liebe diese Lösung =)
Huei Tan

2
Es behandelt nicht nur Arrays mit 1 String-Elementen, sondern auch dieses Array ['$4', ["$6"], ["$12"], ["$25"], ["$25", "$33", ['$45']]].join(',').split(',')
alucic

Ich habe diese Methode selbst entdeckt, wusste jedoch, dass sie bereits irgendwo dokumentiert worden sein muss. Meine Suche endete hier. Der Nachteil dieser Lösung ist, dass sie Zahlen, Boolesche Werte usw. zu Zeichenfolgen zwingt. Versuchen Sie [1,4, [45, 't', ['e3', 6]]].toString().split(',') ---- oder ----- [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"]

(Ich schreibe dies nur als separate Antwort, basierend auf dem Kommentar von @danhbear.)


7

Ich empfehle eine platzsparende Generatorfunktion :

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

Erstellen Sie bei Bedarf ein Array abgeflachter Werte wie folgt:

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

Ich mag diesen Ansatz. Ähnlich wie stackoverflow.com/a/35073573/1175496 , verwendet jedoch den Spread-Operator ..., um den Generator zu durchlaufen.
Die rote Erbse

6

Ich würde lieber das gesamte Array so wie es ist in einen String umwandeln, aber im Gegensatz zu anderen Antworten würde ich dies mit JSON.stringifyder toString()Methode tun und nicht verwenden , was zu einem unerwünschten Ergebnis führt.

Bei dieser JSON.stringifyAusgabe müssen nur noch alle Klammern entfernt, das Ergebnis erneut mit Start- und Endklammern umbrochen und das Ergebnis bereitgestellt werden, mit JSON.parsedem die Zeichenfolge wieder zum "Leben" erweckt wird.

  • Kann unendlich verschachtelte Arrays ohne Geschwindigkeitskosten verarbeiten.
  • Kann Array-Elemente, bei denen es sich um Zeichenfolgen handelt, die Kommas enthalten, richtig verarbeiten.

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

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

console.log(flattened)

  • Nur für mehrdimensionale Anordnungen von Zeichenfolgen / Zahlen (keine Objekte)

Ihre Lösung ist falsch. Es wird das Komma enthalten, wenn innere Arrays abgeflacht werden, ["345", "2", "3,4", "2"]anstatt jeden dieser Werte zu trennen, um Indizes zu trennen
pizzarob

@realseanp - Sie haben den Wert in diesem Array-Element falsch verstanden. Ich habe dieses Komma absichtlich als Wert und nicht als Array-Trennzeichen gesetzt, um die Leistungsfähigkeit meiner Lösung vor allen anderen hervorzuheben, die ausgegeben werden würden "3,4".
vsync

1
Ich habe falsch verstanden
Pizzarob

das scheint definitiv die schnellste Lösung zu sein, die ich dafür gesehen habe; Kennen Sie irgendwelche Fallstricke @vsync (außer der Tatsache, dass es natürlich ein bisschen hackig aussieht - verschachtelte Arrays als Zeichenfolgen behandeln: D)
George Katsanos

@ GeorgeKatsanos - Diese Methode funktioniert nicht für Array-Elemente (und verschachtelte Elemente), die keinen primitiven Wert haben, z. B. ein Element, das auf ein DOM-Element
verweist

6

Sie können auch die neue Array.Flat()Methode ausprobieren . Es funktioniert folgendermaßen:

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

console.log(arr);

Die flat()Methode erstellt ein neues Array, in dem alle Subarray-Elemente bis zur Tiefebene 1 rekursiv verkettet sind (dh Arrays innerhalb von Arrays).

Wenn Sie auch dreidimensionale oder noch höherdimensionale Arrays reduzieren möchten, rufen Sie die flache Methode einfach mehrmals auf. Zum Beispiel (3 Dimensionen):

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

console.log(arr);

Achtung!

Array.Flat()Methode ist relativ neu. Ältere Browser wie z. B. haben die Methode möglicherweise nicht implementiert. Wenn Sie möchten, dass Ihr Code in allen Browsern funktioniert, müssen Sie Ihren JS möglicherweise auf eine ältere Version übertragen. Suchen Sie nach MD-Webdokumenten für die aktuelle Browserkompatibilität.


2
Um flache höherdimensionale Arrays abzuflachen, können Sie einfach die flache Methode mit InfinityArgument aufrufen . So:arr.flat(Infinity)
Mikhail

Das ist eine sehr schöne Ergänzung, danke Mikhail!
Willem van der Veen

6

Verwenden des Spread-Operators:

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


5

Das ist nicht schwer, iterieren Sie einfach über die Arrays und führen Sie sie zusammen:

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

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

5

Es sieht so aus, als wäre dies ein Job für RECURSION!

  • Behandelt mehrere Verschachtelungsebenen
  • Behandelt leere Arrays und Nicht-Array-Parameter
  • Hat keine Mutation
  • Verlässt sich nicht auf moderne Browserfunktionen

Code:

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

Verwendungszweck:

flatten([1,[2,3],4,[[5,6],7]]);
// Result: [1, 2, 3, 4, 5, 6, 7] 

Vorsicht, flatten(new Array(15000).fill([1]))wirft Uncaught RangeError: Maximum call stack size exceededund friert meine devTools für 10 Sekunden ein
pietrovismara

@pietrovismara, mein Test ist um 1s.
Ivan Yan

5

Ich habe es mit Rekursion und Abschlüssen gemacht

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
Einfach und süß, diese Antwort funktioniert besser als die akzeptierte Antwort. Es glättet tief verschachtelte Ebenen, nicht nur die erste Ebene
Om Shankar

1
AFAIK, die lexikalisches Scoping und kein Abschluss ist
dashambles

@dashambles ist korrekt - der Unterschied besteht darin, dass Sie, wenn es sich um einen Verschluss handelt, die innere Funktion nach außen zurückgeben würden. Wenn die äußere Funktion beendet ist, können Sie die innere Funktion weiterhin verwenden, um auf ihren Bereich zuzugreifen. Hier ist die Lebensdauer der äußeren Funktion länger als die der inneren Funktion, so dass niemals ein "Verschluss" erzeugt wird.
Mörre

5

Ich habe neulich mit ES6-Generatoren rumgespielt und dieses Wesentliche geschrieben . Was beinhaltet...

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

Grundsätzlich erstelle ich einen Generator, der das ursprüngliche Eingabearray durchläuft. Wenn er ein Array findet, verwendet er den Operatorield * in Kombination mit einer Rekursion, um die internen Arrays kontinuierlich zu reduzieren. Wenn das Element kein Array ist es nur liefert das einzelne Element. Dann reduziere ich den Generator mit dem ES6 Spread-Operator (auch bekannt als Splat-Operator) in eine neue Array-Instanz.

Ich habe die Leistung nicht getestet, aber ich denke, es ist ein schönes einfaches Beispiel für die Verwendung von Generatoren und des Yield * -Operators.

Aber auch hier habe ich nur vermasselt, also bin ich mir sicher, dass es dafür performantere Möglichkeiten gibt.


1
Ähnliche Antwort (mit Generator und Ertragsdelegation), aber im PHP-Format
The Red Pea

5

einfach die beste lösung ohne lodash

let flatten = arr => [].concat.apply([], arr.map(item => Array.isArray(item) ? flatten(item) : item))
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.