如何在javascript中展平嵌套数组? [复制]
Posted
技术标签:
【中文标题】如何在javascript中展平嵌套数组? [复制]【英文标题】:How to flatten nested array in javascript? [duplicate] 【发布时间】:2015-01-31 17:03:33 【问题描述】:我们知道,通过使用reduce()
方法来展平数组[[0, 1], [2, 3], [4, 5]]
var flattened = [[0, 1], [2, 3], [4, 5]].reduce(function(a, b)
return a.concat(b);
);
那么如何将这个数组[[[0], [1]], [[2], [3]], [[4], [5]]]
展平为[0, 1, 2, 3, 4, 5]
?
【问题讨论】:
在此处查看此讨论:***.com/questions/10865025/… [[[0], [1]], [[2], [3]], [[4], [5]]].toString().split(",") .map(数字); @dandavis 这是黑魔法。如果他有字符串或其他类型怎么办? :D @LeoDeng:我没有看到任何字符串,其他方法太复杂,无法放入评论... @dandavis 我的意思是,我假设他只是以 12345 为例。本质上我认为它应该是一个递归。他发布的代码来自这个页面:developer.mozilla.org/en/docs/Web/javascript/Reference/… 【参考方案1】:完美的递归用例,可以处理更深层次的结构:
function flatten(ary)
var ret = [];
for(var i = 0; i < ary.length; i++)
if(Array.isArray(ary[i]))
ret = ret.concat(flatten(ary[i]));
else
ret.push(ary[i]);
return ret;
flatten([[[[[0]], [1]], [[[2], [3]]], [[4], [5]]]]) // [0, 1, 2, 3, 4, 5]
或者,作为数组方法:
Array.prototype.flatten = function()
var ret = [];
for(var i = 0; i < this.length; i++)
if(Array.isArray(this[i]))
ret = ret.concat(this[i].flatten());
else
ret.push(this[i]);
return ret;
;
[[[[[0]], [1]], [[[2], [3]]], [[4], [5]]]].flatten() // [0, 1, 2, 3, 4, 5]
编辑#1:好吧,认为它有点功能性的方式(命名递归除外,它应该使用 Y-combinator 来实现纯功能性:D)。
function flatten(ary)
return ary.reduce(function(a, b)
if (Array.isArray(b))
return a.concat(flatten(b))
return a.concat(b)
, [])
让我们在一行中采用一些 ES6 语法,使其更短。
const flatten = (ary) => ary.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), [])
但请记住,这个不能作为数组方法应用,因为arrow functions 没有自己的this
。
编辑#2:使用最新的Array.prototype.flat
提案,这非常容易。 array 方法接受一个可选参数depth
,它指定嵌套数组结构应该被展平的深度(默认为1
)。
[[[[[0]], [1]], [[[2], [3]]], [[4], [5]]]].flat() // [[[[0]], [1]], [[[2], [3]]], [[4], [5]]]
[[[[[0]], [1]], [[[2], [3]]], [[4], [5]]]].flat(2) // [[[0]], [1], [[2], [3]], [4], [5]]
[[[[[0]], [1]], [[[2], [3]]], [[4], [5]]]].flat(3) // [[0], 1, [2], [3], 4, 5]
[[[[[0]], [1]], [[[2], [3]]], [[4], [5]]]].flat(4) // [0, 1, 2, 3, 4, 5]
所以要展平任意深度的数组,只需使用Infinity
调用flat
方法。
[[[[[0]], [1]], [[[2], [3]]], [[4], [5]]]].flat(Infinity) // [0, 1, 2, 3, 4, 5]
【讨论】:
这可以通过重用ret
数组来加快速度。有关此类解决方案,请参阅我的答案。
这不是n^2吗? concat
不是恒定时间操作。这是一个 O(n) 操作 AFAIK。【参考方案2】:
带有递归的 ES6 风格:
Redacted
2018 年 6 月更新:
现在有一个Array.prototype.flat
method 的 ES 提案。它目前处于第 3 阶段,这意味着它很可能很快(ish)被浏览器实现,并以其当前形式进入规范。可能有一些 polyfills 漂浮在周围。
例子:
const nested = [[[0], [1]], [[2], [3]], [[4], [5]]];
const flattened = nested.flat(2); // Need to specify depth if > 1
2019 年 6 月更新:
Array.prototype.flat
被正式添加到 ES2019 规范中的语言中。
【讨论】:
ES6 的良好实践 像其他答案所暗示的那样,用无穷大调用此方法有什么缺点吗?我们并不总是知道数组的深度。【参考方案3】:这是递归的替代方法 (see jsfiddle here),并且应该接受任何避免堆栈溢出的深度级别。
var array = [[0, 1], [2, 3], [4, 5, [6, 7, [8, [9, 10]]]]];
console.log(flatten(array), array); // does not mutate array
console.log(flatten(array, true), array); // array is now empty
// This is done in a linear time O(n) without recursion
// memory complexity is O(1) or O(n) if mutable param is set to false
function flatten(array, mutable)
var toString = Object.prototype.toString;
var arrayTypeStr = '[object Array]';
var result = [];
var nodes = (mutable && array) || array.slice();
var node;
if (!array.length)
return result;
node = nodes.pop();
do
if (toString.call(node) === arrayTypeStr)
nodes.push.apply(nodes, node);
else
result.push(node);
while (nodes.length && (node = nodes.pop()) !== undefined);
result.reverse(); // we reverse result to restore the original order
return result;
【讨论】:
你为什么用Object.prototype.toString.call(node) == '[object Array]'
而不是Array.isArray(node)
?
只适用于ie8及以下
偷偷使用 apply 来拆分 arr,喜欢它。
为什么不使用 instanceof 运算符而不是 toString? if (node instanceof Array) nodes.push.apply(nodes, node); else result.push(node);
你也可以这样做,是的。此时的品味问题【参考方案4】:
ES2019 solution:
ary.flat(Infinity);
就是这样。就是这么简单。如果您愿意,可以将Infinity
更改为适当的扁平化级别。
例子:
console.log([[[0], [1]], [[2], [3]], [[4], [5]]].flat(Infinity));
以下适用于older browsers 的其他更长的解决方案。
基于@Leo 的解决方案,但通过重用相同的数组并防止.concat
更快
function flatten(ary, ret = [])
for (const entry of ary)
if (Array.isArray(entry)
flatten(entry, ret);
else
ret.push(entry);
return ret;
console.log(flatten([[[0], [1]], [[2], [3]], [[4], [5]]]));
或者Array.prototype.reduce
,因为你提到了它:
function flatten(ary, ret = [])
return ary.reduce((ret, entry) =>
if (Array.isArray(entry))
flatten(entry, ret);
else
ret.push(entry);
return ret;
, ret);
console.log(flatten([[[0], [1]], [[2], [3]], [[4], [5]]]));
【讨论】:
这里可能是最好和最易读的解决方案 比这里的大多数答案要好得多。没有充分的理由让所有临时数组围绕其他答案浮动。 @timothygu 你能解释一下你将如何用 Big-O 表示法描述这两种解决方案的时间和空间复杂性吗? @baltusaj 渐近分析对于这种特定情况几乎没有用,因为这个问题中的所有解决方案基本上都遵循相同的算法。这是关于将这个解决方案与其他解决方案区分开来的常数项(如创建一个空数组)。 这仍然比接受的答案慢。【参考方案5】:ES6 单行:
function flatten(a)
return Array.isArray(a) ? [].concat(...a.map(flatten)) : a;
此外,非常深的数组的非递归版本(效率不高但相当优雅)
function flatten(a)
var queue = a.slice();
var result = [];
while(queue.length)
let curr = queue.pop();
if(Array.isArray(curr))
queue.push(...curr);
else result.push(curr);
return result;
【讨论】:
你的 ES6 one-liner 看起来很不错,但你能解释一下它是如何工作的吗? @baltusaj 这是一个递归解决方案。如果当前元素不是数组,则返回它。如果是,则将数组的每个元素展平(使用 Array.map),然后连接生成的数组(使用 Array.concat 并将数组传播到函数参数) 谢谢。我猜“传播”是由三个点完成的。 更多 es6 魔法:让 flatten = a => Array.isArray(a) ? [].concat(...a.map(flatten)) : a; 第二次实现失败my tests【参考方案6】:仅展平 2 个级别:
var arr = [1, [2, 3], [4, 5, 6]];
[].concat.apply([], arr) // -> [1, 2, 3, 4, 5, 6]
【讨论】:
【参考方案7】:这已经回答了,但我只是在学习 JS,想知道该怎么做:
var array = [[[0], [1]], [[2], [3]], [[4], [5]]];
var flattend = array.join(",").split(",");
console.log(flattend);
唯一的副作用是连接将所有项目转换为字符串,但这很容易修复
【讨论】:
对于数字和字符串,这个解决方案非常智能,但是当涉及到对象和函数时,问题会变得更加复杂。 我一直在寻找用于字符串数组的简单解决方案。这个很完美。【参考方案8】:我喜欢我的解决方案 :)
var flattenClosure = function(a)
var store = [];
return function()
var internMapper = function(b)
if (Array.isArray(b))
return b.map(internMapper);
store.push(b);
return b;
a.map(internMapper);
return store;
;
console.log(flattenClosure([[[[[[[[1]]]], [2], [4], [6, 8, 9], 2]]], 10, 11, [15, 17, 20], [], 33])());
【讨论】:
【参考方案9】:如果您知道数组仅由数字组成,您可以执行以下操作:
array.join().split(',').map(Number);
【讨论】:
回答了一个类似这样的面试问题。不确定幽默是否受到赞赏......大声笑【参考方案10】:受Eloquent JavaScript 的代码和@axelduch 提供的答案启发(据我所知,效率要高得多。)
function flatten(array, mutable)
var nodes = (mutable && array) || array.slice(); // return a new array.
var flattened = [];
for (var node = nodes.shift(); node !== undefined; node = nodes.shift())
if (Array.isArray(node))
nodes.unshift.apply(nodes, node);
else
flattened.push(node);
return flattened;
【讨论】:
这些解决方案需要更多的支持,apply 的使用非常重要。 看起来像@axelduch is faster的原版【参考方案11】:免责声明:我知道这是一个古老且已经回答的问题,但@Nick 让我参与其中,因为我将他的回答评论为 最昂贵的扁平化方法之一一个数组。我已经多年没有编写 JavaScript 代码了,但这就像骑自行车一样——一旦你学会了你就永远不会忘记 ;)
这是我的完整递归代码(不需要for
循环):
var flattened = [];
function flatten(a, i)
if(a.length > i)
if(Array.isArray(a[i]))
flatten(a[i], 0);
else
flattened.push(a[i]);
flatten(a, i + 1);
flatten([[0, 1], [2, 3], [4, 5]], 0);
console.log(flattened);
我已经针对toString().split(',')
解决方案对其进行了测试,我的速度大约快了 7 倍。这就是我在谈论昂贵时的意思;)
【讨论】:
【参考方案12】:function flatten(array)
return array.reduce(
(previous, current) =>
Array.isArray(current)
? [...previous, ...flatten(current)]
: [...previous, current]
, []
);
【讨论】:
虽然此代码可能会回答问题,但提供有关 why 和/或 如何 此代码回答问题的附加上下文可提高其长期价值.【参考方案13】:var nested = [[[0], [1]], [[2], [3]], [[4], [5]]];
var flattened = [].concat.apply([],[].concat.apply([],nested));
console.log('-> flattened now: ' + flattened);
【讨论】:
如果我们将nested
更改为 [[[0], [[1]]], [[2], [3]], [[4], [5]]]
它会展平所有内容...同样在打印时,不能使用 +
否则某些调试器可能会通过为您展平东西来打印出来,需要使用,
代替
此解决方案将深度限制为最多三个级别。例如,[[[[4]]]]
将返回 [[4]]
而不是 [4]
。【参考方案14】:
基于dashamble的answer,但我相信这更容易理解:
var steamroller = function(arr)
var result = [];
var dropHeavyObject = function(auxArr)
var flatnd = [];
flatnd = auxArr.map(function(x)
if(Array.isArray(x))
return dropHeavyObject(x);
else
result.push(x);
return x;
);
return flatnd;
;
dropHeavyObject(arr);
return result;
【讨论】:
【参考方案15】:lodash 中有三个实用函数与lodash
中的问题flatten
, flattenDeep
, flattenDepth 相关。 flatten
进入单一深度,flattenDeep
一直进入最深层次,而 flattenDepth 让您可以选择要展平的“深度”。
示例:
> var arr = [[[0], [1]], [[2], [3]], [[4], [5]]];
> _.flattenDeep(arr)
[0, 1, 2, 3, 4, 5]
【讨论】:
【参考方案16】:函数式编程实现
通过函数式编程,我们可以简单地从另一个更通用的函数派生flatten
:traverse
。
后者是一个像平面数组一样遍历和归约任意嵌套数组的函数。这是可能的,因为深度未知的嵌套数组只不过是树数据结构的特定版本:
const traverse = f => g => acc => xs =>
let [leaf, stack] = xs[0][0] === undefined
? [xs[0], xs.slice(1)]
: f([]) (xs);
return stack.length
? traverse(f) (g) (g(leaf) (acc)) (stack)
: g(leaf) (acc);
;
const dfs = stack => tree => tree[0] === undefined
? [tree, stack]
: dfs(tree.length > 1 ? concat(stack) (tree.slice(1)) : stack) (tree[0]);
const concat = ys => xs => xs.concat(ys);
const flatten = f => traverse(f) (concat) ([]);
const xs = [[[1,2,3],4,5,6],7,8,[9,10,[11,12],[[13]],14],15];
console.log(flatten(dfs) (xs));
请注意,树数据结构可以通过深度优先 (DFS) 或广度优先 (BFS) 进行遍历。数组通常以深度一阶遍历。
正如我所说,traverse
是一个通用的归约函数,类似于平面数组的归约函数。所以我们也可以很容易地计算出所有元素的总和:
const add = y => x => x + y;
traverse(dfs) (add) (0) (xs); // 120
结论:要以函数方式展平任意嵌套的数组,我们只需要一个单行:const flatten = f => traverse(f) (concat) ([]);
。所有其他涉及的功能都是通用的,并具有一系列其他潜在应用。这是 100% 的可重用性!
【讨论】:
【参考方案17】:function flatten(x)
if (x.length == 0) return [];
if (Array.isArray(x[0]))
return flatten(x[0].concat(flatten(x.slice(1,x.length))));
return [].concat([x[0]], flatten(x.slice(1,x.length)));
递归地展平数组。
【讨论】:
虽然无法处理更深层次的结构,flatten([[[[[0]], [1]], [[[2], [3]]], [[4], [5]]]])
返回[1, 4, 5, 0, 2, 3]
。
感谢您指出这一点。我编辑了我的答案以提供一个保留元素顺序的解决方案(类似于您的出色解决方案)。【参考方案18】:
前几天我遇到了这个问题并发现了一个小技巧......实际上我刚刚意识到它与上一个答案非常相似,并且就像对上述答案的评论所表明的那样,它不适用于对象,但对于简单数字等。它工作得很好。
如果您将嵌套数组转换为字符串,它会用逗号分隔值。然后你可以用逗号分割它以形成一个字符串。如果您随后需要将字符串转换为 int 或 float,您可以运行新数组来转换每个值。
stringArray = [[0, 1], [2, 3], [4, 5]].toString().split(',');
stringArray.forEach((v,i,a) => a[i] = parseFloat(a[i]));
【讨论】:
我认为这是扁平化数组最昂贵的方法之一。 谢谢,亚当。我是新手,当我发现这个作弊时正在试验这些功能;)你如何估算费用? 嘿,你让我陷入了这个问题;)请看看我的回答。 谢谢!好消息是,在我发布上面的新手 hack 后不久,我学会了如何递归地执行此操作,而且(使用递归)非常容易,它真的不值得以另一种方式来做。我明白你所说的费用是什么意思!【参考方案19】:使用JSON.stringify
和JSON.parse
arr = JSON.parse("[" +
JSON.stringify(arr)
.replace(/[\[\]]+/g,"")
.replace(/,,/g,",") +
"]");
【讨论】:
【参考方案20】:// implementation
function flattenArray()
const input = arguments[0]
, output = flatten(input)
function flatten()
return [].concat.apply([], arguments[0])
return input.length === output.length
? output
: flattenArray(output)
// how to use?
flattenArray([1,2,[3]) // return [1,2,3]
测试用例 -> https://github.com/CHAOWEICHIU/ccw-custom-functions/blob/master/test/units/flattenArray.js
【讨论】:
【参考方案21】:function flatten(arrayOfArrays)
return arrayOfArrays.reduce(function(flat, subElem)
return flat.concat(Array.isArray(subElem) ? flatten(subElem) : subElem);
, []);
var arr0 = [0, 1, 2, 3, 4];
var arr1 = [[0,1], 2, [3, 4]];
var arr2 = [[[0, 1], 2], 3, 4];
console.log(flatten(arr0)); // [0, 1, 2, 3, 4]
console.log(flatten(arr1)); // [0, 1, 2, 3, 4]
console.log(flatten(arr2)); // [0, 1, 2, 3, 4]
console.log(flatten([])); // []
【讨论】:
【参考方案22】:var flattenWithStack = function(arr)
var stack = [];
var flat = [];
stack.push(arr);
while(stack.length > 0)
var curr = stack.pop();
if(curr instanceof Array)
stack = stack.concat(curr);
else
flat.push(curr);
return flat.reverse();
非递归。基本上是dfs。
【讨论】:
问题已在多年前得到解答。请改用 cmets 添加您的建议。欢迎使用 ***。 非常慢的方法【参考方案23】:使用 Lisp 约定。
但是,使用 .shift() 和 .concat() 效率低下。
flatten (array)
// get first element (car) and shift array (cdr)
var car = array.shift();
// check to see if array was empty
if (car === undefined)
return [];
// if the first element (car) was an array, recurse on it
else if (_.isArray(car))
return flatten(car).concat(flatten(array));
// otherwise, cons (concatenate) the car to the flattened version of cdr (rest of array)
else
return [car].concat(flatten(array))
【讨论】:
【参考方案24】:如果您有一个 infinitely 嵌套数组,例如下面的 a
,我会这样做。
const a = [[1,2,[3]],4]
Array.prototype.flatten = (array) =>
const newArray = []
const flattenHelper = (array) =>
array.map(i =>
Array.isArray(i) ? flattenHelper(i) : newArray.push(i)
)
flattenHelper(a)
return newArray
const newArray = a.flatten()
console.log(newArray);
【讨论】:
【参考方案25】:这就是我所拥有的:
function steamrollArray(arr)
// the flattened array
var newArr = [];
// recursive function
function flatten(arr, newArr)
// go through array
for (var i = 0; i < arr.length; i++)
// if element i of the current array is a non-array value push it
if (Array.isArray(arr[i]) === false)
newArr.push(arr[i]);
// else the element is an array, so unwrap it
else
flatten(arr[i], newArr);
flatten(arr, newArr);
return newArr;
【讨论】:
这只会展平嵌套数组而不是线性数组。对吗?【参考方案26】:可以这样解决
const array = [[0, 1], [2, 3], [4, 5, [6, 7, [8, [9, 10]]]]];
const flatten(arr) => arr.reduce((acc, item) =>
acc.concat(Array.isArray(item) ? flatten(item) : item);
, []);
console.log(flatten(array));
请注意,对于深度数组,应应用 TCO
具有递归解决方案的 TCO 版本
const array = [[0, 1], [2, 3], [4, 5, [6, 7, [8, [9, 10]]]]];
const flatten = (() =>
const _flatten = (acc, arr) => arr.reduce((acc, item) => acc.concat(Array.isArray(item) ? _flatten([], item) : item), acc);
return arr => _flatten([], arr);
)();
console.log(flatten(array))
【讨论】:
【参考方案27】:认为这个功能有效。
function flatten(arr)
return arr.reduce(function(a,b)
return [].concat(Array.isArray(a)? flatten(a) :a,Array.isArray(b)? flatten(b):b);
);
【讨论】:
以上是关于如何在javascript中展平嵌套数组? [复制]的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 pyspark 在 aws 胶水中展平嵌套 json 中的数组?
javascript 通过递归来展平嵌套数组Ex:[1,2,3,[4,5,[7,8,[10,11,[12,13,[[[[[[14]]]]]]]]]] ]]