【发布时间】:2013-09-25 14:21:22
【问题描述】:
简要总结
我想在 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!
据我所知,我有两个选择:我可以从HashSet 派生ContentHashableHashSet,但是我需要让它让所有修饰符都抛出异常。缺少一个修饰符可能会导致一个潜在的错误。
或者,我可以使用封装和类ContentHashableHashSet 可以包含readonly HashSet。但是我需要重新实现所有设置方法(修饰符除外),以便ContentHashableHashSet 的行为类似于HashSet。据我所知,扩展不适用。
最后,我可以像上面那样封装,然后通过返回 const(或只读?)HashSet 成员来实现所有类似集合的功能。
事后看来,这让人想起 python 的frozenset。有谁知道在 C# 中实现这一点的设计良好的方法?
如果我能够失去ISet 功能,那么我将简单地创建一个排序的ImmutableList,但是我会失去快速联合、快速交叉和次线性等功能(大约 O(log(n) ) ) 使用Contains 设置成员资格。
编辑: 基类 HashSet 不 有虚拟的 Add 和 Remove 方法,因此覆盖它们将在派生类中起作用,但 >不如果你执行HashSet<int> set = new ContentHashableHashSet<int>();。转换为基类将允许编辑。
编辑 2:感谢 @xanatos 推荐一个简单的 GetHashCode 实现:
计算 GetHashCode 的最简单方法是简单地异或 (^) 元素的所有 gethashcode。 xor 运算符是可交换的,因此排序无关紧要。对于比较,您可以使用 SetEquals
编辑 3: 最近有人分享了有关 ImmutableHashSet 的信息,但由于此类是密封的,因此无法从它派生并覆盖 GetHashCode。
我还被告知HashSet 将IEqualityComparer 作为参数,因此这可用于提供不可变的内容哈希集,而无需派生自 ImmutableHashSet;但是,这不是一个非常面向对象的解决方案:每次我想使用ContentHashableHashSet 时,我都需要传递相同的(重要的)参数。我相信你知道,这真的会对你的编码禅宗造成严重破坏,而我将在 python 中与myDictionary[ frozenset(mySet) ] = myValue 一起飞行,我将被困在做同样的事情again and again and again。
感谢您提供的任何帮助。我有一个临时的解决方法(上面的 EDIT 1 中提到了它的问题),但我主要想了解设计这样的东西的最佳方法。
【问题讨论】:
-
我在这种情况下所做的是创建
IReadOnlySet<T> : IReadOnlyCollection<T>接口,我将在其中重新声明非可变集合方法,以及更改某些方法的合同以不修改集合,但返回一个新的(例如IReadOnlySet<T> SymmetricExceptWith(IEnumerable<T> other);)。它并不完全“向后兼容”并且有很多委托,但界面很简单。 -
哇。可能值得重新检查您的架构,看看您是否可以找到一种更简单、更清洁的方法来完成您需要做的任何事情。但是在这种情况下,我什至不确定问题到底是什么。您不只是在为哈希密钥生成而苦苦挣扎吗?
-
@dr.mo 这不是
GetHashCode的问题(这很容易——使用ToString().GetHashCode()),而是在插入集合后项目集可以更改项目,并且此更改可能会导致重复的项目集。 -
如果你对开销没问题,只需在外部集合周围有一个包装类,它在 Add 上复制你的哈希集,并复制任何检索到的值。然后没有人可以修改已添加到您的外部集的哈希集。您需要对外部集进行哪些操作(在您的示例中为
x)?我只看到Add,但想必你想要contains、get等? -
@oliver 你能重写这个类,让它不可变吗?即,将整数列表作为构造函数的参数?
标签: c# set containers immutability