Как настроить равномерность JavaScript для JavaScript

Новый 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)?

Ответ 1

Объект ES6 Set не имеет методов сравнения или настраиваемой расширяемости.

Методы .has(), .add() и .delete() работают только с того, что это один и тот же фактический объект или одно и то же значение для примитива и не имеют возможности подключать или заменять только эту логику.

Вы можете предположительно получить свой собственный объект из Set и заменить методы .has(), .add() и .delete() тем, что сначала провело глубокое сопоставление объектов, чтобы определить, находится ли элемент уже в Set, но производительность, вероятно, не будет хорошей, поскольку базовый объект Set не будет вообще помогать. Вам, вероятно, придется просто выполнить итерацию грубой силы по всем существующим объектам, чтобы найти совпадение, используя собственное пользовательское сравнение, прежде чем вызывать оригинал .add().

Вот некоторая информация из этой статьи и обсуждения функций ES6:

5.2 Почему не удается настроить, как карты и наборы сравнивают ключи и значения?

Вопрос: Было бы неплохо, если бы был способ настроить какую карту ключи и какие установленные элементы считаются равными. Почему нет?

Ответ: эта функция отложена, так как трудно осуществлять надлежащим образом и эффективно. Один из вариантов - передать обратные вызовы коллекции, которые определяют равенство.

Другим вариантом, доступным в Java, является указание равенства посредством метода этот объект реализует (equals() в Java). Однако этот подход проблематично для изменяемых объектов: В общем случае, если объект изменяется, его "местоположение" внутри коллекции также должно измениться. Но это не что происходит на Java. Вероятно, JavaScript, вероятно, станет более безопасным только для сравнения по значению для специальных неизменяемых объектов (так называемые объекты ценности). Сравнение по значению означает, что два значения считаются равными, если их содержимое равно. Примитивные значения сравнивается по значению в JavaScript.

Ответ 2

Как уже упоминалось в ответе 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 возвращают одно и то же значение.

Ответ 3

Чтобы добавить к ответам здесь, я пошел дальше и реализовал оболочку карты, которая принимает пользовательскую хеш-функцию, пользовательскую функцию равенства и сохраняет различные значения, которые имеют эквивалентные (пользовательские) хэши в кодах.

Как и ожидалось, он оказался медленнее, чем метод конкатенации czerny.

Полный источник здесь: https://github.com/makoConstruct/ValueMap

Ответ 4

Сравнивать их напрямую кажется невозможным, но JSON.stringify работает, если ключи были отсортированы. Как я указал в комментарии

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

Но мы можем обойти это с помощью специального метода 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);
}

Набор

Теперь мы используем набор. Но мы используем набор строк вместо объектов

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

Ответ 5

Возможно, вы попытаетесь использовать JSON.stringify() для глубокого сравнения объектов.

например:

  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(результат);код>

Ответ 6

Как отмечается в верхнем ответе, настройка равенства проблематична для изменяемых объектов. Хорошая новость в том, что (и я удивлен, что никто еще не упомянул об этом) существует очень популярная библиотека с именем 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}]

Ответ 7

Создайте новый набор из комбинации обоих наборов, затем сравните длину.

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 равно set2 = true

set1 равно set4 = false

Ответ 8

Для пользователей 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

Ответ 9

Для того, кто нашел этот вопрос в Google (как я) и хотел получить значение карты, используя объект в качестве ключа:

Предупреждение: этот ответ не будет работать со всеми объектами

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

Выход:

Работал