Как стало ясно в обновлении 3 на этот ответ, эта запись:
var hash = {};
hash[X]
фактически не хеширует объект X; на самом деле он просто преобразует X в строку (через .toString(), если это объект, или некоторые другие встроенные преобразования для различных примитивных типов), а затем ищет эту строку без хеширования в «hash». Равенство объектов также не проверяется - если два разных объекта имеют одинаковое преобразование строк, они просто перезапишут друг друга.
Учитывая это - есть ли эффективные реализации хэш-карт в JavaScript?
(Например, второй результат Google javascript hashmap дает реализацию, которая равна O (n) для любой операции. Различные другие результаты игнорируют тот факт, что разные объекты с эквивалентными строковыми представлениями перезаписывают друг друга.
@Claudiu: Вы задаете много вопросов о javascript. Хорошие вопросы. Мне нравится это.
@Claudiu: Кроме того, не могли бы вы дать ссылку на результат Google, на который вы ссылаетесь? Различные локальные версии Google возвращают разные результаты, реализация, о которой вы говорите, мне даже не кажется.
@Tomalak: Я как раз собирался написать то же самое!
ты все. Я добавил поиск Google. @some: ага, я только начал изучать этот язык. это интересно!
@Claudiu Нет, не ссылайтесь на Google. Ссылка на страницу, о которой вы говорили (которую вы случайно нашли через гугл). Связь с Google имеет те же проблемы, что и объяснение того, что искать: Google настраивает результаты на основе местоположения или истории поиска, результаты Google меняются с течением времени (в настоящее время это лучший результат для этого поиска) и все остальное, что может сделать это. показать разные результаты.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Вам нужно будет хранить пары пар объект / значение в каком-то внутреннем состоянии:
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.info("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, но не обязательно, поскольку это зависит от среды выполнения, используемой в браузере, в отношении того, как реализован поиск.
JavaScript не имеет встроенного общего типа карта (иногда называемого ассоциативный массив или Словарь), который позволяет получить доступ к произвольным значениям с помощью произвольных ключей. Фундаментальной структурой данных JavaScript является объект, особый тип карты, который принимает только строки в качестве ключей и имеет особую семантику, такую как прототипное наследование, геттеры и сеттеры, а также еще кое-что вуду.
При использовании объектов в качестве карт вы должны помнить, что ключ будет преобразован в строковое значение через toString(), что приведет к отображению 5 и '5' на одно и то же значение, а все объекты, которые не перезаписывают метод toString(), на значение, индексированное '[object Object]'. Вы также можете непроизвольно получить доступ к его унаследованным свойствам, если не отметите hasOwnProperty().
Встроенный в 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 an 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
привет @Christoph, не могли бы вы обновить свою ссылку, где я могу найти вашу реализацию карты?
@ jsc123: Я разберусь - пока вы можете получить дамп репозитория по адресу pikacode.com/mercurial.intuxication.org/js-hacks.tar.gz
JavaScript не имеет встроенной карты / хэш-карты. Его следует называть ассоциативный массив.
hash["X"] совпадает с hash.X, но допускает "X" в качестве строковой переменной.
Другими словами, hash[x] функционально равен eval("hash."+x.toString()).
Это больше похоже на object.properties, чем на сопоставление значений ключа. Если вы ищете лучшее сопоставление ключей и значений в JavaScript, используйте объект Map.
Попробуйте мою реализацию хеш-таблицы JavaScript: http://www.timdown.co.uk/jshashtable
Он ищет метод hashCode () ключевых объектов, или вы можете предоставить функцию хеширования при создании объекта Hashtable.
Я реализовал 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 association
*/
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 association 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")
}
Ваш код, похоже, не работает с помещением одного и того же объекта в несколько HashMap.
К сожалению, ни один из предыдущих ответов не подошел для моего случая: разные ключевые объекты могут иметь один и тот же хэш-код. Поэтому я написал простую 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 ().
Предпочтение new Array() перед [] состоит в том, чтобы гарантировать абсолютное Java-подобие вашего кода? :)
Моя реализация карты, полученная из Пример Кристофа:
Пример использования:
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 an 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.info(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.info(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.info(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;
};
Вот простой и удобный способ использования чего-то похожего на карту 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']); // Gives the value of map_value_1
... etc. ...
Это работает только для строковых ключей. Я считаю, что OP был заинтересован в использовании ключей любого типа.
Добавляем еще одно решение: HashMap - это практически первый класс, который я перенес с Java на JavaScript. Можно сказать, что накладных расходов много, но реализация почти на 100% равна реализации Java и включает все интерфейсы и подклассы.
Проект можно найти здесь: https://github.com/Airblader/jsava Я также приложу (текущий) исходный код для класса HashMap, но, как было сказано, он также зависит от суперкласса и т. д. Используемая структура ООП - 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's, но похоже, что они в конечном итоге сделали то, что вы делаете здесь для основных библиотек: «Компилятор GWT поддерживает подавляющее большинство самого языка Java. Библиотека времени выполнения GWT эмулирует соответствующее подмножество среды выполнения Java. библиотека.". Может быть, на что-нибудь посмотреть, чтобы увидеть, как другие решили ту же проблему!
Да уж. Я уверен, что решение Google намного превосходит мое, но опять же, я просто получаю удовольствие, играя. К сожалению, исходный код кажется отозван (?), По крайней мере, я не могу его просмотреть, а интересные ссылки кажутся мертвыми. Жаль, я бы с удовольствием посмотрел на это.
Веселая игра - лучший способ учиться =). Спасибо, что поделился
Это выглядит довольно надежным решение: https://github.com/flesler/hashmap
Он подойдет даже для идентичных функций и объектов. Единственный прием, который он использует, - это добавление неясного члена к объекту для его идентификации. Если ваша программа не перезаписывает эту непонятную переменную (это что-то вроде хешид), вы золотой.
В ECMAScript 6 вы можете использовать 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
Но:
Из-за того, что ссылки слабые, ключи WeakMap нельзя перечислить (т. Е. Нет метода, дающего вам список ключей).
о, хвала Иисусу, они наконец добавили слабые ссылки на javascript. самое время ... +1 для этого, но на самом деле было бы ужасно использовать, потому что ссылки слабые
Если производительность не критична (например, количество ключей относительно невелико) и вы не хотите загрязнять свои (или, может быть, не ваши) объекты дополнительными полями, такими как _hash, _id и т. д., То вы можете использовать тот факт, что Array.prototype.indexOf использует строгое равенство. Вот простая реализация:
var Dict = (function(){
// Internet Explorer 8 and earlier does not have any 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.
По сравнению с ECMAScript 6 WeakMap, у него есть две проблемы: время поиска O (n) и неслабость (т.е. это вызовет утечку памяти, если вы не используете delete или clear для освобождения ключей).
Вы можете использовать ECMAScript 6 WeakMap или Map:
WeakMaps are key/value maps in which keys are objects.
Объекты Map представляют собой простые карты "ключ-значение". Любое значение (как объекты, так и примитивные значения) может использоваться как ключ или значение.
Имейте в виду, что ни один из них не поддерживается широко, но вы можете использовать Прокладка ECMAScript 6 (требуется собственный ECMAScript 5 или Прокладка ECMAScript 5) для поддержки Map, но не WeakMap (понять, почему).
В 2019 они очень хорошо поддерживаются и предлагают отличные методы! developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Еще одна реализация карты мной. С рандомизатором, дженериками и итератором =)
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.info('a['+key+']='+value)});
/*
a[test]=1132
a[test14]=666
a[1421test14]=12312666
*/
a.randomize();
/*
a[1421test14]=12312666
a[test]=1132
a[test14]=666
*/
В чем идея? В чем важное отличие в том, как это работает? Каковы последствия (производительность, лучшая / худшая производительность в худшем случае, масштабирование и т. д.)?
В настоящее время есть несколько действительно отличных решений с внешними библиотеками:
У JavaScript также есть Map, предоставляемый на языке.
Это путь вперед в 21 век. Жаль, что я нашел ваш пост после того, как закончил код с какой-то уродливой самодельной картой. WEEE нужно больше голосов за ваш ответ
В Collections.js есть несколько реализаций, но я не могу найти их в underscore.js или lodash ... что вы имели в виду в подчеркивании, что было бы полезно?
@CodeBling понятия не имею. Я думаю, что меня перепутали с функцией карты. Я уберу его из ответа.
Это честно. Любой, кто рассматривает Collections.js, должен знать, что он модифицирует глобальные прототипы Array, Function, Object и Regexp проблемным образом (см. проблемы, с которыми я столкнулся здесь). Хотя я изначально был очень доволен collections.js (и, следовательно, этим ответом), риски, связанные с его использованием, были слишком высоки, поэтому я отказался от него. Только v2 ветка collection.js kriskowal (в частности, v2.0.2 +) исключает глобальные модификации прототипа и безопасен в использовании.
Согласно 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"
но использует ли он реализацию на основе хешей? очевидно, это важно по соображениям производительности, в тех случаях, когда вы использовали бы хэш-карту на других языках.
@Claudiu: Извините за правку, но «Карта» в названии действительно вводила в заблуждение. Откатывайтесь, если не согласны, опекать не собирался. :)