Gibt es irgendeine Art von Hash-Code-Funktion in JavaScript?

150

Grundsätzlich versuche ich, ein Objekt aus eindeutigen Objekten, eine Menge, zu erstellen. Ich hatte die geniale Idee, nur ein JavaScript-Objekt mit Objekten für die Eigenschaftsnamen zu verwenden. Sowie,

set[obj] = true;

Dies funktioniert bis zu einem gewissen Punkt. Es funktioniert hervorragend mit Zeichenfolgen und Zahlen, aber mit anderen Objekten scheinen alle auf denselben Wert zu "hashen" und auf dieselbe Eigenschaft zuzugreifen. Gibt es eine Möglichkeit, einen eindeutigen Hashwert für ein Objekt zu generieren? Wie machen Strings und Zahlen das? Kann ich das gleiche Verhalten überschreiben?

Boog
quelle
32
Der Grund, warum alle Objekte auf denselben Wert 'hashen', ist, dass Sie ihre toString-Methoden nicht überschrieben haben. Da Schlüssel Zeichenfolgen sein müssen, wird die toString-Methode automatisch aufgerufen, um einen gültigen Schlüssel zu erhalten, sodass alle Ihre Objekte in dieselbe Standardzeichenfolge konvertiert werden: "[Objekt Objekt]".
Alanning
4
JSON.stringify(obj)oder obj.toSource()kann je nach Problem und Zielplattform für Sie arbeiten.
AnnanFay
4
@Annan JSON.stringify (obj) konvertiert buchstäblich nur das (ganze) Objekt in einen String. Sie würden also im Grunde nur das Objekt auf sich selbst kopieren. Dies ist sinnlos, Platzverschwendung und nicht optimal.
Metalstorm
1
@Metalstorm True, weshalb es darauf ankommt, was Ihr Problem ist. Als ich diese Frage über Google fand, rief meine endgültige Lösung toSource () für Objekte auf. Eine andere Methode wäre nur die Verwendung eines herkömmlichen Hash für die Quelle.
AnnanFay
@ Annan, toSourcearbeiten nicht in Chrome übrigens
Pacerier

Antworten:

35

JavaScript-Objekte können nur Zeichenfolgen als Schlüssel verwenden (alles andere wird in eine Zeichenfolge konvertiert).

Alternativ können Sie ein Array verwalten, das die betreffenden Objekte indiziert, und seine Indexzeichenfolge als Referenz auf das Objekt verwenden. Etwas wie das:

var ObjectReference = [];
ObjectReference.push(obj);

set['ObjectReference.' + ObjectReference.indexOf(obj)] = true;

Natürlich ist es ein wenig ausführlich, aber Sie könnten ein paar Methoden schreiben, die damit umgehen und alles wohl oder übel bekommen und einstellen.

Bearbeiten:

Ihre Vermutung ist Tatsache - dies ist ein in JavaScript definiertes Verhalten - speziell eine toString-Konvertierung, die bedeutet, dass Sie Ihre eigene toString-Funktion für das Objekt definieren können, das als Eigenschaftsname verwendet wird. - olliej

Dies bringt einen weiteren interessanten Punkt auf; Sie können eine toString-Methode für die Objekte definieren, die Sie hashen möchten, und diese können ihre Hash-ID bilden.

Augenlidlosigkeit
quelle
Eine andere Möglichkeit wäre, jedem Objekt einen zufälligen Wert als Hash zu geben - möglicherweise eine Zufallszahl + Gesamtzahl der Ticks - und dann eine Reihe von Funktionen zum Hinzufügen / Entfernen des Objekts zum Array zu haben.
Sugendran
4
Dies schlägt fehl, wenn Sie dasselbe Objekt zweimal hinzufügen. Es wird denken, dass es anders ist.
Daniel X Moore
"Dies schlägt fehl, wenn Sie dasselbe Objekt zweimal hinzufügen. Es wird denken, dass es anders ist." Guter Punkt. Eine Lösung könnte darin bestehen, Array für ObjectReference in eine Unterklasse zu unterteilen und eine doppelte Prüfung in push () zu verknüpfen. Ich habe jetzt keine Zeit, diese Lösung zu bearbeiten, aber ich hoffe, ich werde mich später daran erinnern.
Augenlidlosigkeit
8
Ich mag diese Lösung, weil sie keine zusätzlichen Eigenschaften im Objekt benötigt. Aber es wird problematisch, wenn Sie versuchen, einen sauberen Müllsammler zu haben. In Ihrem Ansatz wird das Objekt gespeichert, obwohl die anderen Referenzen bereits gelöscht wurden. Dies kann bei größeren Anwendungen zu Problemen führen.
Johnny
35
Was bringt es, die Objekte zu hashen, wenn Sie jedes Mal, wenn Sie auf sie verweisen, einen linearen Scan eines Arrays benötigen?
Bordaigorl
56

Wenn Sie eine hashCode () -Funktion wie die von Java in JavaScript möchten, gehört diese Ihnen:

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

Dies ist die Art der Implementierung in Java (bitweiser Operator).

Bitte beachten Sie, dass hashCode positiv und negativ sein kann, und das ist normal. Siehe HashCode mit negativen Werten . Sie können also in Betracht ziehen, Math.abs()diese Funktion zusammen mit dieser Funktion zu verwenden.

KimKha
quelle
5
Dies schafft - Hash, nicht perfekt
Qodeninja
2
@KimKha charist ein reserviertes Wort in JS und kann einige Probleme verursachen. Ein anderer Name wäre besser.
Szeryf
16
@qodeninja sagt wer? Es ist das erste Mal, dass ich eine solche Behauptung höre. Können Sie auf eine Quelle verlinken? Hashes werden normalerweise mit ganzzahligen Arithmetik- und Bitoperationen fester Größe berechnet. Daher ist zu erwarten, dass positive oder negative Ergebnisse erzielt werden.
Szeryf
7
Pingelig, aber ... "if (this.length == 0) gibt Hash zurück;" ist überflüssig :) Und würde persönlich "Zeichen" in "Code" ändern.
Metalstorm
10
@qodeninja und @szeryf: Sie müssen nur vorsichtig sein, wie Sie es verwenden. Zum Beispiel habe ich versucht, pickOne["helloo".hashCode() % 20]für ein Array pickOnemit 20 Elementen zu tun . Ich habe bekommen, undefinedweil der Hash-Code negativ ist, also ist dies ein Beispiel, in dem jemand (ich) implizit positive Hash-Codes angenommen hat.
Jim Pivarski
31

Der einfachste Weg, dies zu tun, besteht darin, jedem Ihrer Objekte eine eigene toStringMethode zuzuweisen:

(function() {
    var id = 0;

    /*global MyObject */
    MyObject = function() {
        this.objectId = '<#MyObject:' + (id++) + '>';
        this.toString= function() {
            return this.objectId;
        };
    };
})();

Ich hatte das gleiche Problem und dies löste es perfekt für mich mit minimalem Aufwand und war viel einfacher als das erneute Implementieren eines fetten Java-Stils Hashtableund das Hinzufügen equals()und Hinzufügen hashCode()zu Ihren Objektklassen. Stellen Sie einfach sicher, dass Sie nicht auch eine Zeichenfolge '<#MyObject: 12> in Ihren Hash einfügen, da sonst der Eintrag für Ihr vorhandenes Objekt mit dieser ID gelöscht wird.

Jetzt sind alle meine Hashes total kalt. Ich habe vor ein paar Tagen auch einen Blogeintrag zu diesem Thema gepostet .

Daniel X Moore
quelle
28
Aber das geht am eigentlichen Punkt vorbei. Java hat equals()und hashCode()so haben zwei äquivalente Objekte den gleichen Hashwert. Die Verwendung der obigen Methode bedeutet, dass jede Instanz von MyObjecteine eindeutige Zeichenfolge hat. Dies bedeutet, dass Sie einen Verweis auf dieses Objekt behalten müssen, um jemals den richtigen Wert aus der Karte abzurufen. Einen Schlüssel zu haben ist bedeutungslos, weil er nichts mit der Einzigartigkeit eines Objekts zu tun hat. toString()Für den bestimmten Objekttyp, den Sie als Schlüssel verwenden, muss eine nützliche Funktion implementiert werden.
Sethro
@sethro Sie können das toStringfür die Objekte so implementieren, dass es direkt einer Äquivalenzbeziehung zugeordnet wird, sodass zwei Objekte dieselbe Zeichenfolge erstellen, wenn sie als "gleich" betrachtet werden.
Daniel X Moore
3
Richtig, und das ist die einzig richtige Methode toString(), um eine Objectals zu verwenden Set. Ich glaube, ich habe Ihre Antwort als Versuch missverstanden, eine generische Lösung bereitzustellen, um zu vermeiden, dass ein toString()Äquivalent von equals()oder hashCode()von Fall zu Fall geschrieben wird.
Sethro
3
Dowvoted. Dies ist nicht das, was ein Hashcode ist, siehe meine Antworten auf: stackoverflow.com/a/14953738/524126 Und eine echte Implementierung eines Hashcodes: stackoverflow.com/a/15868654/524126
Metalstorm
5
@Metalstorm Die Frage war nicht nach einem "wahren" Hashcode, sondern nach der erfolgreichen Verwendung eines Objekts als Satz in JavaScript.
Daniel X Moore
20

Was Sie beschrieben haben, wird von Harmony WeakMaps abgedeckt , einem Teil der ECMAScript 6- Spezifikation (nächste Version von JavaScript). Das heißt: eine Menge, in der die Schlüssel alles sein können (einschließlich undefinierter) und nicht aufzählbar sind.

Dies bedeutet, dass es unmöglich ist, einen Verweis auf einen Wert zu erhalten, es sei denn, Sie haben einen direkten Verweis auf den Schlüssel (ein beliebiges Objekt!), Der darauf verweist. Es ist wichtig für eine Reihe von Gründen für die Engine-Implementierung in Bezug auf Effizienz und Speicherbereinigung, aber es ist auch super cool, da es neue Semantiken wie widerrufbare Zugriffsberechtigungen und das Übergeben von Daten ermöglicht, ohne den Datensender offenzulegen.

Von 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 sind in den aktuellen Versionen Firefox, Chrome und Edge verfügbar. Sie werden auch in Node v7 und in v6 mit dem --harmony-weak-mapsFlag unterstützt.

Slezica
quelle
1
Was ist der Unterschied zwischen diesen und Map?
smac89
@ smac89 WeakMap hat Einschränkungen: 1) Nimmt nur Objekte als Schlüssel 2) Keine Größeneigenschaft 3) Kein Iterator oder für jede Methode 4) Keine klare Methode. Schlüssel ist Objekt - wenn das Objekt aus dem Speicher gelöscht wird, werden auch Daten aus WeakMap, die mit diesem Objekt verbunden sind, gelöscht. Es ist sehr nützlich, wenn wir die Informationen behalten möchten, die nur existieren sollten, solange das Objekt existiert. WeakMap hat also nur Methoden: setzen, löschen zum Schreiben und abrufen, hat zum Lesen
Ekaterina Tokareva
Dies funktioniert nicht genau richtig ... var m = new Map();m.set({},"abc"); console.log(m.get({}) //=>undefinedEs funktioniert nur, wenn Sie dieselbe Variable haben, auf die Sie ursprünglich im Befehl set verwiesen haben. EGvar m = new Map();a={};m.set(a,"abc"); console.log(m.get(a) //=>undefined
Sancarn
1
@Sancarn Es muss nicht dieselbe Variable sein, aber sie müssen auf dasselbe Objekt zeigen. In Ihrem ersten Beispiel haben Sie zwei verschiedene Objekte, die gleich aussehen, aber eine andere Adresse haben.
Svish
1
@Svish guter Ort! Obwohl ich es jetzt weiß, hätte ich es damals vielleicht nicht getan :)
Sancarn
19

Die von mir gewählte Lösung ähnelt der von Daniel, aber anstatt eine Objektfactory zu verwenden und den toString zu überschreiben, füge ich dem Objekt den Hash explizit hinzu, wenn er zum ersten Mal über eine getHashCode-Funktion angefordert wird. Ein bisschen chaotisch, aber besser für meine Bedürfnisse :)

Function.prototype.getHashCode = (function(id) {
    return function() {
        if (!this.hashCode) {
            this.hashCode = '<hash|#' + (id++) + '>';
        }
        return this.hashCode;
    }
}(0));
theGecko
quelle
7
Wenn Sie diesen Weg gehen möchten, ist es viel besser, den HashCode über Object.definePropertymit enumerableset auf zu setzen false, damit Sie keine for .. inSchleifen zum Absturz bringen .
Sebastian Nowak
14

Für meine spezifische Situation ist mir die Gleichheit des Objekts nur in Bezug auf Schlüssel und primitive Werte wichtig. Die Lösung, die für mich funktioniert hat, bestand darin, das Objekt in seine JSON-Darstellung zu konvertieren und dieses als Hash zu verwenden. Es gibt Einschränkungen wie die Reihenfolge der Schlüsseldefinition, die möglicherweise inkonsistent ist. aber wie gesagt, es hat bei mir funktioniert, weil diese Objekte alle an einem Ort erzeugt wurden.

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
// }
ijmacd
quelle
10

Ich habe vor einiger Zeit ein kleines JavaScript-Modul zusammengestellt, um Hashcodes für Strings, Objekte, Arrays usw. zu erstellen (ich habe es gerade an GitHub übergeben :))

Verwendung:

Hashcode.value("stackoverflow")
// -2559914341
Hashcode.value({ 'site' : "stackoverflow" })
// -3579752159
Metallsturm
quelle
Erstickt der GC von Javascript nicht auch an Zirkelverweisen?
Clayton Rabenda
@ Ryan Long Ich könnte sogar sagen, wenn Sie Zirkelverweise haben, müssen Sie Ihren Code umgestalten;)
Metalstorm
11
@Metalstorm "dann musst du deinen Code umgestalten" Machst du Witze? Jedes Eltern-Kind-Paar des DOM-Elements bildet einen Zirkelverweis.
Chris Middleton
8
Es macht einen schlechten Job beim Hashing von Objekten mit numerischen Eigenschaften, die in vielen Fällen den gleichen Wert zurückgeben, dh var hash1 = Hashcode.value({ a: 1, b: 2 }); var hash2 = Hashcode.value({ a: 2, b: 1 }); console.log(hash1, hash2);protokollieren2867874173 2867874173
Julien Bérubé
9

In der JavaScript-Spezifikation wird der Zugriff auf indizierte Eigenschaften so definiert, dass eine toString-Konvertierung für den Indexnamen durchgeführt wird. Beispielsweise,

myObject[myProperty] = ...;

ist das gleiche wie

myObject[myProperty.toString()] = ...;

Dies ist wie in JavaScript erforderlich

myObject["someProperty"]

ist das gleiche wie

myObject.someProperty

Und ja, es macht mich auch traurig :-(

olliej
quelle
9

In ECMAScript 6 gibt es jetzt eine Set, die so funktioniert, wie Sie es möchten: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set

Es ist bereits in den neuesten Versionen von Chrome, FF und IE11 verfügbar.

Daniel X Moore
quelle
1
Dies sollte die beste Antwort im Jahr 2016 sein. Wenn Sie Babel verwenden, können Sie Set gemäß der ES6-Spezifikation verwenden, und es wird automatisch in der ES5-Ausgabe polygefüllt. babeljs.io/docs/learn-es2015/#map-set-weak-map-weak-set
atroberts20
5

Referenz: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol

Mit dem Es6-Symbol können Sie einen eindeutigen Schlüssel erstellen und auf ein Objekt zugreifen. Jeder von Symbol () zurückgegebene Symbolwert ist eindeutig. Ein Symbolwert kann als Bezeichner für Objekteigenschaften verwendet werden. Dies ist der einzige Zweck des Datentyps.

var obj = {};

obj[Symbol('a')] = 'a';
obj[Symbol.for('b')] = 'b';
obj['c'] = 'c';
obj.d = 'd';
Khalid Azam
quelle
2
Außer dass es nicht wirklich eine Möglichkeit gibt, das Symbol neu zu generieren. Let x = Symbol ('a'); sei y = Symbol ('a'); console.log (x === y); // gibt false zurück Das Symbol funktioniert also nicht wie ein Hash.
Richard Collette
3

Hier ist meine einfache Lösung, die eine eindeutige Ganzzahl zurückgibt.

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;
}
Timothy Perez
quelle
2
Die Komplexität untergräbt die ganze Idee hinter hashCode ()
tuxSlayer
Ich finde das nicht unnötig komplex. Ich war allerdings neugierig: Warum die Ersetzungsphase? Die Ausschlüsse wären sonst auf charCodeAt gut angekommen, nicht wahr?
Greg Pettit
Schade wegen hashcode({a:1, b:2}) === hashcode({a:2, b:1})und vieler anderer Konflikte.
Maaartinus
3

Basierend auf dem Titel können wir mit js starke Hashes generieren. Es kann verwendet werden, um einen eindeutigen Hash aus einem Objekt, einem Array von Parametern, einer Zeichenfolge oder was auch immer zu generieren.

Vermeiden Sie später für die Indizierung mögliche Übereinstimmungsfehler, während Sie gleichzeitig einen Index aus den Parametern abrufen können (vermeiden Sie das Suchen / Schleifen des Objekts usw.):

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] ))

Die obige Ausgabe in meinem Browser sollte auch für Sie gleich sein ( Ist es wirklich? ):

bf1cf3fe6975fe382ab392ec1dd42009380614be03d489f23601c11413cfca2b
93a23971a914e5eacbf0a8d25154cda309c3c1c72fbb9914d47c60f3cb681588
d2f209e194045604a3b15bdfd7502898a0e848e4603c5a818bd01da69c00ad19

https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest#Converting_a_digest_to_a_hex_string

NVRM
quelle
1

Meine Lösung führt eine statische Funktion für das globale ObjectObjekt ein.

(function() {
    var lastStorageId = 0;

    this.Object.hash = function(object) {
        var hash = object.__id;

        if (!hash)
             hash = object.__id = lastStorageId++;

        return '#' + hash;
    };
}());

Ich denke, dass dies mit anderen Objektmanipulationsfunktionen in JavaScript bequemer ist.

Johnny
quelle
1
Objekte mit denselben internen Werten werden in verschiedene Hashes gehasht. Dies ist nicht das, was ein Hash (Code) tut.
Metalstorm
In JavaScript (und ich denke auch in fast jeder anderen Sprache) sind zwei Objekte, die mit denselben internen Werten erstellt wurden, immer noch unterschiedliche Objekte, da der zugrunde liegende Datentyp jeweils durch eine neue Objektinstanz dargestellt wird. jsfiddle.net/h4G9f
Johnny
4
Ja, aber dafür ist ein Hashcode nicht gedacht. Hashcodes werden verwendet, um die Gleichheit eines Objektstatus zu überprüfen. Genau wie bei einem Hash gehen dieselben Eingaben ein (variable Werte), und es wird derselbe Hash ausgegeben. Was Sie suchen, ist eine UUID (die Ihre Funktion bietet).
Metalstorm
1
Du hast recht. Ich verstehe die Frage falsch. Wirklich schade, dass die akzeptierte Antwort auch keine gute Lösung bietet.
Johnny
Wenn ich auch Ihre Funktion übernehme, würde ich eher dazu neigen: jsfiddle.net/xVSsd Gleiches Ergebnis, kürzer (LoC + Zeichen) und wahrscheinlich ein kleines bisschen schneller :)
Metalstorm
1

Ich werde versuchen, etwas tiefer zu gehen als andere Antworten.

Selbst wenn JS eine bessere Hashing-Unterstützung hätte, würde es nicht alles auf magische Weise perfekt hashen. In vielen Fällen müssen Sie Ihre eigene Hash-Funktion definieren. Zum Beispiel hat Java eine gute Hashing-Unterstützung, aber Sie müssen noch nachdenken und etwas arbeiten.

Ein Problem ist mit dem Begriff Hash / Hashcode ... es gibt kryptografisches Hashing und nicht kryptografisches Hashing. Das andere Problem ist, dass Sie verstehen müssen, warum Hashing nützlich ist und wie es funktioniert.

Wenn wir die meiste Zeit über Hashing in JavaScript oder Java sprechen, sprechen wir über nicht kryptografisches Hashing, normalerweise über Hashing für Hashmap / Hashtable (es sei denn, wir arbeiten an Authentifizierung oder Passwörtern, die Sie möglicherweise serverseitig mit NodeJS durchführen. ..).

Es hängt davon ab, über welche Daten Sie verfügen und was Sie erreichen möchten.

Ihre Daten haben eine natürliche "einfache" Einzigartigkeit:

  • Der Hash einer ganzen Zahl ist ... die ganze Zahl, da sie einzigartig ist, Glück gehabt!
  • Der Hash einer Zeichenfolge ... hängt von der Zeichenfolge ab. Wenn die Zeichenfolge eine eindeutige Kennung darstellt, können Sie sie als Hash betrachten (daher ist kein Hashing erforderlich).
  • Alles, was indirekt so ziemlich eine eindeutige Ganzzahl ist, ist der einfachste Fall
  • Dies wird Folgendes berücksichtigen: Hashcode gleich, wenn Objekte gleich sind

Ihre Daten haben eine natürliche "zusammengesetzte" Einzigartigkeit:

  • Beispielsweise können Sie mit einem Personenobjekt einen Hash anhand von Vorname, Nachname, Geburtsdatum usw. berechnen. Sehen Sie, wie Java dies tut: Gute Hash-Funktion für Zeichenfolgen , oder verwenden Sie andere ID-Informationen, die für Ihren Anwendungsfall billig und eindeutig genug sind

Sie haben keine Ahnung, wie Ihre Daten aussehen werden:

  • Viel Glück ... Sie könnten in einen String serialisieren und ihn im Java-Stil hashen, aber das kann teuer sein, wenn der String groß ist und Kollisionen nicht vermieden werden sowie der Hash einer ganzen Zahl (Selbst).

Es gibt keine magisch effiziente Hashing-Technik für unbekannte Daten. In einigen Fällen ist dies recht einfach, in anderen Fällen müssen Sie möglicherweise zweimal überlegen. Selbst wenn JavaScript / ECMAScript mehr Unterstützung bietet, gibt es keine magische Sprachlösung für dieses Problem.

In der Praxis braucht man zwei Dinge: genug Einzigartigkeit, genug Geschwindigkeit

Darüber hinaus ist es toll zu haben: "Hashcode gleich, wenn Objekte gleich sind"

Christophe Roussy
quelle
0

Wenn Sie wirklich ein festgelegtes Verhalten wünschen (ich gehe von Java-Kenntnissen aus), wird es Ihnen schwer fallen, eine Lösung in JavaScript zu finden. Die meisten Entwickler empfehlen einen eindeutigen Schlüssel, um jedes Objekt darzustellen. Dies ist jedoch anders als bei set, da Sie zwei identische Objekte mit jeweils einem eindeutigen Schlüssel erhalten können. Die Java-API sucht nach doppelten Werten, indem sie Hash-Code-Werte und keine Schlüssel vergleicht. Da Objekte in JavaScript keine Hash-Code-Wertdarstellung enthalten, ist es fast unmöglich, dasselbe zu tun. Sogar die Prototype JS-Bibliothek gibt dieses Manko zu, wenn es heißt:

"Hash kann als assoziatives Array betrachtet werden, das eindeutige Schlüssel an Werte bindet (die nicht unbedingt eindeutig sind) ..."

http://www.prototypejs.org/api/hash


quelle
0

Zusätzlich zur Antwort der Augenlidlosigkeit gibt es hier eine Funktion, die eine reproduzierbare, eindeutige ID für jedes Objekt zurückgibt:

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);
}

Wie Sie sehen können, wird eine Liste für die Suche verwendet, die sehr ineffizient ist. Dies ist jedoch die beste, die ich derzeit finden konnte.

cburgmer
quelle
0

Wenn Sie Objekte als Schlüssel verwenden möchten, müssen Sie deren toString-Methode überschreiben, wie einige hier bereits erwähnt haben. Die verwendeten Hash-Funktionen sind alle in Ordnung, funktionieren jedoch nur für dieselben Objekte, nicht für gleiche Objekte.

Ich habe eine kleine Bibliothek geschrieben, die Hashes aus Objekten erstellt, die Sie leicht für diesen Zweck verwenden können. Die Objekte können sogar eine andere Reihenfolge haben, die Hashes sind gleich. Intern können Sie verschiedene Typen für Ihren Hash verwenden (djb2, md5, sha1, sha256, sha512, ripemd160).

Hier ist ein kleines Beispiel aus der Dokumentation:

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[{}]);

Das Paket kann entweder im Browser und in Node-Js verwendet werden.

Repository: https://bitbucket.org/tehrengruber/es-js-hash

Darthmatch
quelle
0

Wenn Sie eindeutige Werte in einem Suchobjekt haben möchten, können Sie Folgendes tun:

Erstellen eines Suchobjekts

var lookup = {};

Einrichten der Hashcode-Funktion

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

Objekt

var key = getHashCode({ 1: 3, 3: 7 });
// key = '1337'
lookup[key] = true;

Array

var key = getHashCode([1, 3, 3, 7]);
// key = '01132337'
lookup[key] = true;

Andere Arten

var key = getHashCode('StackOverflow');
// key = 'StackOverflow'
lookup[key] = true;

Endergebnis

{ 1337: true, 01132337: true, StackOverflow: true }

Beachten Sie, dass getHashCodekein Wert zurückgegeben wird, wenn das Objekt oder Array leer ist

getHashCode([{},{},{}]);
// '012'
getHashCode([[],[],[]]);
// '012'

Dies ähnelt der @ ijmacd-Lösung, hat jedoch nur getHashCodekeine JSONAbhängigkeit.

A1rPun
quelle
Sie müssen ein Problem mit
Zirkelverweisen
@tuxSlayer Danke, dass du mich informiert hast. Sie könnten diesen Code leicht mit Ihren Bedürfnissen erweitern, aber ich hoffe, die Idee ist etwas klar :)
A1rPun
Dies wird sehr lange Schlüssel für große Objekte erzeugen, die den Speicher und die Leistung ziemlich stark
Gershom
0

Ich kombinierte die Antworten von Augenlidlosigkeit und KimKha.

Das Folgende ist ein AngularJS-Dienst, der Zahlen, Zeichenfolgen und Objekte unterstützt.

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

Anwendungsbeispiel:

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')

Ausgabe

432700947 true
-411117486 true
1725787021 true
-1585332251 true
1 true
-1881759168 true

Erläuterung

Wie Sie sehen können, ist das Herzstück des Dienstes die von KimKha erstellte Hash-Funktion. Ich habe den Zeichenfolgen Typen hinzugefügt, damit sich die Struktur des Objekts auch auf den endgültigen Hash-Wert auswirkt. Die Schlüssel werden gehasht, um Kollisionen zwischen Array und Objekt zu verhindern.

Der Objektvergleich ohne Augenlid wird verwendet, um eine unendliche Rekursion durch selbstreferenzierende Objekte zu verhindern.

Verwendung

Ich habe diesen Dienst erstellt, damit ich einen Fehlerdienst haben kann, auf den mit Objekten zugegriffen wird. Damit ein Dienst einen Fehler mit einem bestimmten Objekt registrieren kann und ein anderer feststellen kann, ob Fehler gefunden wurden.

dh

JsonValidation.js

ErrorSvc({id: 1, json: '{attr: "not-valid"}'}, 'Invalid Json Syntax - key not double quoted');

UserOfData.js

ErrorSvc({id: 1, json: '{attr: "not-valid"}'});

Dies würde zurückkehren:

['Invalid Json Syntax - key not double quoted']

Während

ErrorSvc({id: 1, json: '{"attr": "not-valid"}'});

Dies würde zurückkehren

[]
jozsef morrissey
quelle
0

Verwenden Sie einfach versteckte geheime Eigenschaft mit dem defineProperty enumerable: false

Es funktioniert sehr schnell :

  • Die erste gelesene uniqueId: 1.257.500 ops / s
  • Alle anderen: 309.226.485 Operationen / s
var nextObjectId = 1
function getNextObjectId() {
    return nextObjectId++
}

var UNIQUE_ID_PROPERTY_NAME = '458d576952bc489ab45e98ac7f296fd9'
function getObjectUniqueId(object) {
    if (object == null) {
        return null
    }

    var id = object[UNIQUE_ID_PROPERTY_NAME]

    if (id != null) {
        return id
    }

    if (Object.isFrozen(object)) {
        return null
    }

    var uniqueId = getNextObjectId()
    Object.defineProperty(object, UNIQUE_ID_PROPERTY_NAME, {
        enumerable: false,
        configurable: false,
        writable: false,
        value: uniqueId,
    })

    return uniqueId
}
Nikolay Makhonin
quelle