JavaScript哈希表等效
正如在这个答案的更新3中所表明的那样:
var hash = {};
hash[X]
实际上并不散列对象X
; 它实际上只是将X
转换为一个字符串(如果它是一个对象,则通过.toString()
或其他各种基本类型的内置转换),然后在“ hash
”中查看该字符串,而不用散列。 对象相等也不被检查 - 如果两个不同的对象具有相同的字符串转换,它们将只是相互覆盖。
鉴于此 - JavaScript中hashasps的有效实现? (例如,对于任何操作, javascript hashmap
的第二个Google结果会产生一个O(n)的实现。其他各种结果忽略了具有等效字符串表示的不同对象相互覆盖的事实。
为什么不手动散列对象,并将结果字符串用作常规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编码或DojoX UUID中。 尽管后面的解决方案可能会生成唯一的密钥,但我个人会尽力避免使用这些密钥,特别是如果我知道是什么让我的对象变得独特。
2014年更新:在2008年回答这个简单的解决方案仍然需要更多的解释。 让我以问答形式澄清这个想法。
您的解决方案没有真正的散列。 它在哪里???
JavaScript是一种高级语言。 它的基本原始对象(Object)包含一个哈希表来保持属性。 为了提高效率,这个哈希表通常使用低级语言编写。 使用带有字符串键的简单对象,我们使用高效实现的哈希表,而我们没有努力。
你怎么知道他们使用散列?
有三种主要的方法来保持一个对象集合可以被一个关键字寻址:
很明显,JavaScript对象以某种形式使用散列表来处理一般情况。
浏览器供应商真的使用散列表?
真。
他们处理碰撞?
是。 往上看。 如果您在不相等的字符串上发现了冲突,请不要犹豫与供应商提交错误。
那么你的想法是什么?
如果你想散列一个对象,找出使它独一无二的东西,并将它用作关键字。 不要尝试计算真正的哈希或模拟哈希表 - 它已经被底层的JavaScript对象有效地处理了。
对JavaScript Object
使用此键可利用其内置散列表,同时避免使用默认属性造成可能的冲突。
让你开始的例子:
我用你的建议并使用用户名缓存所有对象。 但一些聪明人被命名为“toString”,这是一个内置的财产! 我现在应该怎么做?
显然,如果所产生的密钥完全由拉丁字符组成,那么您应该做一些相关的事情。 例如,在开头或结尾添加您喜欢的任何非拉丁Unicode字符以解除与默认属性的冲突:“#toString”,“#MarySmith”。 如果使用组合键,则使用某种非拉丁分隔符分隔关键组件:“名称,城市,州”。
一般来说,这是我们必须具有创造力的地方,并且选择具有给定限制(唯一性,与默认属性的潜在冲突)的最简单的键。
注意:唯一键不会按定义冲突,而潜在的哈希冲突将由底层Object
处理。
你为什么不喜欢工业解决方案?
恕我直言,最好的代码根本就没有代码:它没有错误,不需要维护,易于理解,并且即时执行。 我看到的所有“JavaScript中的哈希表”都是> 100行代码,并涉及多个对象。 将它与: dict[key] = value
。
还有一点:用JavaScript和原始对象来实现已经实现的东西,是否有可能击败用低级语言编写的原始对象的表现?
我仍然想散列我的对象,没有任何键!
我们很幸运:ECMAScript 6(计划在2015年中期发布,在此之后的1-2年内广为流传)定义了地图和集合。
根据定义来判断,他们可以使用对象的地址作为关键字,这使得对象在没有人工键的情况下即刻可见。 OTOH,两个不同但相同的对象将被映射为不同的。
问题描述
JavaScript没有内置的通用映射类型(有时称为关联数组或字典),它允许通过任意键访问任意值。 JavaScript的基本数据结构是对象,一种特殊类型的映射,它只接受字符串作为键,并具有特殊的语义,如原型继承,getter和setter以及一些进一步的巫术。
将对象用作映射时,必须记住该键将通过toString()
转换为字符串值,这会导致将5
和'5'
映射为相同的值,并且所有不覆盖toString()
方法转换为由'[object Object]'
索引的值。 如果你不检查hasOwnProperty()
你也可能会不由自主地访问它的继承属性。
JavaScript的内置数组类型并没有帮助:JavaScript数组不是关联数组,而是具有更多特殊属性的对象。 如果你想知道他们为什么不能用作地图,请看这里。
尤金的解决方案
Eugene Lazutkin已经描述了使用自定义哈希函数生成唯一字符串的基本思想,该字符串可以用来查找关联值作为字典对象的属性。 这很可能是最快的解决方案,因为对象在内部被实现为散列表。
为了获得任意对象的唯一散列值,一种可能性是使用全局计数器并将散列值缓存在对象本身中(例如,在名为__hash
的属性中)。
这样做的散列函数适用于原始值和对象:
function hash(value) {
return (typeof value) + ' ' + (value instanceof Object ?
(value.__hash || (value.__hash = ++arguments.callee.current)) :
value.toString());
}
hash.current = 0;
这个功能可以像Eugene所描述的那样使用。 为了方便起见,我们将进一步将其包装在一个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
,这是一些人(我自己不包括在内)认为是verboten。
编辑:我的Map
实施的当前版本以及其他JavaScript的好东西可以从这里获得。
我知道这个问题很古老,但现在有一些非常棒的解决方案与外部库。
JavaScript也提供其语言提供的Map
。