数组去重

作者: 江君同学 | 来源:发表于2017-05-18 10:24 被阅读216次

    又在不经意之间看到了一个面试题,应该算是比较久远的了

    数组去重

    为了培养 JavaScript 的语感,今天就把能想到的方法都梳理一遍

    没有编程基础的人的思维

    比如说现在有一个数组

    var a = [1,2,2,3,3,4,4,5]
    

    我猜按照正常的思维逻辑,肯定会这么想

    • a 中的第一项拿出来放到空数组 b
    • a 中的第二项拿出来和数组 b 的所有成员比一下,如果有重复的就跳过,如果没有重复的就把这一项加载到 b 里去
    • a 中的第三项拿出来和数组 b 的所有成员比一下,如果有重复的就跳过,如果没有重复的就把这一项加载到 b 里去
    • 。。。
    • 一直到最后一项

    好了,接下来我们来将思路码出来

    var a = [1,2,2,3,3,4,4,5]
    var b = [] // 声明一个空数组
    
    // 以下为伪代码
    if(a[0] !== b[0]) { // 一开始 b 并没有成员,所以这条语句的意思就是把 a 的第一项添加到 b 上
        b.push(a[0])
    }
    
    if(a[1] !== b[0])  {
        b.push(a[1])
    }
    
    if(a[2] !== b[0] && a[2] !== b[1]){
        b.push(a[2])
    }
    
    ....
    

    能感受到一点规律么 if() {} 这一块可以用 for 循环搞定 而 if 后面的 () 里的内容可以用另一个 for 循环搞定

    赶紧把想法写出来

    var a = [1,2,2,3,3,4,4,5]
    var b = [] // 声明一个空数组
    
    for(var i = 0; i < a.length; i++) {
        var item = a[i]
        for(var j = 0; j < b.length; j++) {
            if(b[j] !== item) {
                // 天哪,写不下去了,我们并不能够用循环模拟出一个 && 的作用啊
            }
        }
    }
    

    代码的思路断了,不过这并不能难倒我,我们可以计数嘛

    先初始化一个 k,只要 b[j] !== itemk 就加 1,最后统计一下每过一轮 for(var i...) 的循环后 k 的值就能得知到底有没有重复的了,好聪明

    var a = [1,2,2,3,3,4,4,4,4,4,4,5]
    var b = [] // 声明一个空数组
    
    for(var i = 0; i < a.length; i++) {
        var item = a[i]
        var k = 0
        for(var j = 0; j < b.length; j++) {
            if(b[j] !== item) {
                k = k + 1
            }
        }
        if(k === b.length || i === 0) { // 加 || 运算符后面这句话是因为当最外层的循环 i = 0 的时候,内层的循环并不会执行
            b.push(item)
            
        }
    }
    console.log(b) // [1, 2, 3, 4, 5]
    

    好了,这就是数组去重的方法,总结一下思路

    • 初始化一个空数组 b
    • 逐个将待处理数组 a 的成员与 b 数组中的每一个成员比较
    • 如果都不相等,才将这个 a 数组里的成员放到 b 数组

    以上就是符合没有学过编程的人但想要解答这道题的基本思路,不知道有没有认同的

    但是我们是程序员啊,不能就这么算了,看看能不能把这段代码再优化优化

    我觉得计数太麻烦了,能不能不计数,想一想

    这时候,我看到了上面总结的思路的最后一句话

    如果都不相等,才将这个 a 数组里的成员放到 b 数组

    换言之

    只要有一个相等,数组 b 就不会被添加新成员

    有了思路就马上实施

    我们把

    if(b[j] !== item) {...}
    

    改成

    if(b[j] === item) {...}
    

    如果数组 b 的成员只要有一个和 item 相等,那么就不会 b.push(item)

    var a = [1,2,2,3,3,4,4,4,4,4,4,5]
    var b = [] // 声明一个空数组
    
    for(var i = 0; i < a.length; i++) {
        var item = a[i]
        for(var j = 0; j < b.length; j++) {
            if(b[j] == item) {
                break; // 只要有相等的情况,就跳出这个 for 循环
            }
        }
        if(j === b.length || i === 0) { // 加 || 运算符后面这句话是因为当最外层的循环 i = 0 的时候,内层的循环并不会执行
            b.push(item)
        }
    }
    
    console.log(b) // [1, 2, 3, 4, 5]
    

    是的,比一开始的代码稍微简洁那么一点。。。

    先排个序呢

    我们知道,怎么给数组排序

    var a = [2,2,1,4,4,4,3,3,5]
    a.sort(function(a, b) {
      return a - b
    })
    console.log(a) // [1, 2, 2, 3, 3, 4, 4, 4, 5]
    

    对于一个还排序的数组去重的方法,我好像有了一个新思路

    从数组的第二个成员开始,与其前一项做对比,如果相等,那么说明有重复,删除之

    说干就干!

    var a = [2,2,1,4,4,4,3,3,5]
    a.sort(function(a, b) {
      return a - b
    })
    for(var i = 1; i < a.length; i++) {
        if(a[i] === a[i - 1]) {
            a.splice(i, 1)
        }
    }
    console.log(a) // [1, 2, 3, 4, 4, 5]
    

    额,并没有成功,有两个重复的4

    这一小段代码很好分析,因为 a.splice(i, 1) 删掉了一个成员,但是 for 循环里面的 i 还是沿用没有删掉成员的值,所以

    var a = [2,2,1,4,4,4,3,3,5]
    a.sort(function(a, b) {
      return a - b
    })
    for(var i = 1; i < a.length; i++) {
        if(a[i] === a[i - 1]) {
            a.splice(i, 1)
            i -= 1 // 因为删掉了一个成员,成员数就得减1
        }
    }
    console.log(a) // [1, 2, 3, 4, 5]
    

    这个方法有个副作用,就是数组的顺序变了

    你听过对象的键名有重复的么

    是啊,对象的键名是没有重复的,这给了我新思路

    var a = [1, 2, 2, 3, 3, 4, 4, 4, 5]
    var b = []
    var o = {}
    for(var i = 0; i < a.length; i++) {
        var item = a[i]
        if(!o[item]) {
            o[item] = true
            b.push(item)
        }
    }
    console.log(b) // [1, 2, 3, 4, 5]
    

    这个方法有个缺陷,他分不清 1"1",因为键名会默认转换为字符串

    var a = [1, '1', 2, 2, 3, 3, 4, 4, 4, 5]
    var b = []
    var o = {}
    for(var i = 0; i < a.length; i++) {
        var item = a[i]
        if(!o[item) {
            o[item] = true
            b.push(item)
        }
    }
    console.log(b) // [1, 2, 3, 4, 5] // 分不清 1 和 '1'
    

    这也有解决的办法

    • 如果键名不存在,毫无疑问,这个值是没有重复的
    • 如果键名存在,那么利用 typeof 判断一下他的类型
    • 如果这个类型之前出现过,不管
    • 如果这个类型之前没出现过,就把它放到新数组

    试试

    var a = [1, '1', 2, 2, 3, 3, 4, 4, 4, 5]
    var b = []
    var o = {}
    for(var i = 0; i < a.length; i++) {
        var item = a[i]
        var type = typeof a[i]
        if(!o[item]) {
            o[item] = [type]
            b.push(item)
        }else if(o[item].indexOf(type) === -1) {
            o[item].push(type)
            b.push(item)
        }
    }
    console.log(b) // [1, "1", 2, 3, 4, 5] // 现在分得清 1 和 '1' 了
    

    当然了,这方法还是有缺陷,因为

    console.log(typeof []) // Object
    console.log(typeof {}) // Object
    

    也就是说如果数组里的成员是简单类型的那么好办,对于复杂类型的就无能为力了

    先进的 ES5

    ES5 新增了 indexOf() 方法,接受两个参数

    • 要查找的项
    • 查找位置起点的索引

    返回查找到的项的位置,如果没找到返回 -1

    看完这段介绍我似乎又有思路了

    var a = [1, 2, 2, 3, 3, 4, 4, 4, 5]
    var b = []
    
    for(var i = 0; i < a.length; i++) {
        if(b.indexOf(a[i]) === -1){
            b.push(a[i])
        }
    }
    console.log(b) //  [1, 2, 3, 4, 5]
    

    搞定

    懒惰的 ES6

    ES6 提供了新的数据结构 Set

    他类似于数组,但是成员的值是唯一的,比如

    const set = new Set([1,2,2,3,3,4,4,4,5])
    console.log(set) // Set(5) {1, 2, 3, 4, 5}
    

    这就简单了,再把这个数据结构转换成数组就行了么

    ES6 又提供了一个新的运算符 ...,他的其中一个功能就是吧任何类似数组的对象转为真正的数组

    const set = new Set([1,2,2,3,3,4,4,4,5])
    console.log([...set]) // [1, 2, 3, 4, 5]
    

    好了

    程序员的三大美德

    Perl 语言的发明人 Larry Wall 说,好的程序员有3种美德:

    • 懒惰
      • 是这样一种品质,它使得你花大力气去避免消耗过多的精力。它敦促你写出节省体力的程序,同时别人也能利用它们。为此你会写出完善的文档,以免别人问你太多问题。
    • 急躁
      • 是这样一种愤怒----当你发现计算机懒洋洋地不给出结果。于是你写出更优秀的代码,能尽快真正的解决问题。至少看上去是这样。
    • 傲慢
      • 极度的自信,使你有信心写出(或维护)别人挑不出毛病的程序。

    这篇文章就展现了程序员的其中一个美德

    懒惰

    (完)


    文档信息

    • 自由转载-非商用-非衍生-保持署名
    • 发表日期:2017年5月17日

    相关文章

      网友评论

        本文标题:数组去重

        本文链接:https://www.haomeiwen.com/subject/hpfsxxtx.html