オブジェクトを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 を参照
提案は次のとおりです。
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
を使用することもできます。プリミティブ値(文字列)をキーとして保存する必要があるため、ニーズに合っている場合があります。 Doc 、 Shim 。
以下に、オブジェクト参照を含むあらゆるタイプのキーで動作する簡単な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エンジンによって実装され、高度に最適化されています。
@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
これは、@ 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に依存しません。
私は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(','))+']';
}
オブジェクトを検索するときは、厳密な等価演算子を使用します:===
var objects = [];
objects.Push(object1);
objects.Push(object2);
objects[0] === object1; // true
objects[1] === object1; // false
実装は、オブジェクトをHashTable
クラスに格納する方法に依存します。
最善の解決策は、可能な場合に 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"
@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];
}
};
@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];
}
};
}
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)
*/
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() ];
}
};
_