La nouvelle ES 6 (Harmony) introduit un nouvel objet Set . L'algorithme d'identité utilisé par Set est similaire à l'opérateur ===
Et n'est donc pas très approprié pour comparer des objets:
var set = new Set();
set.add({a:1});
set.add({a:1});
console.log([...set.values()]); // Array [ Object, Object ]
Comment personnaliser l'égalité pour les objets Set afin d'effectuer une comparaison d'objet approfondie? Existe-t-il quelque chose comme Java equals(Object)
]?
L'objet ES6 Set
ne dispose d'aucune méthode de comparaison ni d'extensibilité de comparaison personnalisée.
Les méthodes .has()
, .add()
et .delete()
fonctionnent uniquement si elles sont identiques au même objet ou à la même valeur pour une primitive et n'ont pas le moyen de s'y connecter ou remplacer simplement cette logique.
Vous pourriez probablement dériver votre propre objet d'un Set
et remplacer les méthodes .has()
, .add()
et .delete()
par quelque chose qui a d'abord comparé les objets en profondeur pour rechercher si l'élément est déjà dans l'ensemble, mais les performances ne seraient probablement pas bonnes, car l'objet Set
sous-jacent ne serait d'aucune utilité. Vous devrez probablement effectuer une itération forcée sur tous les objets existants pour trouver une correspondance à l'aide de votre propre comparaison personnalisée avant d'appeler l'original .add()
.
Voici quelques informations de cet article et cette discussion sur les fonctionnalités de l'ES6:
5.2 Pourquoi ne puis-je pas configurer comment les cartes et les ensembles comparent les clés et les valeurs?
Question: Ce serait bien s’il y avait un moyen de configurer quelles clés de la carte et quels éléments de l’ensemble sont considérés égaux. Pourquoi n’est-ce pas?
Réponse: Cette fonctionnalité a été reportée car il est difficile à mettre en œuvre correctement et efficacement. Une option consiste à remettre des rappels aux collections qui spécifient l'égalité.
Une autre option, disponible en Java, consiste à spécifier l'égalité via une méthode que l'objet implémente (equals () en Java). Cependant, cette approche est problématique pour les objets mutables: en général, si un objet change, son "emplacement" dans une collection doit également changer. Mais ce n’est pas ce qui se passe en Java. JavaScript sera probablement le moyen le plus sûr de ne permettre que la comparaison par valeur pour des objets immuables spéciaux (appelés objets de valeur). La comparaison par valeur signifie que deux valeurs sont considérées égales si leur contenu est égal. Les valeurs primitives sont comparées par valeur en JavaScript.
Comme mentionné dans réponse de jfriend , la personnalisation de la relation d'égalité est probablement impossible.
Le code suivant présente un aperçu de l'efficacité des calculs (mais de la mémoire est coûteuse) solution de contournement:
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());
}
// ...
}
Chaque élément inséré doit implémenter la méthode toIdString()
qui retourne une chaîne. Deux objets sont considérés égaux si et seulement si leurs méthodes toIdString
renvoient la même valeur.
Pour ajouter aux réponses ici, je suis allé de l'avant et j'ai implémenté un wrapper de carte qui prend une fonction de hachage personnalisée, une fonction d'égalité personnalisée et stocke des valeurs distinctes qui ont des hachages équivalents (personnalisés) dans des compartiments.
Comme prévu, il s'est avéré être plus lent que méthode de concaténation de chaînes de czerny .
Source complète ici: https://github.com/makoConstruct/ValueMap
Les comparer directement ne semble pas possible, mais JSON.stringify fonctionne si les clés ont été triées. Comme je l'ai souligné dans un commentaire
JSON.stringify ({a: 1, b: 2})! == JSON.stringify ({b: 2, a: 1});
Mais nous pouvons contourner ce problème avec une méthode stringify personnalisée. D'abord nous écrivons la méthode
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);
}
L'ensemble
Maintenant, nous utilisons un ensemble. Mais nous utilisons un ensemble de chaînes au lieu d'objets
let set = new Set()
set.add({a:1, b:2}.stringifySorted());
set.has({b:2, a:1}.stringifySorted());
// returns true
Obtenir toutes les valeurs
Après avoir créé le jeu et ajouté les valeurs, nous pouvons obtenir toutes les valeurs en
let iterator = set.values();
let done = false;
while (!done) {
let val = iterator.next();
if (!done) {
console.log(val.value);
}
done = val.done;
}
Voici un lien avec tout dans un fichier http://tpcg.io/FnJg2i
Peut-être pouvez-vous essayer d'utiliser JSON.stringify()
pour comparer les objets en profondeur.
par exemple :
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);
Comme le réponse du haut le mentionne, la personnalisation de l'égalité est problématique pour les objets mutables. La bonne nouvelle est (et je suis étonné que personne ne l’ait encore mentionné). Il existe une bibliothèque très populaire appelée immutable-js , qui fournit un riche ensemble de types immuables qui fournissent la valeur deep value sémantique d’égalité vous cherchez.
Voici votre exemple utilisant 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}]
Créez un nouvel ensemble à partir de la combinaison des deux ensembles, puis comparez la longueur.
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 est égal à set2 = true
set1 est égal à set4 = false
Pour les utilisateurs de TypeScript, les réponses des autres (en particulier czerny ) peuvent être généralisées à une classe de base de type Nice et réutilisable:
/**
* 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;
}
Voici un exemple d'implémentation simple: écrasez simplement la méthode stringifyKey
. Dans mon cas, je chaîne quelques propriétés uri
.
class MyMap extends StringifyingMap<MyKey, MyValue> {
protected stringifyKey(key: MyKey): string {
return key.uri.toString();
}
}
Exemple d'utilisation est alors comme s'il s'agissait d'un 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