Эквивалент JavaScript Hashmap


354

Как поясняется в обновлении 3 этого ответа , это обозначение:

var hash = {};
hash[X]

на самом деле не хэширует объект X; на самом деле он просто конвертируется Xв строку (через, .toString()если это объект, или некоторые другие встроенные преобразования для различных типов примитивов), а затем просматривает эту строку без хеширования в " hash". Равенство объектов также не проверяется - если два разных объекта имеют одинаковое преобразование строк, они просто перезаписывают друг друга.

Учитывая это - есть ли эффективные реализации hashmaps в javascript? (Например, 2-й результат Google javascript hashmapдает реализацию, которая является O (n) для любой операции. Различные другие результаты игнорируют тот факт, что разные объекты с эквивалентными строковыми представлениями перезаписывают друг друга.


1
@Claudiu: Извините за редактирование, но «Карта» в названии действительно вводит в заблуждение. Откатись, если ты не согласен, я не собирался опекать. :)
Томалак

6
@Claudiu: Вы задаете много вопросов о JavaScript. Хорошие вопросы Я люблю это.
некоторые

2
@Claudiu: Кроме того, не могли бы вы дать ссылку на результат Google, на который вы ссылаетесь? Разные локальные версии Google дают разные результаты, реализация, на которую вы ссылаетесь, мне даже не показывается.
Томалак

@ Томалак: Я просто собирался написать точно то же самое!
некоторые

3
@Claudiu Нет, не связывайтесь с Google. Ссылка на страницу, о которой вы говорили (которую вы нашли через Google). При связывании с Google возникают все те же проблемы, что и при объяснении того, что искать: Google настраивает результаты на основе местоположения или истории поиска, результаты Google меняются с течением времени (в настоящее время это лучший результат для этого поиска) и все остальное, что может сделать его показать разные результаты.
Джаспер

Ответы:


371

Почему бы не хэшировать свои объекты вручную и не использовать полученные строки в качестве ключей для обычного словаря 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 или UUID DojoX . Хотя последние решения могут создавать уникальные ключи, лично я стараюсь избегать их любой ценой, особенно если я знаю, что делает мои объекты уникальными.

Обновление в 2014 году. Ответ на это простое решение еще в 2008 году требует дополнительных пояснений. Позвольте мне прояснить идею в форме вопросов и ответов.

Ваше решение не имеет реального хэша. Где это находится???

JavaScript - это язык высокого уровня. Его базовый примитив ( Object ) включает в себя хеш-таблицу для сохранения свойств. Эта хеш-таблица обычно написана на языке низкого уровня для эффективности. Используя простой объект со строковыми ключами, мы используем эффективно реализованную хеш-таблицу без каких-либо усилий с нашей стороны.

Откуда вы знаете, что они используют хэш?

Существует три основных способа сохранить коллекцию объектов, адресуемых ключом:

  • Неупорядоченный. В этом случае, чтобы получить объект по его ключу, мы должны пройти все ключи, останавливаясь, когда мы его находим. В среднем это займет n / 2 сравнения.
  • Заказал.
    • Пример # 1: отсортированный массив - выполняя бинарный поиск, мы найдем наш ключ после ~ log2 (n) сравнений в среднем. Намного лучше.
    • Пример № 2: дерево. Опять это будет ~ log (n) попыток.
  • Хеш-таблица. В среднем это требует постоянного времени. Сравните: O (n) против O (log n) против O (1). Boom.

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

Действительно ли производители браузеров используют хеш-таблицы ???

В самом деле.

Они справляются со столкновениями?

Да. Смотри выше. Если вы обнаружили коллизию на неравных строках, пожалуйста, не стесняйтесь сообщать об ошибке поставщику.

Так в чем твоя идея?

Если вы хотите хешировать объект, найдите, что делает его уникальным, и используйте его в качестве ключа. Не пытайтесь вычислить реальный хэш или эмулировать хеш-таблицы - он уже эффективно обрабатывается базовым объектом JavaScript.

Используйте этот ключ с JavaScript, Objectчтобы использовать встроенную хэш-таблицу, избегая возможных конфликтов со свойствами по умолчанию.

Примеры для начала:

  • Если ваши объекты содержат уникальное имя пользователя - используйте его в качестве ключа.
  • Если он содержит уникальный номер клиента - используйте его в качестве ключа.
    • Если он включает в себя уникальные правительственные номера, такие как номер SSN или номер паспорта, и ваша система не допускает дублирование - используйте его в качестве ключа.
  • Если комбинация полей уникальна - используйте ее в качестве ключа.
    • Штатная аббревиатура + номер водительского удостоверения делают отличный ключ.
    • Аббревиатура страны + номер паспорта тоже отличный ключ.
  • Некоторая функция в полях или целый объект может возвращать уникальное значение - использовать его в качестве ключа.

Я использовал ваше предложение и кэшировал все объекты, используя имя пользователя. Но какого-то мудрого парня зовут «toString», который является встроенным свойством! Что мне теперь делать?

Очевидно, что если даже отдаленно возможно, что полученный ключ будет состоять исключительно из латинских символов, вы должны что-то с этим сделать. Например, добавьте любые нелатинские символы Юникода, которые вам нравятся в начале или в конце, чтобы снять конфликт со свойствами по умолчанию: "#toString", "#MarySmith". Если используется составной ключ, разделите ключевые компоненты, используя некий латинский разделитель: «имя, город, штат».

В общем, это место, где мы должны проявлять креативность и выбирать самые простые ключи с заданными ограничениями (уникальность, потенциальные конфликты со свойствами по умолчанию).

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

Почему вам не нравятся промышленные решения?

ИМХО, лучший код - это вовсе не код: он не содержит ошибок, не требует обслуживания, прост в понимании и выполняется мгновенно. Все «хеш-таблицы в 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), вы потеряете свои данные.
Пчеловод

32
Небеса помогут вам, когда и «PAUL AINLEY», и «PAULA INLEY» зарегистрируются в вашей системе ...
Matt R

34
@MattR На самом деле ваш пример будет работать правильно, без помощи небес, даже с фиктивной хэш-функцией. Я надеюсь, что другие читатели поймут, что чрезмерно упрощенная нереалистичная хеш-функция была использована в качестве заполнителя для демонстрации другой техники. И комментарии к коду, и сам ответ подчеркивают, что это не реально. Выбор правильных ключей обсуждается в последнем абзаце ответа.
Евгений Лазуткин

6
@ Евгений Лазуткин - я все еще ошибаюсь, я боюсь. Ваш пример все еще склонен к коллизиям хешей. Не думайте, что если вы просто введете фамилию, это вам как-то поможет!
Мэтт 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++. Тогда hash () становитсяreturn (value && value._hash) || (typeof(value) + ' ' + String(value));
брофа

Ссылка на код не работает: 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. An

1
В Collections.js есть некоторые реализации, но я не могу найти их в underscore.js или lodash ... на что вы ссылались в подчеркивании, что было бы полезно?
Кодирование

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

3
Это честно. Любой, кто рассматривает Collections.js, должен знать, что он изменяет глобальные прототипы Array, Function, Object и Regexp проблемным способом ( см. Проблемы, с которыми я столкнулся здесь ). Хотя поначалу я был очень доволен collection.js (и, следовательно, этим ответом), риски, связанные с его использованием, были слишком высоки, поэтому я отбросил его. Только ветка kriskowal v2 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:

  • WeakMaps - это карты ключ / значение, в которых ключи являются объектами.

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

Эта версия, кажется, только немного быстрее, но теоретически она будет значительно быстрее для больших наборов данных.


Ассоциативный массив, то есть массив из двух кортежей, является Map, а не HashMap; HashMap - это карта, которая использует хэши для повышения производительности.
Эрик Каплун

Правда, но почему по этой теме? Невозможно создать настоящую хэш-карту в JavaScript, поскольку вы не можете получить адреса памяти объектов. И встроенные в JavaScript пары ключ / значение объекта (используемые в моем втором примере) могут выступать в качестве HashMaps, но не обязательно, поскольку это зависит от времени выполнения, используемого в браузере, в отношении того, как реализован поиск.
мясо в горшке

11

К сожалению, ни один из приведенных выше ответов не подходит для моего случая: разные ключевые объекты могут иметь одинаковый хэш-код. Поэтому я написал простую Java-подобную версию HashMap:

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()over []заключается в том, чтобы обеспечить абсолютное сходство 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 за это, но это на самом деле было бы ужасно использовать, потому что ссылки слабы
Claudiu

2

Javascript не встроен в карту / hashmap. Это следует называть ассоциативным массивом .

hash["X"]равно hash.X, но разрешить "X" в качестве строковой переменной. Другими словами,hash[x] функционально равноeval("hash."+x.toString())

Это больше похоже на object.properties, чем на сопоставление ключ-значение. Если вы ищете лучшее отображение ключа / значения в Javascript, используйте объект Map, который вы можете найти в Интернете.


2

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

Он ищет метод ключевых объектов hashCode (), или вы можете предоставить функцию хеширования при создании объекта 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-to-javascript в исходном коде для текущей реализации Java?
Клавдиу

Нет :) Это просто забавный проект для меня, и было немало вещей, в которых я не мог просто «скопировать» код. Я не знаю о компиляторах Java-to-Javascript, хотя я бы поверил, что они существуют. Я не уверен, насколько хорошо они переведут это. Я вполне уверен, что они в любом случае не произведут качественный код.
Инго Бюрк

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

Да. Я уверен, что решение Google намного превосходит мое, но опять же, я просто развлекаюсь. К сожалению, исходный код, похоже, был отозван (?), По крайней мере, я не могу его просмотреть, а интересные ссылки кажутся мертвыми. Жаль, я бы с удовольствием посмотрел на это.
Инго Бюрк

Получать удовольствие от игры - лучший способ учиться =). Спасибо, что поделились
Claudiu

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.