如何在javascript中展平嵌套数组

How to flatten nested array in javascript?

本文关键字:嵌套 数组 javascript      更新时间:2023-09-26

众所周知,使用方法平展数组[[0, 1], [2, 3], [4, 5]] reduce()

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]呢?

递归的完美用例,可以处理更深层次的结构:

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组合器进行纯函数: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), [])

但请记住,这个不能作为数组方法应用,因为箭头函数没有自己的this


编辑#2:使用最新的Array.prototype.flat提案,这非常简单。数组方法接受可选参数 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]

ES6 样式,带递归:

Redacted
<小时 />

2018 年 6 月更新:

现在有一个关于Array.prototype.flat方法的 ES 提案。它目前处于第 3 阶段,这意味着它很可能很快就会被浏览器实现,并以当前的形式进入规范。可能有一些填充物漂浮在周围。

例:

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规范中的语言中。

这是递归的替代方法(参见此处的 jsfiddle(,并且应该接受任何避免堆栈溢出的深度级别。

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

ES2019 解决方案:

ary.flat(Infinity);

就是这样。就是这么简单。如果需要,可以将Infinity更改为适当的平展级别。

例:

console.log([[[0], [1]], [[2], [3]], [[4], [5]]].flat(Infinity));

适用于以下旧版浏览器的其他较长解决方案。


基于 @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]]]));

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

仅展平 2 个级别:

var arr = [1, [2, 3], [4, 5, 6]];
[].concat.apply([], arr) // -> [1, 2, 3, 4, 5, 6]

这已经回答了,但我只是在学习JS,想知道怎么样:

    var array = [[[0], [1]], [[2], [3]], [[4], [5]]];
    var flattend = array.join(",").split(",");
    console.log(flattend);

唯一的副作用是联接将所有项目转换为字符串,但这可以很容易地修复

我喜欢我的解决方案:)

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

如果您知道数组仅由数字组成,则可以执行以下操作:

array.join().split(',').map(Number);

灵感来自 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;
}

免责声明:我知道这是一个古老且已经回答的问题,但@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 倍。这就是我在谈论昂贵性时的意思;)

function flatten(array) {
    return array.reduce(
        (previous, current) =>
            Array.isArray(current)
            ? [...previous, ...flatten(current)]
            : [...previous, current]
        , []
    );
}
var nested = [[[0], [1]], [[2], [3]], [[4], [5]]];
var flattened = [].concat.apply([],[].concat.apply([],nested));
console.log('-> flattened now: ' + flattened);

基于dashamble的回答,但我相信这更容易理解:

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

lodash 中有三个实用函数与您的问题flattenflattenDeep、flattenDepth in lodash 有关。 flatten进入一个深度,flattenDeep一直进入最深的层次,flattenDepth让你选择"多深"来展平。

例:

> var arr = [[[0], [1]], [[2], [3]], [[4], [5]]];
> _.flattenDeep(arr)
   [0, 1, 2, 3, 4, 5]
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。

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

递归平展数组。

前几天我遇到了这个问题,发现了一个小技巧......实际上我刚刚意识到它与最后一个答案非常相似,并且像上述答案的评论一样,它不适用于对象,但对于简单的数字等,它工作得很好。

如果将嵌套数组转换为字符串,则会用逗号分隔值。然后,您可以用逗号将其拆分以形成字符串。如果需要将字符串转换为 int 或 float,则可以运行转换每个值的新数组。

stringArray = [[0, 1], [2, 3], [4, 5]].toString().split(',');
stringArray.forEach((v,i,a) => a[i] = parseFloat(a[i]));
函数

式编程的实现

通过函数式编程,我们可以简单地从另一个更通用的函数中导出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是一个通用的归约函数,就像平面数组的reduce一样。因此,我们也可以轻松计算所有元素的总和:

const add = y => x => x + y;
traverse(dfs) (add) (0) (xs); // 120

结论:要以功能方式扁平化任意嵌套的数组,我们只需要一行:const flatten = f => traverse(f) (concat) ([]);。所有其他涉及的功能都是通用的,并具有一系列其他潜在应用。这是100%的可重用性!

使用 JSON.stringifyJSON.parse

arr = JSON.parse("[" + 
               JSON.stringify(arr)
                   .replace(/['[']]+/g,"")
                   .replace(/,,/g,",") +
               "]");
// 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

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([]));    // []

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

如果你有一个无限嵌套的数组,如下所示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);

这是我得到的:

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

这样解决

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

认为这个函数有效。

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