JavaScript Hashmap еквівалент


353

Як було зрозуміло в оновленні 3 на цю відповідь , це позначення:

var hash = {};
hash[X]

насправді не має хеш-об’єкт X; він насправді просто перетворюється Xна рядок (через, .toString()якщо це об'єкт або деякі інші вбудовані перетворення для різних примітивних типів), а потім шукає цю рядку вгору, не збиваючи її, в " hash". Рівність об'єктів також не перевіряється - якщо два різних об'єкти мають однакове перетворення рядків, вони просто перезаписують один одного.

З огляду на це - чи існують ефективні реалізації хешмапів у JavaScript? (Наприклад, другий результат Google javascript hashmapдає реалізацію, яка є O (n) для будь-якої операції. Різні інші результати ігнорують той факт, що різні об'єкти з еквівалентними представленнями рядків перезаписують один одного.


1
@Claudiu: Вибачте за редагування, але "Карта" в назві була дійсно оманливою. Відкотись, якщо ти не погоджуєшся, я не мав наміру опікуватися. :)
Томалак

6
@Claudiu: Ви задаєте багато питань щодо JavaScript. Хороші запитання. Мені це подобається.
десь

2
@Claudiu: Також, чи можете ви зв’язати результат Google, на який ви посилаєтесь? Різні локальні версії Google дають різні результати, реалізація, на яку ви посилаєтеся, навіть не здається для мене.
Томалак

@Tomalak: Я просто збирався написати абсолютно те саме!
десь

3
@Claudiu Ні, не посилайтеся на Google. Посилання на сторінку, про яку ви говорили (яку вам довелося знайти через google). Пов’язання з google має ті самі проблеми, що і пояснення того, що шукати: налаштування Google за результатами на основі місцезнаходження чи історії пошуку, результати Google змінюються з часом (на даний момент це найкращий результат для цього пошуку) та все, що може зробити це показати різні результати.
Джаспер

Відповіді:


369

Чому б не об’єднати хеш-об’єкти вручну і не використовувати отримані рядки як ключі для звичайного словника JavaScript? Зрештою, ви маєте найкраще становище, щоб знати, що робить ваші об’єкти унікальними. Це я і роблю.

Приклад:

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;

Таким чином ви можете контролювати індексацію, виконану JavaScript, без важкого зняття розподілу пам’яті та обробки переповнення.

Звичайно, якщо ви справді хочете "рішення промислового рівня", ви можете створити клас, параметризований за ключовою функцією, і з усім необхідним API контейнера, але ... ми використовуємо JavaScript і намагаємося бути простими і легкими, тому це функціональне рішення просте і швидке.

Функція клавіш може бути такою ж простою, як вибір правильних атрибутів об'єкта, наприклад, ключа або набору клавіш, які вже є унікальними, комбінації клавіш, які є унікальними разом, або настільки ж складними, як використання деяких криптографічних хешей, наприклад в кодуванні DojoX або Ujoid DojoX . Хоча останні рішення можуть створювати унікальні ключі, я особисто намагаюся уникнути їх за будь-яку ціну, особливо, якщо я знаю, що робить мої об’єкти унікальними.

Оновлення в 2014 році: відповідь ще у 2008 році, це просте рішення все ще потребує додаткових пояснень. Дозвольте мені пояснити цю ідею у формі запитань.

У вашому рішенні немає справжнього хешу. Де це???

JavaScript - мова високого рівня. Його основний примітив ( Object ) включає хеш-таблицю для збереження властивостей. Ця хеш-таблиця зазвичай написана мовою низького рівня для ефективності. Використовуючи простий об’єкт за допомогою рядкових клавіш, ми використовуємо ефективно реалізовану хеш-таблицю без жодних зусиль з нашого боку.

Як ви знаєте, що вони використовують хеш?

Існує три основні способи збереження колекції об'єктів, адресованих ключем:

  • Без упорядкування. У цьому випадку, щоб отримати об’єкт за його ключем, ми повинні перейти всі клавіші, зупиняючись, коли знаходимо його. В середньому це займе n / 2 порівняння.
  • Упорядковано.
    • Приклад №1: відсортований масив - виконуючи двійковий пошук, ми знайдемо наш ключ в середньому після порівняння ~ log2 (n). Значно краще.
    • Приклад №2: дерево. Знову це буде ~ log (n) спроб.
  • Таблиця хешу В середньому це вимагає постійного часу. Порівняйте: O (n) проти O (log n) проти O (1). Бум.

Очевидно, що об’єкти JavaScript використовують хеш-таблиці в якійсь формі для обробки загальних випадків.

Чи справді виробники браузерів використовують хеш-таблиці ???

Дійсно.

Чи справляють вони зіткнення?

Так. Дивись вище. Якщо ви виявили зіткнення на нерівних струнах, будь ласка, не соромтеся подати помилку у постачальника.

То яка ваша ідея?

Якщо ви хочете зафіксувати об'єкт, знайдіть те, що робить його унікальним, і використовуйте його як ключ. Не намагайтеся обчислювати реальний хеш або емуляцію хеш-таблиць - це вже ефективно обробляється основним об’єктом JavaScript.

Використовуйте цю клавішу за допомогою JavaScript, Objectщоб використовувати її вбудовану хеш-таблицю, уникаючи можливих сутичок із властивостями за замовчуванням.

Приклади для початку роботи:

  • Якщо ваші об’єкти містять унікальне ім’я користувача - використовуйте його як ключ.
  • Якщо він містить унікальний номер клієнта - використовуйте його як ключ.
    • Якщо він включає унікальні урядові номери, наприклад, SSN або номер паспорта, і ваша система не дозволяє дублікати - використовуйте його як ключ.
  • Якщо комбінація полів унікальна - використовуйте її як ключ.
    • Абревіатура штату + номер посвідчення водія робить відмінний ключ.
    • Абревіатура країни + номер паспорта - також відмінний ключ.
  • Деяка функція на полях, або цілий об'єкт, може повернути унікальне значення - використовувати його як ключ.

Я використовував вашу пропозицію і кешував усі об'єкти за допомогою імені користувача. Але якогось мудрого хлопця називають "toString", який є вбудованою власністю! Що мені робити зараз?

Очевидно, якщо навіть віддалено можливо, що отриманий ключ складається виключно з латинських символів, ви повинні зробити щось з цим. Наприклад, додайте будь-який не латинський символ Unicode, який вам сподобався на початку або в кінці, щоб скасувати зі властивостями за замовчуванням: "#toString", "#MarySmith". Якщо використовується складений ключ, розділіть ключові компоненти, використовуючи якийсь не латинський роздільник: "ім'я, місто, штат".

Загалом це місце, де ми повинні бути творчими, і вибирати найпростіші клавіші із заданими обмеженнями (унікальність, потенційні зіткнення із властивостями за замовчуванням).

Примітка: унікальні ключі не зіштовхуються за визначенням, тоді як потенційні хеш-сутички будуть оброблятись основними Object.

Чому ви не любите промислові рішення?

IMHO, найкращий код - це взагалі не код: він не має помилок, не вимагає обслуговування, легко зрозуміти та виконує миттєво. Усі "хеш-таблиці в JavaScript", які я бачив, складали> 100 рядків коду та включали кілька об'єктів. Порівняйте з: dict[key] = value.

Ще один момент: чи можна навіть обіграти продуктивність споконвічного об'єкта, написаного мовою низького рівня, використовуючи JavaScript та ті самі первісні об’єкти, щоб реалізувати те, що вже реалізовано?

Я все ще хочу хешувати свої об’єкти без жодних ключів!

Нам пощастило: ECMAScript 6 (запланований до випуску на середину 2015 року, надайте або пройдіть через 1-2 роки після цього, щоб набути широкого поширення) визначає карту та встановлює .

Судячи з визначення, вони можуть використовувати адресу об'єкта як ключ, що робить об'єкти миттєвими відмінними без штучних ключів. OTOH, два різних, але однакові об'єкти, будуть відображені як окремі.

Порівняльний розподіл від MDN :

Об'єкти схожі на "Карти", оскільки обидва дозволяють вам встановлювати значення для клавіш, отримувати ці значення, видаляти ключі та визначати, чи зберігається щось у ключі. Через це (і тому, що не було вбудованих альтернатив), Об'єкти історично використовувались як Карти; однак є важливі відмінності, які в певних випадках дозволяють використовувати карту:

  • Ключами об'єкта є рядки та символи, тоді як вони можуть бути будь-яким значенням для карти, включаючи функції, об'єкти та будь-який примітив.
  • Ключі в Map впорядковуються, поки ключі, додані до об'єкта, не є. Таким чином, під час ітерації над ним об’єкт Map повертає ключі в порядку вставки.
  • Ви можете легко отримати розмір карти за допомогою властивості розміру, при цьому кількість властивостей об’єкта потрібно визначити вручну.
  • Карта є ітерабельною і тому може бути безпосередньо повторена, тоді як ітерація над Об'єктом вимагає отримання ключів певним чином та повторення над ними.
  • Об'єкт має прототип, тому на карті є ключі за замовчуванням, які можуть зіткнутися з вашими ключами, якщо ви не будете обережні. Станом на ES5 це можна обійти за допомогою map = Object.create (null), але це робиться рідко.
  • Карта може працювати краще в сценаріях, що передбачають часте додавання та видалення пар ключів.

13
Це не схоже на належну карту, тому що ви не справляєтесь зіткненнями. Якщо це станеться правдою: hash (obj1) == hash (obj2), ви втратите свої дані.
beefeather

32
Небо допоможе вам, коли і "PAUL AINLEY", і "PAULA INLEY" реєструються у вашій системі ...
Matt R

34
@MattR Насправді ваш приклад буде працювати нормально без небесної допомоги навіть при макетній хеш-функції. Я сподіваюся, що інші читачі зрозуміють, що надмірно спрощена нереалістична хеш-функція була використана як заповнювач для демонстрації іншої техніки. І кодовий коментар, і сама відповідь підкреслюють, що це не реально. Підбір належних ключів обговорюється в останньому абзаці відповіді.
Євген Лазуткін

6
@EugeneLazutkin - ти все ще помилився, боюся. Ваш приклад досі схильний до хеш-зіткнень. Не думайте, що проставлення прізвища спочатку якось допоможе вам!
Matt R

3
@EugeneLazutkin Більшість людей не читає, що ти маєш відповідь, перш ніж навіть з’явиться ES6 ... Дозвольте привітатись за ваші глибокі знання JS
Габріель Андрес Бранколіні

171

Опис проблеми

JavaScript не має вбудованого загального типу карти (іноді його називають асоціативним масивом чи словником ), що дозволяє отримати доступ до довільних значень за допомогою довільних ключів. Фундаментальна структура даних JavaScript - це об'єкт , особливий тип карти, який приймає лише рядки як ключі та має особливу семантику, як прототипічне успадкування, геттери та сетери та деякий подальший вуду.

Використовуючи об'єкти як карти, ви повинні пам’ятати, що ключ буде перетворений у значення рядка через toString(), що призводить до відображення 5і '5'в те саме значення, а також всі об'єкти, які не перезаписують toString()метод, на значення, індексоване '[object Object]'. Ви також можете мимоволі отримати доступ до його успадкованих властивостей, якщо ви не зробите це hasOwnProperty().

Вбудований тип масиву JavaScript не допомагає ні один біт: масиви JavaScript - це не асоціативні масиви, а лише об'єкти з ще кількома спеціальними властивостями. Якщо ви хочете знати, чому їх не можна використовувати як карти, погляньте тут .

Рішення Євгена

Євген Лазуткін вже описав основну ідею використання спеціальної хеш-функції для створення унікальних рядків, які можна використовувати для пошуку пов'язаних значень як властивостей об’єкта словника. Це, швидше за все, буде найшвидшим рішенням, оскільки об'єкти реалізуються внутрішньо як хеш-таблиці .

  • Примітка: Хеш-таблиці (іноді їх називають хеш-картами ) - це особлива реалізація концепції карти за допомогою резервного масиву та пошуку за допомогою числових хеш-значень. Навколишнє середовище виконання може використовувати інші структури (наприклад, дерева пошуку чи пропускні списки ) для реалізації об’єктів JavaScript, але оскільки об'єкти є основною структурою даних, вони повинні бути достатньо оптимізовані.

Для того, щоб отримати унікальне хеш-значення для довільних об'єктів, одна можливість полягає у використанні глобального лічильника та кешування хеш-значення в самому об’єкті (наприклад, у власності з назвою __hash).

Хеш-функція, яка це робить, і працює як для примітивних значень, так і для об'єктів:

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

hash.current = 0;

Цю функцію можна використовувати так, як описав Євген. Для зручності ми додатково загорнемо його в Mapклас.

Моя Mapреалізація

Наступна реалізація додатково зберігатиме пари ключ-значення у подвійно пов'язаному списку, щоб забезпечити швидку ітерацію як ключів, так і значень. Щоб поставити власну хеш-функцію, ви можете перезаписати hash()метод екземпляра після створення.

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

Приклад

Наступний сценарій

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

генерує цей вихід:

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

Подальші міркування

PEZ запропонував замінити toString()метод, імовірно, з нашою хеш-функцією. Це неможливо, оскільки це не працює для примітивних значень (зміна toString()на примітиви - дуже погана ідея). Якщо ми хочемо toString()повернути значущі значення для довільних об'єктів, нам доведеться змінити Object.prototype, які деякі люди (я не включаю ) вважають багатослівними .


Редагувати: Поточну версію моєї Mapреалізації, а також інші смаколики JavaScript можна отримати тут .


ES5 знецінює використання каллі ( goo.gl/EeStE ). Натомість я пропоную Map._counter = 0і в конструкторі Map зробити this._hash = 'object ' + Map._counter++. Тоді хеш () стаєreturn (value && value._hash) || (typeof(value) + ' ' + String(value));
broofa

Посилання на код порушено: mercurial.intuxication.org/hg/js-hacks/raw-file/tip/map.js
ahcox

привіт @Christoph, ти можеш оновити своє посилання, де я можу знайти вашу реалізацію на карті?
NumenorForLife

2
@ jsc123: Я вивчу це - поки ви можете отримати дамп сховища на pikacode.com/mercurial.intuxication.org/js-hacks.tar.gz
Крістоф

58

Я знаю, що це питання досить давнє, але зараз існує кілька справді чудових рішень із зовнішніми бібліотеками.

У JavaScript також є своя мова Map.


2
Це шлях рухатися вперед до 21 століття. Шкода, що я знайшов вашу пошту, закінчивши свій код з якоюсь потворною домашньою картою. WEEE потрібно більше голосу за вашу відповідь
Phung D.

1
Collections.js має деякі реалізації, але я не можу знайти їх у underscore.js або lodash ... на що ви зверталися в підкресленні, що було б корисно?
Кодування,

@CodeBling поняття не маю. Я думаю, я заплутався з функцією карти. Я збираюся його зняти з відповіді.
Джамель Томс

3
Це справедливо. Кожен, хто розглядає Collections.js, повинен знати, що він модифікує глобальні прототипи масивів, функцій, об’єктів та Regexp проблематично ( див. Проблеми, з якими я стикався тут ). Хоча я спочатку був дуже задоволений collection.js (і, отже, ця відповідь), ризики, пов’язані з його використанням, були занадто високими, тому я відмовився від нього. Тільки відділення v2 від kriskowal of collection.js (конкретно, v2.0.2 +) усуває глобальні модифікації прототипу і є безпечним у використанні.
Кодифікація

28

Ось простий та зручний спосіб використання чогось схожого на карту 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
        }

І щоб отримати значення:

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

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

2
Це працює лише для рядкових клавіш. Я вважаю, що ОП зацікавило використання ключів будь-якого типу.
фрактор

26

Відповідно до стандарту ECMAScript 2015 (ES6), JavaScript має реалізацію Map. Більше про це можна ознайомитись тут

Основне використання:

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 WeakMapабо Map:

  • Слабкі карти - це ключові / значущі карти, в яких ключі є об'єктами.

  • Mapоб'єкти - прості карти ключа / значення. Будь-яке значення (як об'єкти, так і примітивні значення) може використовуватися як ключ або значення.

Майте на увазі, що жодна з них не підтримується широко, але ви можете використовувати ES6 Shim (потрібен нативний ES5 або ES5 Shim ) для підтримки Map, але ні WeakMap( див. Чому ).


У 2019 році вони дуже добре підтримуються та мають дивовижні методи! developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Хуанма Менендес

13

Вам доведеться зберігати в парах внутрішнього стану пар об'єктів / значень

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

І використовувати його як таке:

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

Звичайно, ця реалізація також десь уздовж лінії O (n). Наведені вище приклади Євгенія - єдиний спосіб отримати хеш, який працює з будь-якою швидкістю, яку ви очікували від справжнього хешу.

Оновлення:

Інший підхід, відповідно до відповіді Євгена, полягає в тому, щоб якось приєднати унікальний ідентифікатор до всіх об'єктів. Один з моїх улюблених підходів - це взяти один із вбудованих методів, успадкованих від суперкласу Object, замінити його на власну функцію, яка проходить наскрізь і додавати властивості до цього об’єкта функції. Якби ви переписали мій метод HashMap для цього, це виглядатиме так:

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

Ця версія видається лише дещо швидшою, але теоретично вона буде значно швидшою для великих наборів даних.


Асоціативний масив, тобто масив 2-кортезів, - це Map, а не HashMap; HashMap - це карта, яка використовує хеші для кращої продуктивності.
Ерік Каплун

Щоправда, але навіщо розділяти волоски на тему? Немає можливості створити справжню хеш-карту в JavaScript, оскільки ви не можете отримати адреси об’єктів пам'яті. І вбудовані в JavaScript об'єктні пари ключів / значень (використовуються в моєму другому прикладі) можуть виступати як HashMaps, але не обов'язково, оскільки це залежить від часу виконання, який використовується в браузері щодо того, як здійснюється пошук.
гончарне м'ясо

11

На жаль, жоден із наведених відповідей не був хорошим для мого випадку: різні ключові об’єкти можуть мати однаковий хеш-код. Тому я написав просту версію HashMap, схожу на 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;
}

Примітка: ключові об'єкти повинні "реалізувати" hashCode () та equals () методи.


7
Перевага new Array()за кадром [], щоб забезпечити абсолютну Java-подобу вашого коду? :)
Ерік Каплун

6

Я реалізував JavaScript HashMap, з якого можна отримати код http://github.com/lambder/HashMapJS/tree/master

Ось код:

/*
 =====================================================================
 @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
Здається, що ваш код не працює з розміщенням одного і того ж об'єкта в декількох HashMaps.
Ерік Каплун

5

У ECMA6 можна використовувати WeakMap

Приклад:

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

Але:

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

о хвалите Ісусе, вони нарешті додають слабкі посилання на JavaScript. настав час ... +1 для цього, але це насправді було б жахливо використовувати, оскільки посилання слабкі
Клавдіу

2

Javascript не вбудовує Map / hashmap. Його слід назвати асоціативним масивом .

hash["X"]дорівнює hash.X, але дозволити "X" у вигляді змінної рядка. Іншими словами, hash[x]функціонально дорівнюєeval("hash."+x.toString())

Він більше схожий на object.properties, а не на ключове значення. Якщо ви шукаєте кращого відображення ключа / значення у Javascript, будь ласка, використовуйте об’єкт Map, який ви можете знайти в Інтернеті.


2

Спробуйте мою реалізацію хеш-таблиці JavaScript: http://www.timdown.co.uk/jshashtable

Він шукає метод хеш-кодів () ключових об'єктів, або ви можете надати функцію хешування під час створення об’єкта Hashtable.


2

Це виглядає як досить надійне рішення: https://github.com/flesler/hashmap . Він навіть добре підійде для функцій та об'єктів, які виглядають однаково. Єдиний хак, який він використовує, - додавання неясного члена до об'єкта для його ідентифікації. Якщо ваша програма не замінить цю незрозумілу змінну (її щось на зразок хашида ), ви золото.


2

Якщо продуктивність не важлива (наприклад , кількість ключів відносно невелике) , і ви не хочете , щоб забруднити ваш (або , можливо , не ваш) об'єкти з додатковими полями , як _hash, _idі т.д., то ви можете використовувати той факт , що Array.prototype.indexOfвикористовує сувора рівність. Ось проста реалізація:

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

Приклад використання:

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, у нього є два питання: O (n) час пошуку та слабкість (тобто це спричинить витік пам'яті, якщо ви не користуєтесь deleteабо clearне відпускаєте ключі).


2

Моя реалізація карт, що випливає з прикладу Крістофа:

Приклад використання:

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

Додавання ще одного рішення: HashMapмайже перший клас, який я переніс із Java на Javascript. Можна сказати, що накладних витрат багато, але реалізація майже на 100% дорівнює реалізації Java та включає всі інтерфейси та підкласи.

З проектом можна ознайомитись тут: https://github.com/Airblader/jsava Я також додаю (поточний) вихідний код для класу HashMap, але, як зазначено, він також залежить від суперкласу тощо. Використовувана структура OOP є qooxdoo.

Редагувати: Зверніть увагу, що цей код уже застарів і посилайтесь на проект github для поточної роботи. Щодо написання цього питання, є також ArrayListреалізація.

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

Хм цікавий підхід .. Ви розглядали можливість випробувати автоматизований підхід? тобто працює компілятор Java-javascript на вихідний код для поточної реалізації Java?
Клавдіу

Ні :) Це просто цікавий проект для мене, і було дуже багато речей, які я не міг просто "скопіювати" код. Я не знаю компіляторів Java-Javascript, хоча я вважаю, що вони існують. Я не впевнений, наскільки добре вони б це переклали. Я впевнений, що вони ні в якому разі не дадуть якісного коду.
Інго Бюрк

Ах готча. Я думав про компілятор веб-інструментів Google , але, схоже, вони закінчили робити те, що ви робите тут для основних бібліотек: "Компілятор GWT підтримує переважну більшість мови Java. Бібліотека виконання GWT імітує відповідний набір Бібліотека часу виконання Java. " Можливо, щось подивитися, щоб побачити, як інші вирішили ту саму проблему!
Клавдіу

Так. Я впевнений, що рішення Google набагато перевищує моє, але знову ж таки, мені просто весело грати. На жаль, схоже, вихідний код був відкликаний (?), Принаймні я не можу його переглядати, а цікаві посилання, здається, мертві. Шкода, я хотів би на це поглянути.
Інго Бюрк

Весело грати - це найкращий спосіб вчитися =). дякую за обмін
Клавдіу

0

Ще одна реалізована мною карта. З рандомизатором, 'generics' і '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;
    }
}

Приклад:

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
*/
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.