Scal / spłaszcz tablicę tablic


1104

Mam tablicę JavaScript, taką jak:

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

Jak miałbym przejść do scalania oddzielnych wewnętrznych tablic w jeden taki jak:

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


17
Wszystkie rozwiązania, które używają reduce+, concatto O ((N ^ 2) / 2), gdzie jako zaakceptowana odpowiedź (tylko jedno wywołanie do concat) byłoby co najwyżej O (N * 2) w złej przeglądarce i O (N) w dobry. Również rozwiązanie Denys jest zoptymalizowane pod kątem rzeczywistego pytania i do 2x szybsze niż singiel concat. Dla reduceludzi fajnie jest czuć się fajnie pisząc mały kod, ale na przykład, jeśli tablica zawiera 1000 jednoelementowych podrzędnych układów, wszystkie rozwiązania redukujące i konkatujące wykonałyby 500500 operacji, podczas gdy jako pojedyncza konkat lub prosta pętla wykonałaby 1000 operacji.
gman

11
Po prostu operator rozprzestrzeniania użytkownika[].concat(...array)
Oleg Dater

@gman, jak to jest zmniejszyć + konkatat rozwiązanie O ((N ^ 2) / 2)? stackoverflow.com/questions/52752666/… wspomina o innej złożoności.
Usman,

3
Dzięki najnowszym przeglądarkom obsługującym ES2019 : array.flat(Infinity)gdzie Infinityjest maksymalna głębokość do spłaszczenia.
Timothy Gu,

Odpowiedzi:


1860

Możesz użyć concatdo scalenia tablic:

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

console.log(merged);

Użycie applymetody concatspowoduje pobranie drugiego parametru jako tablicy, więc ostatni wiersz jest identyczny z tym:

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

Istnieje również Array.prototype.flat()metoda (wprowadzona w ES2019), której można użyć do spłaszczenia tablic, chociaż jest ona dostępna tylko w Node.js od wersji 11, a wcale nie w Internet Explorerze .

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
Zauważ, że concatnie modyfikuje tablicy źródłowej, więc mergedtablica pozostanie pusta po wywołaniu concat. Lepiej powiedzieć coś takiego:merged = merged.concat.apply(merged, arrays);
Nate

65
var merged = [].concat.apply([], arrays);wydaje się działać dobrze, aby uzyskać go w jednej linii. edycja: jak pokazuje odpowiedź Nikity.
Sean

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

30
Uwaga: ta odpowiedź spłaszcza tylko jeden poziom głębokości. Aby uzyskać rekurencyjne spłaszczenie, zobacz odpowiedź @Trindaz.
Phrogz

209
W nawiązaniu do komentarza @ Seana: składnia ES6 sprawia, że ​​jest to bardzo zwięzłe:var merged = [].concat(...arrays)
Sethi

505

Oto krótka funkcja, która wykorzystuje niektóre z nowszych metod tablic JavaScript do spłaszczenia n-wymiarowej tablicy.

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

Stosowanie:

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
Lubię to podejście. Jest znacznie bardziej ogólny i obsługuje tablice zagnieżdżone
alfredocambera

10
Jaki jest profil wykorzystania pamięci dla tego rozwiązania? Wygląda na to, że tworzy wiele pośrednich tablic podczas rekurencji ogona ....
JBRWilkinson

7
@ayjay, to początkowa wartość akumulatora dla funkcji redukcji, co mdn nazywa wartością początkową. W tym przypadku jest to wartość flatpierwszego wywołania przekazanej funkcji anonimowej reduce. Jeśli nie zostanie określony, pierwsze wywołanie reducewiązania pierwszej wartości z tablicy do flat, co ostatecznie spowoduje 1powiązanie flatw obu przykładach. 1.concatnie jest funkcją.
Noah Freitas,

19
Lub w krótszej, bardziej seksownej formie: const flatten = (arr) => arr.reduce((flat, next) => flat.concat(next), []);
Tsvetomir Tsonev

12
Riffowanie na temat rozwiązań @TsvetomirTsonev i Noego dotyczących arbitralnego zagnieżdżania:const flatten = (arr) => arr.reduce((flat, next) => flat.concat(Array.isArray(next) ? flatten(next) : next), []);
Będzie

314

Istnieje myląco ukryta metoda, która konstruuje nową tablicę bez mutowania oryginalnej:

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


11
W CoffeeScript jest to[].concat([[1],[2,3],[4]]...)
amoebe

8
@amoebe w rezultacie twoja odpowiedź daje [[1],[2,3],[4]]. Rozwiązanie podane przez @Nikita jest poprawne zarówno dla CoffeeScript, jak i JS.
Ryan Kennedy

5
Ahh, znalazłem twój błąd. W notacji powinna znajdować się dodatkowa para nawiasów kwadratowych [].concat([1],[2,3],[4],...).
Ryan Kennedy,

21
Myślę, że też znalazłem twój błąd. Są ...to rzeczywisty kod, a nie niektóre kropki elipsy.
amoebe

3
Użycie funkcji bibliotecznej nie oznacza, że ​​istnieje mniej imperatywny „bałagan”. Tylko że bałagan jest ukryty w bibliotece. Oczywiście korzystanie z biblioteki jest lepsze niż rozwijanie własnej funkcji, ale twierdzenie, że ta technika zmniejsza „imperatywny bałagan”, jest dość niemądre.
paldepind

204

Najlepiej można to zrobić za pomocą funkcji redukcji 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);
}, []);

Lub z ES2015:

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

js-skrzypce

Dokumenty Mozilli


1
@JohnS Rzeczywiście zmniejszaj feedy do konkat jednej wartości (macierzy) na turę. Dowód? wyjmij redukcję () i sprawdź, czy to działa. redukcja () tutaj jest alternatywą dla Function.prototype.apply ()
André Werlang

3
Użyłbym również zmniejszania, ale jedną ciekawą rzeczą, której nauczyłem się z tego fragmentu, jest to, że przez większość czasu nie trzeba przekazywać wartości początkowej :)
José F. Romaniello

2
Problem z redukcją polega na tym, że tablica nie może być pusta, dlatego konieczne będzie dodanie dodatkowej weryfikacji.
calbertts

4
@calbertts Wystarczy podać wartość początkową []i nie są wymagane żadne dalsze sprawdzenia.

7
Ponieważ używasz ES6, możesz również użyć operatora rozkładania jako literału tablicowego . arrays.reduce((flatten, arr) => [...flatten, ...arr])
Putzi San

108

Istnieje nowa natywna metoda o nazwie flat, która dokładnie to robi.

(Pod koniec 2019 r. flatJest teraz publikowany w standardzie ECMA 2019 i core-js@3(biblioteka Babel) włącza go do swojej biblioteki wielopełniaczowej )

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
Szkoda, że ​​nie ma go nawet na pierwszej stronie odpowiedzi. Ta funkcja jest dostępna w Chrome 69 i Firefox 62 (oraz w węźle 11 dla osób pracujących w backendu)
Matt M.,


2
-1; nie, to nie jest część ECMAScript 2018 . To wciąż tylko propozycja, która nie dotarła do żadnej specyfikacji ECMAScript.
Mark Amery

1
Myślę, że teraz możemy to rozważyć ... ponieważ teraz jest to część standardu (2019) .. czy możemy raz jeszcze raz zapoznać się z częścią dotyczącą wydajności?
Yuvaraj

Wygląda na to, że nie jest jeszcze obsługiwany przez żadną przeglądarkę Microsoft (przynajmniej w chwili, gdy piszę ten komentarz)
Laurent S.,

78

Większość odpowiedzi tutaj nie działa na ogromnych tablicach (np. 200 000 elementów), a nawet jeśli tak, są one powolne. Odpowiedź polkovnikov.ph ma najlepszą wydajność, ale nie działa w przypadku głębokiego spłaszczania.

Oto najszybsze rozwiązanie, które działa również na tablicach z wieloma poziomami zagnieżdżenia :

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

Przykłady

Ogromne tablice

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

Świetnie radzi sobie z dużymi tablicami. Na mojej maszynie wykonanie tego kodu zajmuje około 14 ms.

Zagnieżdżone tablice

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

Działa z zagnieżdżonymi tablicami. Ten kod tworzy [1, 1, 1, 1, 1, 1, 1, 1].

Tablice o różnych poziomach zagnieżdżenia

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

Nie ma żadnych problemów z takimi spłaszczonymi tablicami.


Tyle że twój ogromny układ jest dość płaski. To rozwiązanie nie będzie działać w przypadku głęboko zagnieżdżonych tablic. Żadne rozwiązanie rekurencyjne nie będzie. W rzeczywistości żadna przeglądarka, ale Safari ma obecnie TCO, więc żaden algorytm rekurencyjny nie będzie działał dobrze.
pewno

@ naturalnie Ale w jakiej rzeczywistej sytuacji miałbyś tablice z więcej niż kilkoma poziomami zagnieżdżania?
Michał Perłakowski

2
Zwykle, gdy tablica jest generowana z treści generowanych przez użytkownika.
pewno

@ 0xcaff W Chrome nie działa wcale z tablicą 200 000 elementów (dostajesz RangeError: Maximum call stack size exceeded). Dla 20 000 elementów matryca zajmuje 2-5 milisekund.
Michał Perłakowski

3
jaka jest złożoność zapisu O?
George Katsanos,

58

Aktualizacja: okazało się, że to rozwiązanie nie działa z dużymi tablicami. Jeśli szukasz lepszego, szybszego rozwiązania, sprawdź tę odpowiedź .


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

Po prostu rozwija się arri przekazuje jako argumenty concat(), które łączą wszystkie tablice w jedno. Jest to równoważne z [].concat.apply([], arr).

Możesz także wypróbować to dla głębokiego spłaszczenia:

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

Zobacz demo na JSBin .

Odniesienia do elementów ECMAScript 6 zastosowanych w tej odpowiedzi:


Uwaga dodatkowa: metody takie jak find()i funkcje strzałek nie są obsługiwane przez wszystkie przeglądarki, ale to nie znaczy, że nie możesz teraz korzystać z tych funkcji. Wystarczy użyć Babel - przekształca kod ES6 w ES5.


Ponieważ prawie wszystkie odpowiedzi tutaj niewłaściwie wykorzystywane applyw ten sposób, usunąłem moje komentarze z twoich. Nadal uważam, że używanie apply/ rozpowszechnianie w ten sposób jest złe, ale ponieważ nikogo to nie obchodzi ...

@ LUH3417 To nie tak, naprawdę doceniam twoje komentarze. Okazało się, że masz rację - to rozwiązanie naprawdę nie działa z dużymi tablicami. Opublikowałem kolejną odpowiedź, która działa dobrze nawet w przypadku tablic zawierających 200 000 elementów.
Michał Perłakowski

Jeśli używasz ES6, możesz dodatkowo const flatten = arr => [].concat(...arr)
zredukować

Co masz na myśli mówiąc „nie działa z dużymi tablicami”? Jak duży? Co się dzieje?
GEMI,

4
@GEMI Na przykład próba spłaszczenia tablicy 500000 elementów za pomocą tej metody daje „RangeError: Przekroczono maksymalny rozmiar stosu wywołań”.
Michał Perłakowski


41

Ogólne procedury oznaczają, że nie musimy przepisywać złożoności za każdym razem, gdy musimy zastosować określone zachowanie.

concatMap(lub flatMap) jest dokładnie tym, czego potrzebujemy w tej sytuacji.

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

dalekowzroczność

I tak, zgadłeś poprawnie, spłaszcza tylko jeden poziom, dokładnie tak, jak powinno działać

Wyobraź sobie taki zestaw danych

// 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, teraz powiedzmy, że chcemy wydrukować listę pokazującą wszystkich graczy, którzy wezmą udział w game

const gamePlayers = game =>
  flatten (game)

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

Gdyby nasza flattenprocedura spłaszczyła również zagnieżdżone tablice, otrzymalibyśmy ten wynik śmieci…

const gamePlayers = game =>
  badGenericFlatten(game)

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

tarzać się głęboko, kochanie

Nie oznacza to, że czasami nie chcesz spłaszczać również zagnieżdżonych tablic - tylko takie zachowanie nie powinno być domyślne.

Z deepFlattenłatwością możemy wykonać procedurę…

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

Tam. Teraz masz narzędzie do każdego zadania - jedno do zgniatania jednego poziomu zagnieżdżenia flatteni jedno do niszczenia całego zagnieżdżaniadeepFlatten .

Może możesz to nazwać obliteratelub nukejeśli nie podoba ci się to imię deepFlatten.


Nie powtarzaj dwa razy!

Oczywiście powyższe implementacje są sprytne i zwięzłe, ale przy użyciu .mapwezwania do.reduce oznacza, że ​​faktycznie wykonujemy więcej iteracji niż to konieczne

Używanie sprawdzonego kombinatora, do którego dzwonię, mapReducepomaga utrzymać iteracje na minimalnym poziomie; przyjmuje funkcję mapowania m :: a -> b, funkcję redukującą r :: (b,a) ->bi zwraca nową funkcję redukującą - ten kombinator jest sercem przetworników ; jeśli jesteś zainteresowany, napisałem o nich inne odpowiedzi

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


Często, gdy widzę wasze odpowiedzi, chcę je wycofać, ponieważ stały się bezwartościowe. Świetna odpowiedź! concatsam nie wysadza stosu, tylko ...i applyrobi (wraz z bardzo dużymi tablicami). Nie widziałem tego Po prostu czuję się teraz okropnie.

@ LUH3417 nie powinieneś czuć się okropnie. Podajesz również dobre odpowiedzi z dobrze napisanymi wyjaśnieniami. Wszystko tutaj jest nauką - często popełniam błędy i piszę złe odpowiedzi. Odwiedzam je ponownie za kilka miesięcy i myślę: „o czym myślałem?” i ostatecznie całkowicie zmieniają rzeczy. Żadna odpowiedź nie jest idealna. Wszyscy jesteśmy w pewnym momencie na krzywej uczenia się ^ _ ^
Dziękujemy

1
Pamiętaj, że concatw Javascript ma inne znaczenie niż w Haskell. Haskell's concat( [[a]] -> [a]) byłby wywoływany flattenw JavaScript i jest implementowany jako foldr (++) [](JavaScript: foldr(concat) ([])przyjmowanie funkcji curry). JavaScript concatjest dziwnym dodatkiem ( (++)w języku Haskell), który obsługuje zarówno [a] -> [a] -> [a]i a -> [a] -> [a].

Wydaje mi się, że lepszym imieniem było flatMap, ponieważ właśnie tak concatMapjest: bindInstancja listmonady. concatpMapjest implementowany jako foldr ((++) . f) []. Przetłumaczone na język JavaScript: const flatMap = f => foldr(comp(concat) (f)) ([]). Jest to oczywiście podobne do twojej implementacji bez comp.

jaka jest złożoność tego algorytmu?
George Katsanos,

32

Rozwiązanie dla bardziej ogólnego przypadku, gdy w tablicy mogą znajdować się elementy inne niż macierzowe.

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
To podejście było bardzo skuteczne w spłaszczaniu zagnieżdżonej tablicy zestawów wyników otrzymanych z zapytania JsonPath .
kevinjansz

1
Dodano jako metodę tablic: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

To się zepsuje, jeśli ręcznie przekażemy drugi argument. Na przykład spróbuj tego: flattenArrayOfArrays (arr, 10)lub tego flattenArrayOfArrays(arr, [1,[3]]);- te drugie argumenty zostaną dodane do wyniku.
Om Shankar

2
ta odpowiedź nie jest kompletna! wynik rekurencji nigdy nie jest nigdzie przypisywany, więc wynik rekurencji zostaje utracony
r3wt

1
@ r3wt poszedł naprzód i dodał poprawkę. To, co musisz zrobić, to upewnić się, że bieżąca tablica, którą zarządzasz, rfaktycznie połączy wyniki rekurencji.
sierpnia

29

Co z użyciem reduce(callback[, initialValue])metodyJavaScript 1.8

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

Zrobiłbym robotę.


8
[[1], [2,3]].reduce( (a,b) => a.concat(b), [] )jest bardziej seksowny.
golopot

5
Drugi argument w naszym przypadku nie jest prosty[[1], [2,3]].reduce( (a,b) => a.concat(b))
Umair Ahmed,

29

Kolejne rozwiązanie ECMAScript 6 w funkcjonalnym stylu:

Zadeklaruj funkcję:

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

i użyj go:

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

Rozważ także natywną funkcję Array.prototype.flat () (propozycja dla ES6) dostępną w ostatnich wydaniach współczesnych przeglądarek. Dzięki @ (Константин Ван) i @ (Mark Amery) wspomnieli o tym w komentarzach.

flatFunkcja posiada jeden parametr określający oczekiwanej głębokości zagnieżdżenia tablicy, co równa się 1domyślnie.

[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
To miłe i schludne, ale myślę, że przedawkowałeś ES6. Funkcja zewnętrzna nie musi być funkcją strzałki. Chciałbym trzymać się funkcji strzałki dla wywołania zwrotnego zmniejszania, ale samo spłaszczenie powinno być normalną funkcją.
Stephen Simpson

3
@StephenSimpson, ale czy istnieje potrzeba, aby funkcja zewnętrzna była funkcją inną niż arrow? „samo spłaszczenie powinno być normalną funkcją” - przez „normalny” masz na myśli „brak strzałki”, ale dlaczego? Po co zatem używać funkcji strzałki w wywołaniu, aby zmniejszyć? Czy możesz podać swój tok rozumowania?
Dziękuję

@ anomik Moje rozumowanie jest takie, że nie jest konieczne. To głównie kwestia stylu; Powinienem mieć o wiele jaśniejszy komentarz. Nie ma żadnego ważnego powodu do kodowania, aby użyć jednego lub drugiego. Jednak funkcja ta jest łatwiejsza do odczytania i odczytania jako nie-strzałka. Funkcja wewnętrzna jest przydatna jako funkcja strzałki, ponieważ jest bardziej zwarta (i oczywiście nie jest tworzony kontekst). Funkcje strzałek doskonale nadają się do tworzenia kompaktowych, łatwych do odczytania funkcji i unikania tego zamieszania. Mogą one jednak utrudnić czytanie, gdy nie wystarczy strzałka. Inni mogą się jednak nie zgodzić!
Stephen Simpson

PierwszeRangeError: Maximum call stack size exceeded
Matt Westlake,

@Matt, proszę podzielić się informacją, której użyłeś do odtworzenia błędu
diziaq

28

Aby spłaszczyć tablicę tablic jednoelementowych, nie trzeba importować biblioteki, prosta pętla jest jednocześnie najprostszym i najbardziej wydajnym rozwiązaniem:

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

Do downvoters: przeczytaj pytanie, nie głosuj downvote, ponieważ to nie pasuje do twojego zupełnie innego problemu. To rozwiązanie jest zarówno najszybsze, jak i najprostsze dla zadanego pytania.


4
Powiedziałbym: „Nie szukaj rzeczy bardziej tajemniczych”. ^^

4
Mam na myśli ... kiedy widzę ludzi, którzy zalecają do tego bibliotekę ... to szalone ...
Denys Séguret

3
Achh, użycie biblioteki tylko do tego byłoby głupie ... ale jeśli jest już dostępna.

9
@dystroy Warunkowa część pętli for nie jest aż tak czytelna. A może to tylko ja: D
Andreas

4
Tak naprawdę nie ma znaczenia, jak tajemniczy jest. Ten kod „spłaszcza” to ['foo', ['bar']]do ['f', 'bar'].
jonschlinkert

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

Tak to robię w całym kodzie, ale nie jestem pewien, jak prędkość wypada w porównaniu z przyjętą odpowiedzią, jeśli masz bardzo długą tablicę
Michael Aaron Wilson

14

Uwaga: kiedy Function.prototype.apply([].concat.apply([], arrays) ) lub operator rozkładania ( [].concat(...arrays)) jest używany do spłaszczenia tablicy, oba mogą powodować przepełnienie stosu dla dużych tablic, ponieważ każdy argument funkcji jest przechowywany na stosie.

Oto bezpieczna implementacja w stosie w funkcjonalnym stylu, która porównuje najważniejsze wymagania względem siebie:

  • wielokrotnego użytku
  • czytelność
  • zwięzłość
  • wydajność

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

Gdy tylko przyzwyczaisz się do funkcji małych strzałek w formie curry, kompozycji funkcji i funkcji wyższego rzędu, kod ten brzmi jak proza. Programowanie polega zatem jedynie na złożeniu małych elementów, które zawsze działają zgodnie z oczekiwaniami, ponieważ nie zawierają żadnych skutków ubocznych.


1
Ha ha. Całkowicie uszanuj swoją odpowiedź, chociaż czytanie funkcjonalnego programowania nadal jest dla mnie jak czytanie japońskiego znaku po znaku dla mnie (angielski).
Tarwin Stroh-Spijer

2
Jeśli zauważysz, że wdrażasz funkcje języka A ​​w języku B nie jako część projektu, którego jedynym celem jest właśnie to zrobić, to ktoś gdzieś skręcił w niewłaściwy sposób. Czy to może być Ty? Samo używanie const flatten = (arr) => arr.reduce((a, b) => a.concat(b), []);oszczędza wizualne śmieci i wyjaśnia członkom drużyny, dlaczego potrzebujesz 3 dodatkowych funkcji i niektórych wywołań funkcji.
Daerdemandt

3
@Daerdemandt Ale jeśli napiszesz to jako osobne funkcje, prawdopodobnie będziesz w stanie ponownie użyć ich w innym kodzie.
Michał Perłakowski

@ MichałPerłakowski Jeśli potrzebujesz ich użyć w kilku miejscach, nie wymyślaj koła ponownie i wybierz z nich pakiet - udokumentowany i obsługiwany przez inne osoby.
Daerdemandt

12

ES6 One Line Spłaszcz

Zobacz lodash spłaszczony , podkreślenie spłaszczony (płytki true)

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

lub

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

Testowane z

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

Zobacz lodash flattenDeep , podkreślenie spłaszcz

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

Testowane z

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

Twój drugi przykład jest lepiej napisany, Array.prototype.concat.apply([], arr)ponieważ tworzysz dodatkową tablicę, aby dostać się do concatfunkcji. Środowiska wykonawcze mogą go zoptymalizować, ale nie muszą go optymalizować po uruchomieniu, ale dostęp do funkcji w prototypie nie wygląda na bardziej brzydkiego, niż jest to w każdym przypadku.
Mörre

11

Możesz używać Array.flat()z Infinitydowolną głębokością zagnieżdżonej tablicy.

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)

sprawdź tutaj zgodność przeglądarki


8

Podejście 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
Zgadzam się z @monners. Wymyśl najbardziej eleganckie rozwiązanie w całym tym wątku.
Nicolás Fantone

8

Sposób 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))

Sposób ES5 dla flattenfunkcji z rezerwą ES3 dla tablic zagnieżdżonych w liczbie N:

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

Jeśli masz tylko tablice z 1 elementem łańcuchowym:

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

wykona robotę. Bt, który konkretnie pasuje do Twojego przykładu kodu.


3
Ktokolwiek głosował, proszę wyjaśnić dlaczego. Szukałem przyzwoitego rozwiązania i spośród wszystkich rozwiązań najbardziej mi się podobało.
Anonimowy

2
@Anonimowy Nie głosowałem za tym, ponieważ technicznie spełnia on wymagania pytania, ale jest prawdopodobne, ponieważ jest to dość słabe rozwiązanie, które nie jest przydatne w ogólnym przypadku. Biorąc pod uwagę, ile jest lepszych rozwiązań, nigdy nie poleciłbym komuś, kto poszedłby z tym, ponieważ psuje się, gdy masz więcej niż jeden element lub gdy nie są one łańcuchami.
Thor84no

2
Uwielbiam to rozwiązanie =)
Huei Tan

2
Nie obsługuje tylko tablic z 1 elementami strunowymi, obsługuje również tę tablicę ['$4', ["$6"], ["$12"], ["$25"], ["$25", "$33", ['$45']]].join(',').split(',')
alucic

Odkryłem tę metodę na własną rękę, choć wiedziałem, że musiała być gdzieś udokumentowana, moje poszukiwania zakończyły się tutaj. Wadą tego rozwiązania jest to, że zmusza liczby, booleany itp. Do ciągów znaków, spróbuj [1,4, [45, 't', ['e3', 6]]].toString().split(',') ---- lub ----- [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"]

(Piszę to tylko jako osobną odpowiedź na podstawie komentarza @danhbear.)


7

Polecam funkcję generatora zajmującego mało miejsca :

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

W razie potrzeby utwórz tablicę spłaszczonych wartości w następujący sposób:

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

Lubię to podejście. Podobne do stackoverflow.com/a/35073573/1175496 , ale używa operatora rozkładania ...do iteracji przez generator.
The Red Pea

6

Wolałbym przekształcić całą tablicę taką, jaka jest, w ciąg znaków, ale w przeciwieństwie do innych odpowiedzi, zrobiłbym to przy użyciu, JSON.stringifya nie przy użyciu toString()metody, która daje niepożądany wynik.

Przy tym JSON.stringifywyjściu pozostaje tylko usunięcie wszystkich nawiasów, zawinięcie wyniku w nawiasy początkowe i końcowe i podanie wyniku, dzięki JSON.parsektóremu ciąg znaków powraca do „życia”.

  • Może obsługiwać nieskończenie zagnieżdżone tablice bez żadnych kosztów prędkości.
  • Może poprawnie obsługiwać elementy tablicy, które są ciągami zawierającymi przecinki.

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

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

console.log(flattened)

  • Tylko dla wielowymiarowego zestawu ciągów / liczb (nie obiektów)

Twoje rozwiązanie jest nieprawidłowe. Będzie zawierał przecinek podczas spłaszczania wewnętrznych tablic ["345", "2", "3,4", "2"]zamiast oddzielania każdej z tych wartości w celu oddzielenia indeksów
pizzarob

@realseanp - źle zrozumiałeś wartość tego elementu tablicy. Celowo umieściłem ten przecinek jako wartość, a nie jako przecinek ogranicznika Array, aby podkreślić moc mojego rozwiązania ponad wszystkie inne, które mogłyby powstać "3,4".
vsync

1
Nie zrozumiałem
pizzarob

to zdecydowanie zdecydowanie najszybsze rozwiązanie, jakie widziałem w tym zakresie; jesteś świadomy każdy pułapek @vsync (z wyjątkiem faktu, że wygląda trochę hacky oczywiście - Leczenie zagnieżdżonych tablic jako ciągi: D)
George Katsanos

@GeorgeKatsanos - Ta metoda nie będzie działać dla elementów tablicy (i elementów zagnieżdżonych), które nie mają wartości Primitive, na przykład elementu wskazującego na element DOM
vsync

6

Możesz także wypróbować nową Array.Flat()metodę. Działa w następujący sposób:

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

console.log(arr);

The flat()Sposób tworzy się nową tablicę ze wszystkimi elementami macierzy sub-łączonych do niego rekurencyjnie do 1 warstwy na głębokość (tzn tablice wewnątrz macierzy)

Jeśli chcesz również spłaszczyć macierze trójwymiarowe lub nawet o wyższych wymiarach, wystarczy wywołać metodę płaską wiele razy. Na przykład (3 wymiary):

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

console.log(arr);

Bądź ostrożny!

Array.Flat()metoda jest stosunkowo nowa. Starsze przeglądarki takie jak np. Mogły nie wdrożyć tej metody. Jeśli chcesz, aby kod działał we wszystkich przeglądarkach, może być konieczne przeniesienie JS do starszej wersji. Sprawdź dokumenty MD dotyczące bieżącej zgodności przeglądarki.


2
do płaskich tablic wyższych wymiarów można po prostu wywołać metodę płaską z Infinityargumentem. W ten sposób:arr.flat(Infinity)
Michaił,

To bardzo miły dodatek, dzięki Michaił!
Willem van der Veen,

6

Za pomocą operatora rozprzestrzeniania:

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


5

To nie jest trudne, po prostu powtarzaj tablice i łącz je:

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

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

5

Wygląda na to, że wygląda to na REKURS!

  • Obsługuje wiele poziomów zagnieżdżania
  • Obsługuje puste tablice i parametry nieszablonowe
  • Nie ma mutacji
  • Nie polega na nowoczesnych funkcjach przeglądarki

Kod:

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

Stosowanie:

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

ostrożnie, flatten(new Array(15000).fill([1]))rzuca Uncaught RangeError: Maximum call stack size exceededi zamraża moje devTools na 10 sekund
pietrovismara

@pietrovismara, mój test trwa około 1 sekundy.
Ivan Yan

5

Zrobiłem to za pomocą rekurencji i zamknięć

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
Prosta i słodka, ta odpowiedź działa lepiej niż odpowiedź zaakceptowana. Spłaszcza głęboko zagnieżdżone poziomy, nie tylko pierwszy poziom
Om Shankar

1
AFAIK, który jest leksykalnym określeniem zakresu, a nie zamknięciem
dashambles

@dashambles jest poprawny - różnica polega na tym, że gdyby było zamknięciem, zwróciłbyś funkcję wewnętrzną na zewnątrz, a kiedy funkcja zewnętrzna jest zakończona, nadal możesz użyć funkcji wewnętrznej, aby uzyskać dostęp do jej zakresu. Żywotność funkcji zewnętrznej jest tutaj dłuższa niż funkcji wewnętrznej, dlatego nigdy nie powstaje „zamknięcie”.
Mörre

5

Pewnego dnia wygłupiałem się z Generatorami ES6 i napisałem tę treść . Który zawiera...

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

Zasadniczo tworzę generator, który zapętla oryginalną tablicę wejściową, jeśli znajdzie tablicę, używa operatora fed * w połączeniu z rekurencją w celu ciągłego spłaszczania wewnętrznych tablic. Jeśli element nie jest tablicą, po prostu daje pojedynczy element. Następnie za pomocą operatora Spread ES6 (aka splat operator) spłaszczam generator w nowej instancji tablicy.

Nie przetestowałem wydajności tego, ale wydaje mi się, że jest to ładny prosty przykład użycia generatorów i operatora fed *.

Ale znowu, po prostu wygłupiałem się, więc jestem pewien, że są bardziej wydajne sposoby, aby to zrobić.


1
Podobna odpowiedź (przy użyciu delegowania generatora i wydajności), ale w formacie PHP
The Red Pea

5

tylko najlepsze rozwiązanie bez lodash

let flatten = arr => [].concat.apply([], arr.map(item => Array.isArray(item) ? flatten(item) : item))
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.