无论是在实际开发还是工作面试,数组去重都是一个很常见的问题,今天就来总结一下,以备不时之需。首先声明这篇博客中出现的所有数组去重方法并不是我自己一个人想到的,在撰写这篇博客的期间,参考了网上很多资料,希望能帮到大家。
用户需求:假设有数组 array = [1,5,2,3,4,2,3,1,3,4];要求你要写一个函数 unique(),使得 array.unique() 或 unique(array) 的值为 [1,5,2,3,4],也就是把重复的值都去掉,只保留不重复的值。
1、双重循环方法
双重循环方法是比较简单,而且很容易被想到的方法,具体去重步骤如下:
Array.prototype.unique = function () { //数组原型上添加 unique 方法 const newArray = []; let isRepeat; //编辑元素是否重复 for (let i = 0; i < this.length; i++) { //外层循环遍历数组 isRepeat = false; for (let j = i + 1; j < this.length; j++) { //内层循环遍历查找重复元素 if (this[i] === this[j]) { //如果有相同元素,就跳出当前循环,看下一个元素是否重复 isRepeat = true; break; } } if (!isRepeat) { //如果不是重复元素,就push到新数组中 newArray.push(this[i]); } } return newArray; } console.log([1,1,2,1,3,4,5,4].unique()); //[2,1,3,5,4] 不稳定
缺点:采用上述这种方式返回的新数组,能做到数组去重的效果,但是并不能反映原数组中元素的出现顺序,不稳定。
2、indexOf() 检测元素方法
2.1、方法一:如果索引不是第一个索引,说明是重复值
利用 filter() 方法的过滤功能,indexOf() 方法返回第一个索引值。只将数组中元素第一次出现的返回,之后出现的将被过滤掉
//方法一: Array.prototype.unique = function () { return this.filter((item, index) => { return this.indexOf(item) === index; //数组中每个元素的位置和其第一次出现的下标对应 }) } console.log([1,1,2,1,3,4,5,4].unique()); //[1,2,3,4] 稳定
2.2、方法二:如果新数组中含有改元素,说明是重复值
Array.prototype.unique = function () { const newArray = []; this.forEach(item => { if (newArray.indexOf(item) === -1) { //如果新数组中不含有当前元素,就push newArray.push(item); } }); return newArray; } console.log([1,1,2,1,3,4,5,4].unique()); //[1,2,3,4] 稳定
上述的这两种方法输出的数组副本,不会将原数组中的元素出现位置打乱,业界俗称:稳定。
3、将数组元素先 Sort() 排序
3.1、方法一:先对原数组进行排序,然后再进行元素比较
Array.prototype.unique = function () { const newArray = []; this.sort(); //排序 for (let i = 0; i < this.length; i++) { //对排序数组进行遍历 if (this[i] !== this[i + 1]) { //当前元素和后面的元素不相同就push newArray.push(this[i]); } } return newArray; }
3.2、和新数组的最后一个元素进行比较,如果相同就说明是重复元素。
Array.prototype.unique = function () { const newArray = []; this.sort(); //排序 for (let i = 0; i < this.length; i++) { if (this[i] !== newArray[newArray.length - 1]) { //和新数组的最后一个元素比较 newArray.push(this[i]); } } return newArray; }
上述的这两种方法由于都用到 sort() 方法, 会打乱原有数组的顺序,所以:不稳定
4、检测新数组中元素 includes()
Array.prototype.unique = function () { const newArray = []; this.forEach(item => { if (!newArray.includes(item)) { //如果新数组中含有当前元素,就说明是重复值 newArray.push(item); } }); return newArray; } console.log([1,1,2,1,3,4,5,4].unique()); //[1,2,3,4] 稳定
5、配合 sort() 使用 reduce() 去重
Array.prototype.unique = function () { return this.sort().reduce((init, current) => { if(init.length === 0 || init[init.length - 1] !== current){ //类似于3.2方法 init.push(current); } return init; }, []); } //只要涉及到 sort 方法,就是不稳定的
6、Map
JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。为了解决这个问题,ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是 “键” 的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了 “字符串—值” 的对应,Map 结构提供了 “值—值” 的对应,是一种更完善的 Hash 结构实现。
Map 对象保存键值对,并且能够记住键的原始插入顺序。和 Object 类型不同(key 只能是字符串),在 Map 对象中任何值(对象或者原始值) 都可以作为一个键或一个值。详请移步:mdn 文档
使用 Map 对象实现数组去重的主要思路:创建一个空 Map,遍历原始数组,把数组的每一个元素作为 key 存到 Map 对象中,因为 Map 中不会出现相同的 key 值,所以最终得到的 Map 中的所有 key 值就是去重后的结果。
function unique(arr) { let hashMap = new Map(); //创建一个新的Map对象 let result = new Array(); // 数组用于返回结果 for (let i = 0; i < arr.length; i++) { if(hashMap.has(arr[i])) { // 判断 hashMap 中是否已有该 key 值 hashMap.set(arr[i], true); // 后面的true 代表该 key 值在原始数组中重复了,false反之 } else { // 如果 hashMap 中没有该 key 值,添加 hashMap.set(arr[i], false); result.push(arr[i]); } } return result; } console.log(unique([1, 1, 1, 2, 3, 3, 4, 5, 5, "a", "b", "a"])); // [ 1, 2, 3, 4, 5, 'a', 'b' ]
7、Set
Set 对象允许你存储任何类型的 唯一值,无论是原始值或者是对象引用,该方法天生就适合数组去重。mdn 文档
不成,NaN 和 undefined 都可以被存储在 Set 中, NaN 之间被视为相同的值(NaN被认为是相同的,尽管 NaN !== NaN)
Array.prototype.unique = function () { const set = new Set(this); //new 一个 Set 对象,将数组作为参数传递进去 return Array.from(set); //自动实现去重,Array.from() 将其转化为数组 } //可以通过 set.add(args) 添加元素 console.log([1,1,2,1,3,4,5,4].unique()); //[1,2,3,4] 稳定
Array.prototype.unique = function () { return [...new Set(this)]; //简化上述步骤 } console.log([1,1,2,1,3,4,5,4].unique()); //[1,2,3,4] 稳定