15

この質問を解決するために、Hashable Protocol を実装するカスタム構造体をいじってみました。==を入力するときにハッシュ衝突があるかどうかに応じて、等価演算子オーバーロード()が呼び出される回数を確認しようとしていDictionaryます。

アップデート

@mattは、Hashable プロトコルを実装し、どのくらいの頻度hashValue==呼び出されるかを示すカスタム構造体のよりクリーンな例を書きました。以下に彼のコードをコピーしています。私の元の例を見るには、編集履歴をチェックしてください。

struct S : Hashable {
    static func ==(lhs:S,rhs:S) -> Bool {
        print("called == for", lhs.id, rhs.id)
        return lhs.id == rhs.id
    }
    let id : Int
    var hashValue : Int {
        print("called hashValue for", self.id)
        return self.id
    }
    init(_ id:Int) {self.id = id}
}
var s = Set<S>()
for i in 1...5 {
    print("inserting", i)
    s.insert(S(i))
}

これにより、次の結果が生成されます。

/*
inserting 1
called hashValue for 1
inserting 2
called hashValue for 2
called == for 1 2
called hashValue for 1
called hashValue for 2
inserting 3
called hashValue for 3
inserting 4
called hashValue for 4
called == for 3 4
called == for 1 4
called hashValue for 2
called hashValue for 3
called hashValue for 1
called hashValue for 4
called == for 3 4
called == for 1 4
inserting 5
called hashValue for 5
*/

Hashable は Equatable を使用してハッシュの衝突を区別するため (とにかくだと思います)、func ==()ハッシュの衝突が発生した場合にのみ呼び出されることを期待します。ただし、上記の @matt の例ではハッシュの衝突はまったくなく、まだ==呼び出されています。ハッシュの衝突を強制する他の実験 (この質問の編集履歴を参照) では==、ランダムな回数呼び出されたようです。

ここで何が起こっているのですか?

4

3 に答える 3

12

bugs.swift.org hereからの回答をコピーしています。セットについて説明していますが、詳細は辞書にも同じように当てはまります。

ハッシュされたコレクションでは、バケットの数がキースペースよりも小さい場合に衝突が発生する可能性があります。最小容量を指定せずに新しい Set を作成すると、セットにバケットが 1 つしかない場合があるため、2 番目の要素を挿入すると衝突が発生します。次に、挿入メソッドは、負荷係数と呼ばれるものを使用して、ストレージを拡張する必要があるかどうかを決定します。ストレージが拡張された場合、既存の要素を新しいストレージ バッファに移行する必要があります。それは、4 を挿入するときに hashValue への余分な呼び出しがすべて表示されるときです。

バケットの数が要素の数と同じかそれ以上である場合に、予想よりも多くの == の呼び出しが引き続き表示される理由は、バケット インデックス計算の実装の詳細に関係しています。hashValue のビットは、モジュロ演算の前に混合または「シャッフル」されます。これは、不適切なハッシュ アルゴリズムを持つ型の過​​度の衝突を減らすためです。

于 2016-12-05T06:23:33.327 に答える
11

さて、あなたの答えがあります:

https://bugs.swift.org/browse/SR-3330?focusedCommentId=19980&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-19980

実際に起こっていること:

  • 挿入時に一度だけ値をハッシュします。
  • 要素の比較にはハッシュを使用せず、== のみを使用します。比較にハッシュを使用することは、ハッシュを保存する場合にのみ合理的ですが、それはすべてのディクショナリのメモリ使用量が増えることを意味します。評価が必要な妥協点。
  • Dictionary がその要素に適合するかどうかを評価する前に、要素を挿入しようとします。これは、要素がすでに Dictionary にある可能性があるためです。その場合、これ以上の容量は必要ありません。
  • Dictionary のサイズを変更するときは、ハッシュを保存していないため、すべてを再ハッシュする必要があります。

だからあなたが見ているのは:

  • 検索キーの 1 つのハッシュ
  • いくつかの == (スペースの検索)
  • コレクション内のすべての要素のハッシュ (サイズ変更)
  • 検索キーの 1 つのハッシュ (実際には完全に無駄ですが、O 再割り当て後にのみ発生することを考えると大したことではありません)
  • いくつかの == (新しいバッファーでスペースを検索)

私たちは皆、それを完全に間違っていました。ハッシュはまったく使用せず、これが個別のキーであるかどうかを判断するためだけに使用します。 ==そして、コレクションが成長している状況で、2 回目の呼び出しがあります。

于 2016-12-08T18:33:34.127 に答える