Como fazer array associativo / hash em JavaScript

574

Preciso armazenar algumas estatísticas usando JavaScript de uma maneira como faria em C #:

Dictionary<string, int> statistics;

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

Existe um Hashtableou algo como Dictionary<TKey, TValue>em JavaScript?
Como eu poderia armazenar valores dessa maneira?

George2
fonte
1
js é digitado livremente; portanto, não há como declarar apenas uma string ou int; você pode declarar uma var e atribuir uma string ou int. : D
Gordon Gustafson
Você pode querer verificar o xDict. jsfiddle.net/very/MuVwd É um dicionário String => qualquer coisa escrita em Javascript.
Robert
Este artigo tem uma excelente explicação de como arrays associativos são implementados sob o capô em Javascript jayconrod.com/posts/52/a-tour-of-v8-object-representation
Shuklaswag
A resposta aceita foi escrita em 2009 - suporta apenas chaves de string . Para chaves sem sequência, use Map ou WeakMap, como na resposta do Vitalii .
Página

Respostas:

564

Use objetos JavaScript como matrizes associativas .

Matriz associativa: em palavras simples, matrizes associativas usam Strings em vez de números inteiros como índice.

Crie um objeto com

var dictionary = {};

Javascript permite adicionar propriedades a objetos usando a seguinte sintaxe:

Object.yourProperty = value;

Uma sintaxe alternativa para o mesmo é:

Object["yourProperty"] = value;

Se você também pode criar chave para valorizar mapas de objetos com a seguinte sintaxe

var point = { x:3, y:2 };

point["x"] // returns 3
point.y // returns 2

Você pode iterar através de uma matriz associativa usando a construção de loop for..in da seguinte maneira

for(var key in Object.keys(dict)){
  var value = dict[key];
  /* use key/value for intended purpose */
}
Alek Davis
fonte
36
Observe que a abordagem do autor de inicializar uma "matriz associativa" new Array()é desaprovada. O artigo finalmente menciona suas desvantagens e sugere new Object()ou {}como alternativas preferidas, mas isso está próximo do fim e eu temo que a maioria dos leitores não chegue tão longe.
Daniel Lubarov
24
Falhou. O JavaScript não suporta referências de objetos como chaves, enquanto algo como o Dicionário Flash / AS3. Em JavaScript, var obj1 = {}; var obj2 = {}; var table= {}; table[obj1] = "A"; table[obj2] = "B"; alert(table[obj1]); //displays Bporque não é possível diferenciar entre as chaves obj1 e obj2; ambos são convertidos em string e se tornam algo como "Object". Falha total e torna a serialização com segurança de tipo com referências e referências cíclicas intactas difíceis ou sem desempenho em JavaScript. É fácil no Flash / AS3.
Triynko
Bem, a única maneira em JS podemos validar verificando a igualdade ou definindo um método igual por algo como isto: # Point.prototype.equals = function(obj) { return (obj instanceof Point) && (obj.x === this.x) && (obj.y === this.y); };
Nadeem
1
@Leo console.log ({A: 'B', C: 'D'} [foo]) deve fornecer A B.
ychaouche
2
@ Leo O exemplo parece errado. for... inpois um dicionário passa por cima de suas teclas, então Object.keysparece mal colocado ali. Object.keysretorna uma matriz das chaves do dicionário e, for... inpara uma matriz, passa por cima de suas "chaves", que para uma matriz são seus índices, não seus valores.
JHH
434
var associativeArray = {};
associativeArray["one"] = "First";
associativeArray["two"] = "Second";
associativeArray["three"] = "Third";

Se você é oriundo de uma linguagem orientada a objetos, verifique este artigo .

Dani Cricco
fonte
38
Você também pode fazer isso em menos linhas: var associativeArray = {"one": "First", "two": "second", "three": "Third"}; Então associativeArray ["one"] retorna "First" e assocativeArray ["four"] retorna null.
Tony Wickham
2
Desculpe @JuusoOhtonen, eu escrevi o post há 6 anos (é incrível a rapidez com que o tempo passa). Eu atualizei o link. Por favor, verifique-o e não hesite em perguntar se você tem alguma dúvida
Dani Cricco
145

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

  • Um objeto tem um protótipo, portanto, existem chaves padrão no mapa.
  • As chaves de um Objeto são Strings, onde podem ter qualquer valor para um Mapa.
  • Você pode obter facilmente o tamanho de um mapa enquanto precisa controlar o tamanho de um objeto.

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ê deseja que as chaves que não são referenciadas de outros objetos sejam coletadas como lixo, considere usar um WeakMap em vez de um Mapa.

Vitalii Fedorenko
fonte
5
Esperamos que daqui a alguns anos este seja o mais votado em resposta.
Cameron Lee
1
@CameronLee certamente vai #
Loïc Faure-Lacroix
1
Isso Mapé pouco útil quando sua chave é um objeto, mas deve ser comparada por valor, não por referência.
Siyuan Ren
7
Mais de um ano depois que essa resposta foi escrita, ainda não é verdade que "todos os navegadores modernos suportam o mapa". Somente na área de trabalho você pode contar com pelo menos o suporte básico ao Mapa. Não em dispositivos móveis. Por exemplo, o navegador Android não tem suporte a mapas. Mesmo na área de trabalho, algumas implementações estão incompletas. Por exemplo, o IE11 ainda não suporta a enumeração via "para ... de ...", portanto, se você deseja compatibilidade com o IE, deve usar o nojento .forEach kludge. Além disso, JSON.stringify () não funciona no Map em nenhum navegador que eu tentei. Os inicializadores também não funcionam no IE ou no Safari.
Dave Burton
3
Existe um excelente suporte ao navegador. Verifique novamente. De qualquer forma, isso é muito fácil de preencher, portanto, o suporte ao navegador nativo não é um problema.
Brad
132

A menos que você tenha um motivo específico para não, basta usar um objeto normal. As propriedades do objeto em Javascript podem ser referenciadas usando a sintaxe no estilo de hashtable:

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

Ambos fooe barelementos podem agora ser referenciado como:

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

Claro que isso significa que suas chaves precisam ser seqüências de caracteres. Se não forem cadeias de caracteres, elas serão convertidas internamente em cadeias de caracteres, portanto ainda poderá funcionar, YMMV.

roryf
fonte
1
Teclas como números inteiros não me causaram problemas. stackoverflow.com/questions/2380019/…
Jonas Elfström
10
Jonas: lembre-se de que seus números inteiros são convertidos em strings quando a propriedade está sendo configurada: var hash = {}; hash[1] = "foo"; alert(hash["1"]);alerta "foo".
Tim Down
17
E se uma de suas chaves for " proto " ou " parent "?
PleaseStand
5
Observe que os objetos não podem ser usados ​​como chaves em JavaScript. Bem, eles podem, mas são convertidos em suas representações String, para que qualquer Objeto termine exatamente como a mesma chave. Veja a sugestão jshashtable de @ TimDown abaixo.
22613 ericsoco
21
Este exemplo é confuso porque você usa foo e bar como chave e valor em duas instâncias. Muito mais claro para mostrar que var dict = {}; dict.key1 = "val1"; dict["key2"] = "val2";o elemento key1 do dict pode ser referenciado equivalentemente por ambos dict["key1"]e dict.key1.
27414 Jim
49

Como todo objeto em JS se comporta como - e geralmente é implementado como - uma hashtable, eu apenas uso isso ...

var hashSweetHashTable = {};
Shog9
fonte
26
Voto negativo porque não mostra como realmente acessar valores na "hashtable".
IQAndreas
Estou com 9 anos de atraso (eu não sabia muito sobre programação, muito menos esse site naquela época), mas ... E se você estiver tentando armazenar pontos em um mapa e precisar ver se algo já está em um ponto em um mapa? Nesse caso, seria melhor usar o HashTable para isso, procurando por coordenadas (um objeto , não uma string ).
Mike Warren
@MikeWarren if (hashSweetHashTable.foo)deve inserir o bloco if se fooestiver definido.
Koray Tugay
21

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

Dictionary<string,int> dictionary = new Dictionary<string,int>();
dictionary.add("sample1", 1);
dictionary.add("sample2", 2);

ou

var dictionary = new Dictionary<string, int> {
    {"sample1", 1},
    {"sample2", 2}
};

em JavaScript

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

O objeto de dicionário C # contém métodos úteis, como dictionary.ContainsKey() no JavaScript, podemos usar o hasOwnPropertymesmo

if (dictionary.hasOwnProperty("sample1"))
    console.log("sample1 key found and its value is"+ dictionary["sample1"]);
Raj
fonte
1
Upvote para mim não ter que escrever uma resposta sobrehasOwnProperty
brichins
18

Se você precisar que suas chaves sejam qualquer objeto, e não apenas cadeias de caracteres, use minha jshashtable .

Tim Down
fonte
3
Quantas horas passei tropeçando no fato de que os Objetos não podem realmente ser usados ​​como chaves para matrizes no estilo JS como Objeto como associativas antes de encontrar isso? Obrigado Tim.
ericsoco
1
O Flash / AS3 Dictionary, juntamente com a maioria dos outros idiomas, suporta referências de objetos como chaves. O JavaScript ainda não o implementou ainda, mas acho que está em uma especificação futura como uma espécie de classe Map. Novamente com os polyfills enquanto isso; muito para os padrões. Ah, espere ... finalmente, em 2015, parece que o Map chegou: stackoverflow.com/a/30088129/88409 e é suportado por navegadores "modernos", kangax.github.io/compat-table/es6/# Mapa (e não é realmente amplamente suportado). Apenas uma década atrás do AS3.
Triynko
Tim, talvez você deva atualizar o jshashtable para usar o Map () quando disponível.
Dave Burton
1
@DaveBurton: Bom plano. Vou fazê-lo assim que tiver algum tempo.
Tim Baixo
6
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;
    }
}
Birey
fonte
1
Para as pessoas que votaram negativamente, você pode comentar por quê? Esta resposta foi publicada em 2011 e não na data atual.
Birey
2
Não votei em baixa, mas ... você não deve usar uma matriz como objeto. Não tenho 100% de certeza se essa foi sua intenção. Use fatia nas matrizes não excluídas para reindexar; delete está ok, mas será definido como indefinido - melhor ser explícito; use = indefinido em um objeto muito b / c é mais rápido (mas com mais memória). Em resumo: sempre use um objeto: {}não um array: []ou new Array()se você pretende ter chaves de string, caso contrário, o mecanismo js tem um problema - ele verá 2 tipos para 1 variável, o que significa que não há otimização ou será executado com o array e perceberá precisa mudar para objeto (possível realocação).
Graeme Wicksted 04/04/2015
2
Assim como na resposta de Alex Hawkins, forneça algumas explicações sobre por que esse código de aparência bastante complexa é realmente útil e melhor do que as outras respostas mais curtas fornecidas aqui.
Thomas Tempelmann
6

Eu criei isso para obter algum problema, como mapeamento de chave de objeto, capacidade de enumeração (com forEach()método) 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 aula Hashtable

Métodos:

  • get(key)
    Retorna o valor associado à chave especificada.
    Parâmetros::
    key A chave da qual recuperar o valor.

  • put(key, value)
    Associa o valor especificado à chave especificada.
    Parâmetros::
    key A chave à qual associar o valor.
    value: O valor a ser associado à chave.

  • remove(key)
    Remove a chave especificada com seu valor.
    Parâmetros::
    key A chave a ser removida.

  • clear()
    Limpa toda a hashtable, removendo as chaves e os valores.

  • indexOfKey(key)
    Retorna o índice da chave especificada, com base na ordem de adição.
    Parâmetros::
    key cuja chave obtém o índice.

  • indexOfValue(value)
    Retorna o índice do valor especificado, com base na ordem de adição.
    Parâmetros::
    value cujo valor obtém o índice.
    Notas:
    Essas informações são recuperadas pelo indexOf()método de uma matriz, portanto, comparam o objeto apenas com o toString()método

  • entryAt(index)
    Retorna um objeto com duas propriedades: chave e valor, representando a entrada no índice especificado.
    Parâmetros::
    index O índice da entrada a ser obtida.

  • containsKey(key)
    Retorna se a hashtable contém a chave especificada.
    Parâmetros::
    key A chave a ser verificada.

  • containsValue(value)
    Retorna se a hashtable contém o valor especificado.
    Parâmetros::
    value O valor a ser verificado.

  • forEach(iterator)
    Repete todas as entradas no especificado iterator.
    Parâmetros:
    value : Um método com parâmetros: 3 key, valuee index, onde indexrepresenta o índice da entrada.

    Propriedades:

  • length ( Somente leitura )
    Obtém a contagem das entradas 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 entradas na hashtable. Eles são representados na mesma forma do método entryAt().

Davide Cannizzo
fonte
2

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
Alex Hawkins
fonte
3
Parece legal. Agora, explique também POR QUE isso é útil e pode ser mais adequado do que todas as outras respostas aqui.
Thomas Tempelmann 04/04
1

Você pode criar um usando 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)

Ali Ezzat Odeh
fonte