22

Java から C# に何かを移植しています。Java では、hashcodeの はArrayListその中の項目に依存します。C#では、常に同じハッシュコードを取得しListます...

どうしてこれなの?

一部のオブジェクトでは、リスト プロパティ内のオブジェクトによってオブジェクトが等しくないため、ハッシュコードを異なるものにする必要があります。ハッシュコードはオブジェクトの状態に対して常に一意であり、オブジェクトが等しい場合にのみ別のハッシュコードと等しくなると思います。私が間違っている?

4

7 に答える 7

19

正しく機能するためには、ハッシュコードは不変でなければなりません。オブジェクトのハッシュ コードは決して変更してはなりません。

オブジェクトのハッシュコードが変更された場合、そのオブジェクトを含む辞書は機能しなくなります。

コレクションは不変ではないため、実装できませんGetHashCode
代わりにGetHashCode、オブジェクトのインスタンスごとに (うまくいけば) 一意の値を返す default を継承します。(通常はメモリアドレスに基づく)

于 2010-05-26T13:16:14.250 に答える
10

ハッシュコードは、使用されている等価性の定義に依存する必要がありますA == B(A.GetHashCode() == B.GetHashCode()ただし、必ずしも逆である必要はありA.GetHashCode() == B.GetHashCode()ませんA == B

デフォルトでは、値型の等価定義はその値に基づいており、参照型の等価定義はその ID に基づいています (つまり、デフォルトでは、参照型のインスタンスはそれ自体とのみ等しい)。したがって、デフォルトのハッシュコードは値の型は、含まれるフィールドの値に依存し*、参照型の場合はアイデンティティに依存します。実際、等しくないオブジェクトのハッシュコードは、特に下位ビット (再ハッシュの値に影響を与える可能性が最も高い) で異なることが理想的であるため、通常、2 つの同等であるが等しくないオブジェクトには異なる値が必要です。ハッシュ。

オブジェクトはそれ自体と等しいままであるため、オブジェクトが変更された場合でも、このデフォルトの実装がGetHashCode()同じ値を持ち続けることも明確にする必要があります (変更可能なオブジェクトのアイデンティティーは変更されません)。

場合によっては、参照型 (または値型) が等価性を再定義します。この例は文字列です。たとえば、"ABC" == "AB" + "C". 比較される文字列の 2 つの異なるインスタンスがありますが、それらは等しいと見なされます。この場合GetHashCode()、値が等価性が定義されている状態 (この場合は含まれる文字のシーケンス) に関連するようにオーバーライドする必要があります。

これは、不変でもある型で行うのがより一般的ですが、さまざまな理由から、GetHashCode()immutability に依存しません。むしろ、GetHashCode()可変性に直面しても一貫性を保つ必要があります - ハッシュの決定に使用する値を変更すると、ハッシュそれに応じて変更します。ただし、この変更可能なオブジェクトをハッシュを使用して構造体のキーとして使用している場合、これは問題であることに注意してください。オブジェクトを変更すると、その位置に移動せずに、格納する位置が変更されるためです (コレクション内のオブジェクトの位置がその値に依存するその他のケース - たとえば、リストをソートしてからリスト内の項目の 1 つを変更すると、リストはソートされなくなります)。ただし、これは、辞書とハッシュセットで不変オブジェクトのみを使用する必要があるという意味ではありません。むしろ、そのような構造にあるオブジェクトを変更してはならないことを意味し、不変にすることはこれを保証する明確な方法です。

実際、可変オブジェクトをそのような構造に格納することが望ましいケースはかなり多く、この間にそれらを変更しない限り、これは問題ありません。不変性がもたらす保証がないため、別の方法でそれを提供したいと考えています (たとえば、コレクション内で短時間過ごし、1 つのスレッドからのみアクセスできるようにするなど)。

したがって、キー値の不変性は、何かが可能な場合の 1 つですが、一般的にはアイデアです。ただし、ハッシュコードアルゴリズムを定義する人にとって、そのようなケースが常に悪い考えであると想定することはできません(オブジェクトがそのような構造に格納されている間に突然変異が発生したことさえ知りません)。オブジェクトの現在の状態で定義されたハッシュコードを実装するのは、特定の時点でそれを呼び出すことが良いかどうかに関係なく、彼らのためです。したがって、たとえば、変更のたびにメモ化がクリアされない限り、変更可能なオブジェクトでハッシュコードをメモ化するべきではありません。(同じオブジェクトのハッシュコードに繰り返しヒットする構造は、独自のメモ化を行うため、一般的に、ハッシュをメモ化するのは無駄です)。

さて、手元のケースでは、ArrayList は、同一性に基づくデフォルトの等価ケースで動作します。たとえば、次のようになります。

ArrayList a = new ArrayList();
ArrayList b = new ArrayList();
for(int i = 0; i != 10; ++i)
{
  a.Add(i);
  b.Add(i);
}
return a == b;//returns false

さて、これは実際には良いことです。なんで?上記で、a と b が等しいと見なしたいことをどのように知ることができますか? 可能ですが、他のケースでもそうしない十分な理由があります。

さらに、平等を価値ベースからアイデンティティベースに再定義するよりも、アイデンティティベースから価値ベースに再定義する方がはるかに簡単です。最後に、多くのオブジェクトに対して複数の値ベースの等価定義が存在するため (古典的なケースでは、文字列を等価にするものについてさまざまな見解が示されます)、機能する唯一無二の定義さえありません。例えば:

ArrayList c = new ArrayList();
for(short i = 0; i != 10; ++i)
{
  c.Add(i);
}

a == b上記で検討した場合、同様に検討する必要がa == cありますか?答えは、使用している平等の定義で何を気にするかに依存するため、すべてのケースが一致しないため、フレームワークはすべてのケースの正しい答えを知ることができません。

さて、特定のケースで値ベースの平等に関心がある場合、2 つの非常に簡単なオプションがあります。1 つ目は、同等性をサブクラス化してオーバーライドすることです。

public class ValueEqualList : ArrayList, IEquatable<ValueEqualList>
{
  /*.. most methods left out ..*/
  public Equals(ValueEqualList other)//optional but a good idea almost always when we redefine equality
  {
    if(other == null)
      return false;
    if(ReferenceEquals(this, other))//identity still entails equality, so this is a good shortcut
      return true;
    if(Count != other.Count)
      return false;
    for(int i = 0; i != Count; ++i)
      if(this[i] != other[i])
        return false;
    return true;
  }
  public override bool Equals(object other)
  {
    return Equals(other as ValueEqualList);
  }
  public override int GetHashCode()
  {
    int res = 0x2D2816FE;
    foreach(var item in this)
    {
        res = res * 31 + (item == null ? 0 : item.GetHashCode());
    }
    return res;
  }
}

これは、そのようなリストを常にこのように扱いたいと考えていることを前提としています。特定のケースに対して IEqualityComparer を実装することもできます。

public class ArrayListEqComp : IEqualityComparer<ArrayList>
{//we might also implement the non-generic IEqualityComparer, omitted for brevity
  public bool Equals(ArrayList x, ArrayList y)
  {
    if(ReferenceEquals(x, y))
      return true;
    if(x == null || y == null || x.Count != y.Count)
      return false;
    for(int i = 0; i != x.Count; ++i)
      if(x[i] != y[i])
        return false;
    return true;
  }
  public int GetHashCode(ArrayList obj)
  {
    int res = 0x2D2816FE;
    foreach(var item in obj)
    {
        res = res * 31 + (item == null ? 0 : item.GetHashCode());
    }
    return res;
  }
}

要約すれば:

  1. 参照型の既定の等価定義は、ID のみに依存します。
  2. ほとんどの場合、私たちはそれを望んでいます。
  3. クラスを定義する人が、これが必要なものではないと判断した場合、この動作をオーバーライドできます。
  4. クラスを使用する人が再び別の等価性の定義を必要IEqualityComparer<T>IEqualityComparerする場合、その辞書、ハッシュマップ、ハッシュセットなどで等価性の概念を使用することができます。
  5. ハッシュベースの構造の鍵であるオブジェクトを変更するのは悲惨なことです。不変性を使用して、これが起こらないようにすることができますが、必須ではなく、常に望ましいものでもありません。

全体として、このフレームワークは優れたデフォルトと詳細なオーバーライドの可能性を提供してくれます。

*構造体内の小数の場合にはバグがあります。これは、安全な場合とそうでない場合に、構造体でショートカットが使用される場合があるためです。カットは安全ではないため、安全なケースとして誤って識別されます。

于 2011-11-14T11:24:54.433 に答える
8

はい、あなたは間違っています。Java と C# の両方で、等しいということは同じハッシュ コードを持つことを意味しますが、その逆は (必ずしも) 真ではありません。

詳細については、 GetHashCodeを参照してください。

于 2010-05-25T18:38:29.937 に答える
3

ほとんどの重要なクラスのすべてのバリエーションでハッシュコードを一意にすることはできません。C# では、リストの等価性の概念は Java と同じではありません (こちらを参照)。そのため、ハッシュ コードの実装も同じではありません。C# のリストの等価性を反映しています。

于 2010-05-25T18:32:45.723 に答える
2

主な理由はパフォーマンスと人間の性質です。人々はハッシュを高速なものと考える傾向がありますが、通常、オブジェクトのすべての要素を少なくとも 1 回走査する必要があります。

例: ハッシュ テーブルのキーとして文字列を使用する場合、すべてのクエリの複雑さが O(|s|) になります。2 倍長い文字列を使用すると、少なくとも 2 倍のコストがかかります。それが完全なツリー(単なるリストのリスト)であると想像してください-おっと:-)

完全なディープ ハッシュ計算がコレクションの標準操作である場合、膨大な割合のプログラマーが無意識のうちにそれを使用し、フレームワークと仮想マシンが遅いと非難するでしょう。フルトラバーサルと同じくらいコストがかかるものについては、プログラマーが複雑さを認識している必要があります。それを達成するための唯一の方法は、自分で作成する必要があることを確認することです。これも良い抑止力です:-)

もう 1 つの理由は、戦術の更新です。その場でハッシュを計算して更新するか、毎回完全な計算を行うかは、具体的なケースに応じた判断が必要です。

不変性は単なる学問的対処法です。人々は変更をより迅速に検出する方法としてハッシュを使用し (ファイル ハッシュなど)、常に変化する複雑な構造にもハッシュを使用します。ハッシュには、101 の基本以外にも多くの用途があります。 重要なのは、複雑なオブジェクトのハッシュに何を使用するかは、ケースバイケースで判断する必要があるということです。

オブジェクトのアドレス (実際にはハンドルであるため、GC 後に変更されません) をハッシュとして使用することは、実際にはハッシュ値が任意の変更可能なオブジェクトに対して同じままである場合です:-)自分で計算する。

于 2010-07-31T00:50:12.423 に答える
2

あなたは部分的にしか間違っていません。等しいハッシュコードは等しいオブジェクトを意味すると考えるのは間違いですが、等しいオブジェクトには等しいハッシュコードが必要です。つまり、ハッシュコードが異なる場合、オブジェクトも同じであることを意味します。

于 2010-05-25T18:52:32.200 に答える
-1

なぜ哲学的すぎるのか。ヘルパーメソッド(拡張メソッドの場合もある)を作成し、好きなようにハッシュコードを計算します。XOR 要素のハッシュコードである可能性があります

于 2010-05-25T18:30:20.137 に答える