美文网首页让前端飞
[工作中遇到的难点] 数组去重算法高级应用

[工作中遇到的难点] 数组去重算法高级应用

作者: 向布谷鸟说早安 | 来源:发表于2018-10-17 22:38 被阅读25次

    今天在做需求的时候遇到了一个问题。有一堆产品对象放在一个数组arrs里,每个对象都有imageUrl,brand(产品品牌),name(产品名称),dimension(产品规格),quantity(产品数量,可能是面积和个数),id,type(根据位置分类),unit(单位),conpouType(产品套餐类型)等属性;
    要求把imageUrl,brand,name,dimension等属性相同的产品给合并并显示到界面上,但是在判断两个产品是否相等的时候,不能把有些属性比如quantity,type进行比较。
    展示到界面上的产品如图:


    产品展示.png

    其中unit可能是 ‘个’,‘面积’,当unit是个的时候quantity只可能是1。

    刚拿到这个需求,想到javascript经典的去重算法。就开始用算法复杂度最低的去重算法思路来实现这个需求。

    大致思路:

    1.根据产品id,name,先排序;
    2.把arrs中的产品对象排完序后,for循环数组arrs,每次循环都要判断相邻两个对象是否深度相等(有的属性可能也是对象)。如果相等,就把两个对象的quantity相加,否则就不做处理。

    咋一看,好像思路挺简单的,实现起来应该也没什么问题。
    可是我忽略了我们程序中复杂多变的情况。

    坑1:

    有的产品没有产品id:测试去测的时候发现,总是出现相同,最后发现有的产品竟然没有id,经过考虑,个人觉得还是通过id判断比较合适,可是又没有其它字段可以代表没有id的产品。后来发现可以name在这个问题上有一定的决定权,所以就先用id来判断,如果没有id,再用name来判断。

    坑2:

    先用id再用name,看起来简单,在实现的时候还是犯了致命的错误。
    如下代码,咋一看,好像没问题,实际上,问题大了

            contents.sort((a, b) => {
              if(a.Id) {
                if(a.Id > b.Id) return 1;
                if(a.Id< b.Id) return -1;
                if(a.Id=== b.Id) return 0;            
              } else {
                if(a.name > b.name) return 1;
                if(a.name < b.name) return -1;
                if(a.name === b.name) return 0;
              }
            });
    

    在发布前夕,又遇到相同类型的没有合并的问题,这次更神奇,生产上是错的,测试环境是对的。
    看来是数据的问题,调了下代码,发现生产和测试环境的数据顺序不一样,仔细看了下上述排序过程,发现少判断了b.Id是不是存在的。如果a.Id存在,b.Id不存在,并且和b.Id不存在的这个产品相同的其它产品,都是用name排的序,它自己用Id排的序,不出问题才怪。

    坑3:

    上述算法===写成了=

    坑4:

    属性值有错,同一个属性的值,有的是[],有的竟然是undefined,于是找了数据来源方,控制数据类型的一致性。

    坑5:

    没有把type类型,剔除出去,结果在比较对象是否相等的时候,相同name的有好多type是不相等的。
    于是我想了一馊主意。先根据产品Id或者name排序,排完序后,再根据产品type排序。当时的我一定没睡醒。

           contents.sort((a, b) => {
              if(a.Id && b.Id) {
                if(a.Id > b.Id) return 1;
                if(a.Id< b.Id) return -1;
                if(a.Id=== b.Id) return 0;            
              } else {
                if(a.name > b.name) return 1;
                if(a.name < b.name) return -1;
                if(a.name === b.name) return 0;
              }
            });
    
           contents.sort((a, b) => {
              if(a.type) {
                if(a.type > b.type) return 1;
                if(a.type < b.type) return -1;
                if(a.type === b.type) return 0;            
              }
            });
    

    毋庸置疑,这次测试又报bug了。
    调了下代码,发现有两个问题,一个问题是有的产品name可能是空字符串,倘若如此,那不如不显示这些产品。于是去说服了测试,产品。
    另一个问题是本来排的好好的序,name相同的为一组,再用type来排序,把name相同,type不同的类型活生生地拆散。然而,type并不是是否要合并产品的关键字段。所以这才把type从之前的比较对象的数组中剔除了,管它相不相等,按照name不为空的逻辑,正常排序就好。在后面合并的过程中,type不等,name和其它有必要比较的属性都相等相同就可以合并了。

    总结:

    这些bug集中在排序算法上,也怪我平时对这个算法理解的不够深入,总是想当然。倒是排序后的去重算法,没有出现过多的bug,看来平时刷的算法题还是有用的,关键时刻能减少bug。
    针对该算法,写了一个类似的算法,放到了github上。
    水果统计个数

    带着对自己的谴责和对公司代码的无奈写完了这篇文章。只能说,革命尚未成功,同志仍需努力。

    ---------------------更新------------------
    后来这个bug又继续重现,一直有相关产品没能合并。
    原因:
    在比较Id的时候,发现没有Id,但在比较name的时候name相同,但是其它字段,比如brand并不相同。
    这把我先排序再去重的思路完全打乱了,我干嘛要排序呢,既然没有一个指定的字段可以用来排序的,排序完全就是多此一举。
    但是如果用这种思路继续写的话,怎么写才对呢?
    我把每个字段都做了一个比较,得到如下代码:

            inValidContents.sort((a, b) => {
              if ((a.Id && b.Id) && a.Id !== b.Id) {
                  if(a.Id > b.Id) return 1;
                  if(a.Id < b.Id) return -1;
              } else if ((a.name && b.name) && a.name !== b.name) {
                  if(a.name > b.name) return 1;
                  if(a.name < b.name) return -1;
              } else if((a.brand && b.brand) && a.brand !== b.brand) {
                  if(a.brand > b.brand) return 1;
                  if(a.brand < b.brand) return -1;
              } else if ((a.imageUrl && b.imageUrl) && a.imageUrl !== b.imageUrl) {
                  if(a.imageUrl > b.imageUrl) return 1;
                  if(a.imageUrl < b.imageUrl) return -1;
              } else if (!isEqualArray(a.dimension, b.dimension)) {
                  if(getArrayStr(a.dimension) > getArrayStr(b.dimension)) return 1;
                  if(getArrayStr(a.dimension) < getArrayStr(b.dimension)) return -1;
              } else if(!isEqualArray(a.couponTypes, b.couponTypes)) {
                  if(getArrayStr(a.couponTypes) > getArrayStr(b.couponTypes)) return 1;
                  if(getArrayStr(a.couponTypes) < getArrayStr(b.couponTypes)) return -1;
              }
              return 0;
            });
    

    上述代码必须要有是否存在的&&判断,因为结合起来我们复杂的数据类型,如果两个Id不相等,一个是undefined,另一个是“”虽然不相等,但等同于相等,所以不会继续进行判断,但是这次比较结果却是无效的。

    上述,排序用了至少nlogn的复杂度,再进行循环一个个比较,虽然为n,但是该算法由于不太稳定,对属性的依赖过大。
    所以采取新的去重思路,直接就地比较,发现有相同的就去除该数组元素,同时减少该数组长度,直到遍历完全:
    代码如下

        //往前移动,发现与当前重复的,就删除
        function removeDuplicate3(arr) {
          let len = arr.length;
          let results = [];
          for(var i = 0;i < len;i++) {
            for(var j = i + 1;j < len;j++) {
              if(arr[i] === arr[j]) {
                arr.splice(j,1);
                j--;
                len--;
              }
            }
          }
    
          return arr;
        }
        var a = [1,2,3,4,5,6,5,3,2,4,56,4,1,2,1,1,1,1,1,1,];
        console.log(removeDuplicate3(a));
    

    这篇文章涉及到的去重算法

    相关文章

      网友评论

        本文标题:[工作中遇到的难点] 数组去重算法高级应用

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