如何在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】:

函数式编程实现

通过函数式编程,我们可以简单地从另一个更通用的函数派生flattentraverse

后者是一个像平面数组一样遍历和归约任意嵌套数组的函数。这是可能的,因为深度未知的嵌套数组只不过是树数据结构的特定版本:

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 =&gt; 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.stringifyJSON.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中展平嵌套数组? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

展平嵌套数组。 (爪哇)

展平和取消展平 numpy 数组的嵌套列表

如何使用 pyspark 在 aws 胶水中展平嵌套 json 中的数组?

javascript 通过递归来展平嵌套数组Ex:[1,2,3,[4,5,[7,8,[10,11,[12,13,[[[[[[14]]]]]]]]]] ]]

如何在 Azure 流分析中展平嵌套的 json 数据

Javascript函数没有正确展平数组