45

我想创建一个带有Object未转换为字符串的键的哈希表。

像这样的一些事情:

var object1 = new Object();
var object2 = new Object();

var myHash = new HashTable();

myHash.put(object1, "value1");
myHash.put(object2, "value2");

alert(myHash.get(object1), myHash.get(object2)); // I wish that it will print value1 value2

编辑:查看的完整解决方案的答案

4

14 回答 14

22

这是一个简单的Map实现,可以使用任何类型的键,包括对象引用,并且不会以任何方式改变键:

function Map() {
    var keys = [], values = [];

    return {
        put: function (key, value) {
            var index = keys.indexOf(key);
            if(index == -1) {
                keys.push(key);
                values.push(value);
            }
            else {
                values[index] = value;
            }
        },
        get: function (key) {
            return values[keys.indexOf(key)];
        }
    };
}

虽然这产生了与哈希表相同的功能,但它实际上并未使用哈希函数实现,因为它遍历数组并且具有 O(n) 的最坏情况性能。但是,对于绝大多数合理的用例来说,这根本不应该是一个问题。该indexOf功能由 JavaScript 引擎实现,并经过高度优化。

于 2013-12-11T09:55:20.747 回答
20

这是一个建议:

function HashTable() {
    this.hashes = {};
}

HashTable.prototype = {
    constructor: HashTable,

    put: function( key, value ) {
        this.hashes[ JSON.stringify( key ) ] = value;
    },

    get: function( key ) {
        return this.hashes[ JSON.stringify( key ) ];
    }
};

API 与您的问题中显示的完全一样。

但是,您不能在 js 中使用引用(因此两个空对象在哈希表中看起来是一样的),因为您无法获取它。有关更多详细信息,请参阅此答案:如何获取 javascript 对象引用或引用计数?

Jsfiddle 演示:http: //jsfiddle.net/HKz3e/

但是,对于事物的独特之处,您可以使用原始对象,如下所示:

function HashTable() {
    this.hashes = {},
    this.id = 0;
}

HashTable.prototype = {
    constructor: HashTable,

    put: function( obj, value ) {
        obj.id = this.id;
        this.hashes[ this.id ] = value;
        this.id++;
    },

    get: function( obj ) {
        return this.hashes[ obj.id ];
    }
};

Jsfiddle 演示:http: //jsfiddle.net/HKz3e/2/

这意味着您的对象需要有一个id您不会在其他地方使用的名为的属性。如果您想将此属性设置为不可枚举,我建议您看一下defineProperty(但是它不是跨浏览器,即使使用 ES5-Shim,它在 IE7 中也不起作用)。

这也意味着您可以在此哈希表中存储的项目数量受到限制。限于2 53,即。

而现在,“它不会在任何地方工作”的解决方案:使用 ES6 WeakMaps。它们正是为此目的而完成的:将对象作为键。我建议您阅读 MDN 了解更多信息:https ://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/WeakMap

不过,它与您的 API 略有不同(它是set而不是put):

var myMap = new WeakMap(),
    object1 = {},
    object2 = {};

myMap.set( object1, 'value1' );
myMap.set( object2, 'value2' );

console.log( myMap.get( object1 ) ); // "value1"
console.log( myMap.get( object2 ) ); // "value2"

带有弱映射垫片的 Jsfiddle 演示:http: //jsfiddle.net/Ralt/HKz3e/9/

但是,弱映射是在 FF 和 Chrome 中实现的(当您在 chrome 中启用“Experimental javascript features”标志时)。有可用的垫片,例如:https ://gist.github.com/1269991 。使用风险自负。

您也可以使用Maps,它们可能更适合您的需求,因为您还需要将原始值(字符串)存储为键。医生希姆

于 2012-06-05T07:39:35.983 回答
11

我将@Florian Margaine 的建议提升到了更高的水平,并提出了这个建议:

function HashTable(){
    var hash = new Object();
    this.put = function(key, value){
        if(typeof key === "string"){
            hash[key] = value;
        }
        else{
            if(key._hashtableUniqueId == undefined){
                key._hashtableUniqueId = UniqueId.prototype.generateId();
            }
            hash[key._hashtableUniqueId] = value;
        }

    };

    this.get = function(key){
        if(typeof key === "string"){
            return hash[key];
        }
        if(key._hashtableUniqueId == undefined){
            return undefined;
        }
        return hash[key._hashtableUniqueId];
    };
}

function UniqueId(){

}

UniqueId.prototype._id = 0;
UniqueId.prototype.generateId = function(){
    return (++UniqueId.prototype._id).toString();
};

用法

var map = new HashTable();
var object1 = new Object();
map.put(object1, "Cocakola");
alert(map.get(object1)); // Cocakola

//Overriding
map.put(object1, "Cocakola 2");
alert(map.get(object1)); // Cocakola 2

// String key is used as String     
map.put("myKey", "MyValue");
alert(map.get("myKey")); // MyValue
alert(map.get("my".concat("Key"))); // MyValue

// Invalid keys 
alert(map.get("unknownKey")); // undefined
alert(map.get(new Object())); // undefined
于 2012-06-06T05:59:52.483 回答
2

这是一个提议,将@Florian 的解决方案与@Laurent 的解决方案结合起来。

function HashTable() {
    this.hashes = [];
}

HashTable.prototype = {
    constructor: HashTable,

    put: function( key, value ) {
        this.hashes.push({
            key: key,
            value: value
        });
    },

    get: function( key ) {
        for( var i = 0; i < this.hashes.length; i++ ){
            if(this.hashes[i].key == key){
                return this.hashes[i].value;
            }
        }
    }
};

它不会以任何方式更改您的对象,也不依赖 JSON.stringify。

于 2013-11-11T09:58:27.833 回答
1

我知道我迟到了一年,但对于所有其他偶然发现这个线程的人,我已经将有序对象 stringify 编写为 JSON,这解决了上述困境:http ://stamat.wordpress.com/javascript-object -ordered-property-stringify/

我也在玩与主题相关的自定义哈希表实现:http: //stamat.wordpress.com/javascript-quickly-find-very-large-objects-in-a-large-array/

//SORT WITH STRINGIFICATION

var orderedStringify = function(o, fn) {
    var props = [];
    var res = '{';
    for(var i in o) {
        props.push(i);
    }
    props = props.sort(fn);

    for(var i = 0; i < props.length; i++) {
        var val = o[props[i]];
        var type = types[whatis(val)];
        if(type === 3) {
            val = orderedStringify(val, fn);
        } else if(type === 2) {
            val = arrayStringify(val, fn);
        } else if(type === 1) {
            val = '"'+val+'"';
        }

        if(type !== 4)
            res += '"'+props[i]+'":'+ val+',';
    }

    return res.substring(res, res.lastIndexOf(','))+'}';
};

//orderedStringify for array containing objects
var arrayStringify = function(a, fn) {
    var res = '[';
    for(var i = 0; i < a.length; i++) {
        var val = a[i];
        var type = types[whatis(val)];
        if(type === 3) {
            val = orderedStringify(val, fn);
        } else if(type === 2) {
            val = arrayStringify(val);
        } else if(type === 1) {
            val = '"'+val+'"';
        }

        if(type !== 4)
            res += ''+ val+',';
    }

    return res.substring(res, res.lastIndexOf(','))+']';
}
于 2013-07-03T22:58:35.593 回答
1

基于 Peters 的回答,但具有适当的类设计(不滥用闭包),因此这些值是可调试的。重命名为Mapto ObjectMap,因为Map是一个内置函数。还添加了exists方法:

ObjectMap = function() {
    this.keys = [];
    this.values = [];
}

ObjectMap.prototype.set = function(key, value) {
    var index = this.keys.indexOf(key);
    if (index == -1) {
        this.keys.push(key);
        this.values.push(value);
    } else {
        this.values[index] = value;
    }
}

ObjectMap.prototype.get = function(key) {
    return this.values[ this.keys.indexOf(key) ];
}

ObjectMap.prototype.exists = function(key) {
    return this.keys.indexOf(key) != -1;
}

/*
    TestObject = function() {}

    testA = new TestObject()
    testB = new TestObject()

    om = new ObjectMap()
    om.set(testA, true)
    om.get(testB)
    om.exists(testB)
    om.exists(testA)
    om.exists(testB)
*/
于 2018-01-12T18:16:45.400 回答
0

使用JSON.stringify()对我来说完全是尴尬的,并且让客户无法真正控制他们的密钥是如何唯一标识的。用作键的对象应该有一个散列函数,但我的猜测是,在大多数情况下重写该toString()方法,以便它们返回唯一的字符串,会正常工作:

var myMap = {};

var myKey = { toString: function(){ return '12345' }};
var myValue = 6;

// same as myMap['12345']
myMap[myKey] = myValue;

显然,toString()应该对对象的属性做一些有意义的事情来创建一个唯一的字符串。如果您想强制您的密钥有效,您可以创建一个包装器并在get()andput()方法中添加一个检查,例如:

if(!key.hasOwnProperty('toString')){
   throw(new Error('keys must override toString()'));
}

但是,如果您要完成那么多工作,则不妨使用toString();以外的其他东西。让你的意图更清晰的东西。所以一个非常简单的建议是:

function HashTable() {
    this.hashes = {};
}

HashTable.prototype = {
    constructor: HashTable,

    put: function( key, value ) {
        // check that the key is meaningful, 
        // also will cause an error if primitive type
        if( !key.hasOwnProperty( 'hashString' ) ){
           throw( new Error( 'keys must implement hashString()' ) );
        }
        // use .hashString() because it makes the intent of the code clear
        this.hashes[ key.hashString() ] = value;
    },

    get: function( key ) {
        // check that the key is meaningful, 
        // also will cause an error if primitive type
        if( !key.hasOwnProperty( 'hashString' ) ){
           throw( new Error( 'keys must implement hashString()' ) );
        }
        // use .hashString() because it make the intent of the code clear
        return this.hashes[ key.hashString()  ];
    }
};
于 2014-01-17T19:26:54.560 回答
0

受@florian 启发,这里有一种不需要 id 的方法JSON.stringify

'use strict';

module.exports = HashTable;

function HashTable () {
  this.index = [];
  this.table = [];
}

HashTable.prototype = {

  constructor: HashTable,

  set: function (id, key, value) {
    var index = this.index.indexOf(id);
    if (index === -1) {
      index = this.index.length;
      this.index.push(id);
      this.table[index] = {};
    }
    this.table[index][key] = value;
  },

  get: function (id, key) {
    var index = this.index.indexOf(id);
    if (index === -1) {
      return undefined;
    }
    return this.table[index][key];
  }

};
于 2014-12-30T15:38:30.167 回答
0

我采用@Ilya_Gazman 解决方案并通过将'_hashtableUniqueId' 设置为不可枚举的属性来改进它(它不会出现在JSON 请求中,也不会在for 循环中列出)。还删除了 UniqueId 对象,因为仅使用 HastTable 函数闭包就足够了。有关使用详情,请参阅 Ilya_Gazman 帖子

function HashTable() {
   var hash = new Object();

   return {
       put: function (key, value) {
           if(!HashTable.uid){
               HashTable.uid = 0;
           }
           if (typeof key === "string") {
               hash[key] = value;
           } else {
               if (key._hashtableUniqueId === undefined) {
                   Object.defineProperty(key, '_hashtableUniqueId', {
                       enumerable: false,
                       value: HashTable.uid++
                   });
               }
               hash[key._hashtableUniqueId] = value;
           }
       },
       get: function (key) {
           if (typeof key === "string") {
               return hash[key];
           }
           if (key._hashtableUniqueId === undefined) {
               return undefined;
           }
           return hash[key._hashtableUniqueId];
       }
   };
}
于 2016-08-31T18:20:01.153 回答
0

最好的解决方案是尽可能使用Wea​​kMap(即当您的目标浏览器支持它时)

否则,您可以使用以下解决方法(Typescript 编写且碰撞安全):

// Run this in the beginning of your app (or put it into a file you just import)
(enableObjectID)();

const uniqueId: symbol = Symbol('The unique id of an object');

function enableObjectID(): void {
    if (typeof Object['id'] !== 'undefined') {
        return;
    }

    let id: number = 0;

    Object['id'] = (object: any) => {
        const hasUniqueId: boolean = !!object[uniqueId];
        if (!hasUniqueId) {
            object[uniqueId] = ++id;
        }

        return object[uniqueId];
    };
}

然后,您可以简单地为代码中的任何对象获取一个唯一编号(就像指针地址一样)

let objectA = {};
let objectB = {};
let dico = {};

dico[(<any>Object).id(objectA)] = "value1";

// or 

dico[Object['id'](objectA);] = "value1";

// If you are not using typescript you don't need the casting

dico[Object.id(objectA)] = "value1"
于 2017-11-23T13:22:47.930 回答
0

我知道我迟到了,但这里有一个简单的HashMap实现:

Function.prototype.toJSON = Function.prototype.toString;
//taken from https://stackoverflow.com/questions/1249531/how-to-get-a-javascript-objects-class
function getNativeClass(obj) {
    if (typeof obj === "undefined") return "undefined";
    if (obj === null) return "null";
    return Object.prototype.toString.call(obj).match(/^\[object\s(.*)\]$/)[1];
}
function globals() {
    if (typeof global === "object") //node
        return global;
    return this;
}

function lookup(x) {
    return globals()[x];
}

function getAnyClass(obj) {
    if (typeof obj === "undefined") return "undefined";
    if (obj === null) return "null";
    return obj.constructor.name;
}

//taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value#examples
var getCircularReplacer = () => {
    const seen = new WeakSet();
    return (key, value) => {
        if (typeof value === "object" && value !== null) {
            if (seen.has(value)) {
                return "[Circular]";
            }
            seen.add(value);
        }
        return value;
    };
};



function encode(x) {
    if (typeof x === "object" && x !== null) {
        var y = myClone(x);
        x = Object.getPrototypeOf(x);
        for (var i = 0; i < Object.getOwnPropertyNames(y).length; i++) { //Make enumerable
            x[Object.getOwnPropertyNames(y)[i]] = y[Object.getOwnPropertyNames(y)[i]];
        }
    }

    return getAnyClass(x) + " " + JSON.stringify(x, getCircularReplacer());
}

function decode(x) {
    var a = x.split(" ").slice(1).join(" "); //OBJECT
    if (typeof lookup(x.split(" ")[0])) {
        return new (lookup(x.split(" ")[0]))(JSON.parse(a))
    } else {
        return JSON.parse(a);
    }
}


//taken from https://github.com/feross/fromentries/blob/master/index.js
/*! fromentries. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
function fromEntries(iterable) {
    return [...iterable].reduce((obj, [key, val]) => {
        obj[key] = val
        return obj
    }, {})
}


var toEnumerable = (obj) => {
    return fromEntries(
        Object.getOwnPropertyNames(obj).map(prop => [prop, obj[prop]])
    );
};


//taken from https://stackoverflow.com/questions/41474986/how-to-clone-a-javascript-es6-class-instance
function myClone(instanceOfBlah) {
    if (typeof instanceOfBlah !== "object" || !instanceOfBlah) { return instanceOfBlah; }
    const clone = Object.assign({}, toEnumerable(instanceOfBlah));
    const Blah = instanceOfBlah.constructor;
    Object.setPrototypeOf(clone, Blah.prototype);
    return clone;
}

function HashMap(a) {
    if (typeof a === "undefined") {
        a = [];
    }

    a = Array.from(a);

    a = a.map((e) => [encode(e[0]), e[1]]);

    this.a = a;
}


HashMap.from = function (a) {
    var temp = myClone(a);
    //convert to array
    a = [];
    for (var i = 0; i < Object.getOwnPropertyNames(temp).length; i++) {
        a.push([Object.getOwnPropertyNames(temp)[i], temp[Object.getOwnPropertyNames(temp)[i]]]);
    }
    return new HashMap(a);
}

HashMap.prototype.put = function (x, y) {
    this.a.push([encode(x), y]);
}

HashMap.prototype.get = function (x) {
    var t1 = this.a.map((e) => e[0]);
    return this.a[t1.indexOf(encode(x))][1];
}

HashMap.prototype.length = function () {
    return this.a.length;
}

HashMap.prototype.toString = function () {
    var result = [];
    for (var i = 0; i < this.length(); i++) {
        result.push(JSON.stringify(decode(this.a[i][0]), getCircularReplacer()) + " => " + this.a[i][1]);
    }


    return "HashMap {" + result + "}";
}



var foo = new HashMap();
foo.put("SQRT3", Math.sqrt(3));
foo.put({}, "bar");

console.log(foo.get({}));
console.log(foo.toString());

请注意,它是有序的。方法:

  • put:添加一个项目
  • get: 访问一个项目
  • from(静态):从 JavaScript 对象转换
  • toString: 转换为字符串

缩小且未经测试:

function getNativeClass(t){return void 0===t?"undefined":null===t?"null":Object.prototype.toString.call(t).match(/^\[object\s(.*)\]$/)[1]}function globals(){return"object"==typeof global?global:this}function lookup(t){return globals()[t]}function getAnyClass(t){return void 0===t?"undefined":null===t?"null":t.constructor.name}Function.prototype.toJSON=Function.prototype.toString;var getCircularReplacer=()=>{const t=new WeakSet;return(e,r)=>{if("object"==typeof r&&null!==r){if(t.has(r))return"[Circular]";t.add(r)}return r}};function encode(t){if("object"==typeof t&&null!==t){var e=myClone(t);t=Object.getPrototypeOf(t);for(var r=0;r<Object.getOwnPropertyNames(e).length;r++)t[Object.getOwnPropertyNames(e)[r]]=e[Object.getOwnPropertyNames(e)[r]]}return getAnyClass(t)+" "+JSON.stringify(t,getCircularReplacer())}function decode(t){var e=t.split(" ").slice(1).join(" ");return lookup(t.split(" ")[0]),new(lookup(t.split(" ")[0]))(JSON.parse(e))}function fromEntries(t){return[...t].reduce((t,[e,r])=>(t[e]=r,t),{})}var toEnumerable=t=>fromEntries(Object.getOwnPropertyNames(t).map(e=>[e,t[e]]));function myClone(t){if("object"!=typeof t||!t)return t;const e=Object.assign({},toEnumerable(t)),r=t.constructor;return Object.setPrototypeOf(e,r.prototype),e}function HashMap(t){void 0===t&&(t=[]),t=(t=Array.from(t)).map(t=>[encode(t[0]),t[1]]),this.a=t}HashMap.from=function(t){var e=myClone(t);t=[];for(var r=0;r<Object.getOwnPropertyNames(e).length;r++)t.push([Object.getOwnPropertyNames(e)[r],e[Object.getOwnPropertyNames(e)[r]]]);return new HashMap(t)},HashMap.prototype.put=function(t,e){this.a.push([encode(t),e])},HashMap.prototype.get=function(t){var e=this.a.map(t=>t[0]);return this.a[e.indexOf(encode(t))][1]},HashMap.prototype.length=function(){return this.a.length},HashMap.prototype.toString=function(){for(var t=[],e=0;e<this.length();e++)t.push(JSON.stringify(decode(this.a[e][0]),getCircularReplacer())+" => "+this.a[e][1]);return"HashMap {"+t+"}"};

此外,您可以通过更改encodedecode功能自定义编码器和解码器。

正如弗洛里安的回答一样,您不能使用 js 中的引用(因此两个空对象看起来与哈希表相同)。

于 2021-02-19T04:26:41.427 回答
0
class Dict{
    constructor(){
        this.keys = [];
        this.values = [];
        this.set = this.set.bind(this);
    }

    set(key, value){
        this.keys.push(key);
        this.values.push(value);
    }

    get(key){
        return this.values[this.keys.indexOf(key)];
    }

    all(){
        return this.keys.map((kk, ii)=>[kk, this.values[ii]]);
    }
}

let d1 = new Dict();

let k1 = {1: 'a'};
d1.set(k1, 2);
console.log(d1.get(k1));  // 2
let k2 = {2: 'b'};
d1.set(k2, 3);


console.log(d1.all());
// [ [ { '1': 'a' }, 2 ], [ { '2': 'b' }, 3 ] ]
于 2021-06-17T04:09:54.417 回答
0

当您说您不希望将您的对象键转换为字符串时,我会假设这是因为您只是不希望将对象的整个代码内容用作键。当然,这完全有道理。

虽然 Javascript 本身没有“哈希表”,但您可以通过简单地覆盖 Object 的 prototype.toString 并返回每个实例唯一的有效键值来完成您要查找的内容。一种方法是使用Symbol()

function Obj () {
    this.symbol = Symbol() // Guaranteed to be unique to each instance
}

Obj.prototype.toString = function () {
    return this.symbol // Return the unique Symbol, instead of Obj's stringified code
}

let a = new Obj()
let b = new Obj()

let table = {}

table[a] = 'A'
table[b] = 'B'

console.log(table)      // {Symbol(): 'A', Symbol(): 'B'}
console.log(table[a])   // A
console.log(table[b])   // B
于 2022-01-22T20:48:24.550 回答
-1

查找对象时只需使用严格相等运算符:===

var objects = [];
objects.push(object1);
objects.push(object2);

objects[0] === object1; // true
objects[1] === object1; // false

实现将取决于您如何在HashTable类中存储对象。

于 2012-06-05T06:01:56.823 回答