等値比較のショートカットとしてハッシュコードを使用することが理にかなっているケースが 1 つあります。
ハッシュテーブルまたはハッシュセットを構築している場合を考えてみましょう。実際、ハッシュセットについて考えてみましょう (ハッシュテーブルも値を保持することでそれを拡張しますが、それは関係ありません)。
さまざまなアプローチがありますが、それらのすべてに、ハッシュ値を配置できる少数のスロットがあり、オープンアプローチまたはクローズドアプローチのいずれかを採用しています (楽しみのために、反対の専門用語を使用する人もいます)他の人のために); 2 つの異なるオブジェクトの同じスロットで衝突した場合、それらを同じスロットに格納する (ただし、オブジェクトが実際に格納されている場所のリンク リストなどがある) か、別のスロットを選択するために再プローブする (さまざまなオブジェクトがあります)そのための戦略)。
現在、どちらのアプローチでも、ハッシュテーブルで必要な O(1) の複雑さから離れて、O(n) の複雑さに向かっています。このリスクは、利用可能なスロットの数に反比例するため、特定のサイズの後にハッシュテーブルのサイズを変更します (すべてが理想的だったとしても、保存されているアイテムの数がスロット)。
サイズ変更時にアイテムを再挿入することは、明らかにハッシュ コードに依存します。このため、オブジェクトでメモ化することはほとんど意味GetHashCode()
がありませんが (ほとんどのオブジェクトで十分な頻度で呼び出されるわけではありません)、ハッシュ テーブル自体の中でメモ化する (または、生成された結果をメモ化する) ことは確かに理にかなっています。 、Wang/Jenkins ハッシュで再ハッシュして、不適切なGetHashCode()
実装による被害を軽減した場合など)。
さて、挿入するロジックは次のようになります。
- オブジェクトのハッシュ コードを取得します。
- オブジェクトのスロットを取得します。
- スロットが空の場合は、そこにオブジェクトを配置して戻ります。
- スロットに等しいオブジェクトが含まれている場合、ハッシュセットは終了し、ハッシュテーブルの値を置き換える位置が得られます。これをして、戻ってください。
- 衝突戦略に従って次のスロットを試し、項目 3 に戻ります (これを頻繁にループする場合は、おそらくサイズを変更します)。
したがって、この場合、等価性を比較する前にハッシュ コードを取得する必要があります。また、サイズ変更を可能にするために事前に計算された既存のオブジェクトのハッシュ コードもあります。これら 2 つの事実の組み合わせは、項目 4 の比較を次のように実装することが理にかなっていることを意味します。
private bool IsMatch(KeyType newItem, KeyType storedItem, int newHash, int oldHash)
{
return ReferenceEquals(newItem, storedItem) // fast, false negatives, no false positives (only applicable to reference types)
||
(
newHash == oldHash // fast, false positives, no fast negatives
&&
_cmp.Equals(newItem, storedItem) // slow for some types, but always correct result.
);
}
明らかに、これの利点は の複雑さに依存し_cmp.Equals
ます。キーのタイプが だった場合int
、これは完全に無駄になります。キーの型が string で、大文字と小文字を区別しない Unicode で正規化された等価比較を使用していた場合 (したがって、長さを短縮することさえできません)、節約する価値は十分にあります。
一般に、ハッシュコードをメモすることは、パフォーマンスが向上するほど頻繁に使用されないため意味がありませんが、ハッシュセットまたはハッシュテーブル自体に保存することは理にかなっています。