如何为JavaScript Set自定义对象相等性

cze*_*rny 142 javascript set ecmascript-harmony

新的ES 6(Harmony)引入了新的Set对象.Set使用的身份算法类似于===运算符,因此不太适合比较对象:

var set = new Set();
set.add({a:1});
set.add({a:1});
console.log([...set.values()]); // Array [ Object, Object ]
Run Code Online (Sandbox Code Playgroud)

如何自定义Set对象的相等性以进行深层对象比较?有没有像Java这样的东西equals(Object)

jfr*_*d00 94

ES6 Set对象没有任何比较方法或自定义比较可扩展性.

.has(),.add().delete()方法只关闭它是一个基本相同的实际物体或相同的值,不必插入或更换只是逻辑的手段.

你可能从a Set和replace中派生你自己的对象.has(),.add()并且首先.delete()用一些深层对象进行比较的方法来查找该项是否已经在Set中,但是由于底层Set对象没有帮助,性能可能不会很好一点都不 在调用原始对象之前,您可能只需要对所有现有对象进行强力迭代,以使用您自己的自定义比较查找匹配项.add().

以下是本文的一些信息 ES6功能的讨论:

5.2为什么我不能配置映射和集合比较键和值的方式?

问题:如果有一种方法可以配置哪些映射键和哪些集合元素被认为是相等的,那就太好了.为什么不存在?

答:该功能已被推迟,因为难以正确有效地实施.一种选择是将回调交给指定相等的集合.

Java中提供的另一个选项是通过对象实现的方法(Java中的equals())指定相等性.但是,这种方法对于可变对象是有问题的:通常,如果对象发生更改,其集合内的"位置"也必须更改.但这不是Java中发生的事情.JavaScript可能会更安全,只能通过值对特殊的不可变对象(所谓的值对象)进行比较.按值比较意味着如果两个值的内容相等,则认为它们相等.原始值在JavaScript中按值进行比较.

  • @mpen这是不对的,我允许开发人员为他的特定类管理自己的哈希函数,几乎在每种情况下都可以防止碰撞问题,因为开发人员知道对象的性质并且可以得到一个好的密钥.在任何其他情况下,回退到当前比较方法.[Lot](https://msdn.microsoft.com/en-us/library/system.object.gethashcode(v = vs.100).aspx)[of]( https://en.wikipedia.org/wiki/Java_hashCode())[语言](https://docs.ruby-lang.org/en/2.0.0/Hash.html)[已](https:// docs.python.org/2/reference/datamodel.html#object.__hash__)这样做,不是. (5认同)
  • 添加了有关此特定问题的文章参考.看起来挑战是如何在添加到集合时处理与另一个完全相同的对象,但现在已经更改并且不再与该对象相同.是否在"Set"中? (4认同)
  • 为什么不实现一个简单的GetHashCode或类似的? (3认同)

cze*_*rny 27

正如在jfriend00中提到的那样,平等关系定制可能是不可能的.

下面的代码概述了计算效率(但内存昂贵)的解决方法:

class GeneralSet {

    constructor() {
        this.map = new Map();
        this[Symbol.iterator] = this.values;
    }

    add(item) {
        this.map.set(item.toIdString(), item);
    }

    values() {
        return this.map.values();
    }

    delete(item) {
        return this.map.delete(item.toIdString());
    }

    // ...
}
Run Code Online (Sandbox Code Playgroud)

每个inserted元素都必须实现toIdString()返回string的方法.当且仅当它们的toIdString方法返回相同的值时,才认为两个对象是相等的.

  • @BenJ 生成字符串并将其放入 Map 的要点是,这样您的 Javascript 引擎将在本机代码中使用 ~O(1) 搜索来搜索对象的哈希值,而接受相等函数将强制对集合进行线性扫描并检查每个元素。 (3认同)
  • 这种方法的一个挑战是我认为它假定`item.toIdString()`的值是不变的并且不能改变.因为如果它可以,那么`GeneralSet`很容易变得无效,其中包含"重复"项.因此,像这样的解决方案将仅限于某些情况,在这些情况下,在使用集合时对象本身不会发生变化,或者变得无效的集合不是重要的.所有这些问题可能进一步解释了为什么ES6 Set没有公开这个功能,因为它实际上只适用于某些情况. (3认同)
  • 您还可以让构造函数采用一个比较项目是否相等的函数。如果您希望这种相等性成为集合的特征,而不是其中使用的对象的特征,那么这很好。 (2认同)

Rus*_*vis 16

正如顶级答案所提到的,对于可变对象,自定义相等性是有问题的。好消息是(我很惊讶还没有人提到这一点)有一个非常流行的库,称为immutable-js,它提供了一组丰富的不可变类型,这些类型提供了您正在寻找的深层值相等语义

这是您使用immutable-js 的示例:

const { Map, Set } = require('immutable');
var set = new Set();
set = set.add(Map({a:1}));
set = set.add(Map({a:1}));
console.log([...set.values()]); // [Map {"a" => 1}]
Run Code Online (Sandbox Code Playgroud)

  • immutable-js Set/Map 的性能与原生 Set/Map 相比如何? (15认同)

Gua*_*Hsu 6

也许您可以尝试使用JSON.stringify()进行深度对象比较。

例如 :

const arr = [
  {name:'a', value:10},
  {name:'a', value:20},
  {name:'a', value:20},
  {name:'b', value:30},
  {name:'b', value:40},
  {name:'b', value:40}
];

const names = new Set();
const result = arr.filter(
  item => !names.has(JSON.stringify(item)) 
    ? names.add(JSON.stringify(item)) 
    : false
);

console.log(result);
Run Code Online (Sandbox Code Playgroud)

  • 啊,是的,“将其转换为字符串”。Javascript 为一切提供了答案。 (9认同)
  • 这可以工作,但不必如此 JSON.stringify({a:1,b:2}) !== JSON.stringify({b:2,a:1}) 如果所有对象都是由您的程序在同一位置创建的命令你安全。但总的来说这并不是一个真正安全的解决方案 (5认同)

mak*_*ako 5

为了补充这里的答案,我继续实现了一个 Map 包装器,它采用自定义哈希函数、自定义相等函数,并在存储桶中存储具有等效(自定义)哈希值的不同值。

可以预见,结果证明它比czerny 的字符串连接方法

完整来源:https : //github.com/makoConstruct/ValueMap

  • 如果您在密钥派生器的实现中实际使用字符串连接,需要注意的一件事是,如果允许字符串属性具有任何值,则可能需要对其进行特殊处理。例如,如果你有 `{x: '1,2', y: '3'}` 和 `{x: '1', y: '2,3'}`,那么 `String(x) + ' ,' + String(y)` 将为两个对象输出相同的值。一个更安全的选择,假设你可以指望 `JSON.stringify()` 是确定性的,是利用它的字符串转义并使用 `JSON.stringify([x, y])` 代替。 (2认同)