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)包含一个哈希表来保持属性。 为了提高效率,这个哈希表通常使用低级语言编写。 使用带有字符串键的简单对象,我们使用高效实现的哈希表,而我们没有努力。

你怎么知道他们使用散列?

有三种主要的方法来保持一个对象集合可以被一个关键字寻址:

  • 无序。 在这种情况下,通过它的关键字来检索一个对象,当我们找到它时,我们必须检查所有的关键字。 平均而言,它将需要n / 2次比较。
  • 有序。
  • 示例#1:一个有序数组 - 进行二分搜索,我们将在平均〜log2(n)比较之后找到我们的密钥。 好多了。
  • 示例#2:一棵树。 这又会是〜log(n)次尝试。
  • 哈希表。 平均而言,它需要一个恒定的时间。 比较:O(n)与O(log n)对O(1)。 繁荣。
  • 很明显,JavaScript对象以某种形式使用散列表来处理一般情况。

    浏览器供应商真的使用散列表?

    真。

  • Chrome / node.js / V8:JSObject。 使用objects.cc和objects-inl.h中的相关细节查找NameDictionary和NameDictionaryShape。
  • Firefox / Gecko:jsobj.cpp和vm / NativeObject.cpp中的相关详细信息:JSObject,NativeObject和PlainObject。
  • 他们处理碰撞?

    是。 往上看。 如果您在不相等的字符串上发现了冲突,请不要犹豫与供应商提交错误。

    那么你的想法是什么?

    如果你想散列一个对象,找出使它独一无二的东西,并将它用作关键字。 不要尝试计算真正的哈希或模拟哈希表 - 它已经被底层的JavaScript对象有效地处理了。

    对JavaScript Object使用此键可利用其内置散列表,同时避免使用默认属性造成可能的冲突。

    让你开始的例子:

  • 如果您的对象包含唯一的用户名 - 请将其用作密钥。
  • 如果它包含一个唯一的客户号码 - 将其用作关键字。
  • 如果它包含政府颁发的独特号码,如SSN或护照号码,而且您的系统不允许重复,则将其用作关键字。
  • 如果字段的组合是唯一的 - 将其用作关键字。
  • 国家缩写+驾驶执照号码是一个很好的关键。
  • 国家简称+护照号码也是一个很好的关键。
  • 字段或整个对象上的某些函数可以返回一个唯一值 - 将其用作关键字。
  • 我用你的建议并使用用户名缓存所有对象。 但一些聪明人被命名为“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已经描述了使用自定义哈希函数生成唯一字符串的基本思想,该字符串可以用来查找关联值作为字典对象的属性。 这很可能是最快的解决方案,因为对象在内部被实现为散列表。

  • 注意:散列表(有时称为散列映射)是映射概念的特定实现,它使用后备数组和通过数字散列值查找。 运行时环境可能使用其他结构(如搜索树或跳过列表)来实现JavaScript对象,但由于对象是基本的数据结构,因此应该对其进行充分优化。
  • 为了获得任意对象的唯一散列值,一种可能性是使用全局计数器并将散列值缓存在对象本身中(例如,在名为__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的好东西可以从这里获得。


    我知道这个问题很古老,但现在有一些非常棒的解决方案与外部库。

  • collections.js
  • 一成不变
  • JavaScript也提供其语言提供的Map

  • 地图
  • 链接地址: http://www.djcxy.com/p/91907.html

    上一篇: JavaScript Hashmap Equivalent

    下一篇: Why does C++ not have reflection?