Come eseguire array / hashing associativi in ​​JavaScript


574

Devo archiviare alcune statistiche usando JavaScript in un modo come se lo facessi in C #:

Dictionary<string, int> statistics;

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

C'è un Hashtableo qualcosa del genere Dictionary<TKey, TValue>in JavaScript?
Come potrei memorizzare i valori in questo modo?


1
js è tipizzato in modo approssimativo, quindi non c'è modo di dichiarare semplicemente una stringa o int, puoi semplicemente dichiarare una var e assegnarle una stringa o int. : D
Gordon Gustafson,

Potresti voler dare un'occhiata a xDict. jsfiddle.net/very/MuVwd È un dizionario String => qualsiasi cosa scritta in Javascript.
Robert,

Questo articolo ha un'eccellente spiegazione di come gli array associativi siano implementati sottotitoli
Shuklaswag

La risposta accettata è stata scritta nel 2009 - supporta solo chiavi di stringa . Per chiavi senza stringa, usa Map o WeakMap, come nella risposta di Vitalii .
ToolmakerSteve

Risposte:


565

Usa oggetti JavaScript come array associativi .

Matrice associativa: in parole semplici, le matrici associative usano le stringhe anziché i numeri interi come indice.

Crea un oggetto con

var dictionary = {};

Javascript consente di aggiungere proprietà agli oggetti utilizzando la sintassi seguente:

Object.yourProperty = value;

Una sintassi alternativa per lo stesso è:

Object["yourProperty"] = value;

Se puoi anche creare la chiave per valutare le mappe di oggetti con la seguente sintassi

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

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

Puoi iterare attraverso un array associativo usando il costrutto del ciclo for..in come segue

for(var key in Object.keys(dict)){
  var value = dict[key];
  /* use key/value for intended purpose */
}

36
Si noti che l'approccio dell'autore di inizializzare un "array associativo" con new Array()è disapprovato. L'articolo alla fine menziona i suoi svantaggi e suggerisce new Object()o {}come alternative preferite, ma è quasi alla fine e temo che la maggior parte dei lettori non andrà così lontano.
Daniel Lubarov,

24
Fallire. JavaScript non supporta i riferimenti agli oggetti come chiavi, mentre qualcosa come Flash / AS3 Dictionary lo fa. In JavaScript, var obj1 = {}; var obj2 = {}; var table= {}; table[obj1] = "A"; table[obj2] = "B"; alert(table[obj1]); //displays Bperché non è in grado di distinguere tra le chiavi obj1 e obj2; sono entrambi convertiti in stringa e diventano semplicemente qualcosa come "Oggetto". Fallimento totale e rende la serializzazione sicura per tipo con riferimenti e riferimenti ciclici intatti difficili o non performanti in JavaScript. È facile in Flash / AS3.
Triynko,

Bene, l'unico modo in JS che possiamo validare controllando l'uguaglianza o definendo un metodo uguale con qualcosa del genere: 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]) dovrebbe darti A B.
ychaouche

2
@Leo L'esempio sembra sbagliato. for... inperché un dizionario passerà in Object.keysrassegna le sue chiavi, quindi sembra fuori luogo. Object.keysrestituisce una matrice delle chiavi del dizionario e for... inper una matrice scorre sulle sue "chiavi", che per una matrice sono i suoi indici, non i suoi valori.
JHH,

434
var associativeArray = {};
associativeArray["one"] = "First";
associativeArray["two"] = "Second";
associativeArray["three"] = "Third";

Se provieni da un linguaggio orientato agli oggetti, consulta questo articolo .


38
Puoi anche farlo in meno righe: var associativeArray = {"one": "First", "two": "second", "three": "Third"}; Quindi associativeArray ["one"] restituisce "First" e assocativeArray ["four"] restituisce null.
Tony Wickham,

2
Scusa @JuusoOhtonen, ho scritto il post 6 anni fa (è incredibile quanto tempo passa). Ho aggiornato il link. Si prega di controllare e non esitate a chiedere se avete dubbi
Dani Cricco,

145

Tutti i browser moderni supportano un oggetto Map javascript . Ci sono un paio di ragioni che rendono l'utilizzo di una mappa migliore di Object:

  • Un oggetto ha un prototipo, quindi ci sono chiavi predefinite nella mappa.
  • Le chiavi di un oggetto sono stringhe, dove possono avere qualsiasi valore per una mappa.
  • Puoi ottenere facilmente le dimensioni di una mappa mentre devi tenere traccia delle dimensioni di un oggetto.

Esempio:

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 desideri che le chiavi a cui non viene fatto riferimento da altri oggetti vengano raccolte in modo inutile, prendi in considerazione l'utilizzo di una WeakMap anziché di una mappa.


5
Spero che tra qualche anno questa sarà la risposta più votata.
Cameron Lee,

1
@CameronLee sicuramente lo farà
Loïc Faure-Lacroix il

1
Ciò Mapè a malapena utile quando la chiave è un oggetto ma deve essere confrontata per valore, non per riferimento.
Siyuan Ren,

7
Più di un anno dopo che questa risposta è stata scritta, NON è ancora vero che "tutti i browser moderni supportano Map". Solo sul desktop puoi contare almeno sul supporto di base di Map. Non su dispositivi mobili. Ad esempio, il browser Android non supporta affatto la mappa. Anche sul desktop, alcune implementazioni sono incomplete. Ad esempio, IE11 non supporta ancora l'enumerazione tramite "for ... of ...", quindi se vuoi la compatibilità IE devi usare il disgustoso .forEach kludge. Inoltre, JSON.stringify () non funziona con Map in nessun browser che ho provato. Inoltre, gli inizializzatori non funzionano in IE o Safari.
Dave Burton,

3
C'è un eccellente supporto per il browser. Controlla di nuovo. In ogni caso, questo è abbastanza facile da compilare, quindi il supporto del browser nativo non è un problema.
Brad

132

A meno che tu non abbia un motivo specifico per non farlo, usa semplicemente un oggetto normale. Le proprietà degli oggetti in Javascript possono essere referenziate usando la sintassi in stile hashtable:

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

Entrambi fooe gli barelementi possono ora essere indicati come:

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

Ovviamente questo significa che le tue chiavi devono essere stringhe. Se non sono stringhe, vengono convertite internamente in stringhe, quindi potrebbe funzionare ancora, YMMV.


1
Le chiavi come numeri interi non mi hanno causato problemi. stackoverflow.com/questions/2380019/…
Jonas Elfström

10
Jonas: tenere presente che i numeri interi vengono convertiti in stringhe quando viene impostata la proprietà: var hash = {}; hash[1] = "foo"; alert(hash["1"]);avvisi "pippo".
Tim Down,

17
Cosa succede se una delle tue chiavi è " proto " o " genitore "?
favore

5
Si noti che gli oggetti non possono essere utilizzati come chiavi in JavaScript. Bene, possono, ma vengono convertiti nelle loro rappresentazioni di stringa, quindi qualsiasi oggetto finirà per essere esattamente la stessa chiave. Vedi il suggerimento jshashtable di @ TimDown di seguito.
ericsoco,

21
Questo esempio è confuso perché stai usando foo e bar come chiave e valore in due casi. Molto più chiaro per mostrare che var dict = {}; dict.key1 = "val1"; dict["key2"] = "val2";l'elemento key1 di dict può essere referenziato in modo equivalente da entrambi dict["key1"]e dict.key1.
Jim,

49

Poiché ogni oggetto in JS si comporta come - ed è generalmente implementato come - una hashtable, vado solo con quello ...

var hashSweetHashTable = {};

26
Sottovalutato perché non mostra come accedere effettivamente ai valori nella "tabella hash".
IQAndreas

Sono in ritardo di 9 anni (non sapevo molto della programmazione, per non parlare di questo sito allora), ma ... Cosa succede se stai cercando di memorizzare punti su una mappa e devi vedere se qualcosa è già in un punto su una mappa? In tal caso, sarebbe meglio usare HashTable per questo, cercando le coordinate (un oggetto , non una stringa ).
Mike Warren,

@MikeWarren if (hashSweetHashTable.foo)dovrebbe inserire il blocco if se fooè impostato.
Koray Tugay,

21

quindi in C # il codice appare come:

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

o

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

in JavaScript

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

L'oggetto dizionario C # contiene metodi utili come dictionary.ContainsKey() in JavaScript che potremmo usare hasOwnPropertysimili

if (dictionary.hasOwnProperty("sample1"))
    console.log("sample1 key found and its value is"+ dictionary["sample1"]);

1
Voto per me non dover scrivere una risposta suhasOwnProperty
brichins

18

Se richiedi che le tue chiavi siano qualsiasi oggetto piuttosto che solo stringhe, puoi usare il mio jshashtable .


3
Quante ore ho passato inciampando sul fatto che gli oggetti non possono davvero essere usati come chiavi per array JS-style-Object-as-associative prima di averlo trovato? Grazie Tim.
ericsoco,

1
Il dizionario Flash / AS3, insieme alla maggior parte delle altre lingue, supporta i riferimenti agli oggetti come chiavi. JavaScript non lo ha ancora implementato, ma penso che sia in una specifica futura come una sorta di classe Map. Ancora una volta con i polyfill nel frattempo; così tanto per gli standard. Oh, aspetta ... finalmente nel 2015, sembra che Map sia arrivato: stackoverflow.com/a/30088129/88409 , ed è supportato da browser "moderni", lol: kangax.github.io/compat-table/es6/# Mappa (e non ampiamente supportata). Solo un decennio dietro AS3.
Triynko,

Tim, forse dovresti aggiornare jshashtable per usare Map () dove disponibile.
Dave Burton,

1
@DaveBurton: buon piano. Lo farò non appena avrò del tempo.
Tim Down,

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;
    }
}

1
Per le persone che non hanno votato, puoi commentare perché? Questa risposta è stata pubblicata nel 2011 e non nella data corrente.
Birey,

2
Non ho votato verso il basso ma ... non dovresti usare un array come oggetto. Non sono sicuro al 100% se questo era il tuo intento. Usa slice su array non eliminati per reindicizzare; delete è ok ma sarà impostato su indefinito - meglio essere espliciti; use = non definito su un oggetto troppo b / c è più veloce (ma più memoria). In breve: usa sempre un oggetto: {}non un array: []o new Array()se hai intenzione di avere chiavi di stringa altrimenti il ​​motore js ha un problema - vedrà 2 tipi per 1 variabile che significa nessuna ottimizzazione o funzionerà con array e realizzerà deve cambiare in oggetto (possibile riassegnazione).
Graeme Wicksted,

2
Proprio come con la risposta di Alex Hawkins, fornisci alcune spiegazioni sul perché questo codice dall'aspetto piuttosto complesso sia effettivamente utile e migliore delle altre risposte più brevi fornite qui.
Thomas Tempelmann,

6

Ho creato questo per ottenere alcuni problemi, come la mappatura delle chiavi degli oggetti, la capacità di enumerazione (con forEach()metodo) e la cancellazione.

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;
        }
    });
}


Documentazione di classe Hashtable

metodi:

  • get(key)
    Restituisce il valore associato alla chiave specificata.
    Parametri::
    key la chiave da cui recuperare il valore.

  • put(key, value)
    Associa il valore specificato alla chiave specificata.
    Parametri::
    key la chiave a cui associare il valore.
    value: Il valore da associare alla chiave.

  • remove(key)
    Rimuove la chiave specificata con il suo valore.
    Parametri::
    key la chiave da rimuovere.

  • clear()
    Cancella tutta la tabella hash, rimuovendo sia le chiavi che i valori.

  • indexOfKey(key)
    Restituisce l'indice della chiave specificata, in base all'ordine di aggiunta.
    Parametri::
    key la chiave di cui ottenere l'indice.

  • indexOfValue(value)
    Restituisce l'indice del valore specificato, in base all'ordine di aggiunta.
    Parametri::
    value il valore di cui ottiene l'indice.
    Note:
    queste informazioni vengono recuperate con il indexOf()metodo di un array, quindi confronta l'oggetto solo con il toString()metodo.

  • entryAt(index)
    Restituisce un oggetto con due proprietà: chiave e valore, che rappresentano la voce nell'indice specificato.
    Parametri::
    index l'indice della voce da ottenere.

  • containsKey(key)
    Indica se l'hashtable contiene la chiave specificata.
    Parametri::
    key la chiave da verificare.

  • containsValue(value)
    Indica se l'hashtable contiene il valore specificato.
    Parametri::
    value il valore da verificare.

  • forEach(iterator)
    Scorre tutte le voci nel valore specificato iterator.
    Parametri:
    value : Metodo con 3 parametri: key, valuee index, dove indexrappresenta l'indice della voce.

    Proprietà:

  • length ( Sola lettura )
    Ottiene il conteggio delle voci nella tabella hash.

  • keys ( Sola lettura )
    Ottiene una matrice di tutte le chiavi nella tabella hash.

  • values ( Sola lettura )
    Ottiene una matrice di tutti i valori nella tabella hash.

  • entries ( Sola lettura )
    Ottiene una matrice di tutte le voci nella tabella hash. Sono rappresentati nella stessa forma del metodo entryAt().


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

3
Sembra bello. Ora, per favore, spiega anche PERCHÉ questo è utile e potrebbe essere più adatto di tutte le altre risposte qui.
Thomas Tempelmann,

1

Puoi crearne uno usando il seguente:

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)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.