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

IT技术 javascript set ecmascript-harmony
2021-01-23 20:11:59

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

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

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

6个回答

ES6Set对象没有任何比较方法或自定义比较可扩展性。

.has().add().delete()方法只关闭它是一个基本相同的实际物体或相同的值,没有办法插头插入或更换只是逻辑。

您大概可以从 a 派生自己的对象Set并替换.has(),.add().delete()首先进行深度对象比较的方法,以查找该项目是否已经在 Set 中,但性能可能不会很好,因为底层Set对象没有帮助根本。在调用原始.add().

以下是本文和ES6 特性讨论的一些信息

5.2 为什么我不能配置maps和sets如何比较keys和values?

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

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

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

@Jamby,他说每当您修改任何键时,都需要刷新该集合。但我不明白为什么合同需要如此,实际上,没有人在 Java 中以这种方式试过。
2021-03-18 20:11:59
为什么不实现一个简单的 GetHashCode 或类似的东西?
2021-03-25 20:11:59
@Jamby 即使使用哈希函数,您仍然必须处理冲突。你只是推迟了平等问题。
2021-03-26 20:11:59
@Jamby 因为 js 程序员不知道哈希码 :(
2021-03-27 20:11:59
@mpen这是不对的,我允许开发人员为他的特定类管理他自己的哈希函数,这几乎在所有情况下都可以防止冲突问题,因为开发人员知道对象的性质并且可以派生出一个好的密钥。在任何其他情况下,回退到当前的比较方法。很多 语言 已经做到这一点,JS不是。
2021-04-04 20:11:59

正如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());
    }

    // ...
}

每个插入的元素都必须实现toIdString()返回字符串的方法。当且仅当它们的toIdString方法返回相同的值时,两个对象才被认为是相等的

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

正如顶级答案所提到的,对于可变对象,自定义相等性是有问题的。好消息是(我很惊讶还没有人提到这一点)有一个非常流行的库,称为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}]
immutable-js Set/Map 的性能与原生 Set/Map 相比如何?
2021-04-03 20:11:59
感谢您的推荐!
2021-04-05 20:11:59
似乎具有相同的列表值,但仍不等于 Map 键
2021-04-09 20:11:59

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

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

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

“字符串连接”?他的方法是不是更像是“字符串代理”(如果你要给它起个名字)?或者你有什么理由使用“串联”这个词?我很好奇 ;-)
2021-03-11 20:11:59
@binki 这是一个很好的问题,我认为答案提出了一个我花了一段时间才掌握的好观点。通常,在计算哈希码时,会执行类似HashCodeBuilder 的操作,它将各个字段的哈希码相乘,并且不保证唯一(因此需要自定义相等函数)。但是,在生成 id 字符串时,您将连接各个字段的 id 字符串,这些字段保证是唯一的(因此不需要等式函数)
2021-03-24 20:11:59
让你的平等比较建立一些value,保证只有当事情应该被认为是不平等的时候才会发生变化,这是我以前使用过的一种策略。有时以这种方式思考事情会更容易。在这种情况下,您生成的是key而不是hashes只要你有一个密钥派生器,它以现有工具支持的值样式相等的形式输出一个密钥,这几乎总是StringMap甚至在派生密钥方面的旧式普通对象。
2021-03-24 20:11:59
所以如果你有一个Point定义,{ x: number, y: number }那么你id string可能是x.toString() + ',' + y.toString().
2021-03-28 20:11:59
如果您在键派生器的实现中实际使用字符串连接,需要注意的一件事是,如果允许字符串属性具有任何值,则可能需要对其进行特殊处理。例如,如果您有{x: '1,2', y: '3'}{x: '1', y: '2,3'},则将String(x) + ',' + String(y)为两个对象输出相同的值。假设您可以指望JSON.stringify()确定性,一个更安全的选择是利用其字符串转义并JSON.stringify([x, y])改为使用
2021-04-10 20:11:59

也许您可以尝试使用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);

啊,是的,“将其转换为字符串”。Javascript 对一切的回答。
2021-03-22 20:11:59
这可以工作,但不必像 JSON.stringify({a:1,b:2}) !== JSON.stringify({b:2,a:1}) 如果所有对象都是由您的程序以相同的方式创建的命令你是安全的。但一般来说不是一个真正安全的解决方案
2021-03-31 20:11:59