objetos - prototype javascript ejemplos
Cómo personalizar la igualdad de objetos para JavaScript Set (9)
El nuevo ES 6 (Harmony) presenta un nuevo objeto
Set
.
El algoritmo de identidad utilizado por Set es similar al operador
===
y, por lo tanto, no es muy adecuado para comparar objetos:
var set = new Set();
set.add({a:1});
set.add({a:1});
console.log([...set.values()]); // Array [ Object, Object ]
¿Cómo personalizar la igualdad para establecer objetos para hacer una comparación profunda de objetos?
¿Hay algo como Java
equals(Object)
?
Como se menciona en la respuesta de jfriend00, la personalización de la relación de igualdad probablemente no sea posible .
El siguiente código presenta un esquema de solución computacionalmente eficiente (pero costosa en memoria):
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());
}
// ...
}
Cada elemento insertado tiene que implementar el método
toIdString()
que devuelve una cadena.
Dos objetos se consideran iguales si y solo si sus métodos
toIdString
devuelven el mismo valor.
Como se menciona en la respuesta principal , personalizar la igualdad es problemático para los objetos mutables. La buena noticia es que (y me sorprende que nadie haya mencionado esto todavía) hay una biblioteca muy popular llamada immutable-js que proporciona un rico conjunto de tipos inmutables que proporcionan la semántica de igualdad de valor profundo que está buscando.
Aquí está su ejemplo usando 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}]
Cree un nuevo conjunto a partir de la combinación de ambos conjuntos, luego compare la longitud.
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 es igual a set2 = verdadero
set1 es igual a set4 = falso
El objeto
Set
ES6 no tiene ningún método de comparación ni extensibilidad de comparación personalizada.
Los
.has()
,
.add()
y
.delete()
funcionan solo si es el mismo objeto real o el mismo valor para una primitiva y no tienen un medio para conectar o reemplazar solo esa lógica.
Probablemente podría derivar su propio objeto de un
Set
y reemplazar los
.has()
,
.add()
y
.delete()
con algo que primero hizo una comparación profunda de objetos para encontrar si el elemento ya está en el Conjunto, pero el rendimiento probablemente no sería bueno ya que el objeto
Set
subyacente no ayudaría en absoluto.
Probablemente tenga que hacer una iteración de fuerza bruta a través de todos los objetos existentes para encontrar una coincidencia utilizando su propia comparación personalizada antes de llamar al
.add()
original
.add()
.
Aquí hay información de este artículo y discusión sobre las características de ES6:
5.2 ¿Por qué no puedo configurar cómo los mapas y conjuntos comparan claves y valores?
Pregunta: Sería bueno si hubiera una manera de configurar qué teclas del mapa y qué elementos del conjunto se consideran iguales. ¿Por qué no está ahí?
Respuesta: Esa característica se ha pospuesto, ya que es difícil de implementar de manera adecuada y eficiente. Una opción es entregar devoluciones de llamada a colecciones que especifiquen igualdad.
Otra opción, disponible en Java, es especificar la igualdad a través de un método que implemente el objeto (equals () en Java). Sin embargo, este enfoque es problemático para los objetos mutables: en general, si un objeto cambia, su "ubicación" dentro de una colección también tiene que cambiar. Pero eso no es lo que sucede en Java. JavaScript probablemente irá por la ruta más segura de habilitar solo la comparación por valor para objetos inmutables especiales (los llamados objetos de valor). La comparación por valor significa que dos valores se consideran iguales si su contenido es igual. Los valores primitivos se comparan por valor en JavaScript.
No es posible compararlos directamente, pero JSON.stringify funciona si las claves se acaban de ordenar. Como señalé en un comentario
JSON.stringify ({a: 1, b: 2})! == JSON.stringify ({b: 2, a: 1});
Pero podemos solucionar eso con un método de stringify personalizado. Primero escribimos el método
Stringify personalizado
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);
}
El conjunto
Ahora usamos un conjunto. Pero usamos un conjunto de cadenas en lugar de objetos
let set = new Set()
set.add({a:1, b:2}.stringifySorted());
set.has({b:2, a:1}.stringifySorted());
// returns true
Obtén todos los valores
Después de crear el conjunto y agregar los valores, podemos obtener todos los valores al
let iterator = set.values();
let done = false;
while (!done) {
let val = iterator.next();
if (!done) {
console.log(val.value);
}
done = val.done;
}
Aquí hay un enlace con todo en un archivo http://tpcg.io/FnJg2i
Para agregar a las respuestas aquí, seguí adelante e implementé un contenedor de mapas que toma una función hash personalizada, una función de igualdad personalizada y almacena valores distintos que tienen hashes equivalentes (personalizados) en cubos.
Como era de esperar, resultó ser más lento que el método de concatenación de cadenas de Czerny .
Fuente completa aquí: https://github.com/makoConstruct/ValueMap
Para alguien que encontró esta pregunta en Google (como yo) que desea obtener un valor de un Mapa usando un objeto como Clave:
Advertencia: esta respuesta no funcionará con todos los objetos
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");
Salida:
Trabajó
Para los usuarios de Typecript, las respuestas de otros (especialmente ) se pueden generalizar a una clase base agradable, segura y reutilizable:
/**
* 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;
}
La implementación de ejemplo es así de simple: simplemente anule el método
stringifyKey
.
En mi caso, stringifico alguna propiedad
uri
.
class MyMap extends StringifyingMap<MyKey, MyValue> {
protected stringifyKey(key: MyKey): string {
return key.uri.toString();
}
}
El uso de ejemplo es entonces como si fuera un
Map<K, V>
normal.
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
Tal vez pueda intentar usar
JSON.stringify()
para hacer una comparación profunda de objetos.
por ejemplo :
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);