¿Imitando conjuntos en JavaScript?
Estoy trabajando en JavaScript. Me gustaría almacenar una lista de valores de cadena únicos y desordenados, con las siguientes propiedades:
- ¿Una forma rápida de preguntar '¿está A en la lista'?
- una forma rápida de 'eliminar A de la lista si existe en la lista'
- una forma rápida de "agregar A a la lista si aún no está presente".
Lo que realmente quiero es un set. ¿Alguna sugerencia sobre la mejor manera de imitar un conjunto en JavaScript?
Esta pregunta recomienda usar un Object , con las claves almacenando propiedades y todos los valores establecidos en verdadero: ¿es esa una forma sensata?
Si está programando en un entorno compatible con ES6 (como node.js, un navegador específico con las capacidades de ES6 que necesita o transpilando código ES6 para su entorno), puede utilizar el Set
objeto integrado en ES6 . Tiene muy buenas capacidades y se puede utilizar tal cual en su entorno.
Para muchas cosas simples en un entorno ES5, usar un Objeto funciona muy bien. Si obj
es su objeto y A
es una variable que tiene el valor con el que desea operar en el conjunto, entonces puede hacer lo siguiente:
Código de inicialización:
// create empty object
var obj = {};
// or create an object with some items already in it
var obj = {"1":true, "2":true, "3":true, "9":true};
Pregunta 1: Está A
en la lista:
if (A in obj) {
// put code here
}
Pregunta 2: Elimine 'A' de la lista si está ahí:
delete obj[A];
Pregunta 3: agregue 'A' a la lista si aún no estaba allí
obj[A] = true;
Para completar, la prueba de si A
está en la lista es un poco más segura con esto:
if (Object.prototype.hasOwnProperty.call(obj, A))
// put code here
}
debido a un posible conflicto entre métodos integrados y/o propiedades en el objeto base como la constructor
propiedad.
Barra lateral en ES6: la versión funcional actual de ECMAScript 6 o algo así llamado ES 2015 tiene un objeto Set incorporado . Está implementado ahora en algunos navegadores. Dado que la disponibilidad del navegador cambia con el tiempo, puede consultar la línea de Set
esta tabla de compatibilidad de ES6 para ver el estado actual de la disponibilidad del navegador.
Una ventaja del objeto Set incorporado es que no obliga a todas las claves a una cadena como lo hace el Objeto, por lo que puede tener 5 y "5" como claves separadas. E incluso puedes usar Objetos directamente en el conjunto sin una conversión de cadena. Aquí hay un artículo que describe algunas de las capacidades y la documentación de MDN sobre el objeto Set.
Ahora he escrito un polyfill para el objeto de conjunto de ES6 para que pueda comenzar a usarlo ahora y se transferirá automáticamente al objeto de conjunto integrado si el navegador lo admite. Esto tiene la ventaja de que está escribiendo código compatible con ES6 que funcionará hasta IE7. Pero hay algunas desventajas. La interfaz del conjunto de ES6 aprovecha los iteradores de ES6 para que pueda hacer cosas como for (item of mySet)
y automáticamente iterará a través del conjunto por usted. Sin embargo, este tipo de característica de lenguaje no se puede implementar mediante polyfill. Aún puede iterar un conjunto de ES6 sin utilizar las nuevas funciones de idiomas de ES6, pero, francamente, sin las nuevas funciones de idioma, no es tan conveniente como la otra interfaz de conjunto que incluyo a continuación.
Puedes decidir cuál funciona mejor para ti después de mirar ambos. El conjunto de polyfill de ES6 está aquí: https://github.com/jfriend00/ES6-Set .
Para su información, en mis propias pruebas, noté que la implementación del conjunto Firefox v29 no está completamente actualizada con respecto al borrador actual de la especificación. Por ejemplo, no puede encadenar .add()
llamadas a métodos como lo describe la especificación y como lo admite mi polyfill. Probablemente se trate de una especificación en marcha ya que aún no está finalizada.
Objetos de conjuntos prediseñados: si desea un objeto ya creado que tenga métodos para operar en un conjunto que pueda usar en cualquier navegador, puede usar una serie de diferentes objetos prediseñados que implementen diferentes tipos de conjuntos. Hay un miniSet que es un código pequeño que implementa los conceptos básicos de un objeto conjunto. También tiene un conjunto de objetos con más funciones y varias derivaciones, incluido un diccionario (le permite almacenar/recuperar un valor para cada clave) y un conjunto de objetos (le permite mantener un conjunto de objetos, ya sean objetos JS u objetos DOM donde proporciona el función que genera una clave única para cada uno o el ObjectSet generará la clave por usted).
Aquí hay una copia del código del miniSet (el código más actualizado está aquí en github ).
"use strict";
//-------------------------------------------
// Simple implementation of a Set in javascript
//
// Supports any element type that can uniquely be identified
// with its string conversion (e.g. toString() operator).
// This includes strings, numbers, dates, etc...
// It does not include objects or arrays though
// one could implement a toString() operator
// on an object that would uniquely identify
// the object.
//
// Uses a javascript object to hold the Set
//
// This is a subset of the Set object designed to be smaller and faster, but
// not as extensible. This implementation should not be mixed with the Set object
// as in don't pass a miniSet to a Set constructor or vice versa. Both can exist and be
// used separately in the same project, though if you want the features of the other
// sets, then you should probably just include them and not include miniSet as it's
// really designed for someone who just wants the smallest amount of code to get
// a Set interface.
//
// s.add(key) // adds a key to the Set (if it doesn't already exist)
// s.add(key1, key2, key3) // adds multiple keys
// s.add([key1, key2, key3]) // adds multiple keys
// s.add(otherSet) // adds another Set to this Set
// s.add(arrayLikeObject) // adds anything that a subclass returns true on _isPseudoArray()
// s.remove(key) // removes a key from the Set
// s.remove(["a", "b"]); // removes all keys in the passed in array
// s.remove("a", "b", ["first", "second"]); // removes all keys specified
// s.has(key) // returns true/false if key exists in the Set
// s.isEmpty() // returns true/false for whether Set is empty
// s.keys() // returns an array of keys in the Set
// s.clear() // clears all data from the Set
// s.each(fn) // iterate over all items in the Set (return this for method chaining)
//
// All methods return the object for use in chaining except when the point
// of the method is to return a specific value (such as .keys() or .isEmpty())
//-------------------------------------------
// polyfill for Array.isArray
if(!Array.isArray) {
Array.isArray = function (vArg) {
return Object.prototype.toString.call(vArg) === "[object Array]";
};
}
function MiniSet(initialData) {
// Usage:
// new MiniSet()
// new MiniSet(1,2,3,4,5)
// new MiniSet(["1", "2", "3", "4", "5"])
// new MiniSet(otherSet)
// new MiniSet(otherSet1, otherSet2, ...)
this.data = {};
this.add.apply(this, arguments);
}
MiniSet.prototype = {
// usage:
// add(key)
// add([key1, key2, key3])
// add(otherSet)
// add(key1, [key2, key3, key4], otherSet)
// add supports the EXACT same arguments as the constructor
add: function() {
var key;
for (var i = 0; i < arguments.length; i++) {
key = arguments[i];
if (Array.isArray(key)) {
for (var j = 0; j < key.length; j++) {
this.data[key[j]] = key[j];
}
} else if (key instanceof MiniSet) {
var self = this;
key.each(function(val, key) {
self.data[key] = val;
});
} else {
// just a key, so add it
this.data[key] = key;
}
}
return this;
},
// private: to remove a single item
// does not have all the argument flexibility that remove does
_removeItem: function(key) {
delete this.data[key];
},
// usage:
// remove(key)
// remove(key1, key2, key3)
// remove([key1, key2, key3])
remove: function(key) {
// can be one or more args
// each arg can be a string key or an array of string keys
var item;
for (var j = 0; j < arguments.length; j++) {
item = arguments[j];
if (Array.isArray(item)) {
// must be an array of keys
for (var i = 0; i < item.length; i++) {
this._removeItem(item[i]);
}
} else {
this._removeItem(item);
}
}
return this;
},
// returns true/false on whether the key exists
has: function(key) {
return Object.prototype.hasOwnProperty.call(this.data, key);
},
// tells you if the Set is empty or not
isEmpty: function() {
for (var key in this.data) {
if (this.has(key)) {
return false;
}
}
return true;
},
// returns an array of all keys in the Set
// returns the original key (not the string converted form)
keys: function() {
var results = [];
this.each(function(data) {
results.push(data);
});
return results;
},
// clears the Set
clear: function() {
this.data = {};
return this;
},
// iterate over all elements in the Set until callback returns false
// myCallback(key) is the callback form
// If the callback returns false, then the iteration is stopped
// returns the Set to allow method chaining
each: function(fn) {
this.eachReturn(fn);
return this;
},
// iterate all elements until callback returns false
// myCallback(key) is the callback form
// returns false if iteration was stopped
// returns true if iteration completed
eachReturn: function(fn) {
for (var key in this.data) {
if (this.has(key)) {
if (fn.call(this, this.data[key], key) === false) {
return false;
}
}
}
return true;
}
};
MiniSet.prototype.constructor = MiniSet;
Puedes crear un objeto sin propiedades como
var set = Object.create(null)
que puede actuar como un conjunto y elimina la necesidad de usar hasOwnProperty
.
var set = Object.create(null); // create an object with no properties
if (A in set) { // 1. is A in the list
// some code
}
delete set[a]; // 2. delete A from the list if it exists in the list
set[A] = true; // 3. add A to the list if it is not already present