How to adjust JavaScript uniformity for JavaScript

New ES 6 (Harmony) Introduces New Set Object. The identification algorithm used by Set is similar to the === operator and therefore is not suitable for comparing objects:

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

How to set equality for Set objects for deep comparison of objects? Is there something like Java equals(Object) ?

+124
javascript set ecmascript-harmony
Apr 20 '15 at 22:22
source share
9 answers

The ES6 Set object has no comparison methods or custom extensibility.

The .has() , .add() and .delete() work only because they are the same actual object or the same value for the primitive and cannot connect or replace only this logic.

You can presumably get your own object from Set and replace the .has() , .add() and .delete() methods with first that you did a deep mapping of the objects to determine if the element is already in Set, but the performance is probably will not be good, since the base Set object will not help at all. You probably have to just iterate the brute force over all existing objects to find a match using your own custom comparison before calling the original .add() .

Here is some information from this article and a discussion of ES6 features:

5.2 Why can't I configure how maps and sets compare keys and values?

Question: It would be nice if there was a way to configure which card keys and which installed elements are considered equal. Why not?

Answer: this function is delayed, since it is difficult to carry out properly and efficiently. One option is to pass collection callbacks that define equality.

Another option available in Java is to specify equality through a method that this object implements (equals () in Java). However, this approach is problematic for mutable objects: In general, if an object changes, its "location" inside the collection must also change. But this is not what happens in Java. JavaScript is likely to become more secure only for 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.

+85
Apr 20 '15 at 10:40
source share

As mentioned in jfriend00's answer, setting up an equality relationship is probably not possible .

The following code represents a computationally efficient (but 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 element inserted must implement toIdString() which returns a string. Two objects are considered equal if and only if their toIdString methods return the same value.

+26
Apr 21 '15 at 21:40
source share

To add to the answers here, I went ahead and implemented a map wrapper that accepts a custom hash function, a custom equality function, and stores various values ​​that have equivalent (custom) hashes in the codes.

As expected, it turned out to be slower than the czerny concatenation method .

Full source here: https://github.com/makoConstruct/ValueMap

+4
Dec 05 '16 at 1:33
source share

Comparing them directly seems impossible, but JSON.stringify works if the keys have been sorted. As I pointed out in the comment

JSON.stringify ({a: 1, b: 2})! == JSON.stringify ({b: 2, a: 1});

But we can get around this with the special stringify method. First we write a method

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); } 

Set

Now we use the kit. But we use a rowset 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 values

After we created the set and added the values, we can get all the values

 let iterator = set.values(); let done = false; while (!done) { let val = iterator.next(); if (!done) { console.log(val.value); } done = val.done; } 

Here is a link with everyone in one file http://tpcg.io/FnJg2i

+3
Nov 18 '18 at 15:02
source share

You might be trying to use JSON.stringify() for a deep comparison of objects.

eg:

  const arr = [ {name: 'a', value: 10}, {name: 'a', value: 20}, {name: 'a', value: 20}, {name: 'b', : 30}, {name: 'b', : 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();> 
+2
Apr 11 '18 at 19:09
source share

As noted in the top answer , setting equality is problematic for mutable objects. The good news is (and I'm surprised no one has mentioned this yet) that there is a very popular library called immutable-js that provides a rich set of immutable types that provide the semantics of deep equality of values you are looking for.

Here is your example using 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}] 
+1
May 29 '19 at 5:43
source share

Create a new set from a 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 is set2 = true

set1 is set4 = false

0
Jul 03 '19 at 21:20
source share

For Typescript users , the answers of others (especially czerny ) can be generalized into a good type-safe and reusable base class:

 /** * 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; } 

In this case, the implementation of the example will be so simple: just override the stringifyKey method. In my case, I am making the uri string property.

 class MyMap extends StringifyingMap<MyKey, MyValue> { protected stringifyKey(key: MyKey): string { return key.uri.toString(); } } 

An example of use then is as if it were a regular 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 
0
Jul 6 '19 at 9:21
source share

For someone who found this question on Google (like me) and wanted to get the map value using the object as a key:

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"); 

Exit:

Worked

-one
Apr 20 '19 at 18:47
source share



All Articles