深入数组 - 实现数组扁平化的 6 种方式

181 阅读4分钟

开篇思考

  1. 怎样用最简单/普通的方式解决数组扁平化问题?
  2. ES6 中是否有一些高级的方法能直接实现?

扁平化实现思路

本质上将一个嵌套多层(任何层数)的数组转换为只有一层的数组。如下所示:

let arr = [12, 5, [8, 99, 200]];
console.log(flatten(arr)); // [12, 5, 8, 99, 200]

简单来说就是用一个 flatten 函数把多维数组“拍平”,然后输出一维数组。接下来就尝试着实现一个 flatten 函数吧。

方法一:普通递归

普通递归思路比较容易理解,就是通过循环递归方法,一项一项遍历,如果遍历的当前项还是一个数组,那就继续往下遍历,利用递归方法,来实现数组每一项的连接。如下所示。

let arr = [12, 5, [8, 99, 200]];

function flatten(arr) {
    let result = [];
    for(let i = 0; i < arr.length; i++) {
        if(Array.isArray(arr[i])) {
            result = result.concat(flatten(arr[i]));
        } else {
            result.push(arr[i]);
        }
    }
    return result;
}

console(flatten(arr)); // [12, 5, 8, 99, 200]

以上代码核心就是循环遍历过程中的递归操作,在遍历的过程中发现数组元素还是数组的时候进行递归操作,把数组的结果通过 concat 方法拼接到最后要返回的 result 数组上,最后输出得到扁平化后数组。接下来看看下一种实现方式。

方法二:reduce 函数迭代

从以上递归函数中可以看出,无非就是对数组每一项进行处理,那么也可以用 reduce 来实现数组的拼接,还能简化第一种方法的代码,改造后如下所示。

let arr = [12, 5, [8, 99, 200]];

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

console(flatten(arr)); // [12, 5, 8, 99, 200]

用 reduce 的第一个参数用来返回最后的累加结果,思路和第一种递归方法思路是一样的,但使用 reduce 后代码更简洁了,也解决了数组扁平化的问题。接下来看看下一种实现方式。

方法三:扩展运算符

使用扩展运算符和 some 的方法,二者一起使用,达到扁平化目的。

let arr = [12, 5, [8, 99, 200]];

function flatten(arr) {
    while(arr.some(item => Array.isArray(item))) {
        arr = [].concat(...arr);
    }
    return arr;
}

console(flatten(arr)); // [12, 5, 8, 99, 200]

从代码执行过程可以看出,先用数组的 some 方法把数组中依然是数组项过滤,然后执行 concat 操作,利用 ES6 的展开运算符,将其拼接到原数组中,最后返回原数组,从而达到预期效果。

前三种方式本质上还是通过普通递归思路衍生的方法,前两种比较类似,reduce 方法提供的几个参数比较灵活,可以解决的问题很多,值得熟练使用并精通。

除此之外,再试试其他实现方式。

方法四:split 和 toString 一起处理

通过 split 和 toString 两个方法组合使用实现数组扁平化,由于数组默认带一个 toString 方法,因此可以把数组直接转换成逗号分隔符的字符串,然后再使用 split 方法把字符串重新转换成数组。

let arr = [12, 5, [8, 99, 200]];

function flatten(arr) {
    return arr.toString().split(',');
}

console(flatten(arr)); // [12, 5, 8, 99, 200]

通过种两种方法可以直接将多维数组直接转换成逗号连接的字符串,然后再重新分隔成数组。

接下来再试试使用 ES6 中的方法来实现。

方法五:ES6 中的 flat

先看看 flat 语法:

arr.flat([depth])

depth 是 flat 参数,参数含义是数组的展开深度(默认不填数值是 1),那无论多少数组多少层都要展开怎么处理呢?这时候传 Infinity。

let arr = [12, [5, [8, 99, 200]]];

function flatten(arr) {
    return arr.flat(Infinity);
}

console(flatten(arr)); // [12, 5, 8, 99, 200]

这里因为数组的层级是确定的,因此 flat 参数传 2,也能达到想要的效果,但如果日常开发中对数组嵌套层级不确定的情况下,最好使用 Infinity 比较稳妥。

方法六:正则和 JSON 方法一起处理

在第四种方法中,使用过 toString 方法,这里仍然使用 JSON.stringify 的方法先转换为字符串,然后通过正则表达式过滤字符串中的方括号,最后再利用 JSON.parse 把它转换成数组。

let arr = [12, [5, [8, [99, 200]]], 999];

function flatten(arr) {
    let str = JSON.stringify(arr);
    str = str.replace(/(\[|\]))/g, '');
    str = '[' + str + ']';
    return JSON.parse(str);
}

console(flatten(arr)); // [12, 5, 8, 99, 200, 999]

这里利用正则表达式匹配规则,全局匹配(g)左括号或右括号,将其替换成空格,然后拿着正则处理好的结果重新在外层包裹括号,最后通过 JSON.parse 转换成数组返回。

总结

以上就是日常开发中有可能遇到数组扁平化的几种方法,涉及到数组 API、ES6,以及 JSON 方法的相关知识。通过下面表格再看看这六种方式的实现思路。

方法实现难度编码思路
递归实现递归实现,返回新数组
reduce 实现reduce 进行累加操作
扩展运算符实现筛选数组项进行连接
split 和 toString转成字符串再转数组
flat 方法特定功能方法直接操作
正则和 JSON 方法JSON 方法转成字符串转回过程中正则处理

在日常业务开发中往往会遇到各种数组问题,所有要思考最合适的解决方法。