Como fazer uma matriz / hashing associativo em JavaScript

Eu preciso armazenar algumas statistics usando JavaScript de uma maneira como eu faria em c #:

Dictionary statistics; statistics["Foo"] = 10; statistics["Goo"] = statistics["Goo"] + 1; statistics.Add("Zoo", 1); 

Existe uma Hashtable ou algo parecido com Dictionary em JavaScript?
Como eu poderia armazenar valores de tal maneira?

Use objects JavaScript como matrizes associativas .

Matriz Associativa: Em simples palavras, matrizes associativas usam Strings ao invés de números inteiros como índice.

Crie um object com

 var dictionary = {}; 

Javascript permite adicionar propriedades a objects usando a seguinte syntax:

 Object.yourProperty = value; 

Uma syntax alternativa para o mesmo é:

 Object["yourProperty"] = value; 

Se você também pode criar chaves para mapear objects com a seguinte syntax

 var point = { x:3, y:2 }; point["x"] // returns 3 point.y // returns 2 

Você pode percorrer uma matriz associativa usando a construção de loop for..in da seguinte forma

 for(var key in Object.keys(dict)){ var value = dict[key]; /* use key/value for intended purpose */ } 
 var associativeArray = {}; associativeArray["one"] = "First"; associativeArray["two"] = "Second"; associativeArray["three"] = "Third"; 

Se você estiver vindo de um idioma orientado a objects, você deve verificar este artigo .

A menos que você tenha um motivo específico para isso, use apenas um object normal. As propriedades do object em Javascript podem ser referenciadas usando a syntax no estilo hashtable:

 var hashtable = {}; hashtable.foo = "bar"; hashtable['bar'] = "foo"; 

Os elementos foo e bar agora podem ser referenciados como:

 hashtable['foo']; hashtable['bar']; // or hashtable.foo; hashtable.bar; 

Claro que isso significa que suas chaves precisam ser cordas. Se eles não são strings, eles são convertidos internamente para strings, então ele ainda pode funcionar, YMMV.

Todos os navegadores modernos suportam um object Map javascript. Existem algumas razões que tornam o uso de um mapa melhor que o object:

  • Um object tem um protótipo, portanto, há chaves padrão no mapa.
  • As chaves de um object são seqüências de caracteres, onde podem ser qualquer valor para um mapa.
  • Você pode obter o tamanho de um mapa facilmente, enquanto você precisa controlar o tamanho de um object.

Exemplo:

 var myMap = new Map(); var keyObj = {}, keyFunc = function () {}, keyString = "a string"; 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 myMap.get(keyString); // "value associated with 'a string'" myMap.get(keyObj); // "value associated with keyObj" myMap.get(keyFunc); // "value associated with keyFunc" 

Se você quiser que as chaves não referenciadas de outros objects sejam coletadas como lixo, considere o uso de um WeakMap em vez de um Mapa.

Uma vez que cada object no JS se comporta como – e é geralmente implementado como – um hashtable, eu só vou com isso …

 var hashSweetHashTable = {}; 

Se você precisar que suas chaves sejam qualquer object, em vez de apenas cordas, então você pode usar o meu jshashtable .

então em C # o código se parece com:

 Dictionary dictionary = new Dictionary(); dictionary.add("sample1", 1); dictionary.add("sample2", 2); 

ou

 var dictionary = new Dictionary { {"sample1", 1}, {"sample2", 2} }; 

em JavaScript

 var dictionary = { "sample1": 1, "sample2": 2 } 

C # dictionary object contém methods úteis como dictionary.ContainsKey() em JavaScript, poderíamos usar o hasOwnProperty como

 if (dictionary.hasOwnProperty("sample1")) console.log("sample1 key found and its value is"+ dictionary["sample1"]); 
 function HashTable() { this.length = 0; this.items = new Array(); for (var i = 0; i < arguments.length; i += 2) { if (typeof (arguments[i + 1]) != 'undefined') { this.items[arguments[i]] = arguments[i + 1]; this.length++; } } this.removeItem = function (in_key) { var tmp_previous; if (typeof (this.items[in_key]) != 'undefined') { this.length--; var tmp_previous = this.items[in_key]; delete this.items[in_key]; } return tmp_previous; } this.getItem = function (in_key) { return this.items[in_key]; } this.setItem = function (in_key, in_value) { var tmp_previous; if (typeof (in_value) != 'undefined') { if (typeof (this.items[in_key]) == 'undefined') { this.length++; } else { tmp_previous = this.items[in_key]; } this.items[in_key] = in_value; } return tmp_previous; } this.hasItem = function (in_key) { return typeof (this.items[in_key]) != 'undefined'; } this.clear = function () { for (var i in this.items) { delete this.items[i]; } this.length = 0; } } 

Eu criei isso para obter algum problema, como mapeamento de chave de object, capacidade de enumeração (com o método forEach() ) e limpeza.

 function Hashtable() { this._map = new Map(); this._indexes = new Map(); this._keys = []; this._values = []; this.put = function(key, value) { var newKey = !this.containsKey(key); this._map.set(key, value); if (newKey) { this._indexes.set(key, this.length); this._keys.push(key); this._values.push(value); } }; this.remove = function(key) { if (!this.containsKey(key)) return; this._map.delete(key); var index = this._indexes.get(key); this._indexes.delete(key); this._keys.splice(index, 1); this._values.splice(index, 1); }; this.indexOfKey = function(key) { return this._indexes.get(key); }; this.indexOfValue = function(value) { return this._values.indexOf(value) != -1; }; this.get = function(key) { return this._map.get(key); }; this.entryAt = function(index) { var item = {}; Object.defineProperty(item, "key", { value: this.keys[index], writable: false }); Object.defineProperty(item, "value", { value: this.values[index], writable: false }); return item; }; this.clear = function() { var length = this.length; for (var i = 0; i < length; i++) { var key = this.keys[i]; this._map.delete(key); this._indexes.delete(key); } this._keys.splice(0, length); }; this.containsKey = function(key) { return this._map.has(key); }; this.containsValue = function(value) { return this._values.indexOf(value) != -1; }; this.forEach = function(iterator) { for (var i = 0; i < this.length; i++) iterator(this.keys[i], this.values[i], i); }; Object.defineProperty(this, "length", { get: function() { return this._keys.length; } }); Object.defineProperty(this, "keys", { get: function() { return this._keys; } }); Object.defineProperty(this, "values", { get: function() { return this._values; } }); Object.defineProperty(this, "entries", { get: function() { var entries = new Array(this.length); for (var i = 0; i < entries.length; i++) entries[i] = this.entryAt(i); return entries; } }); } 

Documentação da class Hashtable

Métodos:

  • get(key)
    Retorna o valor associado à chave especificada.
    parameters:
    key : a chave da qual recupera o valor.

  • put(key, value)
    Associa o valor especificado à chave especificada.
    parameters:
    key : a chave à qual associa o valor.
    value : o valor a ser associado à chave.

  • remove(key)
    Remove a chave especificada com seu valor.
    parameters:
    key : a chave a ser removida.

  • clear()
    Limpa todo o hashtable, removendo as chaves e os valores.

  • indexOfKey(key)
    Retorna o índice da chave especificada, com base na ordem de adição.
    parameters:
    key : A chave da qual obtém o índice.

  • indexOfValue(value)
    Retorna o índice do valor especificado, com base na ordem de adição.
    parameters:
    value : o valor do qual obtém o índice.
    Notas:
    Esta informação é recuperada pelo método indexOf() de um array, então compara o object apenas com o toString() .

  • entryAt(index)
    Retorna um object com duas propriedades: key e value, representando a input no índice especificado.
    parameters:
    index : o índice da input para obter.

  • containsKey(key)
    Retorna se o hashtable contém a chave especificada.
    parameters:
    key : A chave para verificar.

  • containsValue(value)
    Retorna se a hashtable contém o valor especificado.
    parameters:
    value : o valor a ser verificado.

  • forEach(iterator)
    Itera todas as inputs no iterator especificado.
    parameters:
    value : Um método com 3 parâmetros: key , value e index , em que index representa o índice da input.

    Propriedades:

  • length ( somente leitura )
    Obtém a contagem das inputs na hashtable.

  • keys ( somente leitura )
    Obtém uma matriz de todas as chaves na hashtable.

  • values ( somente leitura )
    Obtém uma matriz de todos os valores na hashtable.

  • entries ( somente leitura )
    Obtém uma matriz de todas as inputs na hashtable. Eles são representados na mesma forma do método entryAt() .

https://gist.github.com/alexhawkins/f6329420f40e5cafa0a4

 var HashTable = function() { this._storage = []; this._count = 0; this._limit = 8; } HashTable.prototype.insert = function(key, value) { //create an index for our storage location by passing it through our hashing function var index = this.hashFunc(key, this._limit); //retrieve the bucket at this particular index in our storage, if one exists //[[ [k,v], [k,v], [k,v] ] , [ [k,v], [k,v] ] [ [k,v] ] ] var bucket = this._storage[index] //does a bucket exist or do we get undefined when trying to retrieve said index? if (!bucket) { //create the bucket var bucket = []; //insert the bucket into our hashTable this._storage[index] = bucket; } var override = false; //now iterate through our bucket to see if there are any conflicting //key value pairs within our bucket. If there are any, override them. for (var i = 0; i < bucket.length; i++) { var tuple = bucket[i]; if (tuple[0] === key) { //overide value stored at this key tuple[1] = value; override = true; } } if (!override) { //create a new tuple in our bucket //note that this could either be the new empty bucket we created above //or a bucket with other tupules with keys that are different than //the key of the tuple we are inserting. These tupules are in the same //bucket because their keys all equate to the same numeric index when //passing through our hash function. bucket.push([key, value]); this._count++ //now that we've added our new key/val pair to our storage //let's check to see if we need to resize our storage if (this._count > this._limit * 0.75) { this.resize(this._limit * 2); } } return this; }; HashTable.prototype.remove = function(key) { var index = this.hashFunc(key, this._limit); var bucket = this._storage[index]; if (!bucket) { return null; } //iterate over the bucket for (var i = 0; i < bucket.length; i++) { var tuple = bucket[i]; //check to see if key is inside bucket if (tuple[0] === key) { //if it is, get rid of this tuple bucket.splice(i, 1); this._count--; if (this._count < this._limit * 0.25) { this._resize(this._limit / 2); } return tuple[1]; } } }; HashTable.prototype.retrieve = function(key) { var index = this.hashFunc(key, this._limit); var bucket = this._storage[index]; if (!bucket) { return null; } for (var i = 0; i < bucket.length; i++) { var tuple = bucket[i]; if (tuple[0] === key) { return tuple[1]; } } return null; }; HashTable.prototype.hashFunc = function(str, max) { var hash = 0; for (var i = 0; i < str.length; i++) { var letter = str[i]; hash = (hash << 5) + letter.charCodeAt(0); hash = (hash & hash) % max; } return hash; }; HashTable.prototype.resize = function(newLimit) { var oldStorage = this._storage; this._limit = newLimit; this._count = 0; this._storage = []; oldStorage.forEach(function(bucket) { if (!bucket) { return; } for (var i = 0; i < bucket.length; i++) { var tuple = bucket[i]; this.insert(tuple[0], tuple[1]); } }.bind(this)); }; HashTable.prototype.retrieveAll = function() { console.log(this._storage); //console.log(this._limit); }; /******************************TESTS*******************************/ var hashT = new HashTable(); hashT.insert('Alex Hawkins', '510-599-1930'); //hashT.retrieve(); //[ , , , [ [ 'Alex Hawkins', '510-599-1930' ] ] ] hashT.insert('Boo Radley', '520-589-1970'); //hashT.retrieve(); //[ , [ [ 'Boo Radley', '520-589-1970' ] ], , [ [ 'Alex Hawkins', '510-599-1930' ] ] ] hashT.insert('Vance Carter', '120-589-1970').insert('Rick Mires', '520-589-1970').insert('Tom Bradey', '520-589-1970').insert('Biff Tanin', '520-589-1970'); //hashT.retrieveAll(); /* [ , [ [ 'Boo Radley', '520-589-1970' ], [ 'Tom Bradey', '520-589-1970' ] ], , [ [ 'Alex Hawkins', '510-599-1930' ], [ 'Rick Mires', '520-589-1970' ] ], , , [ [ 'Biff Tanin', '520-589-1970' ] ] ] */ //overide example (Phone Number Change) // hashT.insert('Rick Mires', '650-589-1970').insert('Tom Bradey', '818-589-1970').insert('Biff Tanin', '987-589-1970'); //hashT.retrieveAll(); /* [ , [ [ 'Boo Radley', '520-589-1970' ], [ 'Tom Bradey', '818-589-1970' ] ], , [ [ 'Alex Hawkins', '510-599-1930' ], [ 'Rick Mires', '650-589-1970' ] ], , , [ [ 'Biff Tanin', '987-589-1970' ] ] ] */ hashT.remove('Rick Mires'); hashT.remove('Tom Bradey'); //hashT.retrieveAll(); /* [ , [ [ 'Boo Radley', '520-589-1970' ] ], , [ [ 'Alex Hawkins', '510-599-1930' ] ], , , [ [ 'Biff Tanin', '987-589-1970' ] ] ] */ hashT.insert('Dick Mires', '650-589-1970').insert('Lam James', '818-589-1970').insert('Ricky Ticky Tavi', '987-589-1970'); hashT.retrieveAll(); /* NOTICE HOW HASH TABLE HAS NOW DOUBLED IN SIZE UPON REACHING 75% CAPACITY ie 6/8. It is now size 16. [, , [ [ 'Vance Carter', '120-589-1970' ] ], [ [ 'Alex Hawkins', '510-599-1930' ], [ 'Dick Mires', '650-589-1970' ], [ 'Lam James', '818-589-1970' ] ], , , , , , [ [ 'Boo Radley', '520-589-1970' ], [ 'Ricky Ticky Tavi', '987-589-1970' ] ], , , , , [ [ 'Biff Tanin', '987-589-1970' ] ] ] */ console.log(hashT.retrieve('Lam James')); //818-589-1970 console.log(hashT.retrieve('Dick Mires')); //650-589-1970 console.log(hashT.retrieve('Ricky Ticky Tavi')); //987-589-1970 console.log(hashT.retrieve('Alex Hawkins')); //510-599-1930 console.log(hashT.retrieve('Lebron James')); //null 

Você pode criar um usando como o seguinte:

 var dictionary = { Name:"Some Programmer", Age:24, Job:"Writing Programs" }; //Iterate Over using keys for (var key in dictionary) { console.log("Key: " + key + " , " + "Value: "+ dictionary[key]); } //access a key using object notation: console.log("Her Name is: " + dictionary.Name)