[javascript] JavaScript 세트의 객체 동등성을 사용자 정의하는 방법

New ES 6 (Harmony)에 새로운 Set 객체가 도입되었습니다 . Set에서 사용하는 ID 알고리즘은 ===연산자와 유사 하므로 객체를 비교하는 데 적합하지 않습니다.

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

심도있는 오브젝트 비교를 위해 Set 오브젝트에 대해 동등성을 사용자 정의하는 방법은 무엇입니까? Java와 같은 것이 equals(Object)있습니까?



답변

ES6 Set객체에는 비교 방법이나 사용자 지정 비교 확장 성이 없습니다.

.has(), .add().delete()방법은 그 프리미티브에 대해 동일한 실제 객체 또는 동일한 가치 오프 동작과에 연결하는 수단을 가지고 아니면 논리 것으로 대체하지 않는다.

당신은 아마도에서 자신의 개체를 도출 할 수 Set및 교체 .has(), .add().delete()항목 설정에 이미있는 경우 깊은 개체를 비교 한 뭔가 방법을 먼저 찾아야하지만, 기본부터 성능은 가능성이 좋지 않을 것 Set오브젝트가 도움이되지 않을 것이다 조금도. original을 호출하기 전에 사용자 정의 비교를 사용하여 일치하는 항목을 찾으려면 기존의 모든 객체를 통해 무차별 강제 반복을 수행해야 할 것입니다 .add().

이 기사의 정보 ES6 기능에 대한 토론 은 다음과 같습니다 .

5.2 맵과 세트가 키와 값을 비교하는 방법을 구성 할 수없는 이유는 무엇입니까?

질문 : 어떤 맵 키와 동일한 설정 요소를 구성 할 수있는 방법이 있다면 좋을 것입니다. 왜 없습니까?

답변 :이 기능은 적절하고 효율적으로 구현하기 어렵 기 때문에 연기되었습니다. 한 가지 옵션은 콜백을 동등성을 지정하는 컬렉션으로 전달하는 것입니다.

Java에서 사용할 수있는 또 다른 옵션은 객체가 구현하는 메소드 (Java의 equals ())를 통해 동등성을 지정하는 것입니다. 그러나이 방법은 변경 가능한 객체에 문제가 있습니다. 일반적으로 객체가 변경되면 컬렉션 내부의 “위치”도 변경되어야합니다. 그러나 그것은 Java에서 일어나는 일이 아닙니다. 자바 스크립트는 아마도 불변의 특수 객체 (값 객체)의 값으로 만 비교할 수있는 안전한 경로 일 것입니다. 값을 기준으로 비교하면 내용이 동일한 경우 두 값이 동일한 것으로 간주됩니다. 기본 값은 JavaScript에서 값으로 비교됩니다.


답변

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메소드가 동일한 값을 리턴하는 경우에만 두 오브젝트가 동일한 것으로 간주됩니다 .


답변

최고 답변에서 언급 했듯이 등식 사용자 정의는 가변 객체에 문제가 있습니다. 좋은 소식은 (아직 아무도 이것을 언급하지 않은 것에 놀랐습니다) 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}]


답변

여기에 답변을 추가하기 위해 사용자 정의 해시 함수, 사용자 정의 평등 함수를 사용하고 버킷에 동등한 (사용자 정의) 해시가있는 고유 값을 저장하는 맵 래퍼를 구현했습니다.

예상대로 czerny의 문자열 연결 방법 보다 느립니다 .

여기에 전체 소스 : https://github.com/makoConstruct/ValueMap


답변

직접 비교하는 것은 불가능한 것처럼 보이지만 키가 방금 정렬 된 경우 JSON.stringify가 작동합니다. 의견에서 지적했듯이

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

그러나 사용자 정의 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을 사용합니다. 그러나 객체 대신 문자열 집합을 사용합니다.

let set = new Set()
set.add({a:1, b:2}.stringifySorted());

set.has({b:2, a:1}.stringifySorted());
// returns true

모든 가치를 얻으십시오

세트를 생성하고 값을 추가 한 후 모든 값을 얻을 수 있습니다.

let iterator = set.values();
let done = false;
while (!done) {
  let val = iterator.next();

  if (!done) {
    console.log(val.value);
  }
  done = val.done;
}

하나의 파일에 모두 링크가 있습니다
http://tpcg.io/FnJg2i


답변

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


답변

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

구현 예제는 다음과 같습니다 stringifyKey. 메소드를 재정의하십시오 . 내 경우에는 일부 uri속성을 지정합니다.

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

이 예제는 일반적인 것처럼 사용합니다 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