resumen qué que porque objetivos identificar como codigo archivo actualidad javascript data-structures language-features hashmap

javascript - que - qué es js



Equivalente de Hashmap de JavaScript (17)

Descripción del problema

JavaScript no tiene un tipo de mapa general incorporado (a veces llamado matriz asociativa o diccionario ) que permite acceder a valores arbitrarios mediante claves arbitrarias. La estructura de datos fundamental de JavaScript es el objeto , un tipo especial de mapa que solo acepta cadenas como claves y tiene una semántica especial como herencia prototípica, captadores y configuradores y algunos vudú más.

Cuando utilice objetos como mapas, debe recordar que la clave se convertirá en un valor de cadena a través de toString() , lo que da como resultado la asignación de 5 y ''5'' al mismo valor y todos los objetos que no sobrescriben a toString() Método al valor indexado por ''[object Object]'' . También puede acceder involuntariamente a sus propiedades heredadas si no verifica hasOwnProperty() .

El tipo de matriz incorporada de JavaScript no ayuda en nada: las matrices de JavaScript no son matrices asociativas, sino objetos con algunas propiedades especiales más. Si desea saber por qué no se pueden utilizar como mapas, consulte aquí .

La solución de Eugene

Eugene Lazutkin ya describió la idea básica de usar una función hash personalizada para generar cadenas únicas que se pueden usar para buscar los valores asociados como propiedades de un objeto de diccionario. Esta será la solución más rápida, ya que los objetos se implementan internamente como tablas hash .

  • Nota: las tablas hash (a veces llamadas mapas hash ) son una implementación particular del concepto de mapa que utiliza una matriz de respaldo y la búsqueda a través de valores hash numéricos. El entorno de ejecución puede usar otras estructuras (como los árboles de búsqueda o las listas de omisión ) para implementar objetos de JavaScript, pero como los objetos son la estructura de datos fundamental, deberían estar lo suficientemente optimizados.

Para obtener un valor de hash único para objetos arbitrarios, una posibilidad es usar un contador global y almacenar en caché el valor de hash en el objeto en sí (por ejemplo, en una propiedad llamada __hash ).

Una función hash que hace esto es y funciona tanto para valores primitivos como para objetos:

function hash(value) { return (typeof value) + '' '' + (value instanceof Object ? (value.__hash || (value.__hash = ++arguments.callee.current)) : value.toString()); } hash.current = 0;

Esta función se puede utilizar como lo describe Eugene. Para mayor comodidad, lo incluiremos en una clase de Map .

Implementación de mi Map

La siguiente implementación almacenará adicionalmente los pares clave-valor en una lista doblemente enlazada para permitir una rápida iteración sobre las claves y los valores. Para proporcionar su propia función hash, puede sobrescribir el método hash() la instancia después de la creación.

// linking the key-value-pairs is optional // if no argument is provided, linkItems === undefined, i.e. !== false // --> linking will be enabled function Map(linkItems) { this.current = undefined; this.size = 0; if(linkItems === false) this.disableLinking(); } Map.noop = function() { return this; }; Map.illegal = function() { throw new Error("illegal operation for maps without linking"); }; // map initialisation from existing object // doesn''t add inherited properties if not explicitly instructed to: // omitting foreignKeys means foreignKeys === undefined, i.e. == false // --> inherited properties won''t be added Map.from = function(obj, foreignKeys) { var map = new Map; for(var prop in obj) { if(foreignKeys || obj.hasOwnProperty(prop)) map.put(prop, obj[prop]); } return map; }; Map.prototype.disableLinking = function() { this.link = Map.noop; this.unlink = Map.noop; this.disableLinking = Map.noop; this.next = Map.illegal; this.key = Map.illegal; this.value = Map.illegal; this.removeAll = Map.illegal; return this; }; // overwrite in Map instance if necessary Map.prototype.hash = function(value) { return (typeof value) + '' '' + (value instanceof Object ? (value.__hash || (value.__hash = ++arguments.callee.current)) : value.toString()); }; Map.prototype.hash.current = 0; // --- mapping functions Map.prototype.get = function(key) { var item = this[this.hash(key)]; return item === undefined ? undefined : item.value; }; Map.prototype.put = function(key, value) { var hash = this.hash(key); if(this[hash] === undefined) { var item = { key : key, value : value }; this[hash] = item; this.link(item); ++this.size; } else this[hash].value = value; return this; }; Map.prototype.remove = function(key) { var hash = this.hash(key); var item = this[hash]; if(item !== undefined) { --this.size; this.unlink(item); delete this[hash]; } return this; }; // only works if linked Map.prototype.removeAll = function() { while(this.size) this.remove(this.key()); return this; }; // --- linked list helper functions Map.prototype.link = function(item) { if(this.size == 0) { item.prev = item; item.next = item; this.current = item; } else { item.prev = this.current.prev; item.prev.next = item; item.next = this.current; this.current.prev = item; } }; Map.prototype.unlink = function(item) { if(this.size == 0) this.current = undefined; else { item.prev.next = item.next; item.next.prev = item.prev; if(item === this.current) this.current = item.next; } }; // --- iterator functions - only work if map is linked Map.prototype.next = function() { this.current = this.current.next; }; Map.prototype.key = function() { return this.current.key; }; Map.prototype.value = function() { return this.current.value; };

Ejemplo

El siguiente guion

var map = new Map; map.put(''spam'', ''eggs''). put(''foo'', ''bar''). put(''foo'', ''baz''). put({}, ''an object''). put({}, ''another object''). put(5, ''five''). put(5, ''five again''). put(''5'', ''another five''); for(var i = 0; i++ < map.size; map.next()) document.writeln(map.hash(map.key()) + '' : '' + map.value());

genera esta salida:

string spam : eggs string foo : baz object 1 : an object object 2 : another object number 5 : five again string 5 : another five

Consideraciones adicionales

PEZ sugirió sobrescribir el método toString() , probablemente con nuestra función hash. Esto no es factible porque no funciona para valores primitivos (cambiar a toString() para primitivos es una idea muy mala). Si queremos que toString() devuelva valores significativos para objetos arbitrarios, tendríamos que modificar Object.prototype , que algunas personas (yo no incluido) consideramos verboten .

Edición: la versión actual de la implementación de mi Map , así como otras funciones de JavaScript, se pueden obtener aquí .

Como queda claro en la actualización 3 de esta respuesta , esta notación:

var hash = {}; hash[X]

en realidad no hash el objeto X ; en realidad, simplemente convierte X en una cadena (a través de .toString() si es un objeto, o algunas conversiones integradas para varios tipos primitivos) y luego busca esa cadena, sin hash, en " hash ". La igualdad de objetos tampoco se verifica: si dos objetos diferentes tienen la misma conversión de cadena, solo se sobrescribirán.

Dado esto, ¿hay implementaciones eficientes de hashmaps en javascript? (Por ejemplo, el segundo resultado de Google del javascript hashmap produce una implementación que es O (n) para cualquier operación. Otros resultados ignoran el hecho de que diferentes objetos con representaciones de cadena equivalentes se sobrescriben entre sí.


¿Por qué no hash tus objetos manualmente y utiliza las cadenas resultantes como claves para un diccionario de JavaScript normal? Después de todo, usted está en la mejor posición para saber qué hace que sus objetos sean únicos. Eso es lo que hago.

Ejemplo:

var key = function(obj){ // some unique object-dependent key return obj.totallyUniqueEmployeeIdKey; // just an example }; var dict = {}; dict[key(obj1)] = obj1; dict[key(obj2)] = obj2;

De esta manera, puede controlar la indexación realizada por JavaScript sin levantar demasiado la asignación de memoria y el manejo de desbordamiento.

Por supuesto, si realmente desea la "solución de grado industrial", puede crear una clase parametrizada por la función clave y con todas las API necesarias del contenedor, pero ... usamos JavaScript, y tratamos de ser simples y ligeros, por lo que Esta solución funcional es simple y rápida.

La función clave puede ser tan simple como seleccionar los atributos correctos del objeto, por ejemplo, una clave o un conjunto de claves, que ya son únicas, una combinación de claves, que son únicas juntas, o tan complejas como usar algunos hashes criptográficos como en DojoX Encoding , o DojoX UUID . Si bien las últimas soluciones pueden producir claves únicas, personalmente trato de evitarlas a toda costa, especialmente si sé qué hace que mis objetos sean únicos.

Actualización en 2014: respondida en 2008, esta solución simple aún requiere más explicaciones. Permítanme aclarar la idea en un formulario de preguntas y respuestas.

Tu solución no tiene un verdadero hash. ¿¿¿Dónde está???

JavaScript es un lenguaje de alto nivel. Su primitivo básico ( Object ) incluye una tabla hash para mantener las propiedades. Esta tabla hash generalmente se escribe en un lenguaje de bajo nivel para la eficiencia. Usando un objeto simple con claves de cadena, usamos una tabla hash implementada de manera eficiente sin ningún esfuerzo de nuestra parte.

¿Cómo sabes que usan hash?

Hay tres formas principales de mantener una colección de objetos direccionables mediante una clave:

  • Desordenado En este caso, para recuperar un objeto por su clave, debemos revisar todas las claves y detenernos cuando lo encontremos. En promedio tomará n / 2 comparaciones.
  • Ordenado.
    • Ejemplo # 1: una matriz ordenada: haciendo una búsqueda binaria encontraremos nuestra clave después de las comparaciones de ~ log2 (n) en promedio. Mucho mejor.
    • Ejemplo # 2: un árbol. Nuevamente será ~ log (n) intentos.
  • Tabla de picadillo. En promedio requiere un tiempo constante. Compare: O (n) contra O (log n) contra O (1). Auge.

Obviamente, los objetos JavaScript utilizan tablas hash de alguna forma para manejar casos generales.

¿Los proveedores de navegadores realmente usan tablas hash?

De Verdad.

¿Manejan las colisiones?

Sí. Véase más arriba. Si encontró una colisión en cadenas desiguales, no dude en presentar un error a un proveedor.

Entonces, ¿cuál es tu idea?

Si desea hashear un objeto, encuentre lo que lo hace único y utilícelo como una clave. No intente calcular hash real o emular tablas hash, ya que el objeto de JavaScript subyacente ya lo maneja de manera eficiente.

Use esta tecla con JavaScript Object para aprovechar su tabla hash incorporada mientras evita los posibles conflictos con las propiedades predeterminadas.

Ejemplos para empezar:

  • Si sus objetos incluyen un nombre de usuario único, utilícelo como clave.
  • Si incluye un número de cliente único, utilícelo como clave.
    • Si incluye números únicos emitidos por el gobierno como un SSN o un número de pasaporte, y su sistema no permite duplicados, utilícelo como una clave.
  • Si una combinación de campos es única, úsela como clave.
    • La abreviatura del estado + número de licencia de conducir es una clave excelente.
    • La abreviatura del país + el número de pasaporte también es una clave excelente.
  • Algunas funciones en los campos, o un objeto completo, pueden devolver un valor único; utilícelo como clave.

Utilicé su sugerencia y almacené en caché todos los objetos usando un nombre de usuario. ¡Pero un tipo sabio se llama "toString", que es una propiedad incorporada! ¿Qué debería hacer ahora?

Obviamente, si es incluso remotamente posible que la clave resultante se componga exclusivamente de caracteres latinos, debe hacer algo al respecto. Por ejemplo, agregue cualquier carácter Unicode que no sea latino que le guste al principio o al final para deshacer el conflicto con las propiedades predeterminadas: "#toString", "#MarySmith". Si se usa una clave compuesta, separe los componentes clave utilizando algún tipo de delimitador no latino: "nombre, ciudad, estado".

En general, este es el lugar donde tenemos que ser creativos y seleccionar las claves más fáciles con las limitaciones dadas (singularidad, posibles conflictos con las propiedades predeterminadas).

Nota: las claves únicas no chocan por definición, mientras que el Object subyacente manejará los posibles choques de hash.

¿Por qué no te gustan las soluciones industriales?

En mi humilde opinión, el mejor código es ningún código: no tiene errores, no requiere mantenimiento, es fácil de entender y se ejecuta de forma instantánea. Todas las "tablas hash en JavaScript" que vi eran> 100 líneas de código e involucraban múltiples objetos. Compáralo con: dict[key] = value .

Otro punto: ¿es incluso posible superar una interpretación de un objeto primordial escrito en un lenguaje de bajo nivel, utilizando JavaScript y los mismos objetos primordiales para implementar lo que ya está implementado?

¡Todavía quiero pinchar mis objetos sin ninguna llave!

Tenemos suerte: ECMAScript 6 (programado para el lanzamiento a mediados de 2015, más o menos uno o dos años después de que se generalice) define el map y el set .

A juzgar por la definición, pueden usar la dirección del objeto como una clave, lo que hace que los objetos se distingan instantáneamente sin claves artificiales. OTOH, dos objetos diferentes, pero idénticos, se asignarán como distintos.


De acuerdo con ECMAScript 2015 (ES6), javascript estándar tiene una implementación de Mapa. Más sobre lo que se puede encontrar map

Uso básico:

var myMap = new Map(); var keyString = "a string", keyObj = {}, keyFunc = function () {}; // setting the values myMap.set(keyString, "value associated with ''a string''"); myMap.set(keyObj, "value associated with keyObj"); myMap.set(keyFunc, "value associated with keyFunc"); myMap.size; // 3 // getting the values myMap.get(keyString); // "value associated with ''a string''" myMap.get(keyObj); // "value associated with keyObj" myMap.get(keyFunc); // "value associated with keyFunc"


Desafortunadamente, ninguna de las respuestas anteriores fue buena para mi caso: diferentes objetos clave pueden tener el mismo código hash. Por lo tanto, escribí una versión simple de HashMap similar a Java:

function HashMap() { this.buckets = {}; } HashMap.prototype.put = function(key, value) { var hashCode = key.hashCode(); var bucket = this.buckets[hashCode]; if (!bucket) { bucket = new Array(); this.buckets[hashCode] = bucket; } for (var i = 0; i < bucket.length; ++i) { if (bucket[i].key.equals(key)) { bucket[i].value = value; return; } } bucket.push({ key: key, value: value }); } HashMap.prototype.get = function(key) { var hashCode = key.hashCode(); var bucket = this.buckets[hashCode]; if (!bucket) { return null; } for (var i = 0; i < bucket.length; ++i) { if (bucket[i].key.equals(key)) { return bucket[i].value; } } } HashMap.prototype.keys = function() { var keys = new Array(); for (var hashKey in this.buckets) { var bucket = this.buckets[hashKey]; for (var i = 0; i < bucket.length; ++i) { keys.push(bucket[i].key); } } return keys; } HashMap.prototype.values = function() { var values = new Array(); for (var hashKey in this.buckets) { var bucket = this.buckets[hashKey]; for (var i = 0; i < bucket.length; ++i) { values.push(bucket[i].value); } } return values; }

Nota: los objetos clave deben "implementar" los métodos hashCode () y equals ().


En ECMA6 puedes usar WeakMap

Ejemplo:

var wm1 = new WeakMap(), wm2 = new WeakMap(), wm3 = new WeakMap(); var o1 = {}, o2 = function(){}, o3 = window; wm1.set(o1, 37); wm1.set(o2, "azerty"); wm2.set(o1, o2); // a value can be anything, including an object or a function wm2.set(o3, undefined); wm2.set(wm1, wm2); // keys and values can be any objects. Even WeakMaps! wm1.get(o2); // "azerty" wm2.get(o2); // undefined, because there is no value for o2 on wm2 wm2.get(o3); // undefined, because that is the set value wm1.has(o2); // true wm2.has(o2); // false wm2.has(o3); // true (even if the value itself is ''undefined'') wm3.set(o1, 37); wm3.get(o1); // 37 wm3.clear(); wm3.get(o1); // undefined, because wm3 was cleared and there is no value for o1 anymore wm1.has(o1); // true wm1.delete(o1); wm1.has(o1); // false

Pero:

Because of references being weak, WeakMap keys are not enumerable (i.e. there is no method giving you a list of the keys).


Esta es una forma fácil y conveniente de usar algo similar al mapa de Java:

var map= { ''map_name_1'': map_value_1, ''map_name_2'': map_value_2, ''map_name_3'': map_value_3, ''map_name_4'': map_value_4 }

Y para obtener el valor:

alert( map[''map_name_1''] ); // fives the value of map_value_1 ...... etc .....


Esto parece una solución bastante robusta: https://github.com/flesler/hashmap . Incluso funcionará bien para funciones y objetos que parecen idénticos. El único truco que utiliza es agregar un miembro oscuro a un objeto para identificarlo. Si tu programa no sobrescribe esa variable oscura (es algo así como hashid ), estás de oro.


He implementado un HashMap de JavaScript cuyo código se puede obtener en http://github.com/lambder/HashMapJS/tree/master

Aquí está el código:

/* ===================================================================== @license MIT @author Lambder @copyright 2009 Lambder. @end ===================================================================== */ var HashMap = function() { this.initialize(); } HashMap.prototype = { hashkey_prefix: "<#HashMapHashkeyPerfix>", hashcode_field: "<#HashMapHashkeyPerfix>", initialize: function() { this.backing_hash = {}; this.code = 0; }, /* maps value to key returning previous assocciation */ put: function(key, value) { var prev; if (key && value) { var hashCode = key[this.hashcode_field]; if (hashCode) { prev = this.backing_hash[hashCode]; } else { this.code += 1; hashCode = this.hashkey_prefix + this.code; key[this.hashcode_field] = hashCode; } this.backing_hash[hashCode] = value; } return prev; }, /* returns value associated with given key */ get: function(key) { var value; if (key) { var hashCode = key[this.hashcode_field]; if (hashCode) { value = this.backing_hash[hashCode]; } } return value; }, /* deletes association by given key. Returns true if the assocciation existed, false otherwise */ del: function(key) { var success = false; if (key) { var hashCode = key[this.hashcode_field]; if (hashCode) { var prev = this.backing_hash[hashCode]; this.backing_hash[hashCode] = undefined; if(prev !== undefined) success = true; } } return success; } } //// Usage // creation var my_map = new HashMap(); // insertion var a_key = {}; var a_value = {struct: "structA"}; var b_key = {}; var b_value = {struct: "structB"}; var c_key = {}; var c_value = {struct: "structC"}; my_map.put(a_key, a_value); my_map.put(b_key, b_value); var prev_b = my_map.put(b_key, c_value); // retrieval if(my_map.get(a_key) !== a_value){ throw("fail1") } if(my_map.get(b_key) !== c_value){ throw("fail2") } if(prev_b !== b_value){ throw("fail3") } // deletion var a_existed = my_map.del(a_key); var c_existed = my_map.del(c_key); var a2_existed = my_map.del(a_key); if(a_existed !== true){ throw("fail4") } if(c_existed !== false){ throw("fail5") } if(a2_existed !== false){ throw("fail6") }


Implementación de mi mapa, derivado del ejemplo de Christoph:

Ejemplo de uso:

var map = new Map(); //creates an "in-memory" map var map = new Map("storageId"); //creates a map that is loaded/persisted using html5 storage

function Map(storageId) { this.current = undefined; this.size = 0; this.storageId = storageId; if (this.storageId) { this.keys = new Array(); this.disableLinking(); } } Map.noop = function() { return this; }; Map.illegal = function() { throw new Error("illegal operation for maps without linking"); }; // map initialisation from existing object // doesn''t add inherited properties if not explicitly instructed to: // omitting foreignKeys means foreignKeys === undefined, i.e. == false // --> inherited properties won''t be added Map.from = function(obj, foreignKeys) { var map = new Map; for(var prop in obj) { if(foreignKeys || obj.hasOwnProperty(prop)) map.put(prop, obj[prop]); } return map; }; Map.prototype.disableLinking = function() { this.link = Map.noop; this.unlink = Map.noop; this.disableLinking = Map.noop; this.next = Map.illegal; this.key = Map.illegal; this.value = Map.illegal; // this.removeAll = Map.illegal; return this; }; // overwrite in Map instance if necessary Map.prototype.hash = function(value) { return (typeof value) + '' '' + (value instanceof Object ? (value.__hash || (value.__hash = ++arguments.callee.current)) : value.toString()); }; Map.prototype.hash.current = 0; // --- mapping functions Map.prototype.get = function(key) { var item = this[this.hash(key)]; if (item === undefined) { if (this.storageId) { try { var itemStr = localStorage.getItem(this.storageId + key); if (itemStr && itemStr !== ''undefined'') { item = JSON.parse(itemStr); this[this.hash(key)] = item; this.keys.push(key); ++this.size; } } catch (e) { console.log(e); } } } return item === undefined ? undefined : item.value; }; Map.prototype.put = function(key, value) { var hash = this.hash(key); if(this[hash] === undefined) { var item = { key : key, value : value }; this[hash] = item; this.link(item); ++this.size; } else this[hash].value = value; if (this.storageId) { this.keys.push(key); try { localStorage.setItem(this.storageId + key, JSON.stringify(this[hash])); } catch (e) { console.log(e); } } return this; }; Map.prototype.remove = function(key) { var hash = this.hash(key); var item = this[hash]; if(item !== undefined) { --this.size; this.unlink(item); delete this[hash]; } if (this.storageId) { try { localStorage.setItem(this.storageId + key, undefined); } catch (e) { console.log(e); } } return this; }; // only works if linked Map.prototype.removeAll = function() { if (this.storageId) { for (var i=0; i<this.keys.length; i++) { this.remove(this.keys[i]); } this.keys.length = 0; } else { while(this.size) this.remove(this.key()); } return this; }; // --- linked list helper functions Map.prototype.link = function(item) { if (this.storageId) { return; } if(this.size == 0) { item.prev = item; item.next = item; this.current = item; } else { item.prev = this.current.prev; item.prev.next = item; item.next = this.current; this.current.prev = item; } }; Map.prototype.unlink = function(item) { if (this.storageId) { return; } if(this.size == 0) this.current = undefined; else { item.prev.next = item.next; item.next.prev = item.prev; if(item === this.current) this.current = item.next; } }; // --- iterator functions - only work if map is linked Map.prototype.next = function() { this.current = this.current.next; }; Map.prototype.key = function() { if (this.storageId) { return undefined; } else { return this.current.key; } }; Map.prototype.value = function() { if (this.storageId) { return undefined; } return this.current.value; };


Javascript no incorpora el mapa / hashmap. Debería llamarse matriz asociativa .

hash["X"] es igual a hash.X , pero permite "X" como variable de cadena. En otras palabras, hash[x] es funcionalmente igual a eval("hash."+x.toString())

Es más similar a object.properties que a la asignación de clave-valor. Si está buscando una mejor asignación de clave / valor en Javascript, utilice el objeto Mapa que puede encontrar en la web.


Pruebe la implementación de la tabla hash de JavaScript: http://www.timdown.co.uk/jshashtable

Busca un método hashCode () de objetos clave, o puede proporcionar una función de hashing al crear un objeto Hashtable.


Puedes usar ES6 WeakMap o map :

  • Los mapas débiles son mapas de clave / valor en los que las claves son objetos.

  • Map objetos del Map son simples mapas de clave / valor. Cualquier valor (tanto los objetos como los valores primitivos) se puede utilizar como una clave o un valor.

Tenga en cuenta que ninguno es ampliamente compatible, pero puede usar ES6 Shim (requiere ES5 nativo o ES5 Shim ) para admitir Map , pero no WeakMap ( ver por qué ).


Sé que esta pregunta es bastante antigua, pero hay algunas soluciones realmente geniales en la actualidad con bibliotecas externas.

JavaScript también tiene su idioma proporcionado Map también.


Si el rendimiento no es crítico (por ejemplo, la cantidad de claves es relativamente pequeña) y no desea contaminar sus objetos (o tal vez no) con campos adicionales como _hash , _id , etc., puede hacer uso del hecho que Array.prototype.indexOf emplea una igualdad estricta. Aquí hay una implementación simple:

var Dict = (function(){ // IE 8 and earlier has no Array.prototype.indexOf function indexOfPolyfill(val) { for (var i = 0, l = this.length; i < l; ++i) { if (this[i] === val) { return i; } } return -1; } function Dict(){ this.keys = []; this.values = []; if (!this.keys.indexOf) { this.keys.indexOf = indexOfPolyfill; } }; Dict.prototype.has = function(key){ return this.keys.indexOf(key) != -1; }; Dict.prototype.get = function(key, defaultValue){ var index = this.keys.indexOf(key); return index == -1 ? defaultValue : this.values[index]; }; Dict.prototype.set = function(key, value){ var index = this.keys.indexOf(key); if (index == -1) { this.keys.push(key); this.values.push(value); } else { var prevValue = this.values[index]; this.values[index] = value; return prevValue; } }; Dict.prototype.delete = function(key){ var index = this.keys.indexOf(key); if (index != -1) { this.keys.splice(index, 1); return this.values.splice(index, 1)[0]; } }; Dict.prototype.clear = function(){ this.keys.splice(0, this.keys.length); this.values.splice(0, this.values.length); }; return Dict; })();

Ejemplo de uso:

var a = {}, b = {}, c = { toString: function(){ return ''1''; } }, d = 1, s = ''1'', u = undefined, n = null, dict = new Dict(); // keys and values can be anything dict.set(a, ''a''); dict.set(b, ''b''); dict.set(c, ''c''); dict.set(d, ''d''); dict.set(s, ''s''); dict.set(u, ''u''); dict.set(n, ''n''); dict.get(a); // ''a'' dict.get(b); // ''b'' dict.get(s); // ''s'' dict.get(u); // ''u'' dict.get(n); // ''n'' // etc.

En comparación con ES6, WeakMap tiene dos problemas: el tiempo de búsqueda O (n) y la no debilidad (es decir, provocará una pérdida de memoria si no usa delete o clear para liberar claves).


Tendrías que almacenar en algunos pares de estado interno de pares objeto / valor

HashMap = function(){ this._dict = []; } HashMap.prototype._get = function(key){ for(var i=0, couplet; couplet = this._dict[i]; i++){ if(couplet[0] === key){ return couplet; } } } HashMap.prototype.put = function(key, value){ var couplet = this._get(key); if(couplet){ couplet[1] = value; }else{ this._dict.push([key, value]); } return this; // for chaining } HashMap.prototype.get = function(key){ var couplet = this._get(key); if(couplet){ return couplet[1]; } }

Y utilízalo como tal:

var color = {}; // unique object instance var shape = {}; // unique object instance var map = new HashMap(); map.put(color, "blue"); map.put(shape, "round"); console.log("Item is", map.get(color), "and", map.get(shape));

Por supuesto, esta implementación también está en algún lugar en la línea de O (n). Los ejemplos anteriores de Eugene son la única forma de obtener un hash que funcione con cualquier tipo de velocidad que se esperaría de un hash real.

Actualizar:

Otro enfoque, en la línea de la respuesta de Eugene, es adjuntar de alguna manera una identificación única a todos los objetos. Uno de mis enfoques favoritos es tomar uno de los métodos incorporados heredados de la superclase Object, reemplazarlo con un pasantía de función personalizada y adjuntar propiedades a ese objeto de función. Si tuvieras que volver a escribir mi método HashMap para hacer esto, se vería así:

HashMap = function(){ this._dict = {}; } HashMap.prototype._shared = {id: 1}; HashMap.prototype.put = function put(key, value){ if(typeof key == "object"){ if(!key.hasOwnProperty._id){ key.hasOwnProperty = function(key){ return Object.prototype.hasOwnProperty.call(this, key); } key.hasOwnProperty._id = this._shared.id++; } this._dict[key.hasOwnProperty._id] = value; }else{ this._dict[key] = value; } return this; // for chaining } HashMap.prototype.get = function get(key){ if(typeof key == "object"){ return this._dict[key.hasOwnProperty._id]; } return this._dict[key]; }

Esta versión parece ser solo un poco más rápida, pero en teoría será significativamente más rápida para grandes conjuntos de datos.


Agregando otra solución: HashMapes prácticamente la primera clase que porté de Java a Javascript. Podría decir que hay muchos gastos generales, pero la implementación es casi 100% igual a la implementación de Java e incluye todas las interfaces y subclases.

El proyecto se puede encontrar aquí: https://github.com/Airblader/jsava También adjuntaré el código fuente (actual) para la clase HashMap, pero como se indicó, también depende de la superclase, etc. es qooxdoo.

Edición: tenga en cuenta que este código ya está desactualizado y consulte el proyecto github para el trabajo actual. A la hora de escribir esto, también hay una ArrayListimplementación.

qx.Class.define( ''jsava.util.HashMap'', { extend: jsava.util.AbstractMap, implement: [jsava.util.Map, jsava.io.Serializable, jsava.lang.Cloneable], construct: function () { var args = Array.prototype.slice.call( arguments ), initialCapacity = this.self( arguments ).DEFAULT_INITIAL_CAPACITY, loadFactor = this.self( arguments ).DEFAULT_LOAD_FACTOR; switch( args.length ) { case 1: if( qx.Class.implementsInterface( args[0], jsava.util.Map ) ) { initialCapacity = Math.max( ((args[0].size() / this.self( arguments ).DEFAULT_LOAD_FACTOR) | 0) + 1, this.self( arguments ).DEFAULT_INITIAL_CAPACITY ); loadFactor = this.self( arguments ).DEFAULT_LOAD_FACTOR; } else { initialCapacity = args[0]; } break; case 2: initialCapacity = args[0]; loadFactor = args[1]; break; } if( initialCapacity < 0 ) { throw new jsava.lang.IllegalArgumentException( ''Illegal initial capacity: '' + initialCapacity ); } if( initialCapacity > this.self( arguments ).MAXIMUM_CAPACITY ) { initialCapacity = this.self( arguments ).MAXIMUM_CAPACITY; } if( loadFactor <= 0 || isNaN( loadFactor ) ) { throw new jsava.lang.IllegalArgumentException( ''Illegal load factor: '' + loadFactor ); } var capacity = 1; while( capacity < initialCapacity ) { capacity <<= 1; } this._loadFactor = loadFactor; this._threshold = (capacity * loadFactor) | 0; this._table = jsava.JsavaUtils.emptyArrayOfGivenSize( capacity, null ); this._init(); }, statics: { serialVersionUID: 1, DEFAULT_INITIAL_CAPACITY: 16, MAXIMUM_CAPACITY: 1 << 30, DEFAULT_LOAD_FACTOR: 0.75, _hash: function (hash) { hash ^= (hash >>> 20) ^ (hash >>> 12); return hash ^ (hash >>> 7) ^ (hash >>> 4); }, _indexFor: function (hashCode, length) { return hashCode & (length - 1); }, Entry: qx.Class.define( ''jsava.util.HashMap.Entry'', { extend: jsava.lang.Object, implement: [jsava.util.Map.Entry], construct: function (hash, key, value, nextEntry) { this._value = value; this._next = nextEntry; this._key = key; this._hash = hash; }, members: { _key: null, _value: null, /** @type jsava.util.HashMap.Entry */ _next: null, /** @type Number */ _hash: 0, getKey: function () { return this._key; }, getValue: function () { return this._value; }, setValue: function (newValue) { var oldValue = this._value; this._value = newValue; return oldValue; }, equals: function (obj) { if( obj === null || !qx.Class.implementsInterface( obj, jsava.util.HashMap.Entry ) ) { return false; } /** @type jsava.util.HashMap.Entry */ var entry = obj, key1 = this.getKey(), key2 = entry.getKey(); if( key1 === key2 || (key1 !== null && key1.equals( key2 )) ) { var value1 = this.getValue(), value2 = entry.getValue(); if( value1 === value2 || (value1 !== null && value1.equals( value2 )) ) { return true; } } return false; }, hashCode: function () { return (this._key === null ? 0 : this._key.hashCode()) ^ (this._value === null ? 0 : this._value.hashCode()); }, toString: function () { return this.getKey() + ''='' + this.getValue(); }, /** * This method is invoked whenever the value in an entry is * overwritten by an invocation of put(k,v) for a key k that''s already * in the HashMap. */ _recordAccess: function (map) { }, /** * This method is invoked whenever the entry is * removed from the table. */ _recordRemoval: function (map) { } } } ) }, members: { /** @type jsava.util.HashMap.Entry[] */ _table: null, /** @type Number */ _size: 0, /** @type Number */ _threshold: 0, /** @type Number */ _loadFactor: 0, /** @type Number */ _modCount: 0, /** @implements jsava.util.Set */ __entrySet: null, /** * Initialization hook for subclasses. This method is called * in all constructors and pseudo-constructors (clone, readObject) * after HashMap has been initialized but before any entries have * been inserted. (In the absence of this method, readObject would * require explicit knowledge of subclasses.) */ _init: function () { }, size: function () { return this._size; }, isEmpty: function () { return this._size === 0; }, get: function (key) { if( key === null ) { return this.__getForNullKey(); } var hash = this.self( arguments )._hash( key.hashCode() ); for( var entry = this._table[this.self( arguments )._indexFor( hash, this._table.length )]; entry !== null; entry = entry._next ) { /** @type jsava.lang.Object */ var k; if( entry._hash === hash && ((k = entry._key) === key || key.equals( k )) ) { return entry._value; } } return null; }, __getForNullKey: function () { for( var entry = this._table[0]; entry !== null; entry = entry._next ) { if( entry._key === null ) { return entry._value; } } return null; }, containsKey: function (key) { return this._getEntry( key ) !== null; }, _getEntry: function (key) { var hash = (key === null) ? 0 : this.self( arguments )._hash( key.hashCode() ); for( var entry = this._table[this.self( arguments )._indexFor( hash, this._table.length )]; entry !== null; entry = entry._next ) { /** @type jsava.lang.Object */ var k; if( entry._hash === hash && ( ( k = entry._key ) === key || ( key !== null && key.equals( k ) ) ) ) { return entry; } } return null; }, put: function (key, value) { if( key === null ) { return this.__putForNullKey( value ); } var hash = this.self( arguments )._hash( key.hashCode() ), i = this.self( arguments )._indexFor( hash, this._table.length ); for( var entry = this._table[i]; entry !== null; entry = entry._next ) { /** @type jsava.lang.Object */ var k; if( entry._hash === hash && ( (k = entry._key) === key || key.equals( k ) ) ) { var oldValue = entry._value; entry._value = value; entry._recordAccess( this ); return oldValue; } } this._modCount++; this._addEntry( hash, key, value, i ); return null; }, __putForNullKey: function (value) { for( var entry = this._table[0]; entry !== null; entry = entry._next ) { if( entry._key === null ) { var oldValue = entry._value; entry._value = value; entry._recordAccess( this ); return oldValue; } } this._modCount++; this._addEntry( 0, null, value, 0 ); return null; }, __putForCreate: function (key, value) { var hash = (key === null) ? 0 : this.self( arguments )._hash( key.hashCode() ), i = this.self( arguments )._indexFor( hash, this._table.length ); for( var entry = this._table[i]; entry !== null; entry = entry._next ) { /** @type jsava.lang.Object */ var k; if( entry._hash === hash && ( (k = entry._key) === key || ( key !== null && key.equals( k ) ) ) ) { entry._value = value; return; } } this._createEntry( hash, key, value, i ); }, __putAllForCreate: function (map) { var iterator = map.entrySet().iterator(); while( iterator.hasNext() ) { var entry = iterator.next(); this.__putForCreate( entry.getKey(), entry.getValue() ); } }, _resize: function (newCapacity) { var oldTable = this._table, oldCapacity = oldTable.length; if( oldCapacity === this.self( arguments ).MAXIMUM_CAPACITY ) { this._threshold = Number.MAX_VALUE; return; } var newTable = jsava.JsavaUtils.emptyArrayOfGivenSize( newCapacity, null ); this._transfer( newTable ); this._table = newTable; this._threshold = (newCapacity * this._loadFactor) | 0; }, _transfer: function (newTable) { var src = this._table, newCapacity = newTable.length; for( var j = 0; j < src.length; j++ ) { var entry = src[j]; if( entry !== null ) { src[j] = null; do { var next = entry._next, i = this.self( arguments )._indexFor( entry._hash, newCapacity ); entry._next = newTable[i]; newTable[i] = entry; entry = next; } while( entry !== null ); } } }, putAll: function (map) { var numKeyToBeAdded = map.size(); if( numKeyToBeAdded === 0 ) { return; } if( numKeyToBeAdded > this._threshold ) { var targetCapacity = (numKeyToBeAdded / this._loadFactor + 1) | 0; if( targetCapacity > this.self( arguments ).MAXIMUM_CAPACITY ) { targetCapacity = this.self( arguments ).MAXIMUM_CAPACITY; } var newCapacity = this._table.length; while( newCapacity < targetCapacity ) { newCapacity <<= 1; } if( newCapacity > this._table.length ) { this._resize( newCapacity ); } } var iterator = map.entrySet().iterator(); while( iterator.hasNext() ) { var entry = iterator.next(); this.put( entry.getKey(), entry.getValue() ); } }, remove: function (key) { var entry = this._removeEntryForKey( key ); return entry === null ? null : entry._value; }, _removeEntryForKey: function (key) { var hash = (key === null) ? 0 : this.self( arguments )._hash( key.hashCode() ), i = this.self( arguments )._indexFor( hash, this._table.length ), prev = this._table[i], entry = prev; while( entry !== null ) { var next = entry._next, /** @type jsava.lang.Object */ k; if( entry._hash === hash && ( (k = entry._key) === key || ( key !== null && key.equals( k ) ) ) ) { this._modCount++; this._size--; if( prev === entry ) { this._table[i] = next; } else { prev._next = next; } entry._recordRemoval( this ); return entry; } prev = entry; entry = next; } return entry; }, _removeMapping: function (obj) { if( obj === null || !qx.Class.implementsInterface( obj, jsava.util.Map.Entry ) ) { return null; } /** @implements jsava.util.Map.Entry */ var entry = obj, key = entry.getKey(), hash = (key === null) ? 0 : this.self( arguments )._hash( key.hashCode() ), i = this.self( arguments )._indexFor( hash, this._table.length ), prev = this._table[i], e = prev; while( e !== null ) { var next = e._next; if( e._hash === hash && e.equals( entry ) ) { this._modCount++; this._size--; if( prev === e ) { this._table[i] = next; } else { prev._next = next; } e._recordRemoval( this ); return e; } prev = e; e = next; } return e; }, clear: function () { this._modCount++; var table = this._table; for( var i = 0; i < table.length; i++ ) { table[i] = null; } this._size = 0; }, containsValue: function (value) { if( value === null ) { return this.__containsNullValue(); } var table = this._table; for( var i = 0; i < table.length; i++ ) { for( var entry = table[i]; entry !== null; entry = entry._next ) { if( value.equals( entry._value ) ) { return true; } } } return false; }, __containsNullValue: function () { var table = this._table; for( var i = 0; i < table.length; i++ ) { for( var entry = table[i]; entry !== null; entry = entry._next ) { if( entry._value === null ) { return true; } } } return false; }, clone: function () { /** @type jsava.util.HashMap */ var result = null; try { result = this.base( arguments ); } catch( e ) { if( !qx.Class.isSubClassOf( e.constructor, jsava.lang.CloneNotSupportedException ) ) { throw e; } } result._table = jsava.JsavaUtils.emptyArrayOfGivenSize( this._table.length, null ); result.__entrySet = null; result._modCount = 0; result._size = 0; result._init(); result.__putAllForCreate( this ); return result; }, _addEntry: function (hash, key, value, bucketIndex) { var entry = this._table[bucketIndex]; this._table[bucketIndex] = new (this.self( arguments ).Entry)( hash, key, value, entry ); if( this._size++ >= this._threshold ) { this._resize( 2 * this._table.length ); } }, _createEntry: function (hash, key, value, bucketIndex) { var entry = this._table[bucketIndex]; this._table[bucketIndex] = new (this.self( arguments ).Entry)( hash, key, value, entry ); this._size++; }, keySet: function () { var keySet = this._keySet; return keySet !== null ? keySet : ( this._keySet = new this.KeySet( this ) ); }, values: function () { var values = this._values; return values !== null ? values : ( this._values = new this.Values( this ) ); }, entrySet: function () { return this.__entrySet0(); }, __entrySet0: function () { var entrySet = this.__entrySet; return entrySet !== null ? entrySet : ( this.__entrySet = new this.EntrySet( this ) ); }, /** @private */ HashIterator: qx.Class.define( ''jsava.util.HashMap.HashIterator'', { extend: jsava.lang.Object, implement: [jsava.util.Iterator], type: ''abstract'', /** @protected */ construct: function (thisHashMap) { this.__thisHashMap = thisHashMap; this._expectedModCount = this.__thisHashMap._modCount; if( this.__thisHashMap._size > 0 ) { var table = this.__thisHashMap._table; while( this._index < table.length && ( this._next = table[this._index++] ) === null ) { // do nothing } } }, members: { __thisHashMap: null, /** @type jsava.util.HashMap.Entry */ _next: null, /** @type Number */ _expectedModCount: 0, /** @type Number */ _index: 0, /** @type jsava.util.HashMap.Entry */ _current: null, hasNext: function () { return this._next !== null; }, _nextEntry: function () { if( this.__thisHashMap._modCount !== this._expectedModCount ) { throw new jsava.lang.ConcurrentModificationException(); } var entry = this._next; if( entry === null ) { throw new jsava.lang.NoSuchElementException(); } if( (this._next = entry._next) === null ) { var table = this.__thisHashMap._table; while( this._index < table.length && ( this._next = table[this._index++] ) === null ) { // do nothing } } this._current = entry; return entry; }, remove: function () { if( this._current === null ) { throw new jsava.lang.IllegalStateException(); } if( this.__thisHashMap._modCount !== this._expectedModCount ) { throw new jsava.lang.ConcurrentModificationException(); } var key = this._current._key; this._current = null; this.__thisHashMap._removeEntryForKey( key ); this._expectedModCount = this.__thisHashMap._modCount; } } } ), _newKeyIterator: function () { return new this.KeyIterator( this ); }, _newValueIterator: function () { return new this.ValueIterator( this ); }, _newEntryIterator: function () { return new this.EntryIterator( this ); }, /** @private */ ValueIterator: qx.Class.define( ''jsava.util.HashMap.ValueIterator'', { extend: jsava.util.HashMap.HashIterator, construct: function (thisHashMap) { this.base( arguments, thisHashMap ); }, members: { next: function () { return this._nextEntry()._value; } } } ), /** @private */ KeyIterator: qx.Class.define( ''jsava.util.HashMap.KeyIterator'', { extend: jsava.util.HashMap.HashIterator, construct: function (thisHashMap) { this.base( arguments, thisHashMap ); }, members: { next: function () { return this._nextEntry().getKey(); } } } ), /** @private */ EntryIterator: qx.Class.define( ''jsava.util.HashMap.EntryIterator'', { extend: jsava.util.HashMap.HashIterator, construct: function (thisHashMap) { this.base( arguments, thisHashMap ); }, members: { next: function () { return this._nextEntry(); } } } ), /** @private */ KeySet: qx.Class.define( ''jsava.util.HashMap.KeySet'', { extend: jsava.util.AbstractSet, construct: function (thisHashMap) { this.base( arguments ); this.__thisHashMap = thisHashMap; }, members: { __thisHashMap: null, iterator: function () { return this.__thisHashMap._newKeyIterator(); }, size: function () { return this.__thisHashMap._size; }, contains: function (obj) { return this.__thisHashMap.containsKey( obj ); }, remove: function (obj) { return this.__thisHashMap._removeEntryForKey( obj ) !== null; }, clear: function () { this.__thisHashMap.clear(); } } } ), /** @private */ Values: qx.Class.define( ''jsava.util.HashMap.Values'', { extend: jsava.util.AbstractCollection, construct: function (thisHashMap) { this.base( arguments ); this.__thisHashMap = thisHashMap; }, members: { __thisHashMap: null, iterator: function () { return this.__thisHashMap._newValueIterator(); }, size: function () { return this.__thisHashMap._size; }, contains: function (obj) { return this.__thisHashMap.containsValue( obj ); }, clear: function () { this.__thisHashMap.clear(); } } } ), /** @private */ EntrySet: qx.Class.define( ''jsava.util.HashMap.EntrySet'', { extend: jsava.util.AbstractSet, construct: function (thisHashMap) { this.base( arguments ); this.__thisHashMap = thisHashMap; }, members: { __thisHashMap: null, iterator: function () { return this.__thisHashMap._newEntryIterator(); }, size: function () { return this.__thisHashMap._size; }, contains: function (obj) { if( obj === null || !qx.Class.implementsInterface( obj, jsava.util.Map.Entry ) ) { return false; } /** @implements jsava.util.Map.Entry */ var entry = obj, candidate = this.__thisHashMap._getEntry( entry.getKey() ); return candidate !== null && candidate.equals( entry ); }, remove: function (obj) { return this.__thisHashMap._removeMapping( obj ) !== null; }, clear: function () { this.__thisHashMap.clear(); } } } ) } } );


Otra implementación de mapa por mí. Con el aleatorizador, ''genéricos'' e ''iterador'' =)

var HashMap = function (TKey, TValue) { var db = []; var keyType, valueType; (function () { keyType = TKey; valueType = TValue; })(); var getIndexOfKey = function (key) { if (typeof key !== keyType) throw new Error(''Type of key should be '' + keyType); for (var i = 0; i < db.length; i++) { if (db[i][0] == key) return i; } return -1; } this.add = function (key, value) { if (typeof key !== keyType) { throw new Error(''Type of key should be '' + keyType); } else if (typeof value !== valueType) { throw new Error(''Type of value should be '' + valueType); } var index = getIndexOfKey(key); if (index === -1) db.push([key, value]); else db[index][1] = value; return this; } this.get = function (key) { if (typeof key !== keyType || db.length === 0) return null; for (var i = 0; i < db.length; i++) { if (db[i][0] == key) return db[i][1]; } return null; } this.size = function () { return db.length; } this.keys = function () { if (db.length === 0) return []; var result = []; for (var i = 0; i < db.length; i++) { result.push(db[i][0]); } return result; } this.values = function () { if (db.length === 0) return []; var result = []; for (var i = 0; i < db.length; i++) { result.push(db[i][1]); } return result; } this.randomize = function () { if (db.length === 0) return this; var currentIndex = db.length, temporaryValue, randomIndex; while (0 !== currentIndex) { randomIndex = Math.floor(Math.random() * currentIndex); currentIndex--; temporaryValue = db[currentIndex]; db[currentIndex] = db[randomIndex]; db[randomIndex] = temporaryValue; } return this; } this.iterate = function (callback) { if (db.length === 0) return false; for (var i = 0; i < db.length; i++) { callback(db[i][0], db[i][1]); } return true; } }

Ejemplo:

var a = new HashMap("string", "number"); a.add(''test'', 1132) .add(''test14'', 666) .add(''1421test14'', 12312666) .iterate(function (key, value) {console.log(''a[''+key+'']=''+value)}); /* a[test]=1132 a[test14]=666 a[1421test14]=12312666 */ a.randomize(); /* a[1421test14]=12312666 a[test]=1132 a[test14]=666 */