私はHashSetデザイナーの頭への洞察を探しています。私の知る限りでは、私の質問はJavaとC#HashSetsの両方に当てはまります。私には自分自身については考えられませんが、それには正当な理由があるはずだと思います。
HashSetにアイテムを挿入した後、列挙せずにそのアイテムを取得することが不可能で、効率的な操作が難しいのはなぜですか?特にHashSetは、効率的な取得をサポートする方法で明示的に構築されているためです。
Remove(x)とContains(x)に、削除または含まれている実際のアイテムを返させると便利な場合があります。これは、必ずしもRemove(x)またはContains(x)関数に渡すアイテムではありません。確かに、私はHashMapを使用して同じ効果を達成できると思いますが、セットでこれを完全に実行できるはずなのに、なぜそのすべてのスペースと労力を無駄にするのでしょうか。
この機能を追加するとフレームワークでの役割または将来の役割と一致しないHashSetの使用が許可されるという設計上の懸念があるかもしれませんが、そうであれば、これらの設計上の問題は何ですか?
編集
さらにいくつかの質問に答えるために、ここに詳細を示します:
C#で値の型をエミュレートするために、オーバーライドされたハッシュコードや等号などの不変の参照型を使用しています。タイプにメンバーA、B、Cがあるとします。ハッシュコード、イコールなどはAとBだけに依存します。AとBIがハッシュセットからその同等のアイテムを取得してCになることを望んでいると仮定します。これにはHashSetを使用できるように見えますが、少なくともこれに正当な理由があるかどうかを知りたいです。擬似コードは次のとおりです。
public sealed class X{
object A;
object B;
object extra;
public int HashCode(){
return A.hashCode() + B.hashCode();
}
public bool Equals(X obj){
return obj.A == A && obj.B == B;
}
}
hashset.insert(new X(1,2, extra1));
hashset.contains(new X(1,2)); //returns true, but I can't retrieve extra
ハッシュセットからアイテムを取得することをどのように提案しましたか?セットは、定義上、順序付けされていないため、問題のオブジェクトを取得するために使用するインデックスはありません。
セットは、概念として、包含、つまり問題の要素がハッシュデータセットにあるかどうかをテストするために使用されます。キー値またはインデックスを使用してデータソースから値を取得する場合は、 Map または List のいずれかを調べることをお勧めします。
EDIT:元の質問の編集に基づいた追加の回答
Soonil、あなたの新しい情報に基づいて、あなたはあなたのデータをJava Enum、これに似たものとして実装することに興味があるかもしれません:
public enum SoonilsDataType {
A, B, C;
// Just an example of what's possible
public static SoonilsDataType getCompositeValue(SoonilsDataType item1,
SoonilsDataType item2) {
if (item1.equals(A) &&
item2.equals(B)) {
return C;
}
}
}
列挙型は、列挙型の「セット」内のすべての値のリストを返すvalues()を自動的に継承します。これを使用して、セットと同じ方法で包含をテストできます。また、その完全なクラスなので、新しい静的メソッドを定義して複合ロジックを実行できます(例のコードで言及したように)。 Enumの唯一の点は、実行時に新しいインスタンスを追加できないことです。これは、望みどおりの結果にならない可能性があります(ただし、セットのデータサイズが実行時に大きくならない場合は、Enumが必要です)。
.Netでは、おそらく探しているのはKeyedCollection http://msdn.Microsoft.com/en-us/library/ms132438.aspx です。
いくつかの「一般的な」賢さで、この抽象クラスを毎回再実装する素晴らしさを回避できます。 (IKeyedObject`1を参照してください。)
注:IKeyedObject`1を実装するデータ転送オブジェクトには、オーバーライドされたGetHashCodeメソッドがthis.Key.GetHashCode();を返すだけです。と同じことが同じです...
私の基本クラスライブラリは通常、次のようなものになります。
public class KeyedCollection<TItem> : System.Collections.ObjectModel.KeyedCollection<TItem, TItem>
where TItem : class
{
public KeyedCollection() : base()
{
}
public KeyedCollection(IEqualityComparer<TItem> comparer) : base(comparer)
{
}
protected override TItem GetKeyForItem(TItem item)
{
return item;
}
}
public class KeyedObjectCollection<TKey, TItem> : System.Collections.ObjectModel.KeyedCollection<TKey, TItem>
where TItem : class, IKeyedObject<TKey>
where TKey : struct
{
public KeyedCollection() : base()
{
}
protected override TItem GetKeyForItem(TItem item)
{
return item.Key;
}
}
///<summary>
/// I almost always implement this explicitly so the only
/// classes that have access without some rigmarole
/// are generic collections built to be aware that an object
/// is keyed.
///</summary>
public interface IKeyedObject<TKey>
{
TKey Key { get; }
}
挿入後にオブジェクトを変更した場合、そのハッシュは変更されている可能性があります(これは、hashCode()がオーバーライドされている場合に特に可能性があります)。ハッシュが変更された場合、オブジェクトが格納されている場所とは異なる場所でハッシュされたオブジェクトを検索しようとするため、セット内でのハッシュの検索は失敗します。
また、異なるインスタンスである等しいオブジェクトを検索する場合は、オブジェクトのhashCodeおよびequalsをオーバーライドしていることを確認する必要があります。
これはすべてJavaの場合です。C#も似ていると思いますが、C#を使用してから数年が経過しているため、他の人にその機能について話してもらいます。
_HashMap<X,X>
_を使用しないのはなぜですか?これはまさにあなたが望むことをします。毎回.put(x,x)
を実行するだけで、.get(x)
を使用して、格納されている要素をxに等しくすることができます。
Set
インターフェースとHashSet
クラスの設計者が、Collection
インターフェースで定義されたremove(Object)
メソッドがSet
;このメソッドは、オブジェクトが正常に削除されたかどうかを示すブール値を返します。デザイナーが、remove(Object)がSet
にすでにある「等しい」オブジェクトを返す機能を提供したい場合、これは別のメソッドシグネチャを意味します。
また、削除されるオブジェクトがremove(Object)に渡されるオブジェクトと論理的に等しい場合、含まれているオブジェクトを返すときに追加される値については、議論の余地があります。しかし、私自身もこの問題を抱えていて、マップを使用して問題を解決しました。
Javaでは、HashSet
は内部でHashMap
を使用するため、代わりにHashMap
を使用してもストレージのオーバーヘッドは発生しません。
これは図書館設計者の見落としでした。 別の答え で説明したように、このメソッドは 。NET Framework 4.7.2 に追加されました(および 。NET Core 2. その前に);見る - HashSet<T>.TryGetValue
。引用 ソース :
/// <summary>
/// Searches the set for a given value and returns the equal value it finds, if any.
/// </summary>
/// <param name="equalValue">The value to search for.
/// </param>
/// <param name="actualValue">
/// The value from the set that the search found, or the default value
/// of <typeparamref name="T"/> when the search yielded no match.</param>
/// <returns>A value indicating whether the search was successful.</returns>
/// <remarks>
/// This can be useful when you want to reuse a previously stored reference instead of
/// a newly constructed one (so that more sharing of references can occur) or to look up
/// a value that has more complete data than the value you currently have, although their
/// comparer functions indicate they are equal.
/// </remarks>
public bool TryGetValue(T equalValue, out T actualValue)
あなたが実際に_Map<X,Y>
_を探しているように見えますが、Yは_extra1
_のタイプです。
(下の暴言)
EqualsメソッドとhashCodeメソッドは、意味のあるオブジェクトの等価性を定義します。 HashSetクラスは、2つのオブジェクトがObject.equals(Object)
で定義されたものと等しい場合、これら2つのオブジェクト間に違いがないと想定しています。
_object extra
_が意味のあるものであるなら、あなたのデザインは理想的ではない、と言って限ります。
[〜#〜]解決済み[〜#〜]。要素を見つけたいと思うことは、私にとって完全に有効なようです。検索に使用される代表者が、見つかった要素と異なる場合があるためです。これは、要素にキーと値の情報が含まれていて、カスタムの等値比較子がキー部分のみを比較する場合に特に当てはまります。コード例を参照してください。コードには、カスタム検索およびを実装する比較子が含まれ、見つかった要素をキャプチャします。これには、比較子のインスタンスが必要です。見つかった要素への参照をクリアします。 Containsを使用して検索を実行します。見つかった要素にアクセスします。比較インスタンスを共有するときは、マルチスレッドの問題に注意してください。
using System;
using System.Collections.Generic;
namespace ConsoleApplication1 {
class Box
{
public int Id;
public string Name;
public Box(int id, string name)
{
Id = id;
Name = name;
}
}
class BoxEq: IEqualityComparer<Box>
{
public Box Element;
public bool Equals(Box element, Box representative)
{
bool found = element.Id == representative.Id;
if (found)
{
Element = element;
}
return found;
}
public int GetHashCode(Box box)
{
return box.Id.GetHashCode();
}
}
class Program
{
static void Main()
{
var boxEq = new BoxEq();
var hashSet = new HashSet<Box>(boxEq);
hashSet.Add(new Box(3, "Element 3"));
var box5 = new Box(5, "Element 5");
hashSet.Add(box5);
var representative = new Box(5, "Representative 5");
boxEq.Element = null;
Console.WriteLine("Contains {0}: {1}", representative.Id, hashSet.Contains(representative));
Console.WriteLine("Found id: {0}, name: {1}", boxEq.Element.Id, boxEq.Element.Name);
Console.WriteLine("Press enter");
Console.ReadLine();
}
}
} // namespace
私は自分のオブジェクトにKeyValuePairsとして自分自身を定義させることで、マップの使用方法について興味深い提案を受けました。良いコンセプトですが、残念ながらKeyValuePairはインターフェースではなく(なぜでしょうか?)構造体であり、その計画を空から発射します。制約によりこのオプションが許可されているので、最後に私は自分のセットをロールします。
同じことを考えて、ソースコードを細かく見ることができるようになった後:
ソース: http://referencesource.Microsoft.com/#System.Core/System/Collections/Generic/HashSet.cs
セットは、一意のアイテム(オブジェクトまたは値)のコレクションです。 .net実装では、比較子のEqualsメソッドが2つのアイテムに対してtrueを返す場合、アイテムは別のアイテムと同じです(一意ではありません)。 2つのアイテムのハッシュコードが同じ場合は除きます。したがって、アイテムの存在の確認は2段階のプロセスです。最初にハッシュセットを使用して比較するアイテムの数を最小限に抑え、次に圧縮自体を行います。
アイテムを取得する場合は、取得関数に一意の識別子を指定できる必要があります。あなたはあなたが望むアイテムのハッシュコードを知っているかもしれません。しかし、それだけでは十分ではありません。複数のアイテムが同じハッシュを持つことができるからです。また、Equalメソッドを呼び出せるように、アイテム自体を指定する必要もあります。そして明らかにあなたがアイテムを持っているなら、それを手に入れる理由はありません。
2つの一意のアイテムが同じハッシュコードを返さないことを要求するデータ構造を作成できます。そして、あなたはそれからアイテムを得ることができました。追加するよりも速く*、ハッシュを知っていれば検索が可能になります。等しくないが同じハッシュを返す2つの項目がそこに配置される場合、最初の項目は上書きされます。私の知る限り、このタイプは.netに存在せず、辞書と同じではありません。
* GetHashメソッドが同じであることを前提とします。
これらの言語のセットオブジェクトは、変更可能なオブジェクトではなく、ほとんどが値のセットとして設計されました。彼らは等号を使用して、それらに入れられたオブジェクトが一意であることを確認します。そのため、containsとremoveはオブジェクトではなくブール値を返します。渡された値をチェックまたは削除します。
そして実際には、セットでcontains(X)を実行し、異なるオブジェクトYを取得することを期待する場合、XとYは等しい(つまり、X.equals(Y)=> true)が、多少異なることを意味します。間違っているようです.
短い答え;アイテムが不変であることを保証できないためです。
HashCodeはメンバークラス内の固定フィールドに基づいていますが、クラスはハッシュを変更せずに更新できる追加情報を保持しています。
私の解決策は、ICollection <T>に基づく汎用のMyHashSet <T>を実装することでしたが、Dictionary <int、List <T >>をラップして必要な検索効率を提供しました。ここで、intキーはTのHashCodeです。ただし、これはは、メンバーオブジェクトのHashCodeが変更される可能性がある場合、ディクショナリルックアップとそれに続くリスト内のアイテムの等価比較では、変更されたアイテムが見つからないことを示しています。メンバーを不変に強制するメカニズムはないため、唯一の解決策はロットを列挙することです。