在C#中实现一个可内容哈希的HashSet(比如python的`frozenset`)

时间:2013-09-20 08:57:38

标签: c# set containers immutability

摘要

我想在C#中构建一组项目集。内部项集具有由内容定义的GetHashCodeEquals方法。用数学符号表示:

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 具有虚拟AddRemove方法,因此覆盖它们将在派生类中起作用,但如果您执行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 中提到),但我最想了解设计这样的最佳方法。

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);
    }
}