次のPythonクラスがあるとしましょう(問題はJava equals
とhashCode
でも同じです)
_class Temperature:
def __init__(self, degrees):
self.degrees = degrees
_
ここで、degrees
は、ケルビンのフロートとしての温度です。ここで、Temperature
の同等性テストとハッシュを次のような方法で実装したいと思います。
a == b
_がhash(a) == hash(b)
を意味するという契約を尊重します。_def __eq__(self, other):
return abs(self.degrees - other.degrees) < EPSILON
def __hash__(self):
return # What goes here?
_
Pythonのドキュメントでは ハッシュ値 について少し説明しており、hash(2) == hash(2.0)
を保証していますが、これはまったく同じ問題ではありません。
私は正しい軌道に乗っていますか?もしそうなら、この状況でハッシュを実装する標準的な方法は何ですか?
更新:floatのこのタイプの等価性テストにより、_==
_とequals
の推移性が排除されることがわかりました。しかし、フロートと「常識」を直接比較すべきではないというのはどうしてでしょうか。浮動小数点数を比較して等価演算子を実装すると、静的分析ツールは不平を言うでしょう。彼らはそうする権利がありますか?
直接等値テストの代わりにフロートをイプシロンの差と比較する方法で温度の等値テストとハッシュを実装し、
ファジー等式は、Javaがequals
メソッドに課す要件、つまりtransitivityに違反します。つまり、x == y
およびy == z
、次にx == z
。しかし、たとえば0.1のイプシロンでファジー等式を実行すると、0.1 == 0.2
および0.2 == 0.3
、 だが 0.1 == 0.3
は保持しません。
Pythonはそのような要件を文書化していませんが、それでも非推移的な等式を持つことの含意はそれを非常に悪い考えにします;そのような型についての推論は頭痛を誘発します。
ですから、そうしないことを強くお勧めします。
正確な等価性を提供し、ハッシュを明白な方法で基にし、ファジーマッチングを実行する別の方法を提供するか、Kainによって提案された等価クラスアプローチを使用します。後者の場合でも、値をコンストラクターの等価クラスの代表的なメンバーに修正し、残りの部分については単純な正確な等価とハッシュを使用することをお勧めします。このようにタイプについて推論する方がはるかに簡単です。
(しかし、それを行う場合は、浮動小数点の代わりに固定小数点表現を使用することもできます。つまり、整数を使用して1000分の1度または必要な精度をカウントします。)
幸運
あなたは、ハッシュで愚かにしたり、イプシロンを犠牲にすることなく、それを達成することはできません。
例:
各ポイントが固有のハッシュ値にハッシュすると仮定します。
浮動小数点数は連続しているため、指定された浮動小数点値の前に最大k個の数値があり、指定された浮動小数点値の後に最大k個の数値が指定された点のイプシロン内にあります。
同じハッシュ値を共有しない互いのイプシロン内の各2つのポイント。
これが当てはまらない場合がいくつかあります。
ただし、浮動小数点範囲の> = 99%は、特定の浮動小数点値の上または下に少なくとも1つの浮動小数点値を含むイプシロンの任意の値の単一の値にハッシュされます。
結果
浮動小数点範囲全体の99%以上が単一の値にハッシュされ、ハッシュ値の意図(および公平に分散された低衝突ハッシュに依存しているデバイス/コンテナー)を著しく損なう。
または、完全一致のみが許可されるイプシロンです。
粒状
もちろん、代わりにきめ細かなアプローチをとることもできます。
このアプローチでは、特定の解像度まで正確なバケットを定義します。つまり:
[0.001, 0.002)
[0.002, 0.003)
[0.003, 0.004)
...
[122.999, 123.000)
...
各バケットには固有のハッシュがあり、バケット内の浮動小数点は同じバケット内の他の浮動小数点と同等です。
残念ながら、2つのフロートがイプシロンの距離にあり、2つの別々のハッシュを持つことは依然として可能です。
温度を内部で整数としてモデル化できます。温度には自然な下限があります(-273.15摂氏)。したがって、double(-273.15は基礎となる整数の0に等しい)です。必要な2番目の要素は、マッピングの粒度です。この細分性はすでに暗黙的に使用されています。それはあなたのEPSILONです。
温度をEPSILONで除算して下限を計算するだけで、ハッシュとイコールが同期して動作します。 Python 3では、整数は無制限です。EPSILONは必要に応じて小さくすることができます。
[〜#〜]注意[〜#〜] EPSILONの値を変更し、オブジェクトをシリアル化した場合、互換性がなくなります!
#Pseudo code
class Temperature:
def __init__(self, degrees):
#CHECK INVALID VALUES HERE
#TRANSFORM TO KELVIN HERE
self.degrees = Math.floor(kelvin/EPSILON)
特定のキーと「ほぼ等しい」ものを見つけることができる浮動小数点ハッシュテーブルを実装するには、いくつかの方法またはその組み合わせを使用する必要があります。
ハッシュテーブルに格納する前に、各値を「ファジー」範囲よりも少し大きい増分に丸め、値を見つけようとする場合は、ハッシュテーブルで、求められる値の上下の丸められた値を確認します。
求められている値の上下にあるキーを使用して、各アイテムをハッシュテーブル内に格納します。
各キーに関連付けられている複数のアイテムが存在する可能性があるため、どちらのアプローチを使用する場合でも、ハッシュテーブルエントリはアイテムではなくリストを識別する必要があることに注意してください。上記の最初のアプローチでは、必要なハッシュテーブルのサイズを最小限に抑えますが、テーブルにないアイテムを検索するたびに、2つのハッシュテーブルルックアップが必要になります。 2番目の方法では、項目がテーブルにないことをすばやく特定できますが、通常は、テーブルに他の方法で必要なエントリの約2倍のエントリを保持する必要があります。 2D空間でオブジェクトを見つけようとしている場合、X方向とY方向に1つのアプローチを使用すると、各アイテムを一度格納するのではなく、ルックアップごとに4つのクエリ操作を必要とするか、 1つのルックアップを使用してアイテムを見つけることができますが、各アイテムを4回保存する必要がある場合、各アイテムを2回保存し、2つのルックアップ操作を使用してそれを見つけます。
もちろん、仮数の最後の8ビットを削除してから比較またはハッシュすることにより、「ほぼ等しい」と定義できます。問題は、数値が互いに非常に近いmayが異なることです。
ここでいくつかの混乱があります。2つの浮動小数点数が等しい場合、それらは等しいです。それらが等しいかどうかを確認するには、「==」を使用します。等しいかどうかを確認したくない場合もありますが、その場合は「==」が適切です。
これは答えではありませんが、役立つかもしれない拡張コメントです。
[〜#〜] mpfr [〜#〜] (GNU MPに基づく)を使用しながら、同様の問題に取り組んでいます。「バケット」アプローチ@ Kain0_0で概説されているように、許容できる結果が得られるようですが、その回答で強調されている制限に注意してください。
Mathematicaのようなコンピュータ代数システムは、あなたがやろうとしていることに応じて、「正確な」(警告エンプター)コンピューター代数システムを使用して、不正確な数値プログラムを補足または検証するのに役立ちます。これにより、丸めを気にすることなく結果を計算できます。たとえば、7*√2 - 5*√2
は2
の代わりに 2.00000001
または類似。もちろん、これは価値があるかもしれないし、そうでないかもしれない追加の複雑さをもたらします。