是否存在通用的数组去重方法?

发布于 2022-09-11 18:08:46 字数 1138 浏览 19 评论 0

数组去重,很多时候都会用到,在具体项目中因为场景确定,很容易写出一个简单的去重方法,但大多是否具有针对性,没法适用于各类,一劳永逸
当然此处的场景所说的去重指的是去除内容大致相同的数据,不管是否同一个引用

如下这段代码,如果希望输出的是 [[1],{a:2},{a:1},[2]]

let obj = {a:1};
let arr = [2];
let test = [ [1], [1], {a:2}, {a:2}, obj, obj, arr, arr ];

Array.prototype.unique = function(){
  ...
}

console.log(test.unique());

通常我们利用SetArray互转实现去重,但是该方式在判断引用类型时,并没那么给力,只有相同的引用才会被判定为相等。

console.log([...new Set(test)]);
// [[1],[1],{a:2},{a:2}, {a:1}, [2]];

如果采用对象的方式,对付普通的数据还好,碰上这种情况也是无能为力

Array.prototype.unique = function() {
  var res = [];
  var json = {};
  for(var i = 0; i < this.length; i++){
    if(!json[this[i]]){
      res.push(this[i]);
      json[this[i]] = true;
    }
  }
  return res;
}
console.log(test.unique());
// [[1],{a:2},[2]] 非常神奇的是{a:1}竟然不见了

当然,如果采用极端点的方式只对引用类型的值先JSON.string(),再来做对比,好像可以,准确的说对简单且狭义的对象类型有效,因为File、Stack、HTMLElement、class、functionObject类型的等值判断没有json数据或字符串那般简单。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

凶凌 2022-09-18 18:08:46

去除复杂引用类型本身就是一个高度自定化的功能,如何定义去重,从机器角度出发,如果两个变量引用相同,那么他们就是重复的。如果比较的是两个对象,如何定义重复是一个依赖于功能需求的定义,比如两个引用对象里的常量相等,但引用不同能不能叫重复;引用相同但常量值不同能不能叫重复,这些是需要你自身去定义的。

述情 2022-09-18 18:08:46

这个可以看一下 lodashbaseUniq 源码
array , iteratee, comparator, 数组,迭代器,比较器3个参数

function baseUniq(array, iteratee, comparator) {...}

这一个方法演化了 union, uniq, unionBy, uniqBy, unionWith, uniqWith 6个方法

如果我们比较 [ { age: 1 }, { age: 1 } ] 是否重复可以 使用 iterateeitem => item.age
如果我们比较 [ { 'x': 1, 'y': 2 }, { 'x': 1, 'y': 2 } ] 是否重复可以 使用 comparator(target, value) => target.x === value.x && target.y === value.y(也可以用其他的方法,遍历,JSON.stringify)

め七分饶幸 2022-09-18 18:08:46

首先谈下我的理解,不可能有一劳永逸的方法。
同意楼上去除复杂引用类型本身就是一个高度自定化的功能,需要自身去定义,按你的说法,
1.待去重数组内的类型是不确定的
2.不同的类型,甚至你自己定义的class,都有不同的"相等"的含义,比如定义user的类,只需id一样。
在java 中,判断对象相等需要重写equals和hashCode方法,equals返回true就是相等了,现在连类都未知,怎么去实现它的equals方法呢?
ps:
感觉实际开发中这种包罗万象的数组存在的可能性微乎其微,可以参考下lodash的unionWith 函数
_.unionWith([arrays], [comparator])

var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
 
_.unionWith(objects, others, _.isEqual);
// => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]

把判断是否相等的代码comparator作为参数,如果有多种类型,就在comparator里面加个类型判断就好了。

歌枕肩 2022-09-18 18:08:46

看了lodash的源码迷迷糊糊的,还是用自己的方式来实现一把。部分人可能对题意所说的通用有着不一样的理解,所以觉得束手束脚有点难。此处的场景是确定,我的思路及实现如下,有人能提出点优化建议更好。

//数组去重
// deep 不为空表示对引用类型的内容进行抽象判断
Array.prototype.unique = function(deep){
  let arr = [...new Set(this)];
  deep && deepUnique(arr);
  return arr;
}

//深度去重,只对引用类型的数据进行再次内容对比
function deepUnique(arr){
  let objArr = [];
  arr.forEach((item,i)=>{
    if(['object','function'].includes(typeof item)){
      objArr.unshift({idx:i, val:item});
    }
  })
  if(objArr.length <= 1) return;
  for(let i=arr.length-1; i>=0; i--){
    if(['object','function'].includes(typeof arr[i])){
      objArr.some((item,idx)=>{
        if((item.idx < i) && isSameObj(arr[i],item.val)){
          objArr.splice(idx,1); //缩短遍历长度
          arr.splice(i,1); //移除重复数据
          return true;
        }
      })
    }
  }
}

//是否内容相同
function isSameObj(val0,val1){
  let res = false;
  let type = isSameType(val0,val1);
  if(!type) return res;
  switch(type){
    case 'HTMLDivElement':
      res = val0.outerHTML === val1.outerHTML;
      break;
    case 'File':
      res = JSON.stringify(val0) === JSON.stringify(val1);
      break;
    case 'class':
    case 'Object':
      let keys0 = Object.keys(val0);
      let keys1 = Object.keys(val1);
      if(JSON.stringify(keys0) === JSON.stringify(keys1)){
        let arr0 = keys0.map(k=>val0[k]);
        let arr1 = keys1.map(k=>val1[k]);
        res = isSameArray(arr0,arr1);
      }
      break;
    case 'Array': 
      res = isSameArray(val0,val1);
      break;
    case 'Function':
      res = val0.toString() === val1.toString();
      break;
    case 'Error':
      res = val0.stack === val1.stack;
      break;
    default:
      break;
  } 
  return res;
}

//是否相同的类型
function isSameType(val0,val1){
  let type = false;
  if(val0.constructor == val1.constructor){
    let arr = val0.constructor.toString().substr(0,100).split(' ');
    type = arr[0] === 'function' ? arr[1].slice(0,-2) : arr[1];
  }
  return type;
}

//数组的值是否相同
function isSameArray(arr0,arr1){
  if(arr0.length !== arr1.length) return false;
  let same = true; 
  for(let i=0; i<arr0.length; i++){
    if(typeof arr0[i] !== typeof arr1[i]){ //类型不同
      same = false;
      break;
    }
    if(arr0[i] && arr1[i] && ['object','function'].includes(typeof arr0[i])){ //非空引用类型
      if(!isSameObj(arr0[i],arr1[i])){
        same = false;
        break;
      }
    }else if((arr0[i] !== arr1[i])){
      if(!((typeof arr0[i] === 'number') && isNaN(arr0[i]) && isNaN(arr1[i]))){ //除了 NaN != NaN 的情况
        same = false;
        break;
      }
    }
  }  
  return same;
}

测试结果如下

let obj = {a:1};
let arr = [2];
let test = [ [1], [1], {a:2}, {a:2}, obj, obj, arr, arr ];
console.log(test.unique()); // [[1],[1],{a:2},{a:2},{a:1},[2]]
console.log(test.unique(true)); // [[1],{a:2},{a:1},[2]]
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文