从一道面试编程题中看看Set、Array.prototype.flat的性能消耗

1,225 阅读3分钟

先上题:

已知如下数组:

var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];

编写一个程序将数组扁平化去并除其中重复部分数据,最终得到一个升序且不重复的数组

对于上述的编程题,相信很多童鞋都见过,也能分分钟写出答案。该题有三个考察点:

  • 数组的扁平化
  • 数组的去重
  • 数组的排序

可以说这都是很基本的操作了,如果你熟悉ES6,那么你可能更加喜欢用Array.flat来扁平,使用Set来去重,使用sort来排序。

也就是一行代码的事

Array.from(new Set(arr.flat(Infinity))).sort((a,b)=>{ return a-b})

我相信这也可能是很多面试官给出的标准答案。

而这个就是最优答案吗?

由于笔者最近在做性能优化,需要在弱性能机器上跑比较复杂的程序,所以对每一行代码的执行时间都特别在意。

所以我们对比下:Set+Flat和原生数组实现的性能消耗

以下数据是在Chrome80下执行所得

去重对比:Set pk 数组遍历+includes
function uniqueBySet(arr) {
    return Array.from(new Set(arr));
}

function uniqueByIncludes(arr) {
    const res = [];
    for (let i = 0; i < arr.length; i++) {
        const a = arr[i];
        !res.includes(a) && res.push(a) 
    }
    return res;
}
扁平化对比:Array.prototype.Flat pk 递归遍历
function flat(arr) {
    return arr.flat(Infinity);
}

function flatByArray(arr, res) {
    res = res || [];
    for (let i = 0; i < arr.length; i++) {
        const a = arr[i];
        if (isArray(a)) {
            flatByArray(a, res)
        } else {
            res.push(a)
        }
    }
    return res
}

测试代码:

通过console.time查看代码执行时间

function isArray(item) {
    return Object.prototype.toString.call(item) === '[object Array]';
}

function set_flat(arr) {
    console.time('flat');
    let _arr = flat(arr);
    console.timeEnd('flat');
    console.time('uniqueBySet');
    _arr = uniqueBySet(_arr);
    console.timeEnd('uniqueBySet');
    return _arr.sort((a,b)=>{ return a-b})
}

function array_includes(arr) {
    console.time('flatByArray');
    let _arr = flatByArray(arr);
    console.timeEnd('flatByArray');
    console.time('uniqueByIncludes');
    _arr = uniqueByIncludes(_arr);
    console.timeEnd('uniqueByIncludes');
    return _arr.sort((a,b)=>{ return a-b})
}

// 产生长度为300000的数组用于测试,通过console.time查看代码执行时间
var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];
var array = []
for (let j = 0; j < 300000; j++) {
    array.push(arr)
}
[set_flat, array_includes].forEach(fun => {
    console.time(fun.name)
    console.log(fun(array));
    console.timeEnd(fun.name)
})

执行结果

对于这个结果,笔者是有点意外。

原生提供的Array.prototype.flat比不上使用数组递归遍历实现。

不过Set去重性能还是比原生的遍历好。

由于扁平化性能消耗相差太大,也导致上述那个非常优雅简洁的答案的性能消耗是原生实现的5倍

总结

从上面的对比可以看出以下答案并非是最优的,至少在性能消耗这一块

Array.from(new Set(arr.flat(Infinity))).sort((a,b)=>{ return a-b})

如果从代码整洁度出发,数据量不大,机器性能还可以的情况下,可以使用上面的写法。

但如果数据量很大,而机器性能又不是很好的时候,flat扁平化处理还是使用原生的递归遍历实现为好。

所以去重可以使用Set。但是扁平化,是否使用Array.prototype.flat,需要根据实际情况考虑

有什么不对的地方,欢迎指正和交流!