JavaScript Hashmap Eşdeğeri


354

Bu cevaba ilişkin güncelleme 3'te açıklandığı gibi , bu gösterim:

var hash = {};
hash[X]

aslında nesneyi hash etmez X; aslında Xbir dizeye ( .toString()bir nesne veya çeşitli ilkel türler için başka bir yerleşik dönüşüm olduğunda) dönüştürür ve sonra bu dizeyi " hash" içinde karma olmadan arar . Nesne eşitliği de denetlenmez - iki farklı nesne aynı dize dönüşümüne sahipse, birbirlerinin üzerine yazarlar.

Bu göz önüne alındığında, javascriptte etkili hashmaps uygulamaları var mı? (Örneğin, 2. Google sonucu, javascript hashmapherhangi bir işlem için O (n) olan bir uygulama sağlar. Diğer çeşitli sonuçlar, eşdeğer dize gösterimine sahip farklı nesnelerin birbirlerinin üzerine yazılması gerçeğini göz ardı eder.


1
@Claudiu: Düzenleme için özür dilerim, ancak başlıktaki "Harita" gerçekten yanıltıcıydı. Kabul etmiyorsanız geri dönün, ben patronluk yapmayı düşünmedim. :)
Tomalak

6
@Claudiu: Javascript hakkında birçok soru soruyorsunuz. Güzel sorular. Bunu sevdim.
Bazıları

2
@Claudiu: Ayrıca, başvurduğunuz Google sonucuna bağlantı verebilir misiniz? Google'ın farklı yerel sürümleri farklı sonuçlar döndürür, bahsettiğiniz uygulama benim için bile görünmüyor.
Tomalak

@Tomalak: Sadece aynı şeyi yazacaktım!
Bazıları

3
@Claudiu Hayır, Google'a bağlantı verme. Bahsettiğiniz sayfanın bağlantısı (Google'da bulduğunuz). Google'a bağlanma, neyi arayacağınızı açıklamakla aynı sorunlara sahiptir: konuma veya arama geçmişine göre google özelleştirme sonuçları, google'ın zamanla değişen sonuçları (şu anda bu arama için en iyi sonuçtur) ve bunu yapabilen diğer her şey farklı sonuçlar gösterir.
Jasper

Yanıtlar:


371

Nesnelerinizi neden kendiniz manuel olarak karıştırmıyorsunuz ve elde edilen dizeleri normal bir JavaScript sözlüğü için anahtar olarak kullanmıyor musunuz? Sonuçta, nesnelerinizi benzersiz kılan şeyleri bilmek için en iyi konumdasınız. İşte bu yaptığım şey.

Misal:

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;

Bu şekilde, bellek ayırmayı ve taşma işlemeyi ağır bir şekilde kaldırmadan JavaScript tarafından yapılan endekslemeyi kontrol edebilirsiniz.

Tabii ki, gerçekten "endüstriyel düzeyde çözüm" istiyorsanız, anahtar işlevi ve kapsayıcı gerekli tüm API ile parametreleştirilmiş bir sınıf inşa edebilirsiniz, ama ... biz JavaScript kullanır, ve basit ve hafif olmaya çalışıyoruz, bu yüzden bu işlevsel çözüm basit ve hızlıdır.

Anahtar işlevi, nesnenin doğru niteliklerini seçmek kadar basit olabilir, örneğin, bir anahtar veya zaten benzersiz olan bir anahtar kümesi, birlikte benzersiz olan bir anahtar kombinasyonu veya gibi bazı şifreleme karmalarını kullanmak kadar karmaşık içerisinde DojoX kodlama veya DojoX UUID'si . İkinci çözümler benzersiz anahtarlar üretebilirken, özellikle de nesnelerimi benzersiz kılan şeyin ne olduğunu bilirsem, kişisel olarak onlardan kaçınmaya çalışırım.

2014 yılında güncelleme: 2008 yılında yanıtlanan bu basit çözüm hala daha fazla açıklama gerektiriyor. Fikri bir Soru-Cevap formunda netleştireyim.

Çözümünüzün gerçek bir karması yok. Nerede???

JavaScript üst düzey bir dildir. Temel ilkel ( Nesne ) özelliklerini korumak için bir karma tablo içerir. Bu karma tablo genellikle verimlilik için düşük düzeyli bir dilde yazılır. Dize anahtarlarıyla basit bir nesne kullanarak, hiçbir çaba harcamadan verimli bir şekilde uygulanan bir karma tablo kullanıyoruz.

Hash kullandıklarını nereden biliyorsun?

Bir anahtarla adreslenebilen bir nesne koleksiyonunu tutmanın üç ana yolu vardır:

  • Sırasız. Bu durumda bir nesneyi anahtarıyla almak için, onu bulduğumuzda durduran tüm anahtarları gözden geçirmemiz gerekir. Ortalama olarak n / 2 karşılaştırması yapılacaktır.
  • Sıralı.
    • Örnek 1: sıralı bir dizi - ikili bir arama yaparken ~ log2 (n) karşılaştırmaları sonrasında ortalama anahtarımızı bulacağız. Çok daha iyi.
    • Örnek 2 - Bir ağaç. Yine ~ log (n) denemeleri olacak.
  • Karma tablo. Ortalama olarak sabit bir zaman gerektirir. Karşılaştırma: O (n) ile O (log n) ile O (1) karşılaştırması. Boom.

Açıkçası JavaScript nesneleri genel durumları işlemek için bazı tablolarda karma tablolar kullanır.

Tarayıcı satıcıları gerçekten karma tablolar kullanıyor ???

Gerçekten mi.

Çarpışmalarla mı baş ediyorlar?

Evet. Yukarıyı görmek. Eşit olmayan dizelerde bir çarpışma bulduysanız, lütfen bir satıcıda hata bildirmekte tereddüt etmeyin.

Peki senin fikrin ne?

Bir nesneyi hash etmek istiyorsanız, nesneyi benzersiz kılan şeyi bulun ve anahtar olarak kullanın. Gerçek karmayı hesaplamaya veya karma tabloları taklit etmeye çalışmayın - zaten temeldeki JavaScript nesnesi tarafından verimli bir şekilde işlenir.

ObjectVarsayılan özelliklerle olası çakışmaları giderirken yerleşik karma tablosundan yararlanmak için bu anahtarı JavaScript ile kullanın .

Başlamanız için örnekler:

  • Nesneleriniz benzersiz bir kullanıcı adı içeriyorsa - bunu anahtar olarak kullanın.
  • Benzersiz bir müşteri numarası içeriyorsa - bunu anahtar olarak kullanın.
    • SSN veya pasaport numarası gibi devlet tarafından verilmiş benzersiz numaralar içeriyorsa ve sisteminiz kopyalara izin vermiyorsa, bunu anahtar olarak kullanın.
  • Alanların bir kombinasyonu benzersizse - bunu anahtar olarak kullanın.
    • Durum kısaltması + sürücü lisans numarası mükemmel bir anahtar yapar.
    • Ülke kısaltması + pasaport numarası da mükemmel bir anahtardır.
  • Alanlardaki bazı işlevler veya tüm nesne benzersiz bir değer döndürebilir - bunu anahtar olarak kullanın.

Önerinizi kullandım ve bir kullanıcı adı kullanarak tüm nesneleri önbelleğe aldım. Ama bazı bilge adam yerleşik bir özellik olan "toString" olarak adlandırılır! Ben şimdi ne yapmalıyım?

Ortaya çıkan anahtarın yalnızca Latin karakterlerinden oluşması uzaktan bile mümkünse, bu konuda bir şeyler yapmalısınız. Örneğin, varsayılan özelliklerle çakışmasını kaldırmak için başında veya sonunda istediğiniz Latin Unicode olmayan karakterleri ekleyin: "#toString", "#MarySmith". Bileşik anahtar kullanılırsa, anahtar bileşenleri Latin olmayan bir tür sınırlayıcı kullanarak ayırın: "ad, şehir, eyalet".

Genel olarak burası, yaratıcı olmamız ve verilen sınırlamalarla (benzersizlik, varsayılan özelliklerle olası çatışmalar) en kolay anahtarları seçmemiz gereken yerdir.

Not: Benzersiz anahtarlar tanım gereği çakışmazken, potansiyel karma çakışmaları temel alınan tarafından ele alınacaktır Object.

Neden endüstriyel çözümleri sevmiyorsunuz?

IMHO, en iyi kod hiç kod değildir: hatasızdır, bakım gerektirmez, anlaşılması kolaydır ve anında yürütülür. Gördüğüm tüm "JavaScript'te karma tablolar"> 100 kod satırıydı ve birden çok nesne içeriyordu. İle karşılaştırın: dict[key] = value.

Başka bir nokta: JavaScript'i ve zaten aynı olan ilkel nesneleri kullanarak, düşük seviyeli bir dilde yazılmış bir ilkel nesnenin performansını yenmek bile mümkün müdür?

Hala nesnelerimi herhangi bir anahtar olmadan karma yapmak istiyorum!

Şansımız var: ECMAScript 6 (2015'in ortalarında piyasaya sürülecek, bundan sonra 1-2 yıl verilecek veya yaygınlaşacak), haritayı ve seti tanımlar .

Tanıma göre, nesnenin adresini bir anahtar olarak kullanabilirler, bu da nesneleri yapay anahtarlar olmadan anında farklı kılar. İki farklı ama aynı nesne olan OTOH, farklı olarak eşlenecektir.

MDN'den karşılaştırma dağılımı :

Nesneler, anahtarları değerlere ayarlamanıza, bu değerleri almanıza, anahtarları silmenize ve bir anahtarda bir şeyin depolanıp depolanmadığını belirlemenize olanak tanıdığı için Haritalar'a benzer. Bu nedenle (ve yerleşik alternatifler olmadığından), Nesneler tarihsel olarak Haritalar olarak kullanılmıştır; ancak, bazı durumlarda bir Harita kullanmayı tercih edilen önemli farklılıklar vardır:

  • Bir Nesnenin anahtarları Dizeler ve Sembollerdir, oysa işlevler, nesneler ve herhangi bir ilkel dahil bir Harita için herhangi bir değer olabilirler.
  • Haritadaki anahtarlar, nesneye eklenen anahtarlar sıralanmamışken sıralanmıştır. Böylece, yinelendiğinde, bir Map nesnesi anahtarları ekleme sırasına göre döndürür.
  • Bir nesnenin özelliklerinin manuel olarak belirlenmesi gerekirken, haritanın boyutunu size özelliğiyle kolayca alabilirsiniz.
  • Bir Harita yinelenebilir ve bu nedenle doğrudan yinelenebilir, oysa bir Nesne üzerinde yineleme, anahtarlarının bir şekilde elde edilmesini ve bunların üzerinde yinelenmesini gerektirir.
  • Bir Nesnenin bir prototipi vardır, bu nedenle haritada dikkatli değilseniz anahtarlarınızla çarpışabilecek varsayılan anahtarlar vardır. ES5'ten itibaren map = Object.create (null) kullanılarak atlanabilir, ancak bu nadiren yapılır.
  • Bir Harita, anahtar çiftlerinin sık sık eklenmesini ve kaldırılmasını içeren senaryolarda daha iyi performans gösterebilir.

13
Bu uygun bir harita gibi görünmüyor çünkü çarpışmalarla başa çıkmıyorsunuz. Bu doğruysa: hash (obj1) == hash (obj2), verilerinizi kaybedersiniz.
beefeather

32
Hem "PAUL AINLEY" hem de "PAULA INLEY" sisteminize kaydolduğunda cennet size yardımcı olur ...
Matt R

34
@MattR Aslında örnek bir cehennem karma fonksiyonu ile bile cennet yardımı olmadan düzgün çalışacaktır. Umarım diğer okuyucular, aşırı basitleştirilmiş gerçekçi olmayan bir karma fonksiyonunun farklı bir tekniği göstermek için yer tutucu olarak kullanıldığını fark edecektir. Hem kod yorumları hem de cevabın kendisi bunun gerçek olmadığını vurguluyor. Doğru anahtarların seçimi cevabın son paragrafında tartışılmaktadır.
Eugene Lazutkin

6
@EugeneLazutkin - hala yanılıyorsun, korkuyorum. Örneğiniz hala karma çarpışmalara eğilimlidir. Sadece soyadını koymanın bir şekilde size yardımcı olacağını düşünmeyin!
Matt R

3
@EugeneLazutkin Çoğu kişi, ES6 bile görünmeden ÖNCE bunu yanıtladığınızı okumuyor ... Derin JS bilginiz için tebrik edeyim.
Gabriel Andrés Brancolini

171

Sorun Açıklaması

JavaScript'in keyfi anahtarlara rastgele değerlere erişmesini sağlayan yerleşik bir genel harita türü (bazen ilişkilendirilebilir dizi veya sözlük olarak adlandırılır ) yoktur. JavaScript'in temel veri yapısıdır nesne , sadece anahtarları olarak dizeleri kabul eder ve prototipi kalıtım, Alıcı ve ayarlayıcıların ve bazı başka voodoo gibi özel anlambilim içeriyor haritalarının özel tip.

Nesneleri harita olarak kullandığınızda, anahtarın bir dize değerine dönüştürüleceğini ve toString()bu da eşlemeyle 5ve '5'aynı değere ve toString()yöntemin dizine eklenmiş değerin üzerine yazmayan tüm nesnelere dönüştürüleceğini hatırlamanız gerekir '[object Object]'. Ayrıca, kontrol etmezseniz devralınan özelliklerine istem dışı olarak erişebilirsiniz hasOwnProperty().

JavaScript'in yerleşik dizi türü bir bite yardımcı olmaz: JavaScript dizileri ilişkilendirilebilir diziler değil, yalnızca birkaç özel özelliğe sahip nesnelerdir. Neden harita olarak kullanılamadıklarını öğrenmek istiyorsanız, buraya bakın .

Eugene'nin Çözümü

Eugene Lazutkin, bir sözlük nesnesinin özellikleri olarak ilişkili değerleri aramak için kullanılabilecek benzersiz dizeler oluşturmak için özel bir karma işlevini kullanma temel fikrini zaten açıklamıştı. Bu büyük olasılıkla en hızlı çözüm olacaktır, çünkü nesneler dahili olarak karma tablolar olarak uygulanır .

  • Not: Karma tablolar (bazen karma haritalar olarak da adlandırılır ), bir yedekleme dizisi ve sayısal karma değerleri üzerinden arama kullanarak harita kavramının belirli bir uygulamasıdır. Çalışma zamanı ortamı , JavaScript nesnelerini uygulamak için diğer yapıları ( arama ağaçları veya atlama listeleri gibi ) kullanabilir, ancak nesneler temel veri yapısı olduğundan, yeterince optimize edilmelidir.

Rasgele nesneler için benzersiz bir karma değer elde etmek için bir olasılık, genel bir sayaç kullanmak ve karma değerini nesnenin kendisinde (örn. Adlı bir özellikte __hash) önbelleğe almaktır .

Bunu yapan ve hem ilkel değerler hem de nesneler için çalışan bir karma işlevi:

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

hash.current = 0;

Bu işlev, Eugene tarafından açıklandığı gibi kullanılabilir. Kolaylık sağlamak için, bir Mapsınıfa daha da saracağız .

Benim Mapuygulama

Aşağıdaki uygulama, hem anahtar hem de değerler üzerinde hızlı yinelemeye izin vermek için anahtar / değer çiftlerini iki kat bağlantılı bir listede saklar. Kendi karma işlevinizi sağlamak için, hash()oluşturma işleminden sonra örneğin yönteminin üzerine yazabilirsiniz .

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

Misal

Aşağıdaki komut dosyası

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

bu çıktıyı üretir:

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

Dikkat edilecek diğer noktalar

PEZ toString(), muhtemelen hash fonksiyonumuzla yöntemin üzerine yazılmasını önerdi . İlkel değerler için çalışmaz, çünkü bu mümkün değildir (değişen toString()ilkel bir olduğunu çok kötü bir fikir). toString()Rasgele nesneler için anlamlı değerler döndürmek istiyorsak Object.prototype, bazı insanların (ben dahil değilim) verboten olduğunu düşündüğü değişiklikleri değiştirmeliyiz .


Düzenleme: Uygulamamın geçerli sürümü ve Mapdiğer JavaScript güzellikleri buradan edinilebilir .


ES5, callee ( goo.gl/EeStE ) kullanımını reddetmektedir . Bunun yerine, öneririm Map._counter = 0ve Harita yapıcısında bunu öneririm this._hash = 'object ' + Map._counter++. Sonra hash () olurreturn (value && value._hash) || (typeof(value) + ' ' + String(value));
broofa


hi @Christoph, Harita uygulamanızı nerede bulabileceğime ilişkin bağlantınızı güncelleyebilir misiniz?
NumenorForLife

2
@ jsc123: Buna bakacağım - şimdilik pikacode.com/mercurial.intuxication.org/js-hacks.tar.gz
Christoph

58

Bu sorunun oldukça eski olduğunu biliyorum, ama bugünlerde dış kütüphanelerle gerçekten harika çözümler var.

JavaScript'in dili de vardır Map.


2
21. yüzyıla ilerlemenin yolu budur. Çirkin ev yapımı Harita ile kodumu bitirdikten sonra yazınızı bulduğum çok kötü. WEEE'nin cevabınız için daha fazla oy alması gerekiyor
Phung D. An

1
Collections.js'nin bazı uygulamaları var, ancak underscore.js veya lodash'da herhangi bir şey bulamıyorum ... alt çizgide faydalı olabilecek ne kastettiniz?
Kodlama

@CodeBling hiçbir fikrim yok. Ben harita fonksiyonu ile karıştı düşünüyorum. cevabından kaldıracağım.
Jamel Toms

3
Bu adil. Collections.js'yi düşünen herkes, global Array, Function, Object ve Regexp prototiplerini sorunlu bir şekilde değiştirdiğinin farkında olmalıdır ( burada karşılaştığım sorunlara bakın ). Başlangıçta collections.js'den (ve dolayısıyla bu cevaptan) çok memnun olmama rağmen, onu kullanmayla ilişkili riskler çok yüksekti, bu yüzden bıraktım. Yalnızca kriskowal'ın collections.js'nin v2 dalı (özellikle v2.0.2 +) global prototip değişikliklerini ortadan kaldırır ve kullanımı güvenlidir.
20'de Kodlama

28

Java haritasına benzer bir şey kullanmanın kolay ve kullanışlı bir yolu:

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
        }

Ve değeri elde etmek için:

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

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

2
Bu yalnızca dize anahtarları için geçerlidir. OP'nin her türlü anahtarı kullanmakla ilgilendiğine inanıyorum.
fraktor

26

ECMAScript 2015'e (ES6) göre standart javascript bir Harita uygulamasına sahiptir. Daha yaklaşık bulunamadı hangi burada

Temel kullanım:

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

ES6'yı kullanabilirsiniz WeakMapveya Map:

  • WeakMaps, anahtarların nesne olduğu anahtar / değer eşlemeleridir.

  • Mapnesneler basit anahtar / değer haritalarıdır. Herhangi bir değer (hem nesneler hem de ilkel değerler) bir anahtar veya değer olarak kullanılabilir.

Her ikisinin de yaygın olarak desteklenmediğini unutmayın, ancak ES6 Shim'i kullanabilirsiniz (yerel ES5 veya ES5 Shim gerektirir ) Map, ancak desteklemeyin WeakMap( nedenini görün ).


2019'da çok iyi destekleniyorlar ve harika yöntemlere sahipler! developer.mozilla.org/tr-TR/docs/Web/JavaScript/Reference/…
Juanma Menendez

13

Nesne / değer çiftlerinin bazı dahili durum beyitlerinde saklamanız gerekir

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

Ve böyle kullanın:

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

Tabii ki, bu uygulama aynı zamanda O (n) çizgileri boyunca bir yerdedir. Eugene'nin yukarıdaki örnekleri, gerçek bir karmaşadan bekleyebileceğiniz her türlü hızda çalışan bir karma elde etmenin tek yoludur.

Güncelleme:

Eugene'nin cevabı boyunca bir başka yaklaşım, bir şekilde tüm nesnelere benzersiz bir kimlik eklemektir. En sevdiğim yaklaşımlardan biri, Object üst sınıfından miras alınan yerleşik yöntemlerden birini almak, onu özel işlev geçişi ile değiştirmek ve bu işlev nesnesine özellikler eklemek. Bunu yapmak için HashMap yöntemimi yeniden yazsaydınız, şöyle görünecektir:

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

Bu sürüm sadece biraz daha hızlı görünmektedir, ancak teoride büyük veri setleri için önemli ölçüde daha hızlı olacaktır.


İlişkilendirilebilir bir dizi, yani 2 tupleslik bir dizi, bir HashMap değil, bir Haritadır; HashMap, daha iyi performans için karma kullanan bir Haritadır.
Erik Kaplun

Doğru, ama neden konuyla ilgili kıllar ayrılıyor? Nesne belleği adresleri alamadığınız için JavaScript'te gerçek bir karma harita oluşturmanın bir yolu yoktur. JavaScript'in yerleşik nesne anahtarı / değer çiftleri (ikinci örneğimde kullanılır), aramanın nasıl uygulandığıyla ilgili olarak tarayıcıda kullanılan çalışma zamanına bağlı olduğu için HashMaps olarak işlev görebilir.
pottedmeat

11

Ne yazık ki, yukarıdaki cevapların hiçbiri benim durumum için iyi değildi: farklı anahtar nesneler aynı karma koduna sahip olabilir. Bu nedenle, Java benzeri basit bir HashMap sürümü yazdım:

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

Not: anahtar nesnelerin hashCode () ve equals () yöntemlerini "uygulaması" gerekir.


7
Tercihinin new Array()over []kodunuzun mutlak Java benzerlik sağlamak için mi? :)
Erik Kaplun

6

Http://github.com/lambder/HashMapJS/tree/master adresinden edinebileceğiniz bir JavaScript HashMap uyguladım

İşte kod:

/*
 =====================================================================
 @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
Kodunuz, aynı nesneyi birden çok HashMaps'ye koymakla çalışmıyor gibi görünüyor .
Erik Kaplun

5

ECMA6 size kullanabilirsiniz WeakMap

Misal:

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

Fakat:

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

oh övgü İsa sonunda javascript'e zayıf referanslar ekliyorlar. zamanı geldi ... +1 + bunun için, ancak referanslar zayıf olduğu için kullanmak gerçekten korkunç olurdu
Claudiu

2

Javascript, Map / hashmap'i oluşturmaz. Buna ilişkisel dizi adı verilmelidir .

hash["X"]eşittir hash.Xancak dize değişkeni olarak "X" e izin verir. Başka bir deyişle, hash[x]işlevsel olarak eşittireval("hash."+x.toString())

Anahtar / değer eşlemesinden ziyade object.properties ile daha benzerdir. Javascript içinde daha iyi bir Anahtar / değer eşlemesi arıyorsanız, lütfen web'de bulabileceğiniz Harita nesnesini kullanın.



2

Bu oldukça sağlam bir çözüm gibi görünüyor: https://github.com/flesler/hashmap . Aynı görünen fonksiyonlar ve nesneler için bile iyi çalışır. Kullandığı tek saldırı, nesneyi tanımlamak için belirsiz bir üye eklemektir. Programınız bu belirsiz değişkenin üzerine yazmazsa ( hashid gibi bir şey ), altınsınız demektir .


2

Performans (örneğin tuşları miktarı nispeten küçük) kritik değildir ve sizi (ya da belki değil) gibi ek alanları ile nesneleri kirletmeye istemiyorsanız _hash, _idvb, o zaman aslında yararlanabilir Array.prototype.indexOfistihdam katı eşitlik. İşte basit bir uygulama:

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

Kullanım örneği:

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.

ES6 WeakMap ile karşılaştırıldığında iki sorunu vardır: O (n) arama süresi ve zayıflık (yani, tuşları kullanmazsanız deleteveya clearbırakmazsanız bellek sızıntısına neden olur ).


2

Harita Uygulamam, Christoph'un örneğinden türetildi:

Örnek Kullanım:

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

Başka bir çözüm eklemek: HashMapJava'dan Javascript'e taşıdığım ilk sınıftır. Çok fazla ek yük olduğunu söyleyebilirsiniz, ancak uygulama Java'nın uygulamasına neredeyse% 100 eşittir ve tüm arabirimleri ve alt sınıfları içerir.

Proje burada bulunabilir: https://github.com/Airblader/jsava HashMap sınıfı için (geçerli) kaynak kodunu da ekleyeceğim, ancak belirtildiği gibi süper sınıfa da bağlıdır. Qooxdoo.

Düzenleme: Lütfen bu kodun eski olduğunu ve mevcut iş için github projesine bakın. Bunu yazarken bir de ArrayListuygulama var.

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 ilginç yaklaşım .. otomatik bir yaklaşım denemeyi düşündünüz mü? yani, geçerli java uygulaması için kaynak kodunda bir Java-javascript derleyicisi çalıştırmak?
Claudiu

Hayır :) Bu benim için sadece eğlenceli bir proje ve ben sadece "kopya" kodu olamazdı oldukça birkaç şey vardı. Java-Javascript derleyicilerinin farkında değilim, ancak var olduklarına inanıyorum. Bunu ne kadar iyi tercüme edeceklerinden emin değilim. Yine de, her durumda kaliteli kod üretmeyeceklerinden oldukça eminim.
Ingo Bürk

Ah yakaladım. Ben düşünüyordum Google Web Kit'in derleyicisi, ama onlar çekirdek kütüphaneler için buraya yaptığı şeyi yapmayı sona erdi gibi görünüyor. "GWT derleyici Java dili kendisi büyük çoğunluğunu destekleyen GWT çalışma zamanı kitaplığı ilgili bir alt kümesini emüle Java çalışma zamanı kitaplığı. " Belki başkalarının aynı sorunu nasıl çözdüğünü görmek için bakılması gereken bir şey!
Claudiu

Evet. Eminim Google'ın çözümü benimkinin çok ötesindedir, ama sonra tekrar oynamaktan zevk alıyorum. Ne yazık ki, kaynak kodu iptal edilmiş gibi görünüyor (?), En azından göz atamıyorum ve ilginç bağlantılar ölü gibi görünüyor. Çok kötü, ona bakmak isterdim.
Ingo Bürk

Eğlenmek eğlenmek için en iyi yoldur =). paylaşım için teşekkürler
Claudiu

0

Yine başka bir harita uygulaması. Randomizer ile 'jenerikler' ve 'yineleyici' =)

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

Misal:

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
*/
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.