Hợp nhất / làm phẳng một mảng các mảng


1104

Tôi có một mảng JavaScript như:

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

Làm thế nào tôi có thể hợp nhất các mảng bên trong riêng biệt thành một như sau:

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


17
Tất cả các giải pháp sử dụng reduce+ concatlà O ((N ^ 2) / 2) trong đó dưới dạng câu trả lời được chấp nhận (chỉ một cuộc gọi đến concat) sẽ có nhiều nhất là O (N * 2) trên trình duyệt xấu và O (N) trên a tốt một. Ngoài ra giải pháp Denys được tối ưu hóa cho câu hỏi thực tế và nhanh hơn gấp 2 lần so với câu hỏi đơn concat. Đối với reducemọi người, thật vui khi cảm thấy thú vị khi viết mã nhỏ, nhưng ví dụ, nếu mảng có 1000 phần tử thì tất cả các giải pháp rút gọn + concat sẽ thực hiện 500500 thao tác trong đó vòng lặp đơn hoặc vòng đơn sẽ thực hiện 1000 thao tác.
gman

12
Đơn giản là nhà điều hành lây lan người dùng[].concat(...array)
Oleg Dater

@gman làm thế nào là giảm + giải pháp concat O ((N ^ 2) / 2)? stackoverflow.com/questions/52752666/ đá đề cập đến một sự phức tạp khác nhau.
Usman

4
Với các trình duyệt mới nhất hỗ trợ ES2019 : array.flat(Infinity)đâu Infinitylà độ sâu tối đa để làm phẳng.
Timothy Gu

Câu trả lời:


1860

Bạn có thể sử dụng concatđể hợp nhất các mảng:

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

console.log(merged);

Sử dụng applyphương thức concatsẽ chỉ lấy tham số thứ hai dưới dạng một mảng, vì vậy dòng cuối cùng giống hệt như sau:

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

Ngoài ra còn có Array.prototype.flat()phương thức (được giới thiệu trong ES2019) mà bạn có thể sử dụng để làm phẳng các mảng, mặc dù nó chỉ có sẵn trong Node.js bắt đầu với phiên bản 11, và hoàn toàn không có trong Internet Explorer .

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


10
Lưu ý rằng concatkhông sửa đổi mảng nguồn, vì vậy mergedmảng sẽ vẫn trống sau khi gọi đến concat. Tốt hơn nên nói một cái gì đó như:merged = merged.concat.apply(merged, arrays);
Nate

65
var merged = [].concat.apply([], arrays);dường như hoạt động tốt để có được nó trên một dòng. chỉnh sửa: như câu trả lời của Nikita đã hiển thị.
Sean

44
Hoặc Array.prototype.concat.apply([], arrays).
danhbear

30
Lưu ý: câu trả lời này chỉ làm phẳng một cấp độ sâu. Đối với một flatten đệ quy, xem câu trả lời của @Trindaz.
Phrogz

209
Thêm vào nhận xét của @ Sean: Cú pháp ES6 làm cho siêu ngắn gọn này:var merged = [].concat(...arrays)
Sethi

505

Đây là một hàm ngắn sử dụng một số phương thức mảng JavaScript mới hơn để làm phẳng một mảng n chiều.

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

Sử dụng:

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
Tôi thích cách tiếp cận này. Nó tổng quát hơn nhiều và hỗ trợ các mảng lồng nhau
alfredocambera

10
Hồ sơ sử dụng bộ nhớ cho giải pháp này là gì? Có vẻ như nó tạo ra rất nhiều mảng trung gian trong quá trình đệ quy đuôi ....
JBRWilkinson

7
@ayjay, đó là giá trị tích lũy bắt đầu cho hàm giảm, cái mà mdn gọi là giá trị ban đầu. Trong trường hợp này, đó là giá trị của flattrong lệnh gọi đầu tiên đến hàm ẩn danh được truyền tới reduce. Nếu nó không được chỉ định, thì lệnh gọi đầu tiên reduceliên kết giá trị đầu tiên ra khỏi mảng flat, cuối cùng sẽ dẫn đến 1bị ràng buộc flattrong cả hai ví dụ. 1.concatkhông phải là một chức năng.

19
Hoặc ở dạng ngắn hơn, quyến rũ hơn: const flatten = (arr) => arr.reduce((flat, next) => flat.concat(next), []);
Tsvetomir Tsonev 8/8/2016

12
Riffing trên @TsvetomirTsonev và các giải pháp cho việc lồng nhau tùy ý:const flatten = (arr) => arr.reduce((flat, next) => flat.concat(Array.isArray(next) ? flatten(next) : next), []);
Sẽ vào

314

Có một phương thức ẩn khó hiểu, nó xây dựng một mảng mới mà không làm thay đổi cái ban đầu:

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


11
Trong CoffeeScript, đây là[].concat([[1],[2,3],[4]]...)
amoebe

8
@amoebe câu trả lời của bạn [[1],[2,3],[4]]là kết quả. Giải pháp mà @Nikita đưa ra là chính xác cho CoffeeScript cũng như JS.
Ryan Kennedy

5
Ahh, tôi tìm thấy lỗi của bạn. Bạn nên có thêm một cặp dấu ngoặc vuông trong ký hiệu của mình [].concat([1],[2,3],[4],...).
Ryan Kennedy

21
Tôi nghĩ rằng tôi đã tìm thấy lỗi của bạn, quá. Các ...mã thực tế, không phải là một số dấu chấm lửng.
amoebe

3
Sử dụng một chức năng thư viện không có nghĩa là có "mớ hỗn độn" ít bắt buộc hơn. Chỉ là sự lộn xộn được giấu trong một thư viện. Rõ ràng sử dụng một thư viện tốt hơn so với cuộn chức năng của riêng bạn, nhưng tuyên bố rằng kỹ thuật này làm giảm "mớ hỗn độn" là khá ngớ ngẩn.
paldepind

204

Nó có thể được thực hiện tốt nhất bằng chức năng giảm 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);
}, []);

Hoặc, với ES2015:

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

js-fiddle

Tài liệu Mozilla


1
@JohnS Trên thực tế giảm nguồn cấp dữ liệu vào concat một giá trị (mảng) mỗi lượt. Bằng chứng? đưa ra giảm () và xem nếu nó hoạt động. giảm () ở đây là một thay thế cho Function.prototype.apply ()
André Werlang 23/215

3
Tôi cũng đã sử dụng giảm nhưng một điều thú vị tôi học được từ đoạn trích này là hầu hết thời gian bạn không cần phải vượt qua một giá trị ban đầu :)
José F. Romaniello

2
Vấn đề với giảm là mảng không thể trống, do đó bạn sẽ cần thêm xác nhận bổ sung.
calbertts

4
@calbertts Chỉ cần vượt qua giá trị ban đầu []và không cần xác nhận thêm.

8
Vì bạn sử dụng ES6, bạn cũng có thể sử dụng toán tử trải rộng dưới dạng mảng . arrays.reduce((flatten, arr) => [...flatten, ...arr])
Putzi San

109

Có một phương pháp bản địa mới gọi là căn hộ để thực hiện điều này một cách chính xác.

(Tính đến cuối năm 2019, flathiện đã được xuất bản trong tiêu chuẩn ECMA 2019 và core-js@3(thư viện của babel) bao gồm nó trong thư viện polyfill của họ )

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
Thật xấu hổ vì điều này thậm chí không có trên trang đầu tiên của câu trả lời. Tính năng này có sẵn trong Chrome 69 và Firefox 62 (và Nút 11 cho những người làm việc trong phần phụ trợ)
Matt M.


2
-1; Không, đây không phải là một phần của ECMAScript 2018 . Đây vẫn chỉ là một đề xuất chưa được thực hiện cho bất kỳ thông số ECMAScript nào.
Đánh dấu Amery

1
Tôi nghĩ bây giờ chúng ta có thể xem xét điều này .. bởi vì bây giờ nó là một phần của tiêu chuẩn (2019) .. chúng ta có thể xem lại phần hiệu suất của điều này một lần không?
Yuvaraj

Có vẻ như nó chưa được hỗ trợ bởi bất kỳ trình duyệt Microsoft nào (ít nhất là tại thời điểm tôi viết nhận xét này)
Laurent S.

78

Hầu hết các câu trả lời ở đây không hoạt động trên các mảng lớn (ví dụ 200 000 phần tử) và ngay cả khi chúng có, chúng vẫn chậm. câu trả lời của Polkovnikov.ph có hiệu suất tốt nhất, nhưng nó không hoạt động để làm phẳng sâu.

Đây là giải pháp nhanh nhất, cũng hoạt động trên các mảng với nhiều cấp độ lồng nhau :

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

Ví dụ

Mảng lớn

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

Nó xử lý các mảng lớn chỉ tốt. Trên máy của tôi, mã này mất khoảng 14 ms để thực thi.

Mảng lồng nhau

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

Nó hoạt động với các mảng lồng nhau. Mã này sản xuất [1, 1, 1, 1, 1, 1, 1, 1].

Mảng với các cấp độ lồng khác nhau

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

Nó không có bất kỳ vấn đề với các mảng làm phẳng như thế này.


Ngoại trừ mảng lớn của bạn là khá bằng phẳng. Giải pháp này sẽ không hoạt động cho các mảng lồng nhau sâu. Không có giải pháp đệ quy sẽ. Trên thực tế không có trình duyệt nhưng Safari có TCO ngay bây giờ, vì vậy không có thuật toán đệ quy nào sẽ hoạt động tốt.
nitely

@nitely Nhưng trong tình huống thực tế nào bạn sẽ có các mảng với nhiều cấp độ lồng nhau?
Michał Perłakowski

2
Thông thường, khi mảng được tạo ra từ nội dung do người dùng tạo.
nitely

@ 0xcaff Trong Chrome, nó hoàn toàn không hoạt động với mảng 200 000 phần tử (bạn nhận được RangeError: Maximum call stack size exceeded). Đối với mảng 20 000 phần tử, phải mất 2-5 mili giây.
Michał Perłakowski

3
sự phức tạp của ký hiệu O này là gì?
George Katsanos

58

Cập nhật: hóa ra giải pháp này không hoạt động với các mảng lớn. Bạn đang tìm kiếm một giải pháp tốt hơn, nhanh hơn, hãy xem câu trả lời này .


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

Chỉ đơn giản là mở rộng arrvà chuyển nó dưới dạng đối số concat(), hợp nhất tất cả các mảng thành một. Nó tương đương với[].concat.apply([], arr) .

Bạn cũng có thể thử điều này để làm phẳng sâu:

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

Xem bản demo trên JSBin .

Tài liệu tham khảo cho các yếu tố ECMAScript 6 được sử dụng trong câu trả lời này:


Lưu ý bên lề: các phương thức như find()và các chức năng mũi tên không được hỗ trợ bởi tất cả các trình duyệt, nhưng điều đó không có nghĩa là bạn không thể sử dụng các tính năng này ngay bây giờ. Chỉ cần sử dụng Babel - nó biến đổi mã ES6 thành ES5.


Bởi vì hầu hết tất cả các câu trả lời ở đây sử dụng sai applytheo cách này, tôi đã xóa nhận xét của tôi khỏi bạn. Tôi vẫn nghĩ sử dụng apply/ truyền bá theo cách này là lời khuyên tồi, nhưng vì không ai quan tâm ...

@ LUH3417 Không phải như vậy, tôi thực sự đánh giá cao ý kiến ​​của bạn. Hóa ra bạn đúng - giải pháp này thực sự không hoạt động với các mảng lớn. Tôi đã đăng một câu trả lời khác hoạt động tốt ngay cả với mảng 200 000 phần tử.
Michał Perłakowski 17/8/2016

Nếu bạn đang sử dụng ES6, bạn có thể giảm thêm để:const flatten = arr => [].concat(...arr)
Xóa

Ý bạn là gì "không hoạt động với mảng lớn"? Lớn bao nhiêu? Chuyện gì xảy ra
GEMI

4
@GEMI Ví dụ: cố gắng làm phẳng một mảng 500000 phần tử bằng phương thức này sẽ cho "RangeError: vượt quá kích thước ngăn xếp cuộc gọi tối đa".
Michał Perłakowski

51

Bạn có thể sử dụng gạch dưới :

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

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

20
Whoah, ai đó đã thêm Perl vào JS? :-)
JBRWilkinson

9
@JBRWilkinson Có lẽ JS muốn học cho bạn một Haskell vì điều tốt đẹp!?
styfle

1+ - Bạn cũng có thể chỉ định rằng bạn muốn một mảng phẳng bằng cách chỉ định truecho đối số thứ hai .
Josh Crozier

7
Vì mục đích hoàn chỉnh về nhận xét của @ styfle: learnyouahaskell.com
Simon A. Eugster

41

Các thủ tục chung có nghĩa là chúng ta không phải viết lại độ phức tạp mỗi lần chúng ta cần sử dụng một hành vi cụ thể.

concatMap(hoặc flatMap) chính xác là những gì chúng ta cần trong tình huống này.

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

tầm nhìn xa

Và vâng, bạn đã đoán đúng, nó chỉ làm phẳng một cấp độ, đó chính xác là cách nó nên làm việc

Hãy tưởng tượng một số dữ liệu được đặt như thế này

// 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, bây giờ nói rằng chúng ta muốn in một danh sách mà chương trình tất cả các cầu thủ sẽ được tham gia vào game...

const gamePlayers = game =>
  flatten (game)

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

Nếu flattenquy trình của chúng tôi cũng làm phẳng các mảng lồng nhau, chúng tôi sẽ kết thúc với kết quả rác này

const gamePlayers = game =>
  badGenericFlatten(game)

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

sâu, em bé

Điều đó không có nghĩa là đôi khi bạn cũng không muốn làm phẳng các mảng lồng nhau - chỉ đó không phải là hành vi mặc định.

Chúng tôi có thể làm một deepFlattenthủ tục dễ dàng

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

Đó Bây giờ bạn có một công cụ cho mỗi công việc - một công cụ flattenđể xóa một cấp độ lồng và một công cụ để xóa sổ tất cả các lồngdeepFlatten .

Có lẽ bạn có thể gọi nó obliteratehoặc nukenếu bạn không thích cái tên đó deepFlatten.


Đừng lặp lại hai lần!

Tất nhiên các triển khai trên là thông minh và súc tích, nhưng sử dụng .maptheo sau là một cuộc gọi đến.reduce có nghĩa là chúng ta thực sự đang thực hiện nhiều lần lặp hơn mức cần thiết

Sử dụng một tổ hợp đáng tin cậy mà tôi đang gọi mapReducegiúp giữ các lần lặp lại ở mức tối thiểu; nó có một chức năng ánh xạ m :: a -> b, một hàm khử r :: (b,a) ->bvà trả về một hàm khử mới - bộ kết hợp này là trung tâm của các bộ chuyển đổi ; nếu bạn quan tâm, tôi đã viết những câu trả lời khác về chúng

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


Thường xuyên, khi tôi thấy câu trả lời của bạn, tôi muốn rút tiền của tôi, vì chúng đã trở nên vô giá trị. Câu trả lời chính xác! concatbản thân nó không làm nổ tung ngăn xếp, chỉ ...applykhông (cùng với các mảng rất lớn). Tôi đã không nhìn thấy nó. Tôi chỉ cảm thấy khủng khiếp ngay bây giờ.

@ LUH3417 bạn không nên cảm thấy khủng khiếp. Bạn cũng cung cấp câu trả lời tốt với lời giải thích bằng văn bản. Tất cả mọi thứ ở đây là một kinh nghiệm học tập - tôi thường mắc lỗi và viết câu trả lời xấu quá. Tôi xem lại chúng trong một vài tháng và nghĩ rằng "tôi đang nghĩ gì vậy?" và kết thúc hoàn toàn sửa đổi mọi thứ. Không có câu trả lời là hoàn hảo. Tất cả chúng ta đều đang ở một thời điểm nào đó trên đường cong học tập ^ _ ^
Cảm ơn bạn

1
Xin lưu ý rằng concattrong Javascript có một ý nghĩa khác với trong Haskell. Haskell's concat( [[a]] -> [a]) sẽ được gọi flattentrong Javascript và được triển khai dưới dạng foldr (++) [](Javascript: foldr(concat) ([])giả sử các hàm được xử lý). Javascript concatlà một phần bổ sung kỳ lạ ( (++)trong Haskell), có thể xử lý cả [a] -> [a] -> [a]a -> [a] -> [a].

Tôi đoán một cái tên tốt hơn là flatMapbởi vì đó chính xác concatMaplà: bindVí dụ của listđơn nguyên. concatpMapđược thực hiện như foldr ((++) . f) []. Được dịch sang Javascript : const flatMap = f => foldr(comp(concat) (f)) ([]). Điều này là tất nhiên tương tự như việc thực hiện của bạn mà không có comp.

sự phức tạp của thuật toán đó là gì?
George Katsanos

32

Một giải pháp cho trường hợp tổng quát hơn, khi bạn có thể có một số phần tử không phải là mảng trong mảng của bạn.

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
Cách tiếp cận này rất hiệu quả trong việc làm phẳng dạng mảng kết quả của các tập kết quả mà bạn nhận được từ truy vấn JsonPath .
kevinjansz

1
Được thêm vào như một phương thức của mảng: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

Điều này sẽ phá vỡ nếu chúng ta vượt qua thủ công trong đối số thứ hai. Ví dụ: thử cái này: flattenArrayOfArrays (arr, 10)hoặc cái này flattenArrayOfArrays(arr, [1,[3]]);- những đối số thứ hai được thêm vào đầu ra.
Om Shankar

2
Câu trả lời này không đầy đủ! kết quả của đệ quy không bao giờ được chỉ định ở bất cứ đâu, do đó, kết quả của việc thu hồi bị mất
r3wt

1
@ r3wt đã đi trước và thêm một sửa chữa. Những gì bạn cần làm là đảm bảo rằng mảng hiện tại bạn đang duy trì r, sẽ thực sự kết hợp các kết quả từ đệ quy.
Tháng Tám

29

Để làm phẳng một mảng các mảng phần tử đơn, bạn không cần nhập thư viện, một vòng lặp đơn giản là giải pháp đơn giản và hiệu quả nhất:

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

Để downvoters: vui lòng đọc câu hỏi, đừng downvote vì nó không phù hợp với vấn đề rất khác của bạn. Giải pháp này là nhanh nhất và đơn giản nhất cho câu hỏi được hỏi.


4
Tôi sẽ nói, "Đừng tìm kiếm những thứ khó hiểu hơn." ^^

4
Ý tôi là ... khi tôi thấy mọi người khuyên nên sử dụng một thư viện cho điều đó ... thật điên rồ ...
Denys Séguret

3
Ahh, sử dụng một thư viện chỉ cho việc này sẽ là ngớ ngẩn ... nhưng nếu nó đã có sẵn.

9
@dystroy Phần có điều kiện của vòng lặp for không thể đọc được. Hay chỉ là tôi: D
Andreas

4
Nó không thực sự quan trọng như thế nào là khó hiểu. Mã này "làm phẳng" này ['foo', ['bar']]để ['f', 'bar'].
jonschlinkert

29

Sử dụng reduce(callback[, initialValue])phương pháp nàoJavaScript 1.8

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

Sẽ làm công việc.


8
[[1], [2,3]].reduce( (a,b) => a.concat(b), [] )gợi cảm hơn
golopot

5
Đừng cần tranh luận thứ hai trong trường hợp đơn giản của chúng tôi[[1], [2,3]].reduce( (a,b) => a.concat(b))
Umair Ahmed

29

Một giải pháp ECMAScript 6 khác theo phong cách chức năng:

Khai báo một hàm:

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

và sử dụng nó:

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

Cũng xem xét một hàm riêng Array.prototype.flat () (đề xuất cho ES6) có sẵn trong các bản phát hành cuối cùng của trình duyệt hiện đại. Cảm ơn @ (Toàn cầu) và @ (Mark Amery) đã đề cập đến nó trong các bình luận.

Các flatchức năng có một tham số, xác định độ sâu dự kiến làm tổ mảng, bằng 1theo mặc định.

[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
Điều này là tốt đẹp và gọn gàng nhưng tôi nghĩ rằng bạn đã thực hiện quá liều ES6. Không cần chức năng bên ngoài là chức năng mũi tên. Tôi sẽ gắn bó với chức năng mũi tên để giảm cuộc gọi lại nhưng bản thân nó phải là một chức năng bình thường.
Stephen Simpson

3
@StephenSimpson nhưng có cần cho chức năng bên ngoài là chức năng không phải là khoản vay không? "Làm phẳng chính nó phải là một chức năng bình thường" - bởi "bình thường" bạn có nghĩa là "phi mũi tên", nhưng tại sao? Tại sao sử dụng chức năng mũi tên trong cuộc gọi để giảm sau đó? Bạn có thể cung cấp dòng lý luận của bạn?
Cảm ơn bạn

@naomik Lý luận của tôi là không cần thiết. Đó chủ yếu là vấn đề về phong cách; Tôi nên có nhiều rõ ràng hơn trong bình luận của tôi. Không có lý do mã hóa chính để sử dụng cái này hay cái kia. Tuy nhiên, chức năng dễ nhìn và đọc hơn là không phải mũi tên. Hàm bên trong hữu ích như một hàm mũi tên vì nó nhỏ gọn hơn (và dĩ nhiên không có ngữ cảnh nào được tạo ra). Các chức năng mũi tên là tuyệt vời để tạo ra chức năng nhỏ gọn dễ đọc và tránh sự nhầm lẫn này. Tuy nhiên, chúng thực sự có thể làm cho nó khó đọc hơn khi một mũi tên không đủ sẽ đủ. Những người khác có thể không đồng ý mặc dù!
Stephen Simpson

Bắt mộtRangeError: Maximum call stack size exceeded
Matt Westlake

@Matt, vui lòng chia sẻ thông tin bạn sử dụng để tái tạo lỗi
diziaq

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

Đây là cách tôi thực hiện trong suốt mã của mình nhưng tôi không chắc tốc độ so với câu trả lời được chấp nhận như thế nào nếu bạn có một mảng rất dài
Michael Aaron Wilson

14

Xin lưu ý: Khi Function.prototype.apply( [].concat.apply([], arrays)) hoặc toán tử trải ([].concat(...arrays) ) được sử dụng để làm phẳng một mảng, cả hai đều có thể gây ra tràn ngăn xếp cho các mảng lớn, bởi vì mọi đối số của hàm được lưu trữ trên ngăn xếp.

Dưới đây là một triển khai ngăn xếp an toàn theo kiểu chức năng, cân nhắc các yêu cầu quan trọng nhất đối với nhau:

  • tái sử dụng
  • khả năng đọc
  • sự đồng nhất
  • hiệu suất

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

Ngay khi bạn quen với các hàm mũi tên nhỏ ở dạng bị cắt, thành phần hàm và các hàm bậc cao hơn, mã này sẽ đọc như văn xuôi. Lập trình sau đó chỉ bao gồm tập hợp các khối xây dựng nhỏ luôn hoạt động như mong đợi, vì chúng không chứa bất kỳ tác dụng phụ nào.


1
Haha. Hoàn toàn tôn trọng câu trả lời của bạn, mặc dù đọc lập trình chức năng như thế này vẫn giống như đọc ký tự tiếng Nhật theo ký tự đối với tôi (người nói tiếng Anh).
Tarwin Stroh-Spijer

2
Nếu bạn thấy mình thực hiện các tính năng của ngôn ngữ A trong ngôn ngữ B không phải là một phần của dự án với mục tiêu duy nhất là thực hiện chính xác điều này thì một người nào đó đã rẽ sai. Có thể là bạn? Chỉ cần const flatten = (arr) => arr.reduce((a, b) => a.concat(b), []);giúp bạn tiết kiệm rác trực quan giải thích cho đồng đội tại sao bạn cần thêm 3 chức năng một số chức năng gọi nữa.
Daerdemandt

3
@Daerdemandt Nhưng nếu bạn viết nó dưới dạng các hàm riêng biệt, có lẽ bạn sẽ có thể sử dụng lại chúng trong mã khác.
Michał Perłakowski

@ MichałPerłakowski Nếu bạn cần sử dụng chúng ở một số nơi thì đừng phát minh lại bánh xe và chọn một gói từ những thứ này - được người khác ghi lại và hỗ trợ.
Daerdemandt

12

ES6 Một dòng phẳng

Xem lodash flatten , undererscore flatten (nông true)

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

hoặc là

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

Đã thử nghiệm với

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 Một dòng Deep Flatten

Xem lodash flattenDeep , gạch dưới gạch dưới

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

Đã thử nghiệm với

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

Ví dụ thứ 2 của bạn được viết tốt hơn Array.prototype.concat.apply([], arr)vì bạn tạo một mảng bổ sung chỉ để đến concathàm. Runtimes có thể hoặc không thể tối ưu hóa nó đi khi họ chạy nó, nhưng việc truy cập chức năng trên nguyên mẫu trông không có gì xấu hơn điều này đã có trong mọi trường hợp.
Mörre

11

Bạn có thể sử dụng Array.flat()với Infinitybất kỳ độ sâu nào của mảng lồng nhau.

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)

kiểm tra ở đây để tương thích trình duyệt


8

Một cách tiếp cận 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
Đồng ý với @monners. Tay xuống giải pháp thanh lịch nhất trong toàn bộ chủ đề này.
Nicolás Fantone

8

Cách 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))

Cách ES5 cho flattenchức năng với dự phòng ES3 cho mảng lồng nhau N lầ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

Nếu bạn chỉ có mảng với 1 phần tử chuỗi:

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

sẽ làm việc Bt đặc biệt phù hợp với ví dụ mã của bạn.


3
Bất cứ ai xuống bình chọn, xin vui lòng giải thích tại sao. Tôi đã tìm kiếm một giải pháp tốt và trong tất cả các giải pháp tôi thích giải pháp này nhất.
Ẩn danh

2
@ Đồng nghĩa Tôi đã không đánh giá thấp nó vì về mặt kỹ thuật nó đáp ứng các yêu cầu của câu hỏi, nhưng có thể vì đây là một giải pháp khá kém không hữu ích trong trường hợp chung. Xem xét có bao nhiêu giải pháp tốt hơn ở đây, tôi không bao giờ khuyên ai đó nên sử dụng giải pháp này vì nó phá vỡ khoảnh khắc bạn có nhiều hơn một yếu tố hoặc khi chúng không phải là chuỗi.
Thor84no

2
Tôi thích giải pháp này =)
Huei Tan

2
Nó không xử lý chỉ các mảng với 1 phần tử chuỗi, nó cũng xử lý mảng này ['$4', ["$6"], ["$12"], ["$25"], ["$25", "$33", ['$45']]].join(',').split(',')
alucic

Tôi đã tự mình phát hiện ra phương pháp này, tuy nhiên biết rằng nó phải được ghi lại ở đâu đó, cuộc tìm kiếm của tôi kết thúc ở đây. Hạn chế của giải pháp này là, nó ép các số, booleans, v.v. vào chuỗi, thử [1,4, [45, 't', ['e3', 6]]].toString().split(',') ---- hoặc ----- [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"]

(Tôi chỉ viết đây là một câu trả lời riêng biệt, dựa trên nhận xét của @danhbear.)


7

Tôi đề nghị một chức năng tạo không gian hiệu quả :

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

Nếu muốn, tạo một mảng các giá trị dẹt như sau:

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

Tôi thích cách tiếp cận này. Tương tự như stackoverflow.com/a/35073573/1175496 , nhưng sử dụng toán tử trải ...để lặp qua trình tạo.
Hạt đậu đỏ

6

Tôi thà chuyển đổi toàn bộ mảng, theo nguyên trạng, thành một chuỗi, nhưng không giống như các câu trả lời khác, sẽ làm điều đó bằng cách sử dụng JSON.stringifyvà không sử dụng toString()phương thức, tạo ra kết quả không mong muốn.

Với JSON.stringifyđầu ra đó , tất cả những gì còn lại là xóa tất cả các dấu ngoặc, bao bọc kết quả bằng dấu ngoặc bắt đầu và kết thúc một lần nữa và phục vụ kết quả JSON.parseđể đưa chuỗi trở lại "cuộc sống".

  • Có thể xử lý các mảng lồng vô hạn mà không có bất kỳ chi phí tốc độ.
  • Có thể xử lý đúng các mục Array là chuỗi chứa dấu phẩy.

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

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

console.log(flattened)

  • Chỉ dành cho Mảng / Số nhiều chiều (không phải đối tượng)

Giải pháp của bạn không chính xác. Nó sẽ chứa dấu phẩy khi làm phẳng các mảng bên trong ["345", "2", "3,4", "2"]thay vì tách từng giá trị đó thành các chỉ số riêng biệt
pizzarob

@realseanp - bạn đã hiểu nhầm giá trị trong mục Array đó. Tôi cố tình đặt dấu phẩy đó làm giá trị chứ không phải là dấu phẩy phân cách mảng để nhấn mạnh sức mạnh của giải pháp của tôi trên tất cả các giải pháp khác, sẽ xuất ra "3,4".
vsync

1
Tôi đã hiểu lầm
pizzarob

đó dường như là giải pháp nhanh nhất tôi từng thấy cho việc này; Bạn có biết về bất kỳ cạm bẫy nào không @vsync (ngoại trừ thực tế là nó có vẻ hơi hack - coi các mảng lồng nhau là chuỗi: D)
George Katsanos

@GeorgeKatsanos - Phương thức này sẽ không hoạt động đối với các mục mảng (và các mục lồng nhau) không có giá trị Nguyên thủy, ví dụ: một mục trỏ đến phần tử DOM
vsync

6

Bạn cũng có thể thử Array.Flat()phương pháp mới . Nó hoạt động theo cách sau:

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

console.log(arr);

Các flat() phương pháp tạo ra một mảng mới với tất cả các yếu tố phụ mảng nối vào nó một cách đệ quy đến lớp 1 có chiều sâu (tức là mảng bên trong mảng)

Nếu bạn muốn làm phẳng các mảng 3 chiều hoặc thậm chí cao hơn, bạn chỉ cần gọi phương thức phẳng nhiều lần. Ví dụ: (3 chiều):

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

console.log(arr);

Hãy cẩn thận!

Array.Flat()phương pháp tương đối mới. Các trình duyệt cũ hơn như có thể không thực hiện phương thức này. Nếu bạn muốn mã của bạn hoạt động trên tất cả các trình duyệt, bạn có thể phải chuyển mã JS của bạn sang phiên bản cũ hơn. Kiểm tra tài liệu web MD để biết khả năng tương thích trình duyệt hiện tại.


2
để các mảng chiều cao hơn bằng phẳng, bạn chỉ cần gọi phương thức phẳng bằng Infinityđối số. Như thế này:arr.flat(Infinity)
Mikhail

Đó là một bổ sung rất hay, cảm ơn Mikhail!
Willem van der Veen

6

Sử dụng toán tử trải rộng:

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


5

Điều đó không khó, chỉ cần lặp qua các mảng và hợp nhất chúng:

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

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

5

Có vẻ như đây là một công việc cho RECURSION!

  • Xử lý nhiều cấp độ lồng
  • Xử lý các mảng trống và các tham số không phải là mảng
  • Không có đột biến
  • Không phụ thuộc vào các tính năng trình duyệt hiện đại

Mã số:

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

Sử dụng:

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

cẩn thận, flatten(new Array(15000).fill([1]))ném Uncaught RangeError: Maximum call stack size exceededvà đóng băng devTools của tôi trong 10 giây
pietrovismara

@pietrovismara, bài kiểm tra của tôi khoảng 1s.
Ivan Yan

5

Tôi đã thực hiện nó bằng cách sử dụng đệ quy và đóng

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
Đơn giản và ngọt ngào, câu trả lời này hoạt động tốt hơn câu trả lời được chấp nhận. Nó san phẳng các cấp độ lồng nhau sâu sắc, không chỉ cấp độ đầu tiên
Om Shankar

1
AFAIK là phạm vi từ vựng và không phải là đóng cửa
gạch ngang

@dashambled là chính xác - sự khác biệt là nếu đó là một bao đóng, bạn sẽ trả lại hàm bên trong ra bên ngoài và khi chức năng bên ngoài kết thúc, bạn vẫn có thể sử dụng hàm bên trong để truy cập phạm vi của nó. Ở đây tuổi thọ của hàm ngoài dài hơn hàm bên trong nên không bao giờ "đóng".
Mörre

5

Tôi đã vui vẻ với ES6 Generators vào một ngày khác và đã viết ý chính này . Trong đó có ...

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

Về cơ bản, tôi đang tạo một trình tạo vòng lặp trên mảng đầu vào ban đầu, nếu nó tìm thấy một mảng, nó sử dụng toán tửield * kết hợp với đệ quy để liên tục làm phẳng các mảng bên trong. Nếu mục không phải là một mảng, nó chỉ mang lại một mục duy nhất. Sau đó, sử dụng toán tử lây lan ES6 (còn gọi là toán tử splat) Tôi làm phẳng trình tạo thành một thể hiện mảng mới.

Tôi đã không kiểm tra hiệu năng của việc này, nhưng tôi cho rằng đây là một ví dụ đơn giản tuyệt vời về việc sử dụng máy phát điện và toán tử suất *.

Nhưng một lần nữa, tôi chỉ ngốc nghếch nên tôi chắc chắn có nhiều cách hiệu quả hơn để làm điều này.


1
Câu trả lời tương tự (sử dụng trình tạo và phân phối sản lượng), nhưng ở định dạng PHP
The Red Pea

5

chỉ là giải pháp tốt nhất mà không cần lodash

let flatten = arr => [].concat.apply([], arr.map(item => Array.isArray(item) ? flatten(item) : item))
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.