基本的に、私はユニークなオブジェクトのオブジェクト、セットを作成しようとしています。プロパティ名のオブジェクトとともにJavaScriptオブジェクトを使用するという素晴らしいアイデアがありました。といった、
set[obj] = true;
これはある程度まで機能します。文字列と数字でうまく機能しますが、他のオブジェクトでは、すべて同じ値に「ハッシュ」され、同じプロパティにアクセスするようです。オブジェクトに一意のハッシュ値を生成する方法はありますか?文字列と数字はどのようにそれを行うのですか、同じ動作をオーバーライドできますか?
JavaScriptオブジェクトは、文字列のみをキーとして使用できます(他のものはすべて文字列に変換されます)。
あるいは、問題のオブジェクトにインデックスを付ける配列を維持し、そのインデックス文字列をオブジェクトへの参照として使用することもできます。このようなもの:
var ObjectReference = [];
ObjectReference.Push(obj);
set['ObjectReference.' + ObjectReference.indexOf(obj)] = true;
明らかにそれは少し冗長ですが、それを処理し、すべてのウィリーニリーを取得および設定するメソッドをいくつか書くことができます。
編集:
あなたの推測は事実です-これはJavaScriptで定義された動作です-具体的にはtoString変換が発生するため、プロパティ名として使用されるオブジェクトに独自のtoString関数を定義できます。 -オリエージ
これは別の興味深い点をもたらします。ハッシュするオブジェクトでtoStringメソッドを定義でき、ハッシュ識別子を形成できます。
JavaScriptのJavaのようなhashCode()関数が必要な場合は、次のようにします。
String.prototype.hashCode = function(){
var hash = 0;
for (var i = 0; i < this.length; i++) {
var character = this.charCodeAt(i);
hash = ((hash<<5)-hash)+character;
hash = hash & hash; // Convert to 32bit integer
}
return hash;
}
これがJava(ビット演算子)の実装方法です。
これを行う最も簡単な方法は、各オブジェクトに独自のtoString
メソッドを与えることです。
(function() {
var id = 0;
/*global MyObject */
MyObject = function() {
this.objectId = '<#MyObject:' + (id++) + '>';
this.toString= function() {
return this.objectId;
};
};
})();
私は同じ問題を抱えていたため、最小限の手間で完璧に解決しました。また、いくつかの脂肪質のJavaスタイルHashtable
を再実装し、オブジェクトクラスにequals()
とhashCode()
を追加する方がはるかに簡単でした。文字列「<#MyObject:12>」もハッシュに貼り付けないでください。そうしないと、既存のオブジェクトのエントリがそのIDで消去されます。
今、私のハッシュはすべて完全に寒くなっています。また、数日前に この正確なトピック に関するブログエントリを投稿しました。
説明した内容は、Harmony WeakMaps 、 ECMAScript 6 仕様(JavaScriptの次のバージョン)の一部でカバーされています。つまり、キーは任意のもの(未定義を含む)であり、列挙不可能なセットです。
これは、値にリンクするキー(オブジェクト!)への直接参照がない限り、値への参照を取得できないことを意味します。効率とガベージコレクションに関連する多くのエンジン実装の理由にとって重要ですが、取消し可能なアクセス許可やデータ送信者を公開せずにデータを渡すなどの新しいセマンティクスを可能にするという点でも非常にクールです。
MDN から:
var wm1 = new WeakMap(),
wm2 = new WeakMap();
var o1 = {},
o2 = function(){},
o3 = window;
wm1.set(o1, 37);
wm1.set(o2, "azerty");
wm2.set(o1, o2); // A value can be anything, including an object or a function.
wm2.set(o3, undefined);
wm2.set(wm1, wm2); // Keys and values can be any objects. Even WeakMaps!
wm1.get(o2); // "azerty"
wm2.get(o2); // Undefined, because there is no value for o2 on wm2.
wm2.get(o3); // Undefined, because that is the set value.
wm1.has(o2); // True
wm2.has(o2); // False
wm2.has(o3); // True (even if the value itself is 'undefined').
wm1.has(o1); // True
wm1.delete(o1);
wm1.has(o1); // False
WeakMapsは、現在のFirefox、ChromeおよびEdgeで使用できます。 Node v7およびv6では--harmony-weak-maps
フラグを使用してサポートされています。
私が選択したソリューションはダニエルのものと似ていますが、オブジェクトファクトリを使用してtoStringをオーバーライドするのではなく、getHashCode関数を通じて最初に要求されたときに明示的にハッシュをオブジェクトに追加します。少し面倒ですが、私のニーズには適しています:)
Function.prototype.getHashCode = (function(id) {
return function() {
if (!this.hashCode) {
this.hashCode = '<hash|#' + (id++) + '>';
}
return this.hashCode;
}
}(0));
私の特定の状況では、キーとプリミティブ値に関する限り、オブジェクトの等価性のみが重要です。私のために働いた解決策は、オブジェクトをJSON表現に変換し、それをハッシュとして使用することでした。キー定義の順序に一貫性がない可能性があるなどの制限があります。しかし、これらのオブジェクトはすべて1か所で生成されていたので、私が言ったようにうまくいきました。
var hashtable = {};
var myObject = {a:0,b:1,c:2};
var hash = JSON.stringify(myObject);
// '{"a":0,"b":1,"c":2}'
hashtable[hash] = myObject;
// {
// '{"a":0,"b":1,"c":2}': myObject
// }
JavaScript仕様では、インデックス付きプロパティへのアクセスを、インデックス名に対してtoString変換を実行することとして定義しています。例えば、
myObject[myProperty] = ...;
と同じです
myObject[myProperty.toString()] = ...;
これはJavaScriptのように必要です
myObject["someProperty"]
と同じです
myObject.someProperty
そしてはい、それは私も悲しくなります:-(
小さなJavaScriptモジュール 少し前に、文字列、オブジェクト、配列などのハッシュコードを作成しました(ちょうど GitHub :)にコミットしました)
使用法:
Hashcode.value("stackoverflow")
// -2559914341
Hashcode.value({ 'site' : "stackoverflow" })
// -3579752159
ECMAScript 6には、希望どおりに機能するSet
があります。 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set =
最新のChrome、FF、およびIE11で既に利用可能です。
リファレンス: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol
es6シンボルを使用して、一意のキーを作成し、オブジェクトにアクセスできます。 Symbol()から返されるすべてのシンボル値は一意です。シンボル値は、オブジェクトプロパティの識別子として使用できます。これがデータ型の唯一の目的です。
var obj = {};
obj[Symbol('a')] = 'a';
obj[Symbol.for('b')] = 'b';
obj['c'] = 'c';
obj.d = 'd';
一意の整数を返す簡単なソリューションを次に示します。
function hashcode(obj) {
var hc = 0;
var chars = JSON.stringify(obj).replace(/\{|\"|\}|\:|,/g, '');
var len = chars.length;
for (var i = 0; i < len; i++) {
// Bump 7 to larger prime number to increase uniqueness
hc += (chars.charCodeAt(i) * 7);
}
return hc;
}
私のソリューションでは、グローバルObject
オブジェクトに静的関数を導入しています。
(function() {
var lastStorageId = 0;
this.Object.hash = function(object) {
var hash = object.__id;
if (!hash)
hash = object.__id = lastStorageId++;
return '#' + hash;
};
}());
これは、JavaScriptの他のオブジェクト操作関数の方が便利だと思います。
まぶたの答えに加えて、オブジェクトの再現可能な一意のIDを返す関数を次に示します。
var uniqueIdList = [];
function getConstantUniqueIdFor(element) {
// HACK, using a list results in O(n), but how do we hash e.g. a DOM node?
if (uniqueIdList.indexOf(element) < 0) {
uniqueIdList.Push(element);
}
return uniqueIdList.indexOf(element);
}
ご覧のとおり、非常に非効率的な検索用のリストを使用していますが、今のところ見つけることができる最高のものです。
ルックアップオブジェクトに一意の値を設定する場合は、次のようなことができます。
ルックアップオブジェクトの作成
var lookup = {};
ハッシュコード関数を設定する
function getHashCode(obj) {
var hashCode = '';
if (typeof obj !== 'object')
return hashCode + obj;
for (var prop in obj) // No hasOwnProperty needed
hashCode += prop + getHashCode(obj[prop]); // Add key + value to the result string
return hashCode;
}
オブジェクト
var key = getHashCode({ 1: 3, 3: 7 });
// key = '1337'
lookup[key] = true;
配列
var key = getHashCode([1, 3, 3, 7]);
// key = '01132337'
lookup[key] = true;
その他のタイプ
var key = getHashCode('StackOverflow');
// key = 'StackOverflow'
lookup[key] = true;
最終結果
{ 1337: true, 01132337: true, StackOverflow: true }
オブジェクトまたは配列が空の場合、getHashCode
は値を返さないことに注意してください
getHashCode([{},{},{}]);
// '012'
getHashCode([[],[],[]]);
// '012'
これは@ijmacdソリューションに似ていますが、getHashCode
にはJSON
依存関係がありません。
私は他の答えよりも少し深くしようとします。
JSのハッシュサポートが優れていたとしても、魔法のようにすべてを完全にハッシュするわけではありません。多くの場合、独自のハッシュ関数を定義する必要があります。たとえば、Javaは優れたハッシュサポートを備えていますが、まだ考えて作業を行う必要があります。
1つの問題は、ハッシュ/ハッシュコードという用語に関するものです...暗号化ハッシュと非暗号化ハッシュがあります。もう1つの問題は、ハッシュがなぜ有用で、どのように機能するかを理解する必要があるということです。
JavaScriptまたはJavaでのハッシュについて話すとき、ほとんどの場合、非暗号化ハッシュについて話します。通常は、ハッシュマップ/ハッシュテーブルのハッシュについて話します(認証またはパスワードに取り組んでいない限り、 NodeJSを使用したサーバー側...)。
それはあなたが持っているデータと達成したいものに依存します。
データには自然な「単純な」一意性があります。
データには、自然な「複合」一意性があります。
あなたのデータがどうなるかわかりません:
未知のデータに対する魔法のように効率的なハッシュ手法はありません。場合によっては非常に簡単であり、場合によっては考え直す必要があります。そのため、JavaScript/ECMAScriptがさらにサポートを追加したとしても、この問題に対する魔法の言語ソリューションはありません。
実際には、2つのことが必要です。十分な一意性と十分な速度
それに加えて、「オブジェクトが等しい場合はハッシュコードが等しい」ことは素晴らしいことです。
タイトルに基づいて、jsを使用して強力なハッシュを生成できます。これを使用して、オブジェクト、paramsの配列、文字列などから一意のハッシュを生成できます。
後でインデックスを作成するために、パラメータからインデックスを取得できるようにする一方で、一致する可能性のあるエラーを回避します(オブジェクトの検索/ループなどを回避します)。
async function H(m) {
const msgUint8 = new TextEncoder().encode(m)
const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8)
const hashArray = Array.from(new Uint8Array(hashBuffer))
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
console.log(hashHex)
}
/* Examples ----------------------- */
H("An obscure ....")
H(JSON.stringify( {"hello" : "world"} ))
H(JSON.stringify( [54,51,54,47] ))
振る舞いを設定したい場合(私はJavaの知識で行っています)、JavaScriptで解決策を見つけるのは難しいでしょう。ほとんどの開発者は各オブジェクトを表す一意のキーを推奨しますが、これはセットとは異なり、一意のキーを持つ2つの同一のオブジェクトを取得できます。 Java APIは、キーではなくハッシュコード値を比較することで重複値をチェックします。JavaScriptにはオブジェクトのハッシュコード値表現がないため、同じことを行うことはほとんど不可能になります。 Prototype JSライブラリーでさえ、この欠点を認めています。
「ハッシュは、一意のキーを値(必ずしも一意ではない)にバインドする連想配列と考えることができます...」
まぶたとキムカの答えを組み合わせました。
以下は、angularjsサービスであり、数値、文字列、およびオブジェクトをサポートしています。
exports.Hash = () => {
let hashFunc;
function stringHash(string, noType) {
let hashString = string;
if (!noType) {
hashString = `string${string}`;
}
var hash = 0;
for (var i = 0; i < hashString.length; i++) {
var character = hashString.charCodeAt(i);
hash = ((hash<<5)-hash)+character;
hash = hash & hash; // Convert to 32bit integer
}
return hash;
}
function objectHash(obj, exclude) {
if (exclude.indexOf(obj) > -1) {
return undefined;
}
let hash = '';
const keys = Object.keys(obj).sort();
for (let index = 0; index < keys.length; index += 1) {
const key = keys[index];
const keyHash = hashFunc(key);
const attrHash = hashFunc(obj[key], exclude);
exclude.Push(obj[key]);
hash += stringHash(`object${keyHash}${attrHash}`, true);
}
return stringHash(hash, true);
}
function Hash(unkType, exclude) {
let ex = exclude;
if (ex === undefined) {
ex = [];
}
if (!isNaN(unkType) && typeof unkType !== 'string') {
return unkType;
}
switch (typeof unkType) {
case 'object':
return objectHash(unkType, ex);
default:
return stringHash(String(unkType));
}
}
hashFunc = Hash;
return Hash;
};
使用例:
Hash('hello world'), Hash('hello world') == Hash('hello world')
Hash({hello: 'hello world'}), Hash({hello: 'hello world'}) == Hash({hello: 'hello world'})
Hash({hello: 'hello world', goodbye: 'adios amigos'}), Hash({hello: 'hello world', goodbye: 'adios amigos'}) == Hash({goodbye: 'adios amigos', hello: 'hello world'})
Hash(['hello world']), Hash(['hello world']) == Hash(['hello world'])
Hash(1), Hash(1) == Hash(1)
Hash('1'), Hash('1') == Hash('1')
出力
432700947 true
-411117486 true
1725787021 true
-1585332251 true
1 true
-1881759168 true
説明
ご覧のように、サービスの中心はKimKhaによって作成されたハッシュ関数です。オブジェクトの構造が最終ハッシュ値にも影響するように、文字列に型を追加しました。キーは配列|オブジェクトの衝突を防ぐためにハッシュされます。
まぶたのないオブジェクトの比較は、自己参照オブジェクトによる無限再帰を防ぐために使用されます。
使用法
オブジェクトを使用してアクセスするエラーサービスを使用できるように、このサービスを作成しました。そのため、あるサービスが特定のオブジェクトにエラーを登録し、別のサービスがエラーが見つかったかどうかを判断できます。
すなわち
JsonValidation.js
ErrorSvc({id: 1, json: '{attr: "not-valid"}'}, 'Invalid Json Syntax - key not double quoted');
serOfData.js
ErrorSvc({id: 1, json: '{attr: "not-valid"}'});
これは戻ります:
['Invalid Json Syntax - key not double quoted']
ながら
ErrorSvc({id: 1, json: '{"attr": "not-valid"}'});
これは戻ります
[]
オブジェクトをキーとして使用する場合は、ここで既に説明したように、toStringメソッドを上書きする必要があります。使用されたハッシュ関数はすべて問題ありませんが、同じオブジェクトに対してのみ機能し、同等のオブジェクトに対しては機能しません。
オブジェクトからハッシュを作成する小さなライブラリを作成しました。このライブラリは、この目的で簡単に使用できます。オブジェクトは異なる順序を持つことさえでき、ハッシュは同じになります。内部的には、ハッシュにさまざまなタイプを使用できます(djb2、md5、sha1、sha256、sha512、ripemd160)。
ドキュメントの小さな例を次に示します。
var hash = require('es-hash');
// Save data in an object with an object as a key
Object.prototype.toString = function () {
return '[object Object #'+hash(this)+']';
}
var foo = {};
foo[{bar: 'foo'}] = 'foo';
/*
* Output:
* foo
* undefined
*/
console.log(foo[{bar: 'foo'}]);
console.log(foo[{}]);
パッケージは、ブラウザとNode-Jのどちらでも使用できます。