Gabungkan / ratakan array array

1105

Saya memiliki larik JavaScript seperti:

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

Bagaimana saya bisa menggabungkan array batin terpisah menjadi seperti:

["$6", "$12", "$25", ...]
Andy
sumber
gist.github.com/Nishchit14/4c6a7349b3c778f7f97b912629a9f228 Tautan ini menggambarkan ratakan ES5 & ES6
Nishchit Dhanani
18
Semua solusi yang menggunakan reduce+ concatadalah O ((N ^ 2) / 2) di mana sebagai jawaban yang diterima (hanya satu panggilan ke concat) akan paling banyak O (N * 2) pada browser yang buruk dan O (N) pada bagus Juga solusi Denys dioptimalkan untuk pertanyaan aktual dan 2x lebih cepat dari yang tunggal concat. Untuk reduceorang - orang itu menyenangkan untuk merasa keren menulis kode kecil tetapi misalnya jika array memiliki 1000 elemen satu subarrays semua solusi mengurangi + concat akan melakukan operasi 500500 di mana sebagai concat tunggal atau loop sederhana akan melakukan 1.000 operasi.
GM
12
Cukup operator penyebaran pengguna[].concat(...array)
Oleg Dater
@ gman bagaimana mengurangi + solusi concat O ((N ^ 2) / 2)? stackoverflow.com/questions/52752666/… menyebutkan kompleksitas yang berbeda.
Usman
4
Dengan browser terbaru yang mendukung ES2019 : di array.flat(Infinity)mana Infinitykedalaman maksimum untuk diratakan.
Timothy Gu

Jawaban:

1860

Anda dapat menggunakan concatuntuk menggabungkan array:

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

console.log(merged);

Menggunakan applymetode concathanya akan mengambil parameter kedua sebagai array, sehingga baris terakhir identik dengan ini:

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

Ada juga Array.prototype.flat()metode (diperkenalkan dalam ES2019) yang dapat Anda gunakan untuk meratakan array, meskipun hanya tersedia di Node.js dimulai dengan versi 11, dan tidak sama sekali di Internet Explorer .

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

Gumbo
sumber
10
Catatan yang concattidak mengubah larik sumber, sehingga mergedlarik akan tetap kosong setelah panggilan ke concat. Lebih baik mengatakan sesuatu seperti:merged = merged.concat.apply(merged, arrays);
Nate
65
var merged = [].concat.apply([], arrays);tampaknya berfungsi dengan baik untuk mendapatkannya di satu baris. sunting: seperti yang ditunjukkan oleh jawaban Nikita.
Sean
44
Atau Array.prototype.concat.apply([], arrays).
danhbear
30
Catatan: jawaban ini hanya mendatar satu tingkat. Untuk perataan rekursif, lihat jawabannya oleh @Trindaz.
Phrogz
209
Lebih jauh lagi ke komentar @ Sean: sintaks ES6 membuat ini sangat ringkas:var merged = [].concat(...arrays)
Sethi
505

Berikut adalah fungsi singkat yang menggunakan beberapa metode array JavaScript yang lebih baru untuk meratakan array n-dimensi.

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

Pemakaian:

flatten([[1, 2, 3], [4, 5]]); // [1, 2, 3, 4, 5]
flatten([[[1, [1.1]], 2, 3], [4, 5]]); // [1, 1.1, 2, 3, 4, 5]
Noah Freitas
sumber
17
Saya suka pendekatan ini. Ini jauh lebih umum dan mendukung array bersarang
alfredocambera
10
Apa profil penggunaan memori untuk solusi ini? Sepertinya itu menciptakan banyak array menengah selama rekursi ekor ....
JBRWilkinson
7
@ayjay, ini adalah nilai akumulator awal untuk fungsi pengurangan, yang disebut mdn sebagai nilai awal. Dalam hal ini nilai flatdalam panggilan pertama ke fungsi anonim diteruskanreduce . Jika tidak ditentukan, maka panggilan pertama untuk reducemengikat nilai pertama dari array flat, yang pada akhirnya akan menghasilkan 1terikat flatpada kedua contoh. 1.concatbukan fungsi.
Noah Freitas
19
Atau dalam bentuk yang lebih pendek dan seksi: const flatten = (arr) => arr.reduce((flat, next) => flat.concat(next), []);
Tsvetomir Tsonev
12
Riffing pada solusi @TsvetomirTsonev dan Nuh untuk bersarang secara sewenang-wenang:const flatten = (arr) => arr.reduce((flat, next) => flat.concat(Array.isArray(next) ? flatten(next) : next), []);
Will
314

Ada metode tersembunyi yang membingungkan, yang membangun array baru tanpa mengubah yang asli:

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

Nikita Volkov
sumber
11
Dalam CoffeeScript ini[].concat([[1],[2,3],[4]]...)
amoebe
8
@amoebe jawaban Anda memberi [[1],[2,3],[4]]sebagai hasilnya. Solusi yang @Nikita berikan benar untuk CoffeeScript dan juga JS.
Ryan Kennedy
5
Ahh, saya menemukan kesalahan Anda. Anda harus memiliki sepasang kurung kotak tambahan di notasi Anda [].concat([1],[2,3],[4],...).
Ryan Kennedy
21
Saya pikir saya menemukan kesalahan Anda juga. The ...adalah kode aktual, tidak beberapa titik elipsis.
amoebe
3
Menggunakan fungsi perpustakaan tidak berarti ada "mess" yang kurang penting. Hanya saja kekacauan itu tersembunyi di dalam perpustakaan. Jelas menggunakan perpustakaan lebih baik daripada menggulirkan fungsi Anda sendiri, tetapi mengklaim bahwa teknik ini mengurangi "kekacauan keharusan" cukup konyol.
paldepind
204

Ini bisa dilakukan dengan mengurangi fungsi 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);
}, []);

Atau, dengan ES2015:

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

js-biola

Mozilla docs

pengguna2668376
sumber
1
@JohnS Sebenarnya mengurangi feed ke concat nilai (array) tunggal per giliran. Bukti? ambil kurangi () dan lihat apakah itu berhasil. kurangi () di sini adalah alternatif untuk Function.prototype.apply ()
André Werlang
3
Saya akan menggunakan pengurangan juga tetapi satu hal menarik yang saya pelajari dari cuplikan ini adalah bahwa sebagian besar waktu Anda tidak perlu melewati initialValue :)
José F. Romaniello
2
Masalah dengan pengurangan adalah bahwa array tidak boleh kosong, jadi Anda perlu menambahkan validasi tambahan.
calbertts
4
@calbertts Cukup berikan nilai awal []dan tidak perlu validasi lebih lanjut.
8
Karena Anda menggunakan ES6, Anda juga bisa menggunakan spread-operator sebagai array literal . arrays.reduce((flatten, arr) => [...flatten, ...arr])
Putzi San
109

Ada metode asli baru yang disebut flat untuk melakukan ini dengan tepat.

(Sampai akhir 2019, flatsekarang diterbitkan dalam standar ECMA 2019, dan core-js@3(perpustakaan babel) memasukkannya ke perpustakaan polyfill mereka )

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

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

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

// Flatten all levels
const arr4 = [2, 2, 5, [5, [5, [6]], 7]];
arr4.flat(Infinity);
// [2, 2, 5, 5, 5, 6, 7];
Alister
sumber
4
Sayang sekali ini bahkan tidak ada di halaman pertama jawaban. Fitur ini tersedia di Chrome 69 dan Firefox 62 (dan Node 11 untuk mereka yang bekerja di backend)
Matt M.
2
-1; tidak, ini bukan bagian dari ECMAScript 2018 . Itu masih hanya proposal yang belum sampai ke spesifikasi ECMAScript.
Mark Amery
1
Saya pikir sekarang kita dapat mempertimbangkan ini .. karena sekarang ini adalah bagian dari standar (2019) .. bisakah kita meninjau kembali bagian kinerja sekali ini?
Yuvaraj
Tampaknya itu belum didukung oleh peramban Microsoft apa pun (setidaknya pada saat saya menulis komentar ini)
Laurent S.
78

Sebagian besar jawaban di sini tidak bekerja pada array besar (misalnya 200 000 elemen), dan bahkan jika itu, mereka lambat. Jawaban polkovnikov.ph memiliki kinerja terbaik, tetapi tidak berfungsi untuk meratakan.

Ini adalah solusi tercepat, yang juga berfungsi pada array dengan beberapa level sarang :

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

Contohnya

Array besar

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

Ini menangani array besar dengan baik. Di komputer saya kode ini membutuhkan sekitar 14 ms untuk dijalankan.

Array bersarang

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

Ini bekerja dengan array bersarang. Kode ini menghasilkan [1, 1, 1, 1, 1, 1, 1, 1].

Array dengan berbagai tingkat sarang

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

Itu tidak memiliki masalah dengan meratakan array seperti ini.

Michał Perłakowski
sumber
Kecuali array besar Anda cukup datar. Solusi ini tidak akan berfungsi untuk array yang sangat bersarang. Tidak akan ada solusi rekursif. Bahkan tidak ada browser tetapi Safari memiliki TCO sekarang, jadi tidak ada algoritma rekursif yang akan bekerja dengan baik.
tepatnya
@nably Tapi dalam situasi dunia nyata apa Anda akan memiliki array dengan lebih dari beberapa tingkat bersarang?
Michał Perłakowski
2
Biasanya, ketika array dihasilkan dari konten yang dibuat pengguna.
tepatnya
@ 0xcaff Di Chrome tidak berfungsi sama sekali dengan array 200 000-elemen (Anda dapatkan RangeError: Maximum call stack size exceeded). Untuk 20.000 elemen array dibutuhkan 2-5 milidetik.
Michał Perłakowski
3
apa kompleksitas notasi O ini?
George Katsanos
58

Pembaruan: ternyata solusi ini tidak berfungsi dengan array besar. Jika Anda mencari solusi yang lebih baik, lebih cepat, periksa jawaban ini .


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

Hanya memperluas arrdan meneruskannya sebagai argumen concat(), yang menggabungkan semua array menjadi satu. Ini setara dengan[].concat.apply([], arr) .

Anda juga dapat mencoba ini untuk meratakan:

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

Lihat demo di JSBin .

Referensi untuk elemen ECMAScript 6 yang digunakan dalam jawaban ini:


Catatan sisi: metode suka find()dan fungsi panah tidak didukung oleh semua browser, tetapi itu tidak berarti bahwa Anda tidak dapat menggunakan fitur ini sekarang. Cukup gunakan Babel - ini mengubah kode ES6 menjadi ES5.

Michał Perłakowski
sumber
Karena hampir semua balasan di sini disalahgunakan applydengan cara ini, saya menghapus komentar saya dari Anda. Saya masih berpikir menggunakan apply/ menyebar dengan cara ini adalah saran yang buruk, tetapi karena tidak ada yang peduli ...
@ LUH3417 Tidak seperti itu, saya sangat menghargai komentar Anda. Ternyata Anda benar - solusi ini memang tidak bekerja dengan array yang besar. Saya memposting jawaban lain yang berfungsi dengan baik bahkan dengan array 200 000 elemen.
Michał Perłakowski
Jika Anda menggunakan ES6, Anda dapat meredam lebih lanjut ke:const flatten = arr => [].concat(...arr)
Eruant
Apa maksudmu "tidak bekerja dengan array besar"? Seberapa besar? Apa yang terjadi?
GEMI
4
@GEMI Misalnya mencoba untuk meratakan array elemen 500000 menggunakan metode ini memberikan "RangeError: Ukuran stack panggilan maksimum terlampaui".
Michał Perłakowski
51

Anda dapat menggunakan Garis Bawah :

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

_.flatten(x); // => [1, 2, 3, 4]
Todd Yandell
sumber
20
Whoah, apakah ada yang menambahkan Perl ke JS? :-)
JBRWilkinson
9
@JBRWilkinson Mungkin JS ingin mengajari Anda Haskell untuk Great Good !?
styfle
1+ - Anda juga dapat menentukan bahwa Anda menginginkan array yang rata rata dengan menentukan trueargumen kedua .
Josh Crozier
7
Demi kelengkapan tentang komentar @ styfle: learnyouahaskell.com
Simon A. Eugster
41

Prosedur umum berarti kita tidak perlu menulis ulang kompleksitas setiap kali kita perlu memanfaatkan perilaku tertentu.

concatMap(atau flatMap) persis apa yang kita butuhkan dalam situasi ini.

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

tinjauan ke masa depan

Dan ya, Anda menebaknya dengan benar, itu hanya meratakan satu tingkat, yang persis bagaimana seharusnya bekerja

Bayangkan beberapa kumpulan data seperti ini

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

Oke, sekarang katakan kita ingin mencetak daftar yang menunjukkan semua pemain yang akan berpartisipasi dalam game...

const gamePlayers = game =>
  flatten (game)

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

Jika flattenprosedur kami juga meratakan susunan bersarang, kami akan berakhir dengan hasil sampah ini ...

const gamePlayers = game =>
  badGenericFlatten(game)

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

berguling dalam, sayang

Itu bukan untuk mengatakan kadang-kadang Anda tidak ingin meratakan array bersarang, - hanya itu yang seharusnya tidak menjadi perilaku default.

Kita dapat membuat deepFlattenprosedur dengan mudah ...

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

Sana. Sekarang Anda memiliki alat untuk setiap pekerjaan - satu untuk meremas satu tingkat sarang flatten,, dan satu untuk melenyapkan semua sarangdeepFlatten .

Mungkin Anda bisa menyebutnya obliterateatau nukejika Anda tidak suka namanya deepFlatten.


Jangan beralih dua kali!

Tentu saja implementasi di atas pintar dan ringkas, tetapi menggunakan .mapdiikuti oleh panggilan untuk.reduce berarti kita benar-benar melakukan lebih banyak iterasi daripada yang diperlukan

Menggunakan kombinator terpercaya saya menelepon mapReducemembantu menjaga iterasi ke minium; dibutuhkan fungsi pemetaan m :: a -> b, fungsi pereduksi r :: (b,a) ->bdan mengembalikan fungsi pereduksi baru - kombinator ini adalah jantung dari transduser ; jika Anda tertarik, saya sudah menulis jawaban lain tentang mereka

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

Terima kasih
sumber
Seringkali, ketika saya melihat balasan Anda, saya ingin menarik milik saya, karena mereka telah menjadi tidak berharga. Jawaban bagus! concatitu sendiri tidak meledakkan tumpukan, hanya ...dan applytidak (bersama dengan array yang sangat besar). Saya tidak melihatnya. Aku hanya merasa tidak enak sekarang.
@ LUH3417 kamu seharusnya tidak merasa buruk. Anda juga memberikan jawaban yang baik dengan penjelasan yang ditulis dengan baik. Semuanya di sini adalah pengalaman belajar - saya sering membuat kesalahan dan menulis jawaban buruk juga. Saya mengunjungi mereka kembali dalam beberapa bulan dan berpikir, "Apa yang saya pikirkan?" dan akhirnya sepenuhnya merevisi hal-hal. Tidak ada jawaban yang sempurna. Kita semua pada titik tertentu pada kurva belajar ^ _ ^
Terima kasih
1
Harap dicatat bahwa concatdalam Javascript memiliki arti yang berbeda dengan di Haskell. Haskell's concat( [[a]] -> [a]) akan dipanggil flattendalam Javascript dan diimplementasikan sebagai foldr (++) [](Javascript: foldr(concat) ([])asumsi fungsi kari). Javascript concatadalah append aneh ( (++)dalam Haskell), yang dapat menangani keduanya [a] -> [a] -> [a]dan a -> [a] -> [a].
Saya kira nama yang lebih baik adalah flatMap, karena itulah tepatnya concatMap: bindContoh dari listmonad. concatpMapdiimplementasikan sebagai foldr ((++) . f) []. Diterjemahkan ke dalam Javascript: const flatMap = f => foldr(comp(concat) (f)) ([]). Ini tentu saja mirip dengan implementasi Anda tanpa comp.
apa kompleksitas dari algoritma itu?
George Katsanos
32

Sebuah solusi untuk kasus yang lebih umum, ketika Anda mungkin memiliki beberapa elemen non-array di array Anda.

function flattenArrayOfArrays(a, r){
    if(!r){ r = []}
    for(var i=0; i<a.length; i++){
        if(a[i].constructor == Array){
            r.concat(flattenArrayOfArrays(a[i], r));
        }else{
            r.push(a[i]);
        }
    }
    return r;
}
Trindaz
sumber
4
Pendekatan ini sangat efektif dalam meratakan bentuk array bersarang dari set hasil yang Anda dapatkan dari kueri JsonPath .
kevinjansz
1
Ditambahkan sebagai metode susunan: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
Ini akan rusak jika kita secara manual melewati argumen kedua. Sebagai contoh, coba ini: flattenArrayOfArrays (arr, 10)atau ini flattenArrayOfArrays(arr, [1,[3]]);- argumen kedua ditambahkan ke output.
Om Shankar
2
jawaban ini tidak lengkap! hasil rekursi tidak pernah diberikan di mana pun, sehingga rekursi hilang
r3wt
1
@ r3wt melanjutkan dan menambahkan perbaikan. Yang perlu Anda lakukan adalah memastikan bahwa array saat ini yang Anda pertahankan r,, akan benar-benar menyatukan hasil dari rekursi.
Agustus
29

Untuk meratakan array array elemen tunggal, Anda tidak perlu mengimpor perpustakaan, loop sederhana adalah solusi paling sederhana dan paling efisien :

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

Untuk downvoters: harap baca pertanyaannya, jangan downvote karena tidak sesuai dengan masalah Anda yang sangat berbeda. Solusi ini adalah yang tercepat dan paling sederhana untuk pertanyaan yang diajukan.

Denys Séguret
sumber
4
Saya akan berkata, "Jangan mencari hal-hal yang lebih samar." ^^
4
Maksud saya ... ketika saya melihat orang-orang menyarankan untuk menggunakan perpustakaan untuk itu ... itu gila ...
Denys Séguret
3
Ahh, menggunakan perpustakaan hanya untuk ini akan konyol ... tetapi jika sudah tersedia.
9
@dystroy Bagian bersyarat untuk loop tidak dapat dibaca. Atau hanya saya: D
Andreas
4
Tidak masalah seberapa samarnya itu. Kode ini "meratakan" ini ['foo', ['bar']]ke ['f', 'bar'].
jonschlinkert
29

Bagaimana dengan menggunakan reduce(callback[, initialValue])metodeJavaScript 1.8

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

Akan melakukan pekerjaan itu.

rab
sumber
8
[[1], [2,3]].reduce( (a,b) => a.concat(b), [] )lebih seksi.
golopot
5
Tidak perlu argumen kedua dalam kasus kami yang sederhana[[1], [2,3]].reduce( (a,b) => a.concat(b))
Umair Ahmed
29

Solusi ECMAScript 6 lain dalam gaya fungsional:

Deklarasikan fungsi:

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

dan gunakan:

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

Pertimbangkan juga fungsi asli Array.prototype.flat () (proposal untuk ES6) yang tersedia di rilis terbaru browser modern. Terima kasih kepada @ (Константин Ван) dan @ (Mark Amery) yang menyebutkannya di komentar.

The flatfungsi memiliki satu parameter, menentukan kedalaman diharapkan array bersarang, yang sama 1secara default.

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

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

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

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

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

console.log( arr.flat() );

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

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

diziaq
sumber
3
Ini bagus dan rapi tetapi saya pikir Anda telah melakukan overdosis ES6. Tidak perlu fungsi luar menjadi fungsi panah. Saya akan tetap dengan fungsi panah untuk mengurangi callback tetapi meratakan sendiri seharusnya menjadi fungsi normal.
Stephen Simpson
3
@StephenSimpson tetapi apakah ada kebutuhan untuk fungsi luar menjadi fungsi non -arrow? "Meratakan sendiri seharusnya merupakan fungsi normal" - oleh "normal" maksudmu "non-panah", tapi mengapa? Mengapa menggunakan fungsi panah dalam panggilan untuk mengurangi itu? Bisakah Anda memberikan alasan Anda?
Terima kasih
@naomik Alasan saya adalah tidak perlu. Ini terutama masalah gaya; Seharusnya saya lebih jelas dalam komentar saya. Tidak ada alasan pengkodean utama untuk menggunakan satu atau yang lain. Namun, fungsi ini lebih mudah dilihat dan dibaca sebagai bukan panah. Fungsi dalam berguna sebagai fungsi panah karena lebih kompak (dan tentu saja tidak ada konteks yang dibuat). Fungsi panah sangat bagus untuk membuat fungsi ringkas yang mudah dibaca dan menghindari kebingungan ini. Namun, mereka benar-benar dapat membuatnya lebih sulit untuk dibaca ketika panah yang tidak mencukupi sudah cukup. Yang lain mungkin tidak setuju!
Stephen Simpson
MendapatkanRangeError: Maximum call stack size exceeded
Matt Westlake
@Matt, tolong bagikan evnironment yang Anda gunakan untuk mereproduksi kesalahan
diziaq
22
const common = arr.reduce((a, b) => [...a, ...b], [])
YairTawil
sumber
Ini adalah bagaimana saya melakukannya di seluruh kode saya tetapi saya tidak yakin bagaimana kecepatan dibandingkan dengan jawaban yang diterima jika Anda memiliki array yang sangat panjang
Michael Aaron Wilson
14

Harap dicatat: Ketika Function.prototype.apply( [].concat.apply([], arrays)) atau operator penyebaran ( [].concat(...arrays)) digunakan untuk meratakan array, keduanya dapat menyebabkan stack overflow untuk array besar, karena setiap argumen dari suatu fungsi disimpan pada stack.

Berikut ini adalah implementasi stack-safe dengan gaya fungsional yang menimbang persyaratan yang paling penting terhadap satu sama lain:

  • dapat digunakan kembali
  • keterbacaan
  • keringkasan yg padat isinya
  • kinerja

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

Segera setelah Anda terbiasa dengan fungsi panah kecil dalam bentuk kari, komposisi fungsi dan fungsi urutan lebih tinggi, kode ini berbunyi seperti prosa. Pemrograman kemudian hanya terdiri dari menyusun blok bangunan kecil yang selalu berfungsi seperti yang diharapkan, karena mereka tidak mengandung efek samping.


sumber
1
Ha ha. Benar-benar menghargai jawaban Anda, meskipun membaca pemrograman fungsional seperti ini masih seperti membaca karakter Jepang berdasarkan karakter kepada saya (pembicara bahasa Inggris)
Tarwin Stroh-Spijer
2
Jika Anda mendapati diri Anda menerapkan fitur-fitur bahasa A dalam bahasa B bukan sebagai bagian dari proyek dengan satu-satunya tujuan melakukan hal ini, maka seseorang di tempat yang salah mengambil jalan. Mungkinkah itu kamu? Hanya pergi dengan const flatten = (arr) => arr.reduce((a, b) => a.concat(b), []);menghemat sampah visual dan penjelasan kepada rekan tim Anda mengapa Anda membutuhkan 3 fungsi tambahan dan beberapa panggilan fungsi juga.
Daerdemandt
3
@Daerdemandt Tetapi jika Anda menulisnya sebagai fungsi yang terpisah, Anda mungkin dapat menggunakannya kembali dalam kode lain.
Michał Perłakowski
@ MichałPerłakowski Jika Anda perlu menggunakannya di beberapa tempat maka jangan menemukan kembali roda dan memilih paket dari ini - didokumentasikan dan didukung oleh orang lain.
Daerdemandt
12

ES6 One Line Ratakan

Lihat Lodash rata , garis bawah rata (dangkal true)

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

atau

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

Diuji dengan

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

Lihat meratakan lodash , garis bawah rata

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

Diuji dengan

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

test('flats', () => {
  expect(flattenDeep([1, [2, [3, [4]], 5]])).toEqual([1, 2, 3, 4, 5]);
});
zurfyx
sumber
Contoh ke-2 Anda lebih baik ditulis Array.prototype.concat.apply([], arr)karena Anda membuat array tambahan hanya untuk mendapatkan concatfungsi. Runtimes mungkin atau mungkin tidak mengoptimalkannya ketika mereka menjalankannya, tetapi mengakses fungsi pada prototipe tidak terlihat lebih buruk daripada ini sudah dalam hal apapun.
Mörre
11

Anda dapat menggunakannya Array.flat()dengan Infinityuntuk kedalaman array bersarang.

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)

periksa di sini untuk kompatibilitas browser

Code Maniac
sumber
8

Pendekatan Haskellesque

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

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

Redu
sumber
1
Setuju dengan @monners. Solusi praktis yang paling elegan di seluruh utas ini.
Nicolás Fantone
8

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

Cara ES5 untuk flattenfungsi dengan fallback ES3 untuk array bertingkat N-kali:

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

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

      return arr;
    };
  }
}());

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

Artem Gavrysh
sumber
7

Jika Anda hanya memiliki array dengan 1 elemen string:

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

akan melakukan pekerjaan itu. Bt yang secara spesifik cocok dengan contoh kode Anda.

Florian Salihovic
sumber
3
Siapa pun yang memilih, jelaskan alasannya. Saya sedang mencari solusi yang layak dan dari semua solusi yang paling saya sukai ini.
Anonim
2
@ Anonim Saya tidak melakukan downvote karena secara teknis memenuhi persyaratan pertanyaan, tetapi kemungkinan karena ini adalah solusi yang sangat buruk yang tidak berguna dalam kasus umum. Mempertimbangkan berapa banyak solusi yang lebih baik di sini, saya tidak akan merekomendasikan seseorang untuk pergi dengan yang satu ini karena akan merusak momen Anda memiliki lebih dari satu elemen, atau ketika itu bukan string.
Thor84no
2
Saya suka solusi ini =)
Huei Tan
2
Itu tidak hanya menangani array dengan 1 elemen string, juga menangani array ini ['$4', ["$6"], ["$12"], ["$25"], ["$25", "$33", ['$45']]].join(',').split(',')
alucic
Saya menemukan metode ini sendiri, tetapi tahu itu pasti telah didokumentasikan di suatu tempat, pencarian saya berakhir di sini. Kelemahan dengan solusi ini adalah, ia memaksa angka, boolean dll untuk string, coba [1,4, [45, 't', ['e3', 6]]].toString().split(',') ---- atau ----- [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"]

(Saya hanya menulis ini sebagai jawaban terpisah, berdasarkan komentar dari @danhbear.)

VasiliNovikov
sumber
7

Saya merekomendasikan fungsi generator hemat ruang :

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

Jika diinginkan, buat array nilai rata sebagai berikut:

let flattened = [...flatten([1,[2,[3,[4]]]])]; // [1, 2, 3, 4]
le_m
sumber
Saya suka pendekatan ini. Mirip dengan stackoverflow.com/a/35073573/1175496 , tetapi menggunakan operator spread ...untuk beralih melalui generator.
Kacang Merah
6

Saya lebih suka mengubah seluruh array, apa adanya, menjadi string, tetapi tidak seperti jawaban lain, akan melakukannya dengan menggunakan JSON.stringifydan tidak menggunakan toString()metode, yang menghasilkan hasil yang tidak diinginkan.

Dengan JSON.stringifyoutput itu, yang tersisa hanyalah menghapus semua tanda kurung, membungkus hasilnya dengan tanda tanda mulai & mengakhiri lagi, dan menyajikan hasil JSON.parseyang mengembalikan string ke "hidup".

  • Dapat menangani array bersarang tanpa batas tanpa biaya kecepatan.
  • Dapat dengan benar menangani item Array yang merupakan string yang mengandung koma.

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

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

console.log(flattened)

  • Hanya untuk array multidimensi string / angka (bukan objek)
vsync
sumber
Solusi Anda salah. Ini akan mengandung koma ketika meratakan array dalam ["345", "2", "3,4", "2"]alih-alih memisahkan masing-masing nilai-nilai untuk memisahkan indeks
pizzarob
@realseanp - Anda salah memahami nilai item Array itu. Saya sengaja menempatkan koma sebagai nilai dan bukan sebagai koma pembatas Array untuk menekankan kekuatan solusi saya di atas semua yang lain, yang akan menghasilkan "3,4".
vsync
1
Saya salah paham
pizzarob
yang tampaknya pasti solusi tercepat yang pernah saya lihat untuk ini; apakah Anda mengetahui adanya jebakan @vsync (kecuali faktanya terlihat agak berantakan tentu saja - memperlakukan array bersarang sebagai string: D)
George Katsanos
@GeorgeKatsanos - Metode ini tidak akan berfungsi untuk item array (dan item bersarang) yang tidak memiliki nilai Primitive, misalnya item yang menunjuk ke elemen DOM
vsync
6

Anda juga dapat mencoba Array.Flat()metode baru . Ini bekerja dengan cara berikut:

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

console.log(arr);

Itu flat() Metode menciptakan sebuah array baru dengan semua elemen sub-array bersambung ke dalamnya secara rekursif hingga 1 lapisan kedalaman (yaitu array di dalam array)

Jika Anda juga ingin meratakan array 3 dimensi atau bahkan lebih tinggi, Anda cukup memanggil metode flat beberapa kali. Misalnya (3 dimensi):

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

console.log(arr);

Hati-hati!

Array.Flat()Metode ini relatif baru. Browser lama seperti mis. Mungkin belum menerapkan metode. Jika Anda ingin kode Anda berfungsi pada semua browser, Anda mungkin harus mengubah JS Anda ke versi yang lebih lama. Periksa MD web docs untuk kompatibilitas browser saat ini.

Willem van der Veen
sumber
2
untuk flat array dimensi yang lebih tinggi Anda cukup memanggil metode flat dengan Infinityargumen. Seperti ini:arr.flat(Infinity)
Mikhail
Itu tambahan yang sangat bagus, terima kasih Mikhail!
Willem van der Veen
6

Menggunakan operator spread:

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

Ikan lele
sumber
5

Itu tidak sulit, hanya mengulangi array dan menggabungkannya:

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

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

Sepertinya ini terlihat seperti pekerjaan untuk REKURSI!

  • Menangani beberapa tingkat sarang
  • Menangani parameter array dan non array kosong
  • Tidak memiliki mutasi
  • Tidak mengandalkan fitur browser modern

Kode:

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

Pemakaian:

flatten([1,[2,3],4,[[5,6],7]]);
// Result: [1, 2, 3, 4, 5, 6, 7] 
Jai
sumber
hati-hati, flatten(new Array(15000).fill([1]))melempar Uncaught RangeError: Maximum call stack size exceededdan membekukan devTools saya selama 10 detik
pietrovismara
@pietrovismara, tes saya sekitar 1s.
Ivan Yan
5

Saya telah melakukannya menggunakan rekursi dan penutupan

function flatten(arr) {

  var temp = [];

  function recursiveFlatten(arr) { 
    for(var i = 0; i < arr.length; i++) {
      if(Array.isArray(arr[i])) {
        recursiveFlatten(arr[i]);
      } else {
        temp.push(arr[i]);
      }
    }
  }
  recursiveFlatten(arr);
  return temp;
}
balajivijayan
sumber
1
Sederhana dan manis, jawaban ini berfungsi lebih baik daripada jawaban yang diterima. Itu meratakan tingkat yang sangat dalam ke, bukan hanya tingkat pertama
Om Shankar
1
AFAIK yang merupakan pelingkupan leksikal dan bukan penutup
dashambles
@dashambles benar - perbedaannya adalah bahwa jika itu adalah penutupan Anda akan mengembalikan fungsi bagian dalam ke luar dan ketika fungsi luar selesai Anda masih dapat menggunakan fungsi bagian dalam untuk mengakses ruang lingkupnya. Di sini, masa fungsi luar lebih lama daripada fungsi bagian dalam sehingga "penutupan" tidak pernah dibuat.
Mörre
5

Saya bermain-main dengan Generator ES6 tempo hari dan menulis inti ini . Yang mengandung...

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

Pada dasarnya saya membuat generator yang loop atas array input asli, jika menemukan array menggunakan operator hasil * dalam kombinasi dengan rekursi untuk terus meratakan array internal. Jika item tersebut bukan array, ia hanya menghasilkan item tunggal. Kemudian menggunakan operator Spread ES6 (alias operator percikan) saya meratakan generator menjadi contoh array baru.

Saya belum menguji kinerja ini, tapi saya pikir ini adalah contoh sederhana yang bagus untuk menggunakan generator dan operator hasil *.

Tapi sekali lagi, saya hanya bermain-main jadi saya yakin ada lebih banyak cara untuk melakukan ini.

Ashwell
sumber
1
Jawaban serupa (menggunakan delegasi generator dan hasil), tetapi dalam format PHP
The Red Pea
5

hanya solusi terbaik tanpa lodash

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