How to customize object equality for JavaScript Set

后端 未结 9 1273
臣服心动
臣服心动 2020-11-22 16:48

New ES 6 (Harmony) introduces new Set object. Identity algorithm used by Set is similar to === operator and so not much suitable for comparing objects:

相关标签:
9条回答
  • 2020-11-22 17:11

    For Typescript users the answers by others (especially czerny) can be generalized to a nice 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;
    }
    
    

    Example implementation is then this simple: just override the stringifyKey method. In my case I stringify some uri property.

    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>.

    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 讨论(0)
  • 2020-11-22 17:16

    Maybe you can try to use JSON.stringify() to do deep object comparison.

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

    0 讨论(0)
  • 2020-11-22 17:17

    As the top answer mentions, 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-js that provides a rich set of immutable types which provide the deep value equality semantics you're looking for.

    Here's 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}]
    
    0 讨论(0)
提交回复
热议问题