Hashmap JavaScript equivalente


354

Come chiarito nell'aggiornamento 3 su questa risposta , questa notazione:

var hash = {};
hash[X]

non esegue effettivamente l'hash dell'oggetto X; in realtà si converte Xin una stringa (tramite .toString()se si tratta di un oggetto o di altre conversioni incorporate per vari tipi primitivi) e quindi cerca quella stringa, senza hash, in " hash". Anche l'uguaglianza degli oggetti non viene verificata: se due oggetti diversi hanno la stessa conversione di stringhe, si sovrascriveranno l'un l'altro.

Detto questo - ci sono implementazioni efficienti di hashmap in javascript? (Ad esempio, il secondo risultato di Google javascript hashmapproduce un'implementazione che è O (n) per qualsiasi operazione. Vari altri risultati ignorano il fatto che oggetti diversi con rappresentazioni di stringa equivalenti si sovrascrivono.


1
@Claudiu: Ci scusiamo per la modifica, ma la "Mappa" nel titolo era davvero fuorviante. Torna indietro se non sei d'accordo, non avevo intenzione di patrocinare. :)
Tomalak,

6
@Claudiu: fai molte domande su JavaScript. Buone domande. Mi piace.
circa il

2
@Claudiu: Inoltre, potresti collegarti al risultato di Google a cui ti riferisci? Diverse versioni locali di Google restituiscono risultati diversi, l'implementazione a cui fai riferimento non sembra nemmeno presentarsi per me.
Tomalak,

@Tomalak: stavo per scrivere esattamente la stessa cosa!
circa il

3
@Claudiu No, non collegare a Google. Link alla pagina di cui stavi parlando (che ti è capitato di trovare tramite Google). Il collegamento a google ha tutti gli stessi problemi della spiegazione di cosa cercare: google personalizza i risultati in base alla posizione o alla cronologia delle ricerche, i risultati di google cambiano nel tempo (attualmente, questo è il risultato migliore per quella ricerca) e qualsiasi altra cosa che possa farcela mostra risultati diversi.
Jasper,

Risposte:


370

Perché non eseguire l'hash degli oggetti manualmente e utilizzare le stringhe risultanti come chiavi per un normale dizionario JavaScript? Dopo tutto sei nella posizione migliore per sapere cosa rende unici i tuoi oggetti. Questo è ciò che faccio.

Esempio:

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;

In questo modo è possibile controllare l'indicizzazione eseguita da JavaScript senza il pesante sollevamento dell'allocazione di memoria e la gestione degli overflow.

Naturalmente, se vuoi davvero la "soluzione di livello industriale", puoi creare una classe parametrizzata dalla funzione chiave e con tutte le API necessarie del contenitore, ma ... usiamo JavaScript e proviamo a essere semplici e leggeri, quindi questa soluzione funzionale è semplice e veloce.

La funzione chiave può essere semplice come selezionare gli attributi giusti dell'oggetto, ad esempio una chiave o un insieme di chiavi, che sono già univoci, una combinazione di chiavi, che sono univoche insieme o complesse come l'utilizzo di alcuni hash crittografici come nella codifica DojoX o UUID DojoX . Mentre queste ultime soluzioni possono produrre chiavi uniche, personalmente cerco di evitarle a tutti i costi, soprattutto se so cosa rende unici i miei oggetti.

Aggiornamento nel 2014: nel 2008 questa semplice soluzione richiede ancora più spiegazioni. Vorrei chiarire l'idea in un modulo di domande e risposte.

La tua soluzione non ha un vero hash. Dov'è???

JavaScript è un linguaggio di alto livello. La sua primitiva di base ( Oggetto ) include una tabella hash per mantenere le proprietà. Questa tabella di hash è generalmente scritta in un linguaggio di basso livello per efficienza. Usando un oggetto semplice con chiavi di stringa utilizziamo una tabella hash implementata in modo efficiente senza sforzi da parte nostra.

Come fai a sapere che usano l'hash?

Esistono tre modi principali per mantenere indirizzabile una raccolta di oggetti con una chiave:

  • Non ordinato. In questo caso per recuperare un oggetto con la sua chiave dobbiamo ripassare tutti i tasti fermandoci quando lo troviamo. In media ci vorranno n / 2 confronti.
  • Ordinato.
    • Esempio n. 1: un array ordinato - facendo una ricerca binaria troveremo la nostra chiave dopo il confronto ~ log2 (n) in media. Molto meglio.
    • Esempio n. 2: un albero. Di nuovo saranno ~ log (n) i tentativi.
  • Hash table. In media richiede un tempo costante. Confronta: O (n) vs. O (log n) vs. O (1). Boom.

Ovviamente gli oggetti JavaScript usano tabelle hash in qualche modo per gestire casi generali.

I venditori di browser usano davvero le tabelle hash ???

Veramente.

Gestiscono le collisioni?

Sì. Vedi sopra. Se hai trovato una collisione su stringhe disuguali, non esitare a presentare un bug a un fornitore.

Qual è la tua idea?

Se vuoi eseguire l'hashing di un oggetto, trova ciò che lo rende unico e usalo come chiave. Non provare a calcolare l'hash reale o emulare tabelle hash: è già gestito in modo efficiente dall'oggetto JavaScript sottostante.

Usa questa chiave con JavaScript Objectper sfruttare la sua tabella hash integrata mentre stai alla larga da possibili scontri con proprietà predefinite.

Esempi per iniziare:

  • Se i tuoi oggetti includono un nome utente univoco, utilizzalo come chiave.
  • Se include un numero cliente univoco, utilizzalo come chiave.
    • Se include numeri univoci emessi dal governo come un SSN o un numero di passaporto e il tuo sistema non consente duplicati, utilizzalo come chiave.
  • Se una combinazione di campi è unica, utilizzala come chiave.
    • Abbreviazione dello stato + numero di patente di guida rende una chiave eccellente.
    • Anche l'abbreviazione del paese + il numero del passaporto è una chiave eccellente.
  • Alcune funzioni sui campi o su un intero oggetto possono restituire un valore univoco: utilizzalo come chiave.

Ho usato il tuo suggerimento e ho memorizzato nella cache tutti gli oggetti usando un nome utente. Ma un saggio si chiama "toString", che è una proprietà integrata! Cosa dovrei fare ora?

Ovviamente, se è anche lontanamente possibile che la chiave risultante sarà composta esclusivamente da caratteri latini, dovresti fare qualcosa al riguardo. Ad esempio, aggiungi qualsiasi carattere Unicode non latino che ti piace all'inizio o alla fine per annullare lo scontro con le proprietà predefinite: "#toString", "#MarySmith". Se si utilizza una chiave composita, separare i componenti chiave utilizzando un tipo di delimitatore non latino: "nome, città, stato".

In generale, questo è il luogo in cui dobbiamo essere creativi e selezionare le chiavi più semplici con determinate limitazioni (unicità, potenziali conflitti con proprietà predefinite).

Nota: le chiavi univoche non si scontrano per definizione, mentre i potenziali scatti di hash saranno gestiti dal sottostante Object.

Perché non ti piacciono le soluzioni industriali?

IMHO, il miglior codice non è affatto un codice: non ha errori, non richiede manutenzione, è facile da capire ed esegue istantaneamente. Tutte le "tabelle hash in JavaScript" che ho visto erano> 100 righe di codice e riguardavano più oggetti. Confronta con: dict[key] = value.

Un altro punto: è persino possibile battere una performance di un oggetto primordiale scritto in un linguaggio di basso livello, usando JavaScript e gli stessi oggetti primordiali per implementare ciò che è già implementato?

Voglio ancora eseguire l'hashing dei miei oggetti senza chiavi!

Siamo fortunati: ECMAScript 6 (previsto per metà 2015, dare o prendere 1-2 anni dopo per diffondersi) definisce la mappa e il set .

A giudicare dalla definizione, possono usare l'indirizzo dell'oggetto come chiave, il che rende gli oggetti immediatamente distinti senza chiavi artificiali. OTOH, due oggetti diversi, ma identici, saranno mappati come distinti.

Analisi comparativa da MDN :

Gli oggetti sono simili a Maps in quanto entrambi consentono di impostare chiavi su valori, recuperare tali valori, eliminare chiavi e rilevare se qualcosa è memorizzato in una chiave. Per questo motivo (e perché non c'erano alternative integrate), gli oggetti sono stati storicamente usati come mappe; tuttavia, ci sono differenze importanti che rendono preferibile l'uso di una mappa in alcuni casi:

  • Le chiavi di un oggetto sono stringhe e simboli, mentre possono avere qualsiasi valore per una mappa, comprese funzioni, oggetti e qualsiasi primitiva.
  • Le chiavi in ​​Mappa sono ordinate mentre le chiavi aggiunte all'oggetto non lo sono. Pertanto, quando si scorre su di esso, un oggetto Mappa restituisce le chiavi in ​​ordine di inserimento.
  • Puoi ottenere facilmente le dimensioni di una mappa con la proprietà size, mentre il numero di proprietà in un oggetto deve essere determinato manualmente.
  • Una mappa è iterabile e può quindi essere iterata direttamente, mentre l'iterazione su un oggetto richiede l'ottenimento delle sue chiavi in ​​qualche modo e l'iterazione su di essi.
  • Un oggetto ha un prototipo, quindi ci sono chiavi predefinite nella mappa che potrebbero scontrarsi con le tue chiavi se non stai attento. A partire da ES5 questo può essere aggirato usando map = Object.create (null), ma raramente viene fatto.
  • Una mappa può funzionare meglio in scenari che prevedono l'aggiunta e la rimozione frequenti di coppie di chiavi.

13
Questa non sembra una mappa corretta, perché non gestisci le collisioni. Se questo è vero: hash (obj1) == hash (obj2), perderai i tuoi dati.
Beefeather,

32
Il cielo ti aiuta quando "PAUL AINLEY" e "PAULA INLEY" si registrano nel tuo sistema ...
Matt R

34
@MattR In realtà il tuo esempio funzionerà correttamente senza l'aiuto del cielo anche con una finta funzione hash. Spero che altri lettori si rendano conto che una funzione hash non realistica troppo semplificata è stata utilizzata come segnaposto per dimostrare una tecnica diversa. Entrambi i commenti sul codice e la risposta stessa sottolineano che non è reale. La selezione delle chiavi appropriate è discussa nell'ultimo paragrafo della risposta.
Eugene Lazutkin,

6
@EugeneLazutkin - ti sbagli ancora, temo. Il tuo esempio è ancora soggetto a collisioni di hash. Non pensare che solo il primo nome possa in qualche modo aiutarti!
Matt R

3
@EugeneLazutkin La maggior parte delle persone non legge che tu abbia una risposta PRIMA che appaia ES6 ... Fammi congratularmi per la tua profonda conoscenza di JS.
Gabriel Andrés Brancolini,

171

Descrizione del problema

JavaScript non ha un tipo di mappa generale incorporato (a volte chiamato array o dizionario associativo ) che consente di accedere a valori arbitrari tramite chiavi arbitrarie. La struttura dei dati fondamentali di JavaScript è l' oggetto , un tipo speciale di mappa che accetta solo stringhe come chiavi e ha una semantica speciale come eredità prototipica, getter e setter e altri voodoo.

Quando si utilizzano oggetti come mappe, è necessario ricordare che la chiave verrà convertita in un valore stringa tramite toString(), il che si tradurrà nella mappatura 5e '5'nello stesso valore e in tutti gli oggetti che non sovrascrivono il toString()metodo al valore indicizzato da '[object Object]'. Potresti anche accedere involontariamente alle sue proprietà ereditate se non controlli hasOwnProperty().

Il tipo di array incorporato di JavaScript non aiuta un po ': gli array JavaScript non sono array associativi, ma solo oggetti con alcune proprietà speciali. Se vuoi sapere perché non possono essere usati come mappe, guarda qui .

La soluzione di Eugene

Eugene Lazutkin ha già descritto l'idea di base dell'utilizzo di una funzione hash personalizzata per generare stringhe univoche che possono essere utilizzate per cercare i valori associati come proprietà di un oggetto dizionario. Molto probabilmente questa sarà la soluzione più veloce, poiché gli oggetti vengono implementati internamente come tabelle hash .

  • Nota: le tabelle hash (a volte chiamate mappe hash ) sono un'implementazione particolare del concetto di mappa che utilizza un array di supporto e la ricerca tramite valori hash numerici. L'ambiente di runtime potrebbe utilizzare altre strutture (come alberi di ricerca o skip list ) per implementare oggetti JavaScript, ma poiché gli oggetti sono la struttura di dati fondamentale, dovrebbero essere sufficientemente ottimizzati.

Per ottenere un valore hash univoco per oggetti arbitrari, una possibilità è quella di utilizzare un contatore globale e memorizzare nella cache il valore hash nell'oggetto stesso (ad es. In una proprietà denominata __hash).

Una funzione hash che fa questo è e funziona sia per i valori primitivi che per gli oggetti è:

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

hash.current = 0;

Questa funzione può essere utilizzata come descritto da Eugene. Per comodità, lo avvolgeremo ulteriormente in una Mapclasse.

La mia Mapimplementazione

La seguente implementazione memorizzerà inoltre le coppie chiave-valore in un elenco doppiamente collegato al fine di consentire una rapida iterazione su chiavi e valori. Per fornire la propria funzione hash, è possibile sovrascrivere il hash()metodo dell'istanza dopo la creazione.

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

Esempio

Il seguente script

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 questo output:

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

Ulteriori considerazioni

PEZ ha suggerito di sovrascrivere il toString()metodo, presumibilmente con la nostra funzione hash. Questo non è fattibile perché non funziona per i valori primitivi (cambiando toString()per le primitive è una molto cattiva idea). Se vogliamo toString()restituire valori significativi per oggetti arbitrari, dovremmo modificare Object.prototype, che alcune persone (me stesso non incluso) considerano verboten .


Modifica: la versione corrente della mia Mapimplementazione e altri gadget JavaScript possono essere ottenuti da qui .


ES5 depreca l'uso di callee ( goo.gl/EeStE ). Invece, suggerisco Map._counter = 0, e nel costruttore Mappa fare this._hash = 'object ' + Map._counter++. Quindi hash () diventareturn (value && value._hash) || (typeof(value) + ' ' + String(value));
broofa il

Il collegamento al codice è interrotto: mercurial.intuxication.org/hg/js-hacks/raw-file/tip/map.js
ahcox

ciao @Christoph, potresti aggiornare il tuo link dove posso trovare l'implementazione di Map?
NumenorForLife,

2
@ jsc123: Esaminerò quello - per ora puoi ottenere una copia
Christoph

58

So che questa domanda è piuttosto vecchia, ma al giorno d'oggi ci sono alcune soluzioni davvero eccezionali con librerie esterne.

JavaScript ha anche il suo linguaggio fornito Map.


2
Questo è il modo di andare avanti al 21 ° secolo. Peccato che ho trovato il tuo post dopo aver finito il mio codice con qualche brutta mappa fatta in casa. I RAEE hanno bisogno di più voti per la tua risposta
Phung D. Il

1
Collections.js ha alcune implementazioni, ma non riesco a trovarne nessuna in underscore.js o lodash ... a cosa ti riferivi in ​​underscore che sarebbe utile?
Codebling

@CodeBling nessuna idea. penso di averlo confuso con la funzione mappa. lo rimuoverò dalla risposta.
Jamel Toms,

3
È giusto. Chiunque consideri Collections.js dovrebbe essere consapevole del fatto che modifica i prototipi globali di array, funzioni, oggetti e regexp in modo problematico ( vedere i problemi che ho riscontrato qui ). Sebbene inizialmente fossi molto soddisfatto di collections.js (e quindi di questa risposta), i rischi associati all'utilizzo erano troppo alti, quindi l'ho lasciato cadere. Solo il ramo v2 di collections.js di kriskowal (in particolare, v2.0.2 +) elimina le modifiche globali del prototipo ed è sicuro da usare.
Codebling

28

Ecco un modo semplice e conveniente di usare qualcosa di simile alla mappa 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
        }

E per ottenere il valore:

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

......  etc  .....

2
Funziona solo con le chiavi di stringa. Credo che l'OP fosse interessato all'utilizzo di chiavi di qualsiasi tipo.
fattore

26

Secondo ECMAScript 2015 (ES6) lo standard javascript ha un'implementazione della mappa. Maggiori informazioni su quali potrebbero essere trovate qui

Utilizzo di base:

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"

21

Puoi usare ES6 WeakMapo Map:

  • Le mappe deboli sono mappe chiave / valore in cui le chiavi sono oggetti.

  • Mapgli oggetti sono semplici mappe chiave / valore. Qualsiasi valore (sia oggetti che valori primitivi) può essere usato come chiave o valore.

Tenere presente che nessuno dei due è ampiamente supportato, ma è possibile utilizzare ES6 Shim (richiede ES5 nativo o ES5 Shim ) per supportare Map, ma non WeakMap( vedere perché ).


Nel 2019 sono supportati molto bene e hanno metodi incredibili! developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Juanma Menendez,

13

Dovresti archiviare in alcuni stati couplet interni di coppie oggetto / valore

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

E usalo come tale:

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

Naturalmente, questa implementazione è anche da qualche parte lungo le linee di O (n). Gli esempi di Eugene sopra sono l'unico modo per ottenere un hash che funzioni con qualsiasi tipo di velocità che ti aspetteresti da un vero hash.

Aggiornare:

Un altro approccio, sulla falsariga della risposta di Eugene, è in qualche modo associare un ID univoco a tutti gli oggetti. Uno dei miei approcci preferiti è quello di prendere uno dei metodi integrati ereditati dalla superclasse Object, sostituirlo con un passthrough di funzione personalizzato e associare le proprietà a quell'oggetto funzione. Se dovessi riscrivere il mio metodo HashMap per farlo, sembrerebbe:

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

Questa versione sembra essere solo leggermente più veloce, ma in teoria sarà significativamente più veloce per grandi set di dati.


Un array associativo, ovvero un array di 2 tuple, è una mappa, non una mappa hash; una HashMap è una mappa che utilizza gli hash per migliorare le prestazioni.
Erik Kaplun,

Vero, ma perché dividere i peli sull'argomento? Non è possibile creare una vera mappa hash in JavaScript poiché non è possibile ottenere gli indirizzi di memoria degli oggetti. E le coppie chiave / valore dell'oggetto incorporato di JavaScript (usate nel mio secondo esempio) possono agire come HashMaps, ma non necessariamente, poiché dipende dal runtime utilizzato nel browser per come viene implementata la ricerca.
carne in vaso

11

Sfortunatamente, nessuna delle risposte sopra è stata positiva per il mio caso: diversi oggetti chiave possono avere lo stesso codice hash. Pertanto, ho scritto una semplice versione di HashMap simile 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: gli oggetti chiave devono "implementare" i metodi hashCode () e equals ().


7
La preferenza di new Array()over []è garantire l'assoluta somiglianza Java del tuo codice? :)
Erik Kaplun,

6

Ho implementato una HashMap JavaScript il cui codice può essere ottenuto da http://github.com/lambder/HashMapJS/tree/master

Ecco il codice:

/*
 =====================================================================
 @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")
}

2
Il codice non sembra funzionare inserendo lo stesso oggetto in più HashMaps.
Erik Kaplun,

5

In ECMA6 è possibile utilizzare WeakMap

Esempio:

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

Ma:

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

oh lode Gesù, stanno finalmente aggiungendo riferimenti deboli a JavaScript. è giunto il momento ... +1 per quello, ma questo sarebbe davvero terribile da usare perché i riferimenti sono deboli
Claudiu,

2

Javascript non incorpora Map / hashmap. Dovrebbe essere chiamato matrice associativa .

hash["X"]è uguale a hash.X, ma consenti "X" come variabile stringa. In altre parole, hash[x]è funzionalmente uguale aeval("hash."+x.toString())

È più simile a object.properties piuttosto che alla mappatura dei valori-chiave. Se stai cercando una migliore mappatura chiave / valore in Javascript, utilizza l'oggetto Mappa che puoi trovare sul web.


2

Prova l'implementazione della mia tabella hash JavaScript: http://www.timdown.co.uk/jshashtable

Cerca un metodo hashCode () di oggetti chiave, oppure puoi fornire una funzione di hashing durante la creazione di un oggetto Hashtable.


2

Sembra una soluzione abbastanza robusta: https://github.com/flesler/hashmap . Funzionerà anche bene per funzioni e oggetti che sembrano identici. L'unico hack che utilizza è l'aggiunta di un membro oscuro a un oggetto per identificarlo. Se il tuo programma non sovrascrive quella variabile oscura (è qualcosa come l' hashid ), sei d'oro.


2

Se le prestazioni non è critica (ad esempio, la quantità di chiavi è relativamente piccolo) e non si vuole inquinare il vostro (o forse non è il tuo) oggetti con campi aggiuntivi come _hash, _idecc, allora si può fare uso del fatto che Array.prototype.indexOfimpiega uguaglianza rigorosa. Ecco una semplice implementazione:

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

Esempio di utilizzo:

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.

Rispetto a ES6 WeakMap ha due problemi: O (n) tempo di ricerca e non debolezza (cioè causerà perdita di memoria se non si utilizza delete o si clearrilasciano le chiavi).


2

La mia implementazione della mappa, derivata dall'esempio di Christoph:

Esempio di utilizzo:

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

1

Aggiungendo ancora un'altra soluzione: HashMapè praticamente la prima classe che ho portato da Java a Javascript. Si potrebbe dire che c'è un sacco di overhead, ma l'implementazione è quasi al 100% uguale all'implementazione di Java e include tutte le interfacce e le sottoclassi.

Il progetto può essere trovato qui: https://github.com/Airblader/jsava Allegherò anche il codice sorgente (attuale) per la classe HashMap, ma come detto dipende anche dalla super classe ecc. Il framework OOP utilizzato è qooxdoo.

Modifica: si noti che questo codice è già obsoleto e fare riferimento al progetto github per il lavoro corrente. Al momento della stesura di questo, c'è anche ArrayListun'implementazione.

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

Hmm approccio interessante .. hai pensato di provare un approccio automatizzato? ovvero, eseguire un compilatore da Java a JavaScript sul codice sorgente per l'implementazione java corrente?
Claudiu,

No :) Questo è solo un progetto divertente per me e c'erano alcune cose in cui non potevo semplicemente "copiare" il codice. Non sono a conoscenza dei compilatori da Java a Javascript, anche se credo che esistano. Non sono sicuro di come lo tradurrebbero. Sono abbastanza certo che non produrranno comunque un codice di buona qualità.
Ingo Bürk,

Ah gotcha. Stavo pensando al compilatore di Google Web Toolkit , ma sembra che alla fine abbiano fatto quello che stai facendo qui per le librerie principali: "Il compilatore GWT supporta la stragrande maggioranza del linguaggio Java stesso. La libreria di runtime GWT emula un sottoinsieme rilevante del Libreria di runtime Java. ". Forse qualcosa da guardare per vedere come gli altri hanno risolto lo stesso problema!
Claudiu,

Si. Sono sicuro che la soluzione di Google è ben oltre la mia, ma poi mi sto solo divertendo a giocare. Sfortunatamente, il codice sorgente sembra essere stato revocato (?), Almeno non riesco a sfogliarlo e i collegamenti interessanti sembrano essere morti. Peccato, mi sarebbe piaciuto guardarlo.
Ingo Bürk,

Divertirsi giocando è il modo migliore per imparare =). grazie per la condivisione
Claudiu,

0

Ancora un'altra implementazione della mia mappa. Con randomizer, 'generics' e 'iterator' =)

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

Esempio:

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
*/
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.