如何为 JavaScript Set 自定义对象相等性
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/29759480/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to customize object equality for JavaScript Set
提问by czerny
New ES 6 (Harmony) introduces new Setobject. Identity algorithm used by Set is similar to ===operator and so not much suitable for comparing objects:
新的 ES 6 (Harmony) 引入了新的Set对象。Set 使用的标识算法类似于===运算符,因此不太适合比较对象:
var set = new Set();
set.add({a:1});
set.add({a:1});
console.log([...set.values()]); // Array [ Object, Object ]
How to customize equality for Set objects in order to do deep object comparison? Is there anything like Java equals(Object)?
如何自定义 Set 对象的相等性以进行深度对象比较?有没有类似 Java 的东西equals(Object)?
采纳答案by jfriend00
The ES6 Setobject does not have any compare methods or custom compare extensibility.
ES6Set对象没有任何比较方法或自定义比较可扩展性。
The .has(), .add()and .delete()methods work only off it being the same actual object or same value for a primitive and don't have a means to plug into or replace just that logic.
的.has(),.add()而.delete()方法只关闭它是一个基本相同的实际物体或相同的值,没有办法插头插入或更换只是逻辑。
You could presumably derive your own object from a Setand replace .has(), .add()and .delete()methods with something that did a deep object comparison first to find if the item is already in the Set, but the performance would likely not be good since the underlying Setobject would not be helping at all. You'd probably have to just do a brute force iteration through all existing objects to find a match using your own custom compare before calling the original .add().
您大概可以从 a 派生自己的对象Set并替换.has(),.add()和.delete()首先进行深度对象比较的方法,以查找该项目是否已经在 Set 中,但性能可能不会很好,因为底层Set对象没有帮助根本。在调用原始.add().
Here's some info from this article and discussionof ES6 features:
5.2 Why can't I configure how maps and sets compare keys and values?
Question: It would be nice if there were a way to configure what map keys and what set elements are considered equal. Why isn't there?
Answer: That feature has been postponed, as it is difficult to implement properly and efficiently. One option is to hand callbacks to collections that specify equality.
Another option, available in Java, is to specify equality via a method that object implement (equals() in Java). However, this approach is problematic for mutable objects: In general, if an object changes, its “location” inside a collection has to change, as well. But that's not what happens in Java. JavaScript will probably go the safer route of only enabling comparison by value for special immutable objects (so-called value objects). Comparison by value means that two values are considered equal if their contents are equal. Primitive values are compared by value in JavaScript.
5.2 为什么我不能配置maps和sets如何比较keys和values?
问题:如果有一种方法可以配置哪些映射键和哪些集合元素被认为是相等的,那就太好了。为什么没有?
答:该功能已被推迟,因为很难正确有效地实施。一种选择是将回调传递给指定相等的集合。
Java 中可用的另一个选项是通过对象实现的方法(Java 中的 equals())指定相等性。然而,这种方法对于可变对象是有问题的:通常,如果一个对象发生变化,它在集合中的“位置”也必须发生变化。但这不是 Java 中发生的事情。JavaScript 可能会走更安全的路线,只为特殊的不可变对象(所谓的值对象)启用按值比较。按值比较意味着如果两个值的内容相等,则认为它们相等。原始值在 JavaScript 中按值进行比较。
回答by czerny
As mentioned in jfriend00's answercustomization of equality relation is probably not possible.
正如jfriend00 的回答中提到的,等式关系的定制可能是不可能的。
Following code presents an outline of computationally efficient (but memory expensive) workaround:
以下代码概述了计算效率高(但内存昂贵)的解决方法:
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());
}
// ...
}
Each inserted element has to implement toIdString()method that returns string. Two objects are considered equal if and only if their toIdStringmethods returns same value.
每个插入的元素都必须实现toIdString()返回字符串的方法。当且仅当它们的toIdString方法返回相同的值时,两个对象才被认为是相等的。
回答by mako
To add to the answers here, I went ahead and implemented a Map wrapper that takes a custom hash function, a custom equality function, and stores distinct values that have equivalent (custom) hashes in buckets.
为了补充这里的答案,我继续实现了一个 Map 包装器,它采用自定义哈希函数、自定义相等函数,并在存储桶中存储具有等效(自定义)哈希值的不同值。
Predictably, it turned out to be slowerthan czerny's string concatenation method.
可以预见,结果证明它比czerny 的字符串连接方法慢。
Full source here: https://github.com/makoConstruct/ValueMap
回答by Russell Davis
As the top answermentions, customizing equality is problematic for mutable objects. The good news is (and I'm surprised no one has mentioned this yet) there's a very popular library called immutable-jsthat provides a rich set of immutable types which provide the deep value equality semanticsyou're looking for.
正如最佳答案所提到的,对于可变对象,自定义相等性是有问题的。好消息是(我很惊讶还没有人提到这一点)有一个非常流行的库,称为immutable-js,它提供了一组丰富的不可变类型,这些类型提供了您正在寻找的深层值相等语义。
Here's your example using 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}]
回答by Jan Dolejsi
For Typescript users the answers by others (especially czerny) can be generalized to a nice type-safe and reusable base class:
对于 Typescript 用户,其他人(尤其是czerny)的答案可以推广到一个很好的类型安全和可重用的基类:
/**
* Map that stringifies the key objects in order to leverage
* the javascript native Map and preserve key uniqueness.
*/
abstract class StringifyingMap<K, V> {
private map = new Map<string, V>();
private keyMap = new Map<string, K>();
has(key: K): boolean {
let keyString = this.stringifyKey(key);
return this.map.has(keyString);
}
get(key: K): V {
let keyString = this.stringifyKey(key);
return this.map.get(keyString);
}
set(key: K, value: V): StringifyingMap<K, V> {
let keyString = this.stringifyKey(key);
this.map.set(keyString, value);
this.keyMap.set(keyString, key);
return this;
}
/**
* Puts new key/value if key is absent.
* @param key key
* @param defaultValue default value factory
*/
putIfAbsent(key: K, defaultValue: () => V): boolean {
if (!this.has(key)) {
let value = defaultValue();
this.set(key, value);
return true;
}
return false;
}
keys(): IterableIterator<K> {
return this.keyMap.values();
}
keyList(): K[] {
return [...this.keys()];
}
delete(key: K): boolean {
let keyString = this.stringifyKey(key);
let flag = this.map.delete(keyString);
this.keyMap.delete(keyString);
return flag;
}
clear(): void {
this.map.clear();
this.keyMap.clear();
}
size(): number {
return this.map.size;
}
/**
* Turns the `key` object to a primitive `string` for the underlying `Map`
* @param key key to be stringified
*/
protected abstract stringifyKey(key: K): string;
}
Example implementation is then this simple: just override the stringifyKeymethod. In my case I stringify some uriproperty.
示例实现就是这么简单:只需覆盖该stringifyKey方法。在我的情况下,我将一些uri属性字符串化。
class MyMap extends StringifyingMap<MyKey, MyValue> {
protected stringifyKey(key: MyKey): string {
return key.uri.toString();
}
}
Example usage is then as if this was a regular Map<K, V>.
示例用法就好像这是一个常规的Map<K, V>.
const key1 = new MyKey(1);
const value1 = new MyValue(1);
const value2 = new MyValue(2);
const myMap = new MyMap();
myMap.set(key1, value1);
myMap.set(key1, value2); // native Map would put another key/value pair
myMap.size(); // returns 1, not 2
回答by GuaHsu
Maybe you can try to use JSON.stringify()to do deep object comparison.
也许您可以尝试使用JSON.stringify()进行深度对象比较。
for example :
例如 :
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);
回答by relief.melone
Comparing them directly seems not possible, but JSON.stringify works if the keys just were sorted. As I pointed out in a comment
直接比较它们似乎是不可能的,但 JSON.stringify 如果键刚刚排序就可以工作。正如我在评论中指出的那样
JSON.stringify({a:1, b:2}) !== JSON.stringify({b:2, a:1});
JSON.stringify({a:1, b:2}) !== JSON.stringify({b:2, a:1});
But we can work around that with a custom stringify method. First we write the method
但是我们可以使用自定义的 stringify 方法来解决这个问题。首先我们写方法
Custom Stringify
自定义字符串化
Object.prototype.stringifySorted = function(){
let oldObj = this;
let obj = (oldObj.length || oldObj.length === 0) ? [] : {};
for (let key of Object.keys(this).sort((a, b) => a.localeCompare(b))) {
let type = typeof (oldObj[key])
if (type === 'object') {
obj[key] = oldObj[key].stringifySorted();
} else {
obj[key] = oldObj[key];
}
}
return JSON.stringify(obj);
}
The Set
集合
Now we use a Set. But we use a Set of Strings instead of objects
现在我们使用一个集合。但是我们使用一组字符串而不是对象
let set = new Set()
set.add({a:1, b:2}.stringifySorted());
set.has({b:2, a:1}.stringifySorted());
// returns true
Get all the values
获取所有值
After we created the set and added the values, we can get all values by
创建集合并添加值后,我们可以通过以下方式获取所有值
let iterator = set.values();
let done = false;
while (!done) {
let val = iterator.next();
if (!done) {
console.log(val.value);
}
done = val.done;
}
Here's a link with all in one file http://tpcg.io/FnJg2i
这是一个包含所有文件的链接 http://tpcg.io/FnJg2i
回答by DigaoParceiro
To someone who found this question on Google (as me) wanting to get a value of a Map using an object as Key:
对于在谷歌上发现这个问题的人(就像我一样)想要使用对象作为键来获取 Map 的值:
Warning:this answer will not work with all objects
警告:此答案不适用于所有对象
var map = new Map<string,string>();
map.set(JSON.stringify({"A":2} /*string of object as key*/), "Worked");
console.log(map.get(JSON.stringify({"A":2}))||"Not worked");
Output:
输出:
Worked
工作过
回答by Stefan Musarra
Create a new set from the combination of both sets, then compare the length.
从两个集合的组合中创建一个新集合,然后比较长度。
let set1 = new Set([1, 2, 'a', 'b'])
let set2 = new Set([1, 'a', 'a', 2, 'b'])
let set4 = new Set([1, 2, 'a'])
function areSetsEqual(set1, set2) {
const set3 = new Set([...set1], [...set2])
return set3.size === set1.size && set3.size === set2.size
}
console.log('set1 equals set2 =', areSetsEqual(set1, set2))
console.log('set1 equals set4 =', areSetsEqual(set1, set4))
set1 equals set2 = true
set1 等于 set2 = true
set1 equals set4 = false
set1 等于 set4 = false

