最近在忙秋招笔试面试,结果发现实习喜欢问手写题,校招更喜欢考算法题,也是醉了。但谁让咱是苦逼的打工人呢?那就只有两手都要抓,两手都要硬了。今天总结个比较简单的内容,数组扁平化。
为了方便动手在命令行中测试我们实现的函数,这里先给出一个示例数组:
let arr = [1, [2, [3, 4]]];
console.log(flatten(arr)); // flatten 函数的返回应该是 [1, 2, 3, 4]
ES6 的 flat 方法
flat 是 Array.prototype 上的方法,它接收一个参数 depth,表示想要提取的嵌套数组的深度,默认值为 1。
function flatten (arr) {
return arr.flat(Infinity);
}
递归实现
使用 for 或者 forEach 遍历数组,如果遇到数组就递归的调用 flat 函数,遇到非数组元素则加入结果数组中:
function flatten(arr) {
let res = [];
for (let i = 0; i < arr.length; i++) {
Array.isArray(arr[i]) ? res = res.concat(flatten(arr[i])) : res.push(arr[i]);
}
return res;
}
扩展运算符实现
concat方法创建一个新数组。该数组将首先由调用它的对象中的元素填充。然后,对于每个参数,它的值将被连接到数组中——对于普通对象或基元,参数本身将成为最终数组的一个元素;对于属性Symbol.isConcatSpreadable设置为真的数组或类数组对象,参数的每个元素都将是独立地添加到最终数组中。concat方法不会递归到嵌套数组参数中。
利用 concat 的特性,可以展开一层嵌套数组,配合遍历检查是否仍然存在嵌套数组使用:
function flatten(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
这种方法的确需要反复遍历数组,因此在某些情况下可能会导致较高的时间复杂度。特别是在具有大量嵌套层数的数组中,它可能会变得非常低效。
Array.prototype.toString()
Array 对象覆盖了 Object 的 toString 方法。数组的 toString 方法实际上在内部调用了 join() 方法来拼接数组并返回一个包含所有数组元素的字符串,元素之间用逗号分隔。如果 join 方法不可用或者不是函数,则会使用 Object.prototype.toString 来代替,并返回 [object Array]。
function flatten(arr) {
return arr.toString().split(',').map((item) => {
return +item;
});
}
这种方法有一个明显的缺点:toString 方法输出的是字符串,如果不确定原先数组中元素的类型,不能保证扁平化后元素类型一致。
巧用reduce方法
这种方法和递归实现有些相似:在每次迭代中,我们检查 next 是否是一个数组。如果是数组,我们递归调用 flatten() 函数来展开它,并将结果与 prev 连接起来。如果不是数组,我们直接将 next 添加到 prev 中。
function flatten(arr) {
return arr.reduce(function(prev, next){
return prev.concat(Array.isArray(next) ? flatten(next) : next);
}, []);
}
使用正则替换
简单粗暴的去除掉数组内的中括号,缺点和 toString 方法一致,转成字符串后元素类型不能保证一致。
function flatten(arr) {
return JSON.stringify(arr).replace(/\[|\]/g, '').split(',').map(function(item){
return +item;
});
}
使用Generator函数
网上看到的方法。
function* flatten(arr) {
for (let a of arr) {
if (Array.isArray(a)) {
yield* flatten(a);
} else {
yield a;
}
}
}
let arr = [1,[2,[3,[4,[5]]]]];
for (let f of flatten(arr)){
console.log(f);
}