数组去重在平常开发中的使用频率仍是较高的,也是网上随便一抓一大把的话题,因此,我写这篇文章目的在于概括和总结,既然不少人都在提的数组去重,本身到底了解多少呢。又或者是若是本身在开发中遇到了去重的需求,本身能想到更好的解决方案吗。javascript
此次咱们来理一理怎么作数组去重才能作得最合适,既要考虑兼容性,也要考虑性能和代码的优雅。java
个人学习路径是模仿冴羽(github: mqyqingfeng)的学习方式,感谢像冴羽这样优秀的人在前面领跑,我不想光看不作,因此多实践多输出,但愿将来能走出我本身的路。git
function unique(origin) {
var result = [];
for(var i = 0; i < origin.length; i++) {
var arrayItem = origin[i];
for(var j= 0; j< result.length; j++) {
var resultItem = result[j];
// 若是在结果数组循环中找到了该元素,则跳出循环,进入下一个源数组元素的判断
if(resultItem === arrayItem) {
break;
}
}
// 若是把结果数组循环完都没有找到该元素,就将该元素压入结果数组中
if(j === result.length) {
result.push(arrayItem);
}
}
return result;
}
var array = ['a', 'b', 'c', '1', 0, 'c', 1, '', 1, 0];
console.log(unique(array)); // ["a", "b", "c", "1", 0, 1, ""]
复制代码
以上代码是最简单实现数组去重的方式,优势在于兼容性极好,缺点就是两次 for 循环,时间复杂度为 O(n^2),性能较差。github
数组中的 indexOf 属性是 ES5 的规范,只有 IE8 及更早版本不支持该方法。相对来讲,若是你不须要兼容 IE8 的话,尽可能用 indexOf 来判断一个元素是否在数组中。算法
function unique(origin){
var result = [];
for(var i = 0; i< origin.length; i++) {
var item = origin[i];
if(result.indexOf(item) === -1) {
result.push(item);
}
}
return result;
}
复制代码
数组的 filter() 方法建立一个新的数组,新数组中的元素是经过检查指定数组中符合条件的全部元素。数组
filter 的回调有三个参数,其中第三个参数是当前元素属于的数组对象,这样咱们能够继续利用 indexOf 属性啦。数据结构
function unique(origin) {
var result = origin.filter(function (item, index, array){
// 获取元素在源数组的位置,只返回那些索引等于当前元素索引的值。
return array.indexOf(item) === index;
});
return result;
}
复制代码
filter 兼容到 IE9, 这种方法没有 for 循环,主要利用了 filter 和 indexOf 属性,因此代码相对比较优雅。性能
function unique(origin) {
var result = [];
var hashTable = {};
for(var i = 0; i< origin.length; i++) {
// 若是键对应的值,为真,意味着对象的键中已经有重复的键了。
if(!hashTable[origin[i]]) {
// 将元素做为对象的键,默认键对应的值为 true,
hashTable[origin[i]] = true;
// 若是对象中没有这个键,则将这个元素放入结果数组中去。
result.push(origin[i]);
}
}
return result;
}
复制代码
这种方案的事件复杂度为 O(n), 可是对象的键,默认是字符串类型,这意味着什么呢,数字 1 和 字符串 '1',在键中是相等的,因此,上面这种方法不适合字符串和数字混合的去重。学习
因此咱们将元素的类型也放入对象的键中:优化
function unique(origin) {
var result = [];
var hashTable = {};
for(var i = 0; i< origin.length; i++) {
var current = origin[i];
// 字符串拼接元素的类型和元素
var key = typeof(current) + current;
if(!hashTable[key]) {
hashTable[key] = true;
result.push(current);
}
}
return result;
}
复制代码
function unique(origin) {
return origin.concat.sort().filter(function(item, index, array) {
// !index 表示第 0 个元素应该被返回。
return !index || item !== origin[index-1]
})
}
function unique(array) {
array.sort(); // 排序字符串
array.sort(function(a, b) {
return a-b; // 排序数字
})
for(let i=0; i<array.length; i++) {
if(array[i] === array[i+1]) {
array.splice(i, 1);
i--; // 应该将前一个数删除,而不是删除后一个数。是由于元素被删除以后,后面元素的索引会迁移,因此要 i--;
}
}
return array;
}
复制代码
sort 方法的优势在于利用了排序,返回后一个和前一个不相等的元素。比较简洁和直观。缺点在于改变了元素的原本的排序位置。
ES6 提供了新的数据结构 Set,它相似于数组,可是成员的值都是惟一的,没有重复的值。向 Set 加入值的时候,不会发生类型转变,因此 5 和 '5' 是两个不一样的值。Set内部判断两个值是否相同,用的是相似于 "==="的算法,可是区别是,在set内部认为NaN 等于 NaN ;
Set 能够转换为数组,因此很容易实现去重
function unique(origin) {
return Array.from(new Set(origin));
}
复制代码
ES6 新增了 Map 数据结果,经过 has 和 set 方法就能很方便的对前面的 object key value 方案进行优化。
function unique(origin){
const map = new Map()
return origin.filter((item) => !map.has(item) && map.set(item, true))
}
复制代码
一些常见的数据类型是 ===
和 indexOf
是没法检测的,举个例子:
console.log({} === {}) // false;
console.log(NaN === NaN) // false;
console.log(/a/ === /a/); // false;
console.log(1 === new String('1')) // false;
var arr = [NaN];
console.log(arr.indexOf(NaN)); // -1
复制代码
因此在判断的时候,若是数据里有 NaN 和对象时要避免使用 indexOf
和 ===
;
前面 Set 那里说过了,因此 Set 方法是能够去重 NaN的。
数据去重在网上已经看烦了,但仍是想专门写一篇文章来实践和总结,能在工做中多几个思路也是极好的。感谢那些热爱分享和喜欢输出的人。