摘要
我想在C#中构建一组项目集。内部项集具有由内容定义的GetHashCode
和Equals
方法。用数学符号表示:
x = { }
x.Add( { A, B, C } )
x.Add( { A, D } )
x.Add( { B, C, A } )
now x should be{ { A, B, C }, { A, D } }
在python中,这可以通过frozenset
完成:
x = set()
x.add( frozenset(['A','B','C']) )
x.add( frozenset(['A','D']) )
x.add( frozenset(['B','C','A']) )
/ BriefSummary
我想在C#中有一个可散列的HashSet。这将允许我这样做:
HashSet<ContentHashableHashSet<int>> setOfSets;
虽然有更复杂的方法可以实现这一点,但通过添加覆盖ContentHashableHashSet.ToString()
(输出排序顺序中包含的元素的字符串),可以在实践中轻松实现(尽管不是以最有效的方式)。然后使用ContentHashableHashSet.ToString().GetHashCode()
作为哈希码。
但是,如果在放置setOfSets
后修改了某个对象,则可能会产生多个副本:
var setA = new ContentHashableHashSet<int>();
setA.Add(1);
setA.Add(2);
var setB = new ContentHashableHashSet<int>();
setB.Add(1);
setOfSets.Add(setA);
setOfSets.Add(setB);
setB.Add(2); // now there are duplicate members!
据我所知,我有两个选择:我可以从ContentHashableHashSet
派生HashSet
,但我需要这样做,以便所有修饰符都抛出异常。缺少一个修饰符可能会导致一个阴险的错误。
或者,我可以使用封装,类ContentHashableHashSet
可以包含readonly HashSet
。但是,我需要重新实现所有设置方法(修饰符除外),以便ContentHashableHashSet
的行为类似于HashSet
。据我所知,扩展不适用。
最后,我可以封装如上所述,然后通过返回const(或只读?)HashSet成员来实现所有类似集的功能。
事后看来,这让人联想到python的frozenset
。有没有人知道在C#中实现这个的设计良好的方法?
如果我能够失去ISet
功能,那么我只会创建一个已排序的ImmutableList
,但之后我将失去快速联合,快速交叉和子线性(大致为O(O)的功能。 log(n)))使用Contains
设置成员资格。
编辑:基类HashSet 不具有虚拟Add
和Remove
方法,因此覆盖它们将在派生类中起作用,但如果您执行HashSet<int> set = new ContentHashableHashSet<int>();
, 将无效。转换为基类将允许编辑。
编辑2:感谢@xanatos推荐简单的GetHashCode
实施:
计算GetHashCode的最简单方法是简单地xor(^)元素的所有gethashcodes。 xor运算符是可交换的,因此排序无关紧要。为了进行比较,您可以使用SetEquals
编辑3:最近有人分享了有关ImmutableHashSet的信息,但由于此类已被封存,因此无法从中获取并覆盖GetHashCode
。
我还被告知HashSet
使用IEqualityComparer
作为参数,因此这可用于提供不可变的内容可清除集,而不从ImmutableHashSet派生;但是,这不是一个非常面向对象的解决方案:每次我想使用ContentHashableHashSet
时,我都需要传递相同的(非平凡的)参数。我确定你知道,这可能真的会对你的编码禅造成严重破坏,而且我会用myDictionary[ frozenset(mySet) ] = myValue
在python中飞行,我会被困在做同样的事情again and again and again。
感谢您提供的任何帮助。我有一个临时解决方法(其问题在上面的编辑1 中提到),但我最想了解设计这样的最佳方法。
答案 0 :(得分:1)
隐藏您的一组集的元素,以便它们无法更改。这意味着在添加/检索集合时进行复制,但这可能是可以接受的吗?
// Better make sure T is immutable too, else set hashes could change
public class SetofSets<T>
{
private class HashSetComparer : IEqualityComparer<HashSet<T>>
{
public int GetHashCode(HashSet<T> x)
{
return x.Aggregate(1, (code,elt) => code ^ elt.GetHashCode());
}
public bool Equals(HashSet<T> x, HashSet<T> y)
{
if (x==null)
return y==null;
return x.SetEquals(y);
}
}
private HashSet<HashSet<T>> setOfSets;
public SetofSets()
{
setOfSets = new HashSet<HashSet<T>>(new HashSetComparer());
}
public void Add(HashSet<T> set)
{
setOfSets.Add(new HashSet<T>(set));
}
public bool Contains(HashSet<T> set)
{
return setOfSets.Contains(set);
}
}