JavaScript数组去重的6个方法
方法一
无需思考,我们可以得到O(n^2)复杂度的解法。定义一个变量数组res保存结果,遍历需要去重的数组,如果该元素已经存在在res中了,则说明是重复的元素,如果没有,则放入res中。
functionunique(a){ varres=[]; for(vari=0,len=a.length;i<len;i++){ varitem=a[i]; for(varj=0,jLen=res.length;j<jLen;j++){ if(res[j]===item) break; } if(j===jLen) res.push(item); } returnres; } vara=[1,1,'1','2',1]; varans=unique(a); console.log(ans);//=>[1,"1","2"]
代码非常简单,那么是否能更简洁些?如果不考虑浏览器兼容,我们可以用ES5提供的Array.prototype.indexOf方法来简化代码。
functionunique(a){ varres=[]; for(vari=0,len=a.length;i<len;i++){ varitem=a[i]; (res.indexOf(item)===-1)&&res.push(item); } returnres; } vara=[1,1,'1','2',1]; varans=unique(a); console.log(ans);//=>[1,"1","2"]
既然用了indexOf,那么不妨再加上filter。
functionunique(a){ varres=a.filter(function(item,index,array){ returnarray.indexOf(item)===index; }); returnres; } vara=[1,1,'1','2',1]; varans=unique(a); console.log(ans);//=>[1,"1","2"]
方法二
法一是将原数组中的元素和结果数组中的元素一一比较,我们可以换个思路,将原数组中重复元素的最后一个元素放入结果数组中。
functionunique(a){ varres=a.filter(function(item,index,array){ returnarray.indexOf(item)===index; }); returnres; } vara=[1,1,'1','2',1]; varans=unique(a); console.log(ans);//=>[1,"1","2"]
虽然复杂度还是O(n^2),但是可以看到结果不同,1出现在了数组最后面,因为结果数组取的是元素最后一次出现的位置。
方法三(sort)
如果笔试面试时只答出了上面这样O(n^2)的方案,可能还不能使面试官满意,下面就来说几种进阶方案。
将数组用sort排序后,理论上相同的元素会被放在相邻的位置,那么比较前后位置的元素就可以了。
functionunique(a){ returna.concat().sort().filter(function(item,pos,ary){ return!pos||item!=ary[pos-1]; }); } vara=[1,1,3,2,1,2,4]; varans=unique(a); console.log(ans);//=>[1,2,3,4]
但是问题又来了,1和"1"会被排在一起,不同的Object会被排在一起,因为它们toString()的结果相同,所以会出现这样的错误:
functionunique(a){ returna.concat().sort().filter(function(item,pos,ary){ return!pos||item!=ary[pos-1]; }); } vara=[1,1,3,2,1,2,4,'1']; varans=unique(a); console.log(ans);//=>[1,2,3,4]
当然你完全可以针对数组中可能出现的不同类型,来写这个比较函数。不过这似乎有点麻烦。
方法四(object)
用JavaScript中的Object对象来当做哈希表,这也是几年前笔试时的解法,跟sort一样,可以去重完全由Number基本类型组成的数组。
functionunique(a){ varseen={}; returna.filter(function(item){ returnseen.hasOwnProperty(item)?false:(seen[item]=true); }); } vara=[1,1,3,2,1,2,4]; varans=unique(a); console.log(ans);//=>[1,3,2,4]
还是和方法三一样的问题,因为Object的key值都是String类型,所以对于1和"1"无法分别,我们可以稍微改进下,将类型也存入key中。
functionunique(a){ varret=[]; varhash={}; for(vari=0,len=a.length;i<len;i++){ varitem=a[i]; varkey=typeof(item)+item; if(hash[key]!==1){ ret.push(item); hash[key]=1; } } returnret; } vara=[1,1,3,2,'4',1,2,4,'1']; varans=unique(a); console.log(ans);//=>[1,3,2,"4",4,"1"]
虽然解决了讨厌的1和"1"的问题,但是还有别的问题!
functionunique(a){ varret=[]; varhash={}; for(vari=0,len=a.length;i<len;i++){ varitem=a[i]; varkey=typeof(item)+item; if(hash[key]!==1){ ret.push(item); hash[key]=1; } } returnret; } vara=[{name:"hanzichi"},{age:30},newString(1),newNumber(1)]; varans=unique(a); console.log(ans);//=>[Object,String]
但是如果数组元素全部是基础类型的Number值,键值对法应该是最高效的!
方法五(ES6)
ES6部署了Set以及Array.from方法,太强大了!如果浏览器支持,完全可以这样:
functionunique(a){ returnArray.from(newSet(a)); } vara=[{name:"hanzichi"},{age:30},newString(1),newNumber(1)]; varans=unique(a); console.log(ans);//=>[Object,Object,String,Number]
_.unique
最后来看看underscore对此的实现方式,underscore将此封装到了_.unique方法中,调用方式为_.unique(array,[isSorted],[iteratee])。其中第一个参数是必须的,是需要去重的数组,第二个参数可选,如果数组有序,则可以传入布尔值true,第三个参数可选,如果需要对数组迭代的结果去重,则可以传入一个迭代函数。而数组元素去重是基于===运算符的。
其实很简单,underscore中的实现方式和上面的方法一相似。
我们来看它的核心代码:
for(vari=0,length=getLength(array);i<length;i++){ varvalue=array[i], //如果指定了迭代函数 //则对数组每一个元素进行迭代 computed=iteratee?iteratee(value,i,array):value; //如果是有序数组,则当前元素只需跟上一个元素对比即可 //用seen变量保存上一个元素 if(isSorted){ //如果i===0,则直接push //否则比较当前元素是否和前一个元素相等 if(!i||seen!==computed)result.push(value); //seen保存当前元素,供下一次对比 seen=computed; }elseif(iteratee){ //如果seen[]中没有computed这个元素值 if(!_.contains(seen,computed)){ seen.push(computed); result.push(value); } }elseif(!_.contains(result,value)){ //如果不用经过迭代函数计算,也就不用seen[]变量了 result.push(value); } }
外面的循环遍历数组元素,对于每个元素,如果数组有序,则和前一个元素比较,如果相同,则已经出现过,不加入到结果数组中,否则则加入。而如果有迭代函数,则计算传入迭代函数后的值,对值去重,调用.contains方法,而该方法的核心就是调用.indexOf方法,和我们上面说的方法一异曲同工。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持毛票票!