web-dev-qa-db-ja.com

Javascript HashTableはオブジェクトキーを使用します

オブジェクトをStringに変換せずにキーとして取得するハッシュテーブルを作成します。

このようなもの:

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

EDIT:完全なソリューションについては my answer を参照

37
Ilya Gazman

提案は次のとおりです。

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で参照を使用することはできません(したがって、2つの空のオブジェクトはハッシュテーブルと同じように見えます)。取得する方法がないためです。詳細については、この回答を参照してください。 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で動作します)。

また、このハッシュテーブルに保存できるアイテムの数に制限があることも意味します。に限定 253 、つまり。

そして今、「どこでも動作しません」ソリューション: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"

Weakmap shimを使用したJsfiddleデモ: http://jsfiddle.net/Ralt/HKz3e/9/

ただし、weakmapsはFFで実装され、Chrome(only=で「実験的なJavaScript機能」フラグを有効にした場合chromeしかし)。次のようなシムが利用可能です: https://Gist.github.com/1269991 。ご自身の責任で使用してください。

Mapsを使用することもできます。プリミティブ値(文字列)をキーとして保存する必要があるため、ニーズに合っている場合があります。 DocShim

18

以下に、オブジェクト参照を含むあらゆるタイプのキーで動作する簡単な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エンジンによって実装され、高度に最適化されています。

22
Peter

@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
11
Ilya Gazman

これは、@ 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に依存しません。

2
ipodppod

私は1年遅れていることを知っていますが、このスレッドにつまずく他のすべての人のために、順序付けられたオブジェクトを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(','))+']';
}
1
stamat

オブジェクトを検索するときは、厳密な等価演算子を使用します:===

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

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

実装は、オブジェクトをHashTableクラスに格納する方法に依存します。

0
laurent

最善の解決策は、可能な場合に WeakMap を使用することです(つまり、それをサポートするブラウザーをターゲットにする場合)

それ以外の場合は、次の回避策を使用できます(書かれた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"
0
Flavien Volken

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

};
0
webjay

@Ilya_Gazmanソリューションを取り、列挙できないプロパティとして '_hashtableUniqueId'を設定することで改善しました(JSON要求には表示されず、forループにもリストされません)。 HastTable関数のクロージャのみを使用すれば十分であるため、UniqueIdオブジェクトも削除されました。使用方法の詳細については、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];
       }
   };
}
0
edrian

Petersの回答に基づきますが、適切なクラス設計(クロージャを乱用しない)であるため、値はデバッグ可能です。 Mapは組み込み関数であるため、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)
*/
0
lama12345

JSON.stringify()の使用は完全に厄介であり、クライアントがキーを一意に識別する方法を実際に制御することはできません。キーとして使用されるオブジェクトにはハッシュ関数が必要ですが、ほとんどの場合、toString()メソッドをオーバーライドして、一意の文字列を返すようにするのがうまくいくと思います。

_var myMap = {};

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

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

明らかに、toString()はオブジェクトのプロパティを使って意味のあることを行い、一意の文字列を作成する必要があります。キーが有効であることを強制する場合は、ラッパーを作成し、get()メソッドとput()メソッドで次のようなチェックを追加できます。

_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()  ];
    }
};
_
0
sethro