在以前有次面试的时候,当场敲代码就出了数组去重。当时正好看到 Set ,直接转 Set 再转 Array出了结果。然后面试官又问了问 Set 背后是做了什么工作,能不能手动循环做一下。惭愧的是经提醒,才写出了拿一个辅助Array去记录不重复的数据。方法单一,也没有涉及特殊的基本类型值和复杂的引用者。 这里做一个重新的梳理,数组去重分两步:
- 循环遍历数组,这是外招
- 比较值是否相等,这是内招
内招:严格等价/arr#indexOf
这第一式:双层循环+严格等价
//外招:双层循环 + 内招:严格等价
function deduplicate(arr){
//记录唯一数据
var help_arr = [];
for(var i=0;i<arr.length;i++){
for(var j=0;j<help_arr.length;j++){
if(arr[i]===help_arr[j]){
break;
}
}
//能走到这里说明没有满足严格等价
if(j===help_arr.length){
help_arr.push(arr[i]);
}
}
return help_arr;
}
复制代码
这第二式:单层循环+arr#indexOf
function deduplicate(arr){
//记录唯一数据
var help_arr = [];
for(var i=0;i<arr.length;i++){
//如果没有找到
if(!~help_arr.indexOf(arr[i])){
help_arr.push(arr[i]);
}
}
return help_arr;
}
复制代码
这第x式:arr#filter+arr#indexOf
function deduplicate(arr){
return arr.filter((element,index,array)=>array.indexOf(element)===index);
}
复制代码
这一式使用了 arr#filter 大大简化了代码。
其实前面这三式内招都是一样的,arr#indexOf 底层还是用了严格等价。 但是对于有些值是无法做到去重的:
- NaN:因为 NaN === NaN 的结果是 false
- object/array/regx:因为它们都是对象,引用地址不同
内招:Number.isNaN(..)/Object.is(..,..)
function deduplicate(arr){
let countNaN = 0;
return arr.filter((element,index,array)=>{
if(countNaN===0&&Number.isNaN(element)){
countNaN++;
return true;
}else{
return array.indexOf(element)===index;
}
});
}
复制代码
使用 Number.isNaN或者Object.is对值NaN做特殊处理。
内招:Set
function deduplicate(arr){
return [...new Set(arr)];
}
复制代码
嗯...开挂一样,这也是我当时面试的时候写出来最快的...使用数据结构Set达到的效果也是能处理NaN。
内招:Number.isNaN(..)/Object.is(..,..)+JSON.stringify+JSON.parse
JSON.stringify + JSON.parse 能帮助部分对象去重,说是部分是因为有JSON字符串非安全值:undefined、function、(ES6+)symbol、和带有循环引用的 object,如果对象包含这些值特殊情况就会增多。
function deduplicate(arr){
//引用值做JSON字符串化
const objTransArr = arr.map(e=>{
if(typeof e === "object" && e){
return JSON.stringify(e);
}else{
return e;
}
})
let countNaN = 0;
return arr.filter((element,index,array)=>{
if(countNaN===0&&Number.isNaN(element)){
countNaN++;
return true;
}else if(typeof element === "object" && element){
return objTransArr.indexOf(JSON.stringify(element))===index;
}else{
return array.indexOf(element)===index;
}
});
}
复制代码
这里因为只想对数组内的引用数据做JSON字符串化,所以引入了辅助数组 objTransAff,它是能解决 NaN 和 object/array/regx 的去重,只要引用数据类型内不涉及JSON非法值。
内招:Object键值对+JSON.stringify
function deduplicate(arr){
let obj = {};
return arr.filter((v,i,array)=>{
const k = typeof v + JSON.stringify(v);
return obj.hasOwnProperty(k)?false:(obj[k]=true);
})
}
复制代码
这一招其实挺妙的,通过 typeof v + JSON.stringify(v) 转化为对象的key,这里也解决了NaN和引用数据类型的问题。
小结
到最后其实也没有拿出一个沉重的解决所有去重的方案,但是走到这一步个人认为已经是足够了,因为实际情况下的数组去重并不需要走这么远,现在列出各招数的缺陷,具体情况具体应用就行。
严格等价/arr#indexOf | 缺陷:NaN、引用数据 |
---|---|
严格等价+Number.isNaN(..)/Object.is(..,..) | 缺陷:引用数据 |
Set | 缺陷:引用数据 |
Number.isNaN(..)/Object.is(..,..)+JSON.stringify+JSON.parse | 缺陷:含JSON非法值的引用数据 |
Object键值对+JSON.stringify | 缺陷:含JSON非法值的引用数据 |
按能力从小到大的推荐方法是:
- 严格等价/arr#indexOf 第x式:filter
- Set
- Object键值对+JSON.stringify
参考链接: JavaScript专题之数组去重:这篇写得超级好!
/*封面图是盗来的,原作者在这:https://kaminario.com/company/blog/data-dedupe-all-flash-arrays/ */