Nehmen wir an, wir haben die folgende Python-Klasse (das Problem existiert in Java genauso mit equals
und hashCode
)
class Temperature:
def __init__(self, degrees):
self.degrees = degrees
Wo degrees
ist die Temperatur in Kelvin als Schwimmer. Nun würde Ich mag Gleichheit Prüfung und Hashing implementieren Temperature
in einer Weise , dass
- vergleicht Floats mit einer Epsilon-Differenz anstatt direkter Gleichheitsprüfung,
- und hält den Vertrag ein, der
a == b
implizierthash(a) == hash(b)
.
def __eq__(self, other):
return abs(self.degrees - other.degrees) < EPSILON
def __hash__(self):
return # What goes here?
In der Python-Dokumentation wird ein wenig über das Hashing von Zahlen gesprochen, um sicherzustellen, dass hash(2) == hash(2.0)
dies nicht ganz dasselbe Problem ist.
Bin ich überhaupt auf dem richtigen Weg? Und wenn ja, wie wird Hashing in dieser Situation standardmäßig implementiert?
Update : Jetzt verstehe ich, dass diese Art der Gleichheitsprüfung für Schwimmer die Transitivität von ==
und beseitigt equals
. Aber wie passt das zu dem "Allgemeinwissen", dass Schwimmkörper nicht direkt miteinander verglichen werden sollten? Wenn Sie einen Gleichheitsoperator durch Vergleichen von Floats implementieren, beklagen sich statische Analysetools. Haben sie Recht dazu?
quelle
kelvin
?Antworten:
Die Fuzzy-Gleichheit verletzt die Anforderungen, die Java an die
equals
Methode stellt, nämlich die Transitivität , dh wennx == y
undy == z
dannx == z
. Aber wenn Sie eine Fuzzy-Gleichheit mit zum Beispiel einem Epsilon von 0,1 machen, dann gilt0.1 == 0.2
und0.2 == 0.3
, aber0.1 == 0.3
nicht.Obwohl Python eine solche Anforderung nicht dokumentiert, machen die Auswirkungen einer nicht-transitiven Gleichheit diese Idee dennoch sehr schlecht. Über solche Typen nachzudenken ist kopfschmerzerregend.
Deshalb empfehle ich Ihnen dringend, das nicht zu tun.
Stellen Sie entweder eine exakte Gleichheit bereit und stützen Sie Ihren Hash auf die offensichtliche Weise darauf, und stellen Sie eine separate Methode für das Fuzzy-Matching bereit, oder wählen Sie den von Kain vorgeschlagenen Ansatz für die Äquivalenzklasse. In letzterem Fall empfehle ich, dass Sie Ihren Wert auf ein repräsentatives Mitglied der Äquivalenzklasse im Konstruktor festlegen und dann mit einfacher exakter Gleichheit und Hashing fortfahren. Es ist viel einfacher, auf diese Weise über die Typen nachzudenken.
(Aber wenn Sie das tun, können Sie auch eine Festkomma-Darstellung anstelle von Gleitkomma verwenden, dh Sie verwenden eine Ganzzahl, um Tausendstel Grad zu zählen, oder welche Genauigkeit Sie auch immer benötigen.)
quelle
==
die==
Typen, die sie enthalten, "infizieren" sollte. Das heißt, wenn sie Ihrem Rat zur Bereitstellung einer exakten Gleichheit folgen, sollte ihr statisches Analysetool weiter konfiguriert werden, um zu warnen, wenn Gleichheit verwendet wirdTemperature
. Das ist das Einzige, was Sie wirklich tun können.float approximation
Feld haben, an dem nicht teilgenommen wird==
. Außerdem gibt das statische Analysetool bereits innerhalb der==
Implementierung von Klassen eine Warnung aus, wenn eines der verglichenen Elemente einfloat
Typ ist.float
Feld hat, an dem sie nicht teilnimmt==
, konfigurieren Sie Ihr Tool nicht so, dass eine Warnung==
für diese Klasse ausgegeben wird. Wenn dies der Fall ist, führt das Markieren der Klasse==
als "zu genau" vermutlich dazu, dass das Tool diese Art von Fehler in der Implementierung ignoriert. ZB in Java ist if@Deprecated void foo()
dannvoid bar() { foo(); }
eine Warnung, ist es aber@Deprecated void bar() { foo(); }
nicht. Möglicherweise unterstützen viele Tools dies nicht, einige jedoch.Viel Glück
Sie werden das nicht erreichen können, ohne dumm mit Hashes zu sein oder das Epsilon zu opfern.
Beispiel:
Angenommen, jeder Punkt hat seinen eigenen eindeutigen Hashwert.
Da Gleitkommazahlen sequentiell sind, gibt es bis zu k Zahlen vor einem gegebenen Gleitkommawert und bis zu k Zahlen nach einem gegebenen Gleitkommawert, die innerhalb eines Epsilons des gegebenen Punktes liegen.
Für jeweils zwei Punkte innerhalb von epsilon, die nicht denselben Hashwert haben.
Es gibt einige Fälle, in denen dies nicht zutrifft:
Bei> = 99% des Gleitkommabereichs wird jedoch für jeden Wert von epsilon, der mindestens einen Gleitkommawert über oder unter einem bestimmten Gleitkommawert enthält, ein Hash auf einen einzelnen Wert gesetzt.
Ergebnis
Entweder> = 99% des gesamten Fließkomma-Bereichs wird auf einen einzelnen Wert gehasht, der die Absicht eines Hash-Werts ernsthaft beeinträchtigt (und jedes Gerät / jeder Container, der auf einem fair verteilten Hash mit niedriger Kollision beruht).
Oder das Epsilon ist so, dass nur exakte Übereinstimmungen zulässig sind.
Körnig
Sie können sich stattdessen natürlich für einen granularen Ansatz entscheiden.
Unter diesem Ansatz definieren Sie exakte Buckets bis zu einer bestimmten Auflösung. dh:
Jeder Bucket hat einen eindeutigen Hash, und jeder Gleitkommawert im Bucket gleicht einem anderen Gleitkommawert im selben Bucket.
Leider ist es immer noch möglich, dass zwei Floats einen Abstand von mehreren Kilometern haben und über zwei separate Hashes verfügen.
quelle
Sie können Ihre Temperatur als Ganzzahl unter der Haube modellieren. Die Temperatur hat eine natürliche Untergrenze (-273,15 Grad Celsius). Also, double (-273.15 ist gleich 0 für Ihre zugrunde liegende Ganzzahl). Das zweite Element, das Sie benötigen, ist die Granularität Ihrer Zuordnung. Sie verwenden diese Granularität bereits implizit. es ist dein EPSILON.
Teilen Sie einfach Ihre Temperatur durch EPSILON und nehmen Sie das Wort. Jetzt werden sich Ihr Hash und Ihr Equal synchron verhalten. In Python 3 ist die Ganzzahl nicht begrenzt, EPSILON kann kleiner sein, wenn Sie möchten.
ACHTUNG Wenn Sie den Wert von EPSILON ändern und das Objekt serialisiert haben, sind sie nicht kompatibel!
quelle
Das Implementieren einer Fließkomma-Hash-Tabelle, die Dinge finden kann, die einem bestimmten Schlüssel "ungefähr gleich" sind, erfordert die Verwendung einiger Ansätze oder einer Kombination davon:
Runden Sie jeden Wert auf ein Inkrement, das etwas größer als der "Fuzzy" -Bereich ist, bevor Sie ihn in der Hash-Tabelle speichern. Wenn Sie versuchen, einen Wert zu finden, überprüfen Sie die Hash-Tabelle auf die gerundeten Werte über und unter dem gesuchten Wert.
Speichern Sie jedes Element in der Hash-Tabelle mit Schlüsseln, die über und unter dem gesuchten Wert liegen.
Beachten Sie, dass die Verwendung eines der beiden Ansätze wahrscheinlich erfordert, dass Hash-Tabelleneinträge keine Elemente, sondern Listen identifizieren, da mit jedem Schlüssel wahrscheinlich mehrere Elemente verknüpft sind. Der oben beschriebene erste Ansatz minimiert die erforderliche Hash-Tabellengröße, aber für jede Suche nach einem Element, das nicht in der Tabelle enthalten ist, sind zwei Hash-Tabellen-Lookups erforderlich. Der zweite Ansatz kann schnell feststellen, dass Elemente nicht in der Tabelle enthalten sind, erfordert jedoch im Allgemeinen, dass die Tabelle etwa doppelt so viele Einträge enthält, wie ansonsten erforderlich wären. Wenn versucht wird, Objekte im 2D-Raum zu finden, kann es nützlich sein, einen Ansatz für die X-Richtung und einen für die Y-Richtung zu verwenden, sodass jedes Element nicht nur einmal gespeichert wird, sondern vier Abfrageoperationen für jede Suche erforderlich sind in der Lage, mit einer Suche einen Gegenstand zu finden, wobei jeder Gegenstand viermal gespeichert werden muss,
quelle
Sie können natürlich "fast gleich" definieren, indem Sie beispielsweise die letzten acht Bits der Mantisse löschen und dann vergleichen oder hashen. Das Problem ist , dass die Zahlen sehr nahe beieinander können unterschiedlich sein.
Hier gibt es einige Verwirrung: Wenn zwei Gleitkommazahlen gleich sind, sind sie gleich. Um zu überprüfen, ob sie gleich sind, verwenden Sie "==". Manchmal möchten Sie nicht auf Gleichheit prüfen, aber wenn Sie dies tun, ist „==“ der richtige Weg.
quelle
Dies ist keine Antwort, sondern ein erweiterter Kommentar, der hilfreich sein kann.
Ich habe an einem ähnlichen Problem gearbeitet, während ich MPFR (basierend auf GNU MP) verwendet habe. Der von @ Kain0_0 beschriebene "Bucket" -Ansatz scheint akzeptable Ergebnisse zu liefern. Beachten Sie jedoch die in dieser Antwort hervorgehobenen Einschränkungen.
Ich wollte hinzufügen, dass - je nachdem, was Sie versuchen - die Verwendung eines "exakten" Computeralgebrasystems ( Caveat Emptor ) wie Mathematica dabei helfen kann, ein ungenaues numerisches Programm zu ergänzen oder zu verifizieren. Dies ermöglicht es Ihnen , Ergebnisse zu berechnen , ohne sich Gedanken über die Runden, beispielsweise
7*√2 - 5*√2
nachgeben2
statt2.00000001
oder ähnliches. Dies wird natürlich zusätzliche Komplikationen mit sich bringen, die sich möglicherweise lohnen oder auch nicht.quelle