【问题标题】:Why does Javascript Set not do unique objects?为什么 Javascript Set 不做唯一对象?
【发布时间】:2016-12-31 01:20:19
【问题描述】:

集合应该包含唯一的对象,但它不适用于 javascript 中的对象。

var set = new Set()
<- undefined
set.add({name:'a', value: 'b'})
<- Set {Object {name: "a", value: "b"}}
set.add({name:'a', value: 'b'})
<- Set {Object {name: "a", value: "b"}, Object {name: "a", value: "b"}}

它适用于原语

var b = new Set()
<- undefined
b.add(1)
<- Set {1}
b.add(2)
<- Set {1, 2}
b.add(1)
<- Set {1, 2}

那么如何让它与对象一起工作呢?我知道它们是具有相同值的不同对象,但我正在寻找一个深度独特的集合。

编辑:

这就是我实际在做的事情

    var m = await(M.find({c: cID}).populate('p')) //database call
    var p = new Set();
    m.forEach(function(sm){
        p.add(sm.p)
    })

这是为了得到一个唯一的sm.p列表

【问题讨论】:

  • “我知道它们是具有相同值的不同对象” - 没错。它们是不同的对象。即使它们最初具有唯一的属性值,在添加到集合后也可以修改为具有相同的属性值。
  • @nnnnnn 那么我如何实现我正在寻找的一组具有唯一值的对象? (我也会接受一组唯一的对象值)
  • 这个question 可能会有所帮助。
  • @MattWestlake,很可能您不想将对象用作集合的键,而是使用带有排序键的 JSON 序列化。
  • 如果您正在执行数据库操作,您不能让数据库查询首先返回唯一的结果吗?数据库擅长这类事情。

标签: javascript


【解决方案1】:

好吧,如果您正在寻找一个深度独特的集合,您可以自己制作一个深度集合,方法是扩展原始“集合”,如下所示:

function DeepSet() {
    //
}
DeepSet.prototype = Object.create(Set.prototype);
DeepSet.prototype.constructor = DeepSet;
DeepSet.prototype.add = function(o) {
    for (let i of this)
        if (deepCompare(o, i))
            throw "Already existed";
    Set.prototype.add.call(this, o);
};

【讨论】:

  • 这是添加此行为的好方法。不幸的是,没有原生的deepCompare 方法。
  • 当然。 DeepSet 必须自己实现,就像我们必须自己实现 deepCompare 方法一样。
  • 这是一个非常糟糕的主意。您还需要覆盖 has、delete 和 add。此外,它现在将在 Omega(n) 中运行。
【解决方案2】:

另一种选择是您可以使用JSON.stringify() 来保持对象的唯一性,这样它就可以与字符串而不是对象引用进行比较。

set.add(JSON.stringify({name:'a', value: 'b'}))

然后在所有内容都格式化后,您可以将这些行解析回一个数组,如下所示:

const formattedSet = [...set].map(item) => {
  if (typeof item === 'string') return JSON.parse(item);
  else if (typeof item === 'object') return item;
});

【讨论】:

  • 这样的解决方案需要考虑顺序。不能信任 JSON.stringify() 每次都以相同的顺序给出带有键的结果。除此之外,还有许多值无法正确字符串化,例如 Infinity、NaN 和递归对象。
  • 对某些类型的值进行字符串化的有效点。 Symbol() 和第一类函数也将被删除。在极少数情况下,订单也可能会脱节,如果这对您很重要,您可以执行set.add(JSON.stringify(obj, Object.keys(obj).sort())) 之类的操作来保留这些顶级键的顺序。见stackoverflow.com/a/64565203/6621163
【解决方案3】:

根据 Joe Yichong 的帖子,这里建议扩展 Set for TypeScript。

export class DeepSet extends Set {

  add (o: any) {
    for (let i of this)
      if (this.deepCompare(o, i))
        return this;
    super.add.call(this, o);
    return this;
  };

  private deepCompare(o: any, i: any) {
    return JSON.stringify(o) === JSON.stringify(i)
  }
}

【讨论】:

  • 这缺少了使用集合的意义:O(1) 加法操作。相反,这必须将每个新条目与每个先前条目进行比较。
【解决方案4】:

您可以使用地图。如果您需要基于键的唯一值。对我来说,我想从对象数组中进行独特的选择

let cats = [
    { category: 6 },
    { brand: 'purina' },
    { category: 5 },
    { category: 5 },
    { brand: 'purina' }];

var myMap = new Map();

cats.forEach(object => {
    for (const key in object) {
      myMap.set(key, object[key])
    }
})
console.log(myMap) //Map { 'category' => 5, 'brand' => 'purina' }

这里不再重复类别和品牌

【讨论】:

  • { category: 6 }怎么了?
  • 看起来最后一个总是获胜,(就像在 css 中最后一个属性写入总是获胜:))
  • 这会导致不必要的复杂性和不必要的副作用,通常最后一个总是赢
【解决方案5】:

好吧,Javascript Set 不存储唯一对象,因为每个对象都有自己的引用。因此,Set 将仅存储唯一值,因此即使两个或多个对象具有相同的值,它们的引用也不同。

例子-

var set = new Set();

set.add({ id: 1 });
set.add({ id: 2 });
set.add({ id: 3 });

// Every object has different reference
var d = { id: 4 };

set.add(d); 
set.add(d);

for (const item of set) {
    console.log(item);
}

// Output : 1 2 3 4

注意:解决方案是您可以为同一个对象创建一个引用 并按照上面的示例添加到 Set 中。

【讨论】:

    【解决方案6】:

    如果您负担得起使用库的“费用”,在本例中为 fast-deep-equal(它依赖于数以千计的其他包 - 许多主要包 - 所以它可能已经在您的图表中)然后有一个死的简单扩展设置:UniqueSet

    它与Joe Yichong’s answer基本相同,只是它作为一个模块导出并实现了fast-deep-equal来深入比较每个元素,因此它可以是一种简单方便的方法。你也可以自己写——它没有什么特别的,也没有什么不透明的。

    由于某些数据类型和排序的问题,使用 fast-deep-equal 比 JSON 序列化更可靠,其中一些已经在此处提及。

    【讨论】:

      【解决方案7】:

      一个常见的场景是当我们从 API 获取一些数据时,这会重复一些事件。

      for 循环中,当在sampleArray 上使用push(...) 时,我们需要使用JSON.stringify({...})

      for (const id in response.data) {
        sampleArray.push(JSON.stringify({id, ...});
      }
      

      然后我们可以从sampleArray创建一个Set

      const sampleSet = new Set(sampleArray);

      现在,转换回Array

      const list = Array.from(sampleSet);

      对于每个元素 JSON.parse(...) 再次使其具有正确的格式(注意:我非常需要相同的 sampleArray,您可以使用新变量):

      sampleArray = [];
      list.forEach((element) => {
        JSON.parse(element);
        sampleArray.push(JSON.parse(element));
      });
      

      完整代码:

      for (const id in response.data) {
        sampleArray.push(JSON.stringify({id, ...});
      }
      
      const sampleSet = new Set(sampleArray)
      const list = Array.from(sampleSet);
      
      sampleArray = [];
      list.forEach((element) => {
        JSON.parse(element)
        sampleArray.push(JSON.parse(element))
      });
      

      在这种情况下可以正常工作,因为我们已经控制了 顺序,正如@dankuck 所指出的那样,这是至关重要的。

      【讨论】:

        【解决方案8】:

        这太天真了,我相信有更好的方法来获取唯一数据(比如让数据库查询返回唯一数据,但这取决于数据库),但是您可以检查对象是否已经在插入之前的集合。

        var m = await(M.find({c: cID}).populate('p')) //database call
        var p = new Set();
        m.forEach(function(sm){
            if(!p.has(sm.p)) p.add(sm.p)
        })

        【讨论】:

        • 有点违背了使用集合的意义,还不如只使用数组。
        • 这条评论应该是-1。如果您要检查它是否存在于 0(n),则使用 Set 毫无意义。另外,这不是“天真的”,我有一个 React 应用程序,其中使用像 Set 这样的数据结构(如果我们可以像 Java 或 C# 那样以正确的方式实现它)将是我最好的选择。
        猜你喜欢
        • 2013-05-31
        • 1970-01-01
        • 2017-11-08
        • 1970-01-01
        • 2016-01-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多