数组多种方案去重--以及优缺点

1,391 阅读9分钟

SetMap,其应用场景,很多时候会用在数组去重和数据存储, Set 是一种叫做集合的数据结构,Map 是一种叫做字典的数据结构。

本文在gitthub做了收录:github.com/Michael-lzg…

数组去重12种方案(经典面试必问)

先总结一下数组的方法:

  • pop
  • push
  • shift
  • unshift
  • slice
  • splice
  • sort
  • reverse
  • concat
  • join
  • indexOf
  • lastIndexOf
  • map
  • forEach

还有其他可以处理数组的几个方法~

- includes:方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。

- find:返回第一次找到的那一项

- some:返回一个布尔值,只要一个是true,就返回true

- every:返回一个布尔值,需要每一项都是true,才返回true

- filter:返回一个过滤后的新数组;如果返回true就留下,false就过滤掉

- reduce:收敛

下面我们进入正题~(希望能对你有帮助~小编有点皮!!哈哈哈哈哈哈)

方法一:set :不是一种数据类型,是一种数据结构;成员唯一

let arr = [12,1,12,3,1,88,66,9,66];
function unique(ary) {
        let s  = new Set(ary);
        // Array.from : 将set数据结构转成真正的数组;
        return  Array.from(s)
    }
    unique(arr);
复制代码

方法二:对象属性名不能重复

let arr = [12,1,12,3,1,88,66,9,66];
function unique(ary) {
        let obj = {};
        for(let i=0;i<ary.length;i++){
            let cur = ary[i];
            if(obj[cur]){
                //ary.splice(i,1);// 导致数组塌陷
                ary[i]=ary[ary.length-1];
                ary.length--;// 删除最后一项
                i--;
                continue;
            }
            obj[cur]=cur;// 给obj新增键值对;属性名和属性值是一样的
        }
    }
    unique(arr);
复制代码

方法三:indexOf

let arr = [12,1,12,3,1,88,66,9,66];
 function unique(ary) {
        let newAry = [];
        for(let i=0;i<ary.length;i++){
            let  cur = ary[i];
            if(newAry.indexOf(cur)===-1){
                newAry.push(cur);
            }
        }
        return newAry;
    }
    unique(arr)
复制代码

方法四:sort

let arr = [12,1,12,3,1,88,66,9,66];
function unique(ary) {
       let a = ary.sort(function (a,b) {
           return a-b;
       });
       for(let i=0;i<a.length;i++){
           if(a[i]===a[i+1]){
               a.splice(i+1,1);
               i--;
           }
       }
       return a;
   }
   unique(arr)
复制代码

方法五:includes :包含;如果数组包含那一项,返回true;不包含返回false;

let arr = [12,1,12,3,1,88,66,9,66];
function unique(ary) {
        let newAry = [];
        let len = ary.length;
        for(let i=0;i<len;i++){
            let cur = ary[i];
            if(!newAry.includes(cur)){
                newAry.push(cur);
            }
        }
        return newAry;
    }
    console.log(unique(arr));
复制代码

方法六:hasOwnProperty : 检测属性名是否是对象的一个私有属性;返回一个布尔值;

let arr = [12,1,12,3,1,88,66,9,66];
function unique(ary) {
        let obj = {};
        return ary.filter(function (item,index,a) {
            // item : 数组每一个成员
            // index: 成员对应的索引
            // a : 整个数组
            // hasOwnProperty来校验的该属性是否出现过;
           return  obj.hasOwnProperty(typeof item+item)?false:obj[typeof item+item]=true;
           if(obj.hasOwnProperty(typeof item+item)){
               return false
           }else{
               obj[typeof item+item]=true;
               return true;
           }
        })
    }
    console.log(unique(arr))
复制代码

方法七:filter+indexOf

let arr = [12,1,12,3,1,88,66,9,66];
    function unique(ary) {
        return ary.filter(function (item,index,a) {
            return ary.indexOf(item)===index;
        })
    }
    console.log(unique(arr));

复制代码

方法八:splice

let arr = [12,1,12,3,1,88,66,9,66];
 function unique(ary) {
        for(let i=0;i<ary.length;i++){
            for(j=i+1;j<ary.length;j++){
                if(ary[i]===ary[j]){
                    ary.splice(j,1);
                    j--;
                }
            }
        }
        return ary;
    }
    unique(arr);
复制代码

方法九:递归

let arr = [12,1,12,3,1,88,66,9,66];
function unique(ary) {
        let  len= ary.length;
        ary = ary.sort(function (a,b) {
            return a-b;
        });
        function loop(index) {
            if(index>=1){
                if(ary[index]===ary[index-1]){
                    ary.splice(index,1);
                }
                loop(index-1)
            }
        }
        loop(len-1);
        return ary;
    }
    console.log(unique(arr));
复制代码

方法十:Map :利用了Map数据结构存值的特点;

let arr = [12,1,12,3,1,88,66,9,66];
function unique(ary) {
        let newAry =[];
        let map = new Map();
        for(let i=0;i<ary.length;i++){
            if(!map.has(ary[i])){
                map.set(ary[i],true);
                newAry.push(ary[i]);
            }
        }
    }
    unique(arr);
复制代码

方法十一:reduce

let arr = [12,1,12,3,1,88,66,9,66];
function unique(ary) {
        // reduce : 第一个是函数,第二个参数会传给第一次回调的prev;
        return ary.reduce((prev,next)=>{
            // 该函数返回值是下一次执行的prev;
            return prev.includes(next)?prev:[...prev,next];
        },[])
    }
    console.log(unique(arr));
复制代码

方法十二:类似于方法一的set,用了剩余运算符...

let  arr = [12,1,12,3,1,88,66,9,66];
    let a = [...new Set(arr)];
    console.log(a);

\

Set

Set 本身是一个构造函数,用来生成 Set 数据结构。Set 函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。Set 对象允许你存储任何类型的值,无论是原始值或者是对象引用。它类似于数组,但是成员的值都是唯一的,没有重复的值。

const s = new Set()
[2, 3, 5, 4, 5, 2, 2].forEach((x) => s.add(x))
for (let i of s) {
  console.log(i)
}
// 2 3 5 4
复制代码

Set 中的特殊值

Set 对象存储的值总是唯一的,所以需要判断两个值是否恒等。有几个特殊值需要特殊对待:

  • +0 与 -0 在存储判断唯一性的时候是恒等的,所以不重复
  • undefinedundefined 是恒等的,所以不重复
  • NaNNaN 是不恒等的,但是在 Set 中认为 NaNNaN 相等,所有只能存在一个,不重复。

Set 的属性:

  • size:返回集合所包含元素的数量
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5])
items.size // 5
复制代码

Set 实例对象的方法

  • add(value):添加某个值,返回 Set 结构本身(可以链式调用)。
  • delete(value):删除某个值,删除成功返回 true,否则返回 false
  • has(value):返回一个布尔值,表示该值是否为 Set 的成员。
  • clear():清除所有成员,没有返回值。
s.add(1).add(2).add(2)
// 注意2被加入了两次

s.size // 2

s.has(1) // true
s.has(2) // true
s.has(3) // false

s.delete(2)
s.has(2) // false
复制代码

遍历方法

  • keys():返回键名的遍历器。
  • values():返回键值的遍历器。
  • entries():返回键值对的遍历器。
  • forEach():使用回调函数遍历每个成员。

由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以 keys 方法和 values 方法的行为完全一致。

let set = new Set(['red', 'green', 'blue'])

for (let item of set.keys()) {
  console.log(item)
}
// red
// green
// blue

for (let item of set.values()) {
  console.log(item)
}
// red
// green
// blue

for (let item of set.entries()) {
  console.log(item)
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
复制代码

Array 和 Set 对比

  • ArrayindexOf 方法比 Sethas 方法效率低下
  • Set 不含有重复值(可以利用这个特性实现对一个数组的去重)
  • Set 通过 delete 方法删除某个值,而 Array 只能通过 splice。两者的使用方便程度前者更优
  • Array 的很多新方法 mapfiltersomeevery 等是 Set 没有的(但是通过两者可以互相转换来使用)

Set 的应用

1、Array.from 方法可以将 Set 结构转为数组。

const items = new Set([1, 2, 3, 4, 5])
const array = Array.from(items)
复制代码

2、数组去重

// 去除数组的重复成员
;[...new Set(array)]

Array.from(new Set(array))
复制代码

3、数组的 mapfilter 方法也可以间接用于 Set

let set = new Set([1, 2, 3])
set = new Set([...set].map((x) => x * 2))
// 返回Set结构:{2, 4, 6}

let set = new Set([1, 2, 3, 4, 5])
set = new Set([...set].filter((x) => x % 2 == 0))
// 返回Set结构:{2, 4}
复制代码

4、实现并集 (Union)、交集 (Intersect) 和差集

let a = new Set([1, 2, 3])
let b = new Set([4, 3, 2])

// 并集
let union = new Set([...a, ...b])
// Set {1, 2, 3, 4}

// 交集
let intersect = new Set([...a].filter((x) => b.has(x)))
// set {2, 3}

// 差集
let difference = new Set([...a].filter((x) => !b.has(x)))
// Set {1}
复制代码

weakSet

WeakSet 结构与 Set 类似,也是不重复的值的集合。

  • 成员都是数组和类似数组的对象,若调用 add() 方法时传入了非数组和类似数组的对象的参数,就会抛出错误。
const b = [1, 2, [1, 2]]
new WeakSet(b) // Uncaught TypeError: Invalid value used in weak set
复制代码
  • 成员都是弱引用,可以被垃圾回收机制回收,可以用来保存 DOM 节点,不容易造成内存泄漏。
  • WeakSet 不可迭代,因此不能被用在 for-of 等循环中。
  • WeakSet 没有 size 属性。

Map

Map 中存储的是 key-value 形式的键值对, 其中的 keyvalue 可以是任何类型的, 即对象也可以作为 keyMap 的出现,就是让各种类型的值都可以当作键。Map 提供的是 “值-值”的对应。

Map 和 Object 的区别

  1. Object 对象有原型, 也就是说他有默认的 key 值在对象上面, 除非我们使用 Object.create(null)创建一个没有原型的对象;
  2. Object 对象中, 只能把 StringSymbol 作为 key 值, 但是在 Map 中,key 值可以是任何基本类型(String, Number, Boolean, undefined, NaN….),或者对象(Map, Set, Object, Function , Symbol , null….);
  3. 通过 Map 中的 size 属性, 可以很方便地获取到 Map 长度, 要获取 Object 的长度, 你只能手动计算

Map 的属性

  • size: 返回集合所包含元素的数量
const map = new Map()
map.set('foo', ture)
map.set('bar', false)
map.size // 2
复制代码

Map 对象的方法

  • set(key, val): 向 Map 中添加新元素
  • get(key): 通过键值查找特定的数值并返回
  • has(key): 判断 Map 对象中是否有 Key 所对应的值,有返回 true,否则返回 false
  • delete(key): 通过键值从 Map 中移除对应的数据
  • clear(): 将这个 Map 中的所有元素删除
const m = new Map()
const o = { p: 'Hello World' }

m.set(o, 'content')
m.get(o) // "content"

m.has(o) // true
m.delete(o) // true
m.has(o) // false
复制代码

遍历方法

  • keys():返回键名的遍历器
  • values():返回键值的遍历器
  • entries():返回键值对的遍历器
  • forEach():使用回调函数遍历每个成员
const map = new Map([
  ['a', 1],
  ['b', 2],
])

for (let key of map.keys()) {
  console.log(key)
}
// "a"
// "b"

for (let value of map.values()) {
  console.log(value)
}
// 1
// 2

for (let item of map.entries()) {
  console.log(item)
}
// ["a", 1]
// ["b", 2]

// 或者
for (let [key, value] of map.entries()) {
  console.log(key, value)
}
// "a" 1
// "b" 2

// for...of...遍历map等同于使用map.entries()

for (let [key, value] of map) {
  console.log(key, value)
}
// "a" 1
// "b" 2
复制代码

数据类型转化

Map 转为数组

let map = new Map()
let arr = [...map]
复制代码

数组转为 Map

Map: map = new Map(arr)
复制代码

Map 转为对象

let obj = {}
for (let [k, v] of map) {
  obj[k] = v
}
复制代码

对象转为 Map

for( let k of Object.keys(obj)){
  map.set(k,obj[k])
}
复制代码

Map的应用

在一些 Admin 项目中我们通常都对个人信息进行展示,比如将如下信息展示到页面上。传统方法如下。

<div class="info-item">
  <span>姓名</span>
  <span>{{info.name}}</span>
</div>
<div class="info-item">
  <span>年龄</span>
  <span>{{info.age}}</span>
</div>
<div class="info-item">
  <span>性别</span>
  <span>{{info.sex}}</span>
</div>
<div class="info-item">
  <span>手机号</span>
  <span>{{info.phone}}</span>
</div>
<div class="info-item">
  <span>家庭住址</span>
  <span>{{info.address}}</span>
</div>
<div class="info-item">
  <span>家庭住址</span>
  <span>{{info.duty}}</span>
</div>
复制代码

js 代码

mounted() {
  this.info = {
    name: 'jack',
    sex: '男',
    age: '28',
    phone: '13888888888',
    address: '广东省广州市',
    duty: '总经理'
  }
}
复制代码

我们通过 Map 来改造,将我们需要显示的 label 和 value 存到我们的 Map 后渲染到页面,这样减少了大量的html代码

<template>
  <div id="app">
    <div class="info-item" v-for="[label, value] in infoMap" :key="value">
      <span>{{label}}</span>
      <span>{{value}}</span>
    </div>
  </div>
</template>
复制代码

js 代码

data: () => ({
  info: {},
  infoMap: {}
}),
mounted () {
  this.info = {
    name: 'jack',
    sex: '男',
    age: '28',
    phone: '13888888888',
    address: '广东省广州市',
    duty: '总经理'
  }
  const mapKeys = ['姓名', '性别', '年龄', '电话', '家庭地址', '身份']
  const result = new Map()
  let i = 0
  for (const key in this.info) {
    result.set(mapKeys[i], this.info[key])
    i++
  }
  this.infoMap = result
}
复制代码

WeakMap

WeakMap 结构与 Map 结构类似,也是用于生成键值对的集合。

  • 只接受对象作为键名(null 除外),不接受其他类型的值作为键名
  • 键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的
  • 不能遍历,方法有 getsethasdelete

总结

Set

  • 是一种叫做集合的数据结构(ES6新增的)
  • 成员唯一、无序且不重复
  • [value, value],键值与键名是一致的(或者说只有键值,没有键名)
  • 允许储存任何类型的唯一值,无论是原始值或者是对象引用
  • 可以遍历,方法有:adddeletehasclear

WeakSet

  • 成员都是对象
  • 成员都是弱引用,可以被垃圾回收机制回收,可以用来保存 DOM 节点,不容易造成内存泄漏
  • 不能遍历,方法有 adddeletehas

Map

  • 是一种类似于字典的数据结构,本质上是键值对的集合
  • 可以遍历,可以跟各种数据格式转换
  • 操作方法有:setgethasdeleteclear

WeakMap

  • 只接受对象作为键名(null 除外),不接受其他类型的值作为键名
  • 键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的
  • 不能遍历,方法有 getsethasdelete

双重for循环去重

优点: 

          兼容性好 

          '1'和1可以区分

缺点:

          对象不可以去重 因为对象不可以用来比较   

          NaN不可以去重


Array.prototype.unique = function () { 
    var newArr = [], 
        isRepeat,                
        len = this.length;            
    for (var i = 0; i < len; i++) {                
        isRepeat = false;                
        for (var j = 0; j < newArr.length; j++) {                    
            if (this[i] === newArr[j]) {                        
                isRepeat = true;                        
                break;                    
            }                
        }                
        if (!isRepeat) {                    
            newArr.push(this[i]);                
        }            
    }            
    return newArr;        
}复制代码

indexOf去重

优点: 

          '1'和1可以区分

缺点: 

           对象不可以去重 因为对象不可以用来比较

           NaN不可以去重

Array.prototype.unique = function() {            
    var res = [],                
    len = this.length;            
    for (var i = 0; i < len; i++) {     
        var current = this[i];                
        if (res.indexOf(current) === -1) {                    
            res.push(current);                
        }            
    }            
    return res;        
}复制代码

相邻元素去重

优点:

          '1'和1可以区分

缺点:

           对象不可以去重 因为对象不可以用来比较

           NaN不可以去重

           不能识别undefined

Array.prototype.unique = function () {            
    var newArr = [],                
    len = this.length;            
    this.sort(function(a, b) { // 改变原数组       
         return a-b;         
    });            
    for (var i = 0; i < len; i++) {                
        if (this[i] !== this[i + 1]) {           
             newArr.push(this[i]);    
        }            
    }            
    return newArr;        
}复制代码

filter

优点:

          '1'和1可以区分  

 缺点:        

           对象不可以去重 因为对象不可以用来比较                

           NaN识别不了


Array.prototype.unique = function() {            
    var res = this.filter(function(item, index, array){                
        return array.indexOf(item) === index;            
    })            
    return res;        
}复制代码

includes

优点: 

          '1'和1可以区分 

          NaN可以去重

缺点:    

          对象不可以去重 因为对象不可以用来比较        \


Array.prototype.unique = function () {            
    var newArr = [];            
    this.forEach(item => {                
        if (!newArr.includes(item)) {                    
            newArr.push(item);                
        }            
    });            
    return newArr;        
}复制代码

reduce

优点:

          可以区分'1'和1

缺点:

          对象不可以区分 

          NaN不可以区分

Array.prototype.unique = function () {            
    return this.sort().reduce((init, current) => {                
        if (init.length === 0 || init[init.length - 1] !== current) {                    
            init.push(current);                
        }                
        return init;            
    }, []);        
}复制代码

对象键值对

基本思路:利用了对象的key不可以重复的特性来进行去重。

优点: 

          NaN可以去重 

          正则可以去重

 缺点:

           '1'和1不能区分 

            对象不可以去重 因为对象作为 key 会变成 [object Object]

Array.prototype.unique = function () {            
    var obj = {},                
    arr = [],                
    len = this.length;            
    for (var i = 0; i < len; i++) {                
        if (!obj[this[i]]) {                    
            obj[this[i]] = 'abc'; // 不能是 = this[i] 万一数组去重0                    
            arr.push(this[i]);                
        }            
    }            
    return arr;        
}

// 改变版本1   
Array.prototype.unique = function () {            
    const newArray = [];            
    const tmp = {};            
    for (let i = 0; i < this.length; i++) {                
        if (!tmp[typeof this[i] + this[i]]) {                    
            tmp[typeof this[i] + this[i]] = 1;                    
            newArray.push(this[i]);                
        }            
    }            
    return newArray;        
}

// 改进版本2  
Array.prototype.unique = function () {            
    const newArray = [];            
    const tmp = {};            
    for (let i = 0; i < this.length; i++) {                
        // 使用JSON.stringify()进行序列化                
        if (!tmp[typeof this[i] + JSON.stringify(this[i])]) {                    
            // 将对象序列化之后作为key来使用                    
            tmp[typeof this[i] + JSON.stringify(this[i])] = 1;                    
            newArray.push(this[i]);                
        }            
    }            
    return newArray;        
}
复制代码

Map

原理: key对应value,key和value唯一 ,任何值都可以当属性

优点:

        NaN可以去重

缺点:

        对象不可以去重

Array.prototype.unique = function () {            
    var newArr = [],            
        tmp = new Map(),
        len = this.length;            
    for (var i = 0; i < len; i++) {                
        if (!tmp.get(this[i])) {                    
            tmp.set(this[i], 1);                    
            newArr.push(this[i]);                
        }            
    }            
    return newArr;        
}

//简化版
Array.prototype.unique = function() {            
    var map = new Map()            
    return arr.filter((a) => !map.has(a) && map.set(a, 1))        
}复制代码

Set

 原理: 它类似于数组,但是成员的值都是唯一的,没有重复的值

优点: 

          NaN去重

缺点: 

         对象不可以去重


 Array.prototype.unique = function () {            
    return [...new Set(this)];        
}


原文章连接:juejin.cn/post/684490… 作者原文链接:juejin.cn/post/684490… 作者原文链接:juejin.cn/post/684490…