合并/展平数组


1104

我有一个像这样的JavaScript数组:

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

我将如何将单独的内部数组合并为一个类似的数组:

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


17
所有使用reduce+ 的解决方案concat都是O((N ^ 2)/ 2),其中作为可接受的答案(仅对的一个调用concat)在浏览器错误的情况下最多为O(N * 2),而在错误的浏览器中最多为O(N)。好一个 此外,Denys解决方案针对实际问题进行了优化,比单个解决方案快2倍concat。对于reduce人们来说,编写微小的代码很酷,这很有趣,但是例如,如果该数组有1000个单元素子数组,那么所有reduce + concat解决方案都将执行500500次操作,而单个concat或simple循环将执行1000次操作。
gman

11
简单的用户传播运营商[].concat(...array)
Oleg Dater

@gman如何减少+ concat解决方案O((N ^ 2)/ 2)?stackoverflow.com/questions/52752666/…提到了另一种复杂性。
乌斯曼(Usman)

3
使用支持ES2019的最新浏览器:展平的最大深度array.flat(Infinity)在哪里Infinity
蒂莫西·顾

Answers:


1860

您可以使用concat合并数组:

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

console.log(merged);

使用applyof方法concat将仅将第二个参数作为数组,因此最后一行与此相同:

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

还有Array.prototype.flat()一种方法(在ES2019中引入)可用于展平阵列,尽管该方法仅在从版本11开始的Node.js中可用,而在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
请注意,concat它不会修改源数组,因此merged在调用之后,该数组将保持为空concat。最好这样说:merged = merged.concat.apply(merged, arrays);
Nate

65
var merged = [].concat.apply([], arrays);似乎可以很好地将它放在一行上。编辑:正如尼基塔的答案已经显示。
肖恩

44
或者Array.prototype.concat.apply([], arrays)
danhbear 2014年

30
注意:此答案只能使水平变深。有关递归展平的信息,请参见@Trindaz的答案。
Phrogz 2014年

208
进一步@Sean的评论:ES6语法使这个内容更加简洁:var merged = [].concat(...arrays)
Sethi 2015年

505

这是一个简短的函数,它使用一些较新的JavaScript数组方法来展平n维数组。

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

用法:

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
我喜欢这种方法。它更加通用,并支持嵌套数组
alfredocambera 2015年

10
此解决方案的内存使用情况配置文件是什么?看起来它在尾部递归过程中创建了很多中间数组....
JBRWilkinson

7
@ayjay,它是reduce函数的起始累加器值,mdn称为初始值。在这种情况下,这是flat传递给的匿名函数的第一次调用中的值reduce。如果未指定,则第一次调用reduce将数组中的第一个值绑定到flat,这最终将导致在两个示例中都将1其绑定flat1.concat不是功能。
Noah Freitas 2015年

19
或更短,更性感的形式: const flatten = (arr) => arr.reduce((flat, next) => flat.concat(next), []);
Tsvetomir Tsonev

12
嘲笑@TsvetomirTsonev和Noah的任意嵌套解决方案:const flatten = (arr) => arr.reduce((flat, next) => flat.concat(Array.isArray(next) ? flatten(next) : next), []);
将于

314

有一个令人迷惑的隐藏方法,该方法构造了一个新数组,而不改变原始数组:

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


11
在CoffeeScript中,这是[].concat([[1],[2,3],[4]]...)
amoebe 2013年

8
结果是@amoebe [[1],[2,3],[4]]。@Nikita提供的解决方案对于CoffeeScript和JS都是正确的。
瑞安·肯尼迪

5
啊,我发现了你的错误。您的符号中有一对额外的方括号,应为[].concat([1],[2,3],[4],...)
瑞安·肯尼迪

21
我想我也发现了您的错误。的...是实际的代码,而不是一些省略号点。
amoebe 2014年

3
使用库函数并不意味着必须有更少的命令性“混乱”。只是一团糟被藏在图书馆内。显然,使用库比滚动自己的函数要好,但是声称此技术可以减少“命令性混乱”是很愚蠢的。
paldepind 2014年

204

最好通过javascript reduce函数来完成。

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

或者,使用ES2015:

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

js小提琴

Mozilla文档


1
@JohnS实际上每转减少给concat馈入的单个(数组)值。证明?取出reduce()看看是否可行。减少()是这里Function.prototype.apply的()的替代
安德烈Werlang

3
我会用减少以及但是我从这个片段了解到一个有趣的事情是,大部分的时间你不需要通过一个初值 :)
何塞F. Romaniello

2
reduce的问题在于数组不能为空,因此您需要添加一个额外的验证。
calbertts '16

4
@calbertts只需传递初始值即可[],无需进一步验证。

7
由于您使用ES6,因此也可以将spread-operator用作数组literalarrays.reduce((flatten, arr) => [...flatten, ...arr])
Putzi San

108

有一个称为flat的新本机方法可以准确地做到这一点。

(截至2019年末,flat现已在ECMA 2019标准中发布,并且core-js@3(babel的库)将其包含在其polyfill 库中

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

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

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

// Flatten all levels
const arr4 = [2, 2, 5, [5, [5, [6]], 7]];
arr4.flat(Infinity);
// [2, 2, 5, 5, 5, 6, 7];

4
可惜的是,这甚至没有答案的第一页。此功能在Chrome 69和Firefox 62(以及在后端工作的Node 11)中可用
Matt


2
-1; 不,这不是ECMAScript 2018的一部分。它仍然只是一个提案,尚未纳入任何ECMAScript规范。
Mark Amery

1
我认为现在我们可以考虑这个问题,因为它已经成为标准(2019)的一部分。
Yuvaraj

似乎它尚未受到任何Microsoft浏览器的支持(至少在我撰写此评论时)
Laurent

78

这里的大多数答案不适用于巨大的数组(例如20万个元素),即使这样做,也很慢。polkovnikov.ph的答案具有最佳性能,但不适用于深度展平。

这是最快的解决方案,它也适用于具有多层嵌套的数组

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

例子

巨大的数组

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

它可以处理巨大的数组。在我的机器上,此代码大约需要14毫秒才能执行。

嵌套数组

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

它适用于嵌套数组。此代码产生[1, 1, 1, 1, 1, 1, 1, 1]

具有不同嵌套级别的数组

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

展平像这样的数组没有任何问题。


除了庞大的数组很平坦之外。此解决方案不适用于深度嵌套的数组。没有递归解决方案。实际上,除了Safari之外,目前没有其他浏览器具有TCO,因此任何递归算法都无法很好地执行。
奈特雷

@nitely但是,在什么实际情况下,您将拥有多个嵌套级别以上的数组?
米哈尔Perłakowski

2
通常,从用户生成的内容中生成数组时。
奈特雷

@ 0xcaff在Chrome中,它对于200 000元素的数组根本不起作用(得到RangeError: Maximum call stack size exceeded)。对于2万个元素的数组,需要2到5毫秒的时间。
米哈尔Perłakowski

3
O符号的复杂度是多少?
乔治·卡萨诺斯

58

更新:事实证明,该解决方案不适用于大型阵列。如果您正在寻找更好,更快的解决方案,请查看此答案


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

只是简单地扩展arr并将其作为参数传递给concat(),它将所有数组合并为一个。等同于[].concat.apply([], arr)

您还可以尝试以下方法进行深展平:

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

参见有关JSBin的演示。

此答案中使用的ECMAScript 6元素的参考:


旁注:find()并非所有浏览器都支持诸如和箭头功能之类的方法,但这并不意味着您现在无法使用这些功能。只需使用Babel-它将ES6代码转换为ES5。


因为此处几乎所有答复都apply以这种方式滥用,所以我从您的评论中删除了我的评论。我仍然认为以apply这种方式使用/ spread是不好的建议,但由于没有人在乎...

@ LUH3417不是那样,我非常感谢您的评论。事实证明您是对的-此解决方案确实不适用于大型阵列。我发布了另一个答案,即使对于20万个元素的数组,它也可以正常工作。
米哈尔Perłakowski

如果您使用的是ES6,则可以进一步const flatten = arr => [].concat(...arr)
简化

您是什么意思“不适用于大型阵列”?多大?怎么了?
GEMI

4
@GEMI例如,尝试使用此方法展平500000个元素的数组将给出“ RangeError:超出最大调用堆栈大小”。
—MichałPerłakowski18年


41

通用过程意味着我们不必在每次需要利用特定行为时都重写复杂性。

concatMap(或flatMap)正是我们在这种情况下需要的。

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

预见性

是的,您猜对了,它只会展平一个级别,这正是它应该如何工作的

想象一些这样的数据集

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

好的,现在说我们要打印一个名单,显示所有将要参加的球员game……

const gamePlayers = game =>
  flatten (game)

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

如果我们的flatten过程也使嵌套数组变平,那么我们最终将得到这个垃圾结果……

const gamePlayers = game =>
  badGenericFlatten(game)

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

滚滚深,宝贝

这并不是说有时您也不想展平嵌套数组-只是那不应该成为默认行为。

我们可以轻松地进行deepFlatten程序……

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

那里。现在,您有一个用于每项工作的工具–一个用于挤压一层嵌套的工具,flatten另一个用于消除所有嵌套的工具。deepFlatten

也许您可以称呼它,obliterate或者nuke您不喜欢这个名字deepFlatten


不要重复两次!

当然,以上实现都是聪明而简洁的,但是使用.map后面的调用.reduce意味着我们实际上进行了不必要的迭代

使用我要调用的可信赖组合器mapReduce有助于将迭代次数保持在最低限度;它具有映射函数m :: a -> b,归约函数r :: (b,a) ->b并返回新的归约函数-该组合器是换能器的核心; 如果您有兴趣,我已经写了其他有关他们的答案

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


通常,当我看到您的答复时,我想撤回我的答复,因为它们变得一文不值。好答案!concat本身不吹堆栈向上,只有...apply确实(具有非常大的阵列一起)。我没看到 我现在感觉很糟糕。

@ LUH3417您不应该感到可怕。您还提供了很好的答案以及写得很好的解释。这里的一切都是学习经验–我经常犯错误,也写错答案。我在几个月后再次访问了他们,​​并认为“我在想wtf?” 最终彻底修改了东西。没有答案是完美的。我们都处于学习曲线上的某个时刻^ _ ^
谢谢您

1
请注意,concatJavascript与Haskell具有不同的含义。Haskell的concat[[a]] -> [a])将用flattenJavascript 调用,并实现为foldr (++) [](Javascript:foldr(concat) ([])假定为函数)。Javascript concat是一个怪异的追加((++)在Haskell中),可以同时处理[a] -> [a] -> [a]a -> [a] -> [a]

我想一个更好的名字是flatMap,因为这正是concatMap:Monad 的bind实例listconcatpMap被实现为foldr ((++) . f) []。翻译成Javascript :const flatMap = f => foldr(comp(concat) (f)) ([])。当然,这与不带的实现类似comp

该算法的复杂性是什么?
乔治·卡萨诺斯

32

一种更一般情况的解决方案,当您的数组中可能包含一些非数组元素时。

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
这种方法在扁平化从JsonPath查询获得的结果集的嵌套数组形式方面非常有效。
kevinjansz 2013年

1
作为数组的方法添加: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 2014年

如果我们手动传递第二个参数,这将中断。例如,尝试执行以下操作:flattenArrayOfArrays (arr, 10)或执行以下操作flattenArrayOfArrays(arr, [1,[3]]);-将第二个参数添加到输出中。
Om Shankar,2015年

2
这个答案不完整!递归的结果从未分配到任何地方,因此递归的结果丢失了
r3wt '16

1
@ r3wt继续并添加了修复程序。您需要做的是确保要维护的当前数组r将实际连接递归的结果。
八月

29

那使用reduce(callback[, initialValue])方法呢JavaScript 1.8

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

会做的工作。


8
[[1], [2,3]].reduce( (a,b) => a.concat(b), [] )更性感。
golopot

5
在我们的案例中,不需要第二个论点[[1], [2,3]].reduce( (a,b) => a.concat(b))
Umair Ahmed

29

功能样式的另一个ECMAScript 6解决方案:

声明一个函数:

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

并使用它:

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

还考虑在最新版本的现代浏览器中可用的本机函数Array.prototype.flat()(ES6的建议)。感谢@(КонстантинВан)和@(Mark Amery)在评论中提到了它。

flat函数有一个参数,用于指定期望的数组嵌套深度,1默认情况下等于。

[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
很好,很整洁,但是我认为您已经过量使用了ES6。不需要外部功能是箭头功能。对于reduce回调,我会坚持使用箭头功能,但flatten本身应该是正常功能。
斯蒂芬·辛普森

3
@StephenSimpson但外部函数是否需要为非箭头函数?“扁平化本身应该是正常的功能” –“正常”是指“非箭头”,但是为什么呢?为什么在调用中使用箭头函数来减少呢?您能提供您的推理方法吗?
谢谢你

@naomik我的理由是这是不必要的。这主要是风格问题;我的评论应该更加清楚。没有使用任何一个的主要编码原因。但是,该功能更易于查看和理解为非箭头。内部函数可用作箭头函数,因为它更紧凑(当然也没有创建上下文)。箭头功能非常适合创建紧凑易读的功能并避免这种混淆。但是,当使用非箭头满足时,它们实际上会使阅读起来更加困难。其他人可能会不同意!
斯蒂芬·辛普森

获得一个RangeError: Maximum call stack size exceeded
马特西湖

@Matt,请分享您用来重现错误的环境
diziaq

28

要展平单元素数组的数组,无需导入库,简单的循环既是最简单的也是 最有效的解决方案:

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

致拒绝投票的人:请阅读问题,不要拒绝投票,因为它不适合您的非常不同的问题。对于提出的问题,此解决方案既最快又最简单。


4
我会说:“不要寻找更神秘的东西。” ^^

4
我的意思是……当我看到人们建议使用图书馆来实现这一目标时……太疯狂了……
DenysSéguret2012年

3
啊,仅仅为此使用一个库是很愚蠢的……但是,如果它已经可用的话。

9
@dystroy for循环的条件部分不那么可读。还是我自己:D
Andreas

4
它到底有多神秘并不重要。此代码将此“拉平” ['foo', ['bar']]['f', 'bar']
jonschlinkert 2014年

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

这就是我在整个代码中所做的工作,但是如果您有很长的数组,我不确定速度与公认的答案相比如何
Michael Aaron Wilson

14

请注意:Function.prototype.apply[].concat.apply([], arrays))或点差运算符([].concat(...arrays))来使数组变平时,由于函数的每个参数都存储在堆栈中,因此两者都可能导致大型数组的堆栈溢出。

这是一种函数式的堆栈安全实现,权衡了最重要的要求:

  • 可重用性
  • 可读性
  • 简明
  • 性能

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

一旦您习惯了咖喱形式的小箭头功能,功能组成和高阶功能,此代码就像散文一样读起来。然后,编程仅由放在一起始终可以按预期运行的小构建块组成,因为它们不包含任何副作用。


1
哈哈。完全尊重您的回答,尽管阅读这样的函数式编程仍然像对我(说英语的人)逐字逐句地阅读日语。
Tarwin Stroh-Spijer's

2
如果您发现自己以语言B来实现语言A的功能不是作为项目的一部分,而其唯一目标就是要做到这一点,那么某个地方的人走错了路。可以是你吗?只是使用它会const flatten = (arr) => arr.reduce((a, b) => a.concat(b), []);节省您的视觉垃圾并向队友解释为什么您需要3个额外的功能以及一些函数调用。
Daerdemandt

3
@Daerdemandt但是,如果将其编写为单独的函数,则可能可以在其他代码中重用它们。
米哈尔Perłakowski

@MichałPerłakowski如果你需要在多个地方使用他们,那么不要重新发明轮子,然后从包这些 -其他人的文档和支持。
Daerdemandt

12

ES6一线展平

请参见lodash展平下划线展平(浅true

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

要么

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

经过测试

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一线深压

lodash flattenDeep下划线flatten

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

经过测试

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

第二个示例的编写更好,Array.prototype.concat.apply([], arr)因为您创建了一个额外的数组来访问该concat函数。运行时在运行时可能会优化它,也可能不会优化它,但是在任何情况下访问原型上的函数看起来都不比现在更难看。
Mörre

11

您可以将Array.flat()with Infinity用于任何深度的嵌套数组。

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)

在此处查看浏览器兼容性


8

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
同意@monners。传授整个线程中最优雅的解决方案。
尼古拉斯Fantone

8

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

ES5 flatten函数的功能,N嵌套数组的ES3后备功能:

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

如果只有带有1个字符串元素的数组:

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

会做的工作。Bt与您的代码示例特别匹配。


3
谁投反对票,请解释原因。我一直在寻找一个体面的解决方案,在所有我最喜欢这一解决方案的解决方案中。
2014年

2
@Anonymous我没有拒绝投票,因为它在技术上可以满足问题的要求,但这很可能是因为这是一个很差的解决方案,在一般情况下没有用。考虑到这里有多少更好的解决方案,我永远不建议有人使用此解决方案,因为它会在您具有多个元素时或在它们不是字符串时中断。
Thor84no 2015年

2
我喜欢这个解决方案=)

2
它不仅仅处理具有1个字符串元素的数组,还处理该数组 ['$4', ["$6"], ["$12"], ["$25"], ["$25", "$33", ['$45']]].join(',').split(',')
alucic

我自己发现了这种方法,但是知道它必须已经记录在某个地方,我的搜索到此为止。此解决方案的缺点是,它会将数字,布尔值等[1,4, [45, 't', ['e3', 6]]].toString().split(',')[1,4, [45, 't', ['e3', 6], false]].toString().split(',')
强制转换

7
var arrays = [["a"], ["b", "c"]];
Array.prototype.concat.apply([], arrays);

// gives ["a", "b", "c"]

(我只是根据@danhbear的评论将其作为一个单独的答案来编写。)


7

我建议一个节省空间的生成器函数

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

如果需要,创建一个展平值数组,如下所示:

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

我喜欢这种方法。与stackoverflow.com/a/35073573/1175496相似,但是使用散布运算符...迭代生成器。
红豌豆

6

我宁愿将整个数组按原样转换为字符串,但与其他答案不同,我会使用JSON.stringify而不是使用toString()会产生不良结果的方法。

使用该JSON.stringify输出,剩下的就是删除所有括号,再次将结果括在起始和结束括号中,然后提供JSON.parse将字符串带回“ life”的结果。

  • 可以处理无限的嵌套数组,而没有任何速度成本。
  • 可以正确处理包含逗号的字符串数组项。

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

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

console.log(flattened)

  • 仅适用于字符串/数字的多维数组(不适用于对象)

您的解决方案不正确。展平内部数组时,它将包含逗号,["345", "2", "3,4", "2"]而不是将这些值中的每一个分隔为单独的索引
Pizzarob 2016年

@realseanp-您误解了该Array项中的值。我故意将该逗号作为值而不是数组定界符逗号,以强调我的解决方案在输出所有其他问题之上的力量"3,4"
vsync

1
我确实误会了
pizzarob's

这似乎绝对是我所见过的最快的解决方案;您是否意识到@vsync的任何陷阱(除了它看起来确实有点怪异的事实-将嵌套数组视为字符串:D)
George Katsanos

@GeorgeKatsanos-此方法不适用于非原始值的数组项(和嵌套项),例如,指向DOM元素的项
vsync

6

您也可以尝试新Array.Flat()方法。它以以下方式工作:

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

console.log(arr);

flat()方法创建一个新数组,将所有子数组元素递归连接到该数组中,直至达到1层深度(即数组内部的数组)

如果您还希望展平3维或什至更高维的数组,则只需多次调用flat方法。例如(3个维度):

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

console.log(arr);

小心!

Array.Flat()方法相对较新。较旧的浏览器(例如ie)可能尚未实现该方法。如果您希望代码在所有浏览器上都能工作,则可能必须将JS转换为旧版本。检查MD Web文档是否具有当前浏览器兼容性。


2
要平展高维数组,您只需使用Infinity参数调用flat方法即可。像这样:arr.flat(Infinity)
Mikhail

这是一个非常好的补充,谢谢米哈伊尔!
威廉·范德温

6

使用价差运算符:

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


5

这并不难,只需遍历数组并合并它们:

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

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

5

看起来这似乎是RECURSION的工作!

  • 处理多层嵌套
  • 处理空数组和非数组参数
  • 没有突变
  • 不依赖现代浏览器功能

码:

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

用法:

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

小心,flatten(new Array(15000).fill([1]))抛出Uncaught RangeError: Maximum call stack size exceeded并冻结了我的devTools 10秒钟
pietrovismara

@pietrovismara,我的测试大约是1秒。
伊万·严

5

我已经使用递归和闭包完成了

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
简单而可爱,这个答案比公认的答案更好。它将深层嵌套的层次扩展到了不仅是第一层次
Om Shankar

1
AFAIK是词法作用域,而不是闭包
短评

@dashambles是正确的-区别在于,如果它是一个闭包,则将内部函数返回到外部,而当外部函数完成时,您仍然可以使用内部函数来访问其范围。在此,外部函数的寿命比内部函数的寿命长,因此永远不会创建“关闭”。
Mörre

5

前几天,我正和ES6 Generators混在一起,写了这个要点。其中包含...

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

基本上,我正在创建一个在原始输入数组上循环的生成器,如果它找到一个数组,则它将使用yield *运算符与递归结合以连续地对内部数组进行展平。如果该项不是数组,则仅产生单个项。然后使用ES6 Spread运算符(又名splat运算符)将生成器展平到一个新的数组实例中。

我还没有测试它的性能,但是我认为这是一个使用生成器和yield *运算符的简单示例。

但是再次,我只是在无聊中,所以我确定还有更多高效的方法可以做到这一点。


1
类似的答案(使用生成器和收益委托),但采用PHP格式
Red Pea

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.