JavaScript Hashmap Setara


354

Sebagaimana dijelaskan dalam pembaruan 3 pada jawaban ini, notasi ini:

var hash = {};
hash[X]

sebenarnya tidak hash objek X; sebenarnya hanya mengkonversi Xke string (melalui .toString()apakah itu objek, atau beberapa konversi bawaan untuk berbagai jenis primitif) dan kemudian melihat string itu, tanpa hashing, di " hash". Kesetaraan objek juga tidak dicentang - jika dua objek yang berbeda memiliki konversi string yang sama, mereka hanya akan saling menimpa.

Dengan ini - apakah ada implementasi hashmaps yang efisien dalam javascript? (Misalnya, hasil Google ke-2 javascript hashmapmenghasilkan implementasi yang O (n) untuk operasi apa pun. Berbagai hasil lainnya mengabaikan fakta bahwa objek yang berbeda dengan representasi string setara menimpa satu sama lain.


1
@Claudiu: Maaf untuk hasil edit, tetapi "Peta" pada judulnya benar-benar menyesatkan. Kembalikan jika Anda tidak setuju, saya tidak bermaksud menggurui. :)
Tomalak

6
@Claudiu: Anda banyak bertanya tentang javascript. Pertanyaan yang bagus Aku suka itu.
beberapa

2
@Claudiu: Dapatkah Anda menautkan ke hasil Google yang Anda rujuk? Versi lokal Google yang berbeda menghasilkan hasil yang berbeda, implementasi yang Anda rujuk tampaknya tidak muncul untuk saya.
Tomalak

@ Tomalak: Saya hanya akan menulis hal yang persis sama!
beberapa

3
@Claudiu Tidak, jangan taut ke google. Tautan ke halaman yang sedang Anda bicarakan (yang kebetulan Anda temukan melalui google). Menautkan ke Google memiliki semua masalah yang sama dengan menjelaskan apa yang harus dicari: google mengkustomisasi hasil berdasarkan lokasi atau pada riwayat pencarian, hasil google berubah dari waktu ke waktu (saat ini, ini adalah hasil teratas untuk pencarian itu) dan hal lain yang dapat membuatnya menunjukkan hasil yang berbeda.
Jasper

Jawaban:


370

Mengapa tidak hash objek Anda sendiri secara manual, dan gunakan string yang dihasilkan sebagai kunci untuk kamus JavaScript biasa? Bagaimanapun, Anda berada dalam posisi terbaik untuk mengetahui apa yang membuat objek Anda unik. Itu yang saya lakukan.

Contoh:

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;

Dengan cara ini Anda dapat mengontrol pengindeksan yang dilakukan oleh JavaScript tanpa mengangkat alokasi memori, dan penanganan overflow.

Tentu saja, jika Anda benar-benar menginginkan "solusi tingkat industri", Anda dapat membangun kelas yang diparameterisasi oleh fungsi utama, dan dengan semua API yang diperlukan dari wadah, tapi ... kami menggunakan JavaScript, dan berusaha menjadi sederhana dan ringan, jadi solusi fungsional ini sederhana dan cepat.

Fungsi kunci dapat sesederhana memilih atribut yang tepat dari objek, misalnya, kunci, atau seperangkat kunci, yang sudah unik, kombinasi kunci, yang unik bersama-sama, atau serumit menggunakan beberapa hash kriptografi seperti dalam DojoX Encoding , atau DojoX UUID . Sementara solusi terakhir dapat menghasilkan kunci unik, secara pribadi saya mencoba menghindarinya dengan segala cara, terutama, jika saya tahu apa yang membuat objek saya unik.

Pembaruan pada tahun 2014: Dijawab pada tahun 2008 solusi sederhana ini masih membutuhkan lebih banyak penjelasan. Izinkan saya mengklarifikasi ide dalam bentuk tanya jawab.

Solusi Anda tidak memiliki hash nyata. Dimana itu???

JavaScript adalah bahasa tingkat tinggi. Primitif dasarnya ( Object ) termasuk tabel hash untuk menyimpan properti. Tabel hash ini biasanya ditulis dalam bahasa tingkat rendah untuk efisiensi. Menggunakan objek sederhana dengan kunci string, kami menggunakan tabel hash yang diimplementasikan secara efisien tanpa upaya dari pihak kami.

Bagaimana Anda tahu mereka menggunakan hash?

Ada tiga cara utama untuk menjaga koleksi objek tetap dialamatkan oleh kunci:

  • Tidak dipesan. Dalam hal ini untuk mengambil objek dengan kuncinya kita harus membahas semua kunci yang berhenti ketika kita menemukannya. Rata-rata akan membutuhkan n / 2 perbandingan.
  • Dipesan.
    • Contoh # 1: array yang diurutkan - melakukan pencarian biner, kami akan menemukan kunci kami setelah ~ log2 (n) perbandingan rata-rata. Jauh lebih baik.
    • Contoh # 2: sebuah pohon. Sekali lagi itu akan ~ log (n) upaya.
  • Meja hash. Rata-rata membutuhkan waktu yang konstan. Bandingkan: O (n) vs O (log n) vs O (1). Ledakan.

Jelas objek JavaScript menggunakan tabel hash dalam beberapa bentuk untuk menangani kasus umum.

Apakah vendor browser benar-benar menggunakan tabel hash ???

Betulkah.

Apakah mereka menangani tabrakan?

Iya. Lihat di atas. Jika Anda menemukan tabrakan pada string yang tidak setara, jangan ragu untuk mengajukan bug ke vendor.

Jadi apa idemu?

Jika Anda ingin meng-hash suatu objek, temukan apa yang membuatnya unik dan gunakan sebagai kunci. Jangan mencoba menghitung hash asli atau meniru hash tables - itu sudah ditangani secara efisien oleh objek JavaScript yang mendasarinya.

Gunakan kunci ini dengan JavaScript Objectuntuk memanfaatkan tabel hash bawaan sambil menghindari kemungkinan bentrokan dengan properti default.

Contoh untuk Anda mulai:

  • Jika objek Anda menyertakan nama pengguna yang unik - gunakan itu sebagai kunci.
  • Jika menyertakan nomor pelanggan unik - gunakan itu sebagai kunci.
    • Jika itu termasuk nomor unik yang dikeluarkan pemerintah seperti SSN, atau nomor paspor, dan sistem Anda tidak mengizinkan duplikat - gunakan sebagai kunci.
  • Jika kombinasi bidang unik - gunakan sebagai kunci.
    • Singkatan negara + nomor SIM membuat kunci yang sangat baik.
    • Singkatan negara + nomor paspor juga merupakan kunci yang sangat baik.
  • Beberapa fungsi pada bidang, atau keseluruhan objek, dapat mengembalikan nilai unik - gunakan sebagai kunci.

Saya menggunakan saran Anda dan cache semua objek menggunakan nama pengguna. Tetapi beberapa orang bijak bernama "toString", yang merupakan properti bawaan! Apa yang harus saya lakukan sekarang?

Jelas, jika bahkan memungkinkan bahwa kunci yang dihasilkan secara eksklusif terdiri dari karakter Latin, Anda harus melakukan sesuatu. Misalnya, tambahkan karakter Unicode non-Latin yang Anda sukai di awal atau di akhir untuk tidak berbenturan dengan properti default: "#toString", "#MarySmith". Jika kunci komposit digunakan, pisahkan komponen kunci menggunakan semacam pembatas non-Latin: "nama, kota, negara".

Secara umum ini adalah tempat, di mana kita harus kreatif, dan memilih kunci termudah dengan batasan yang diberikan (keunikan, potensi bentrokan dengan properti default).

Catatan: kunci unik tidak berbenturan dengan definisi, sementara potensi bentrokan hash akan ditangani oleh yang mendasarinya Object.

Mengapa Anda tidak menyukai solusi industri?

IMHO, kode terbaik adalah tidak ada kode sama sekali: ia tidak memiliki kesalahan, tidak memerlukan pemeliharaan, mudah dimengerti, dan dijalankan secara instan. Semua "tabel hash dalam JavaScript" yang saya lihat adalah> 100 baris kode, dan melibatkan beberapa objek. Bandingkan dengan: dict[key] = value.

Poin lain: apakah mungkin untuk mengalahkan kinerja objek primordial yang ditulis dalam bahasa tingkat rendah, menggunakan JavaScript dan objek primordial yang sama untuk mengimplementasikan apa yang sudah diterapkan?

Saya masih ingin hash objek saya tanpa kunci apa pun!

Kami beruntung: ECMAScript 6 (dijadwalkan untuk rilis pertengahan 2015, memberi atau mengambil 1-2 tahun setelah itu menjadi luas) mendefinisikan peta dan set .

Dilihat oleh definisi mereka dapat menggunakan alamat objek sebagai kunci, yang membuat objek langsung berbeda tanpa kunci buatan. OTOH, dua objek berbeda, namun identik, akan dipetakan sebagai berbeda.

Rincian perbandingan dari MDN :

Objek serupa dengan Peta yang memungkinkan Anda mengatur kunci ke nilai, mengambil nilai-nilai itu, menghapus kunci, dan mendeteksi apakah ada sesuatu yang disimpan pada suatu kunci. Karena ini (dan karena tidak ada alternatif bawaan), Objek telah digunakan sebagai Maps secara historis; namun, ada perbedaan penting yang membuat penggunaan Peta lebih disukai dalam kasus-kasus tertentu:

  • Kunci dari suatu Objek adalah String dan Simbol, sedangkan mereka dapat berupa nilai apa pun untuk Peta, termasuk fungsi, objek, dan primitif apa pun.
  • Kunci dalam Peta diurutkan sedangkan kunci yang ditambahkan ke objek tidak. Jadi, ketika iterasi di atasnya, objek Peta mengembalikan kunci dalam urutan penyisipan.
  • Anda bisa mendapatkan ukuran Peta dengan mudah dengan properti ukuran, sedangkan jumlah properti di Objek harus ditentukan secara manual.
  • Peta adalah iterable dan dengan demikian dapat langsung iterated, sedangkan iterasi atas suatu Object membutuhkan memperoleh kunci-kuncinya dengan beberapa cara dan iterasi atas mereka.
  • Objek memiliki prototipe, jadi ada kunci default di peta yang bisa bertabrakan dengan kunci Anda jika Anda tidak hati-hati. Pada ES5 ini dapat dilewati dengan menggunakan map = Object.create (null), tetapi ini jarang dilakukan.
  • Peta mungkin berkinerja lebih baik dalam skenario yang melibatkan penambahan dan penghapusan pasangan kunci yang sering.

13
Ini tidak terlihat seperti peta yang tepat, karena Anda tidak menangani tabrakan. Jika ini benar: hash (obj1) == hash (obj2), Anda akan kehilangan data Anda.
daging sapi

32
Surga membantu Anda ketika "PAUL AINLEY" dan "PAULA INLEY" mendaftar di sistem Anda ...
Matt R

34
@MattR Sebenarnya contoh Anda akan bekerja dengan baik tanpa bantuan surga bahkan dengan fungsi hash tiruan. Saya berharap bahwa pembaca lain akan menyadari bahwa fungsi hash non-realistis yang terlalu disederhanakan digunakan sebagai pengganti untuk menunjukkan teknik yang berbeda. Kedua kode komentar, dan jawabannya sendiri menekankan bahwa itu tidak nyata. Pemilihan kunci yang tepat dibahas dalam paragraf terakhir dari jawaban.
Eugene Lazutkin

6
@EugeneLazutkin - Anda masih salah, saya khawatir. Contoh Anda masih rentan terhadap tabrakan hash. Jangan berpikir bahwa menempatkan nama belakang terlebih dahulu akan membantu Anda!
Matt R

3
@EugeneLazutkin Sebagian besar orang tidak membaca bahwa Anda telah menjawab ini SEBELUM ES6 bahkan muncul ... Izinkan saya mengucapkan selamat atas pengetahuan mendalam JS Anda.
Gabriel Andrés Brancolini

171

Deskripsi masalah

JavaScript tidak memiliki tipe peta umum bawaan (kadang-kadang disebut array asosiatif atau kamus ) yang memungkinkan untuk mengakses nilai arbitrer dengan kunci arbitrer. Struktur data fundamental JavaScript adalah objek , tipe peta khusus yang hanya menerima string sebagai kunci dan memiliki semantik khusus seperti warisan prototipe, pengambil dan setter dan beberapa voodoo lebih lanjut.

Saat menggunakan objek sebagai peta, Anda harus ingat bahwa kunci akan dikonversi ke nilai string melalui toString(), yang menghasilkan pemetaan 5dan '5'ke nilai yang sama dan semua objek yang tidak menimpa toString()metode ke nilai yang diindeks oleh '[object Object]'. Anda mungkin juga secara tidak sadar mengakses properti warisannya jika Anda tidak memeriksanya hasOwnProperty().

Built-in JavaScript untuk berbagai jenis tidak membantu satu bit: array JavaScript tidak array asosiatif, tetapi hanya objek dengan beberapa sifat istimewa. Jika Anda ingin tahu mengapa mereka tidak dapat digunakan sebagai peta, lihat di sini .

Solusi Eugene

Eugene Lazutkin sudah menggambarkan ide dasar menggunakan fungsi hash khusus untuk menghasilkan string unik yang dapat digunakan untuk mencari nilai terkait sebagai properti dari objek kamus. Ini kemungkinan besar akan menjadi solusi tercepat, karena objek diimplementasikan secara internal sebagai tabel hash .

  • Catatan: Tabel hash (kadang-kadang disebut peta hash ) adalah implementasi tertentu dari konsep peta menggunakan array backing dan pencarian melalui nilai hash numerik. Lingkungan runtime mungkin menggunakan struktur lain (seperti pohon pencarian atau lewati daftar ) untuk mengimplementasikan objek JavaScript, tetapi karena objek adalah struktur data mendasar, mereka harus dioptimalkan secara memadai.

Untuk mendapatkan nilai hash unik untuk objek arbitrer, satu kemungkinan adalah dengan menggunakan penghitung global dan cache nilai hash di objek itu sendiri (misalnya dalam properti bernama __hash).

Fungsi hash yang melakukan ini dan berfungsi untuk nilai dan objek primitif adalah:

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

hash.current = 0;

Fungsi ini dapat digunakan seperti yang dijelaskan oleh Eugene. Untuk kenyamanan, kami akan membungkusnya dalam Mapkelas.

MapImplementasi saya

Implementasi berikut ini juga akan menyimpan pasangan kunci-nilai dalam daftar yang tertaut ganda untuk memungkinkan pengulangan yang cepat atas kunci dan nilai. Untuk menyediakan fungsi hash Anda sendiri, Anda bisa menimpa metode instance hash()setelah pembuatan.

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

Contoh

Script berikut

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

menghasilkan output ini:

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

Pertimbangan lebih lanjut

PEZ menyarankan untuk menimpa toString()metode ini, mungkin dengan fungsi hash kami. Ini tidak layak karena tidak bekerja untuk nilai-nilai primitif (perubahan toString()untuk primitif adalah ide yang sangat buruk). Jika kita ingin toString()mengembalikan nilai yang berarti untuk objek arbitrer, kita harus memodifikasi Object.prototype, yang beberapa orang (termasuk saya sendiri) mempertimbangkan verboten .


Sunting: Versi Mapimplementasi saya saat ini dan juga barang JavaScript lainnya dapat diperoleh dari sini .


ES5 tidak lagi menggunakan callee ( goo.gl/EeStE ). Sebagai gantinya, saya sarankan Map._counter = 0, dan di konstruktor Peta lakukan this._hash = 'object ' + Map._counter++. Kemudian hash () menjadireturn (value && value._hash) || (typeof(value) + ' ' + String(value));
broofa


hai @Christoph, bisakah Anda memperbarui tautan ke tempat saya dapat menemukan implementasi Peta Anda?
NumenorForLife

2
@ jsc123: Saya akan membahasnya - untuk saat ini Anda bisa mendapatkan tempat penyimpanan di pikacode.com/mercurial.intuxication.org/js-hacks.tar.gz
Christoph

58

Saya tahu pertanyaan ini cukup lama, tetapi ada beberapa solusi yang sangat bagus saat ini dengan perpustakaan eksternal.

JavaScript juga memiliki bahasa yang disediakan Map.


2
Ini adalah cara untuk maju ke abad ke-21. Sayang sekali saya menemukan posting Anda setelah menyelesaikan kode saya dengan beberapa Peta rumah yang dibuat jelek. WEEE membutuhkan lebih banyak suara untuk jawaban Anda
Phung D. An

1
Collections.js memiliki beberapa implementasi, tetapi saya tidak dapat menemukannya di underscore.js atau lodash ... apa yang Anda maksud dengan underscore yang berguna?
Codebling

@ CodeBling tidak tahu. saya pikir saya bingung dengan fungsi peta. saya akan menghapusnya dari jawabannya.
Jamel Toms

3
Itu adil. Siapa pun yang mempertimbangkan Collections.js harus menyadari bahwa itu memodifikasi prototipe Array, Function, Object, dan Regexp global dengan cara yang bermasalah ( lihat masalah yang saya temui di sini ). Meskipun saya awalnya sangat senang dengan collections.js (dan dengan demikian jawaban ini), risiko yang terkait dengan menggunakannya terlalu tinggi, jadi saya menjatuhkannya. Hanya cabang v2 dari collections.js kriskowal (khususnya, v2.0.2 +) yang menghilangkan modifikasi prototipe global dan aman untuk digunakan.
Codebling

28

Berikut ini cara mudah dan nyaman menggunakan sesuatu yang mirip dengan peta 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
        }

Dan untuk mendapatkan nilai:

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

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

2
Ini hanya berfungsi untuk tombol string. Saya percaya OP tertarik menggunakan kunci apa pun.
fraktur

26

Menurut ECMAScript 2015 (ES6) javascript standar memiliki implementasi Peta. Lebih banyak tentang yang dapat ditemukan di sini

Penggunaan dasar:

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

Anda dapat menggunakan ES6 WeakMapatau Map:

  • WeakMaps adalah peta kunci / nilai di mana kunci adalah objek.

  • Mapobjek adalah peta kunci / nilai sederhana. Nilai apa pun (baik objek maupun nilai primitif) dapat digunakan sebagai kunci atau nilai.

Sadarilah bahwa tidak ada yang didukung secara luas, tetapi Anda dapat menggunakan ES6 Shim (memerlukan ES5 asli atau ES5 Shim ) untuk mendukung Map, tetapi tidak WeakMap( lihat alasannya ).


Pada tahun 2019 mereka sangat didukung dan memiliki metode luar biasa! developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Juanma Menendez

13

Anda harus menyimpan dalam beberapa bait keadaan internal pasangan objek / nilai

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

Dan gunakan seperti itu:

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

Tentu saja, implementasi ini juga di suatu tempat di sepanjang garis O (n). Contoh-contoh Eugene di atas adalah satu-satunya cara untuk mendapatkan hash yang bekerja dengan kecepatan apa pun yang Anda harapkan dari hash nyata.

Memperbarui:

Pendekatan lain, di sepanjang garis jawaban Eugene adalah entah bagaimana melampirkan ID unik untuk semua objek. Salah satu pendekatan favorit saya adalah mengambil salah satu metode bawaan yang diwarisi dari superclass Object, menggantinya dengan fungsi kustom passthrough dan melampirkan properti ke objek fungsi tersebut. Jika Anda menulis ulang metode HashMap saya untuk melakukan ini, akan terlihat seperti:

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

Versi ini tampaknya hanya sedikit lebih cepat, tetapi secara teori itu akan secara signifikan lebih cepat untuk set data yang besar.


Array asosiatif, yaitu array 2-tupel, adalah Peta, bukan HashMap; HashMap adalah Peta yang menggunakan hash untuk kinerja yang lebih baik.
Erik Kaplun

Benar, tapi mengapa membagi rambut pada topik? Tidak ada cara untuk membuat peta hash sejati dalam JavaScript karena Anda tidak bisa mendapatkan alamat memori objek. Dan pasangan kunci / nilai objek bawaan JavaScript (digunakan dalam contoh kedua saya) dapat bertindak sebagai HashMaps, tetapi tidak harus, karena tergantung pada runtime yang digunakan di browser tentang bagaimana pencarian dilakukan.
pottedmeat

11

Sayangnya, tidak ada jawaban di atas yang baik untuk kasus saya: objek kunci yang berbeda mungkin memiliki kode hash yang sama. Oleh karena itu, saya menulis versi HashMap sederhana seperti 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;
}

Catatan: objek kunci harus "mengimplementasikan" kode hashCode () dan equals ().


7
Preferensi new Array()lebih []adalah untuk memastikan Jawa-kemiripan mutlak kode Anda? :)
Erik Kaplun

6

Saya telah mengimplementasikan JavaScript HashMap yang kodenya dapat diperoleh dari http://github.com/lambder/HashMapJS/tree/master

Ini kodenya:

/*
 =====================================================================
 @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
Kode Anda tampaknya tidak berfungsi dengan meletakkan objek yang sama di banyak HashMaps.
Erik Kaplun

5

Di ECMA6 Anda dapat menggunakan WeakMap

Contoh:

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

Tapi:

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

oh puji yesus mereka akhirnya menambahkan referensi lemah ke javascript. sudah waktunya ... +1 untuk itu, tapi ini sebenarnya akan mengerikan untuk digunakan karena referensi lemah
Claudiu

2

Javascript tidak membangun Map / hashmap. Itu harus disebut array asosiatif .

hash["X"]sama dengan hash.X, tetapi izinkan "X" sebagai variabel string. Dengan kata lain, hash[x]secara fungsional sama denganeval("hash."+x.toString())

Ini lebih mirip sebagai object.properties daripada pemetaan kunci-nilai. Jika Anda mencari pemetaan Kunci / nilai yang lebih baik dalam Javascript, silakan gunakan objek Peta yang dapat Anda temukan di web.



2

Ini terlihat seperti solusi yang cukup kuat: https://github.com/flesler/hashmap . Ia bahkan akan bekerja dengan baik untuk fungsi dan objek yang terlihat identik. Satu-satunya peretas yang digunakannya adalah menambahkan anggota yang tidak dikenal ke objek untuk mengidentifikasinya. Jika program Anda tidak menimpa variabel tidak jelas itu (seperti hashid ), berarti Anda berwarna emas.


2

Jika kinerja tidak kritis (misalnya jumlah tombol relatif kecil) dan Anda tidak ingin mencemari Anda (atau mungkin tidak Anda) objek dengan bidang tambahan seperti _hash, _id, dll, maka Anda dapat menggunakan fakta bahwa Array.prototype.indexOfmempekerjakan kesetaraan yang ketat. Berikut ini adalah implementasi sederhana:

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

Contoh penggunaan:

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.

Dibandingkan dengan ES6 WeakMap, ia memiliki dua masalah: O (n) waktu pencarian dan non-kelemahan (yaitu akan menyebabkan kebocoran memori jika Anda tidak menggunakan deleteatau clearmelepaskan kunci).


2

Implementasi My Map, berasal dari contoh Christoph:

Contoh penggunaan:

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

Menambahkan solusi lain: HashMapkelas pertama yang saya porting dari Jawa ke Javascript. Bisa dibilang ada banyak overhead, tetapi implementasinya hampir 100% sama dengan implementasi Java dan mencakup semua antarmuka dan subkelas.

Proyek ini dapat ditemukan di sini: https://github.com/Airblader/jsava Saya juga akan melampirkan kode sumber (saat ini) untuk kelas HashMap, tetapi sebagaimana dinyatakan juga tergantung pada kelas super dll. Kerangka kerja OOP yang digunakan adalah qooxdoo.

Sunting: Harap dicatat bahwa kode ini sudah kedaluwarsa dan merujuk ke proyek github untuk pekerjaan saat ini. Saat penulisan ini, ada juga ArrayListimplementasi.

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 pendekatan yang menarik .. sudahkah Anda mempertimbangkan untuk mencoba pendekatan otomatis? yaitu, menjalankan kompiler Java-ke-javascript pada kode sumber untuk implementasi java saat ini?
Claudiu

Tidak :) Ini hanya proyek yang menyenangkan bagi saya dan ada beberapa hal yang saya tidak bisa begitu saja "menyalin" kode. Saya tidak mengetahui kompiler Java-to-Javascript, meskipun saya percaya mereka ada. Saya tidak yakin seberapa baik mereka menerjemahkan ini. Tapi saya cukup yakin mereka tidak akan menghasilkan kode berkualitas baik.
Ingo Bürk

Ah paham. Saya sedang memikirkan kompiler Google Web Toolkit , tetapi tampaknya mereka akhirnya melakukan apa yang Anda lakukan di sini untuk pustaka inti: "Kompiler GWT mendukung sebagian besar bahasa Jawa itu sendiri. Pustaka runtime GWT mengemulasi subset yang relevan dari Pustaka runtime Java. " Mungkin sesuatu untuk dilihat untuk melihat bagaimana orang lain memecahkan masalah yang sama!
Claudiu

Ya. Saya yakin solusi Google jauh melampaui saya, tetapi sekali lagi, saya hanya bersenang-senang bermain-main. Sayangnya, kode sumbernya sepertinya telah dicabut (?), Setidaknya saya tidak bisa menjelajahinya dan tautan yang menarik sepertinya sudah mati. Sayang sekali, aku akan senang melihatnya.
Ingo Bürk

Bersenang-senang bermain-main adalah cara terbaik untuk belajar =). terima kasih sudah berbagi
Claudiu

0

Namun implementasi peta lain oleh saya. Dengan pengacak, 'generik' dan '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;
    }
}

Contoh:

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
*/
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.