一道关于复杂数组去重的面试题

98 阅读1分钟

有一道面试题如下,给复杂嵌套的数组去重。

const arr = [1,'1',1,true,false,true,'2','1'];
const arr1 = [{a:1},{b:1},{a:1},{b:1},{b:1},{b:1}];
const arr2 = [{a:1,b:2},{b:1},{a:1,b:2},{a:1,b:2},];
const arr3 = [[1,{a:1}],[2],[3],[1,{a:1}]];
const arr4 = [{a:1,b:2},{b:1},{b:2,a:1}];

乍看之下很简单,就好像我们经常练习的题目一样,使用ES6的Set来去重

   [...new Set(arr)]

然而测试一下,发现没有什么用,Set类型存储的是原始值或对象引用,对于里面嵌套的数组/对象根本不会比较。
这里涉及到一个严格比较(strict equality)的概念。

JavaScript 有两种比较方式:严格比较运算符和转换类型比较运算符。对于严格比较运算符(===)来说,仅当两个操作数的类型相同且值相等为 true,而对于被广泛使用的比较运算符(==)来说,会在进行比较之前,将两个操作数转换成相同的类型。对于关系运算符(比如 <=)来说,会先将操作数转为原始值,使它们类型相同,再进行比较运算。

对于这道面试题来说,实现的作用类似与lodash库里面的isEqual,比较的是里面的两个值是否相等。自己写了个简单的实现:

//此处只考虑标准的数组/对象,并且数组/对象的元素只可能是嵌套的基础类型/数组/对象,不考虑null,arguments,类数组等其他类型
//判断是否是对象或数组
function isObject(obj) {
    return typeof obj === 'object' && obj !== null
}
//有人写了个更简单的,我就拿来用了
function isEqual(object,other){
    //对于简单类型,直接严格相等判断
    if(object === other){
        return true;
    }
    // 对于数组/对象类型需要进行比较
    let objType = Object.prototype.toString.call(object);
    let otherType =  Object.prototype.toString.call(object);
    let isSameTag = objType===otherType;
    if(!isSameTag){
        return false;
    }
    if(objType === '[object Array]'){
        if(object.length !== other.length){
            return false;
        }
        for(let i=0,len=object.length;i<len;i++){
            if(!isEqual(object[i],other[i])){
                return false;
            }
        }
        return true;
    }else if(objType === '[object Object]'){
        if(Object.keys(object).length !== Object.keys(other).length){
            return false;
        }
        for(let item in object){
            if(!Object.prototype.hasOwnProperty.call(other,item)){
                return false;
            }
            if(!isEqual(object[item],other[item])){
                return false;
            }
        }
        return true;
    }else{
        return false;
    }
//此处只考虑标准的数组/对象,并且数组/对象的元素只可能是嵌套的简单类型/数组/对象,不考虑null,symbol,类数组等其他类型
}

function unique(arr){
    let res = [];
    for(let item of arr){
        let bool = res.some(e=>{
            return isEqual(e,item)
        })
        if(!bool) res.push(item);
    }
    return res;
}
const arr = [1,'1',1,true,false,true,'2','1'];
const arr1 = [{a:1},{b:1},{a:1},{b:1},{b:1},{b:1}];
const arr2 = [{a:1,b:2},{b:1},{a:1,b:2},{a:1,b:2},];
const arr3 = [[1,{a:1}],[2],[3],[1,{a:1}]];
const arr4 = [{a:1,b:2},{b:1},{b:2,a:1}];

console.log(unique(arr));
console.log(unique(arr1));
console.log(unique(arr2));
console.log(unique(arr3));
console.log(unique(arr4))

意外发现的一个知识点:

let a = Symbol("b");
Object.prototype.toString.call(a);//"[object Symbol]"
Symbol.prototype.valueOf.call(a) == Symbol.prototype.valueOf.call(a)//true

参考文档: