实现在C#中的内容哈希的HashSet的(如Python的`frozenset`)(Implemen

2019-11-02 12:31发布

小结

我想建立一套套在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() (outputing包含在排序顺序元素的字符串),然后再使用使用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!

据我所看到的,我有两个选择:我可以得出ContentHashableHashSetHashSet ,但后来我需要让这个所有的调节器抛出异常。 缺少一种调节剂可能会导致一个阴险的bug。

或者,我可以使用封装和类ContentHashableHashSet可以包含一个readonly HashSet 。 但后来,我需要重新实现所有设置方法(除了修饰),以便ContentHashableHashSet可以表现得像一个HashSet 。 据我所知,扩展将不适用。

最后,我可以如上封装,然后所有的设置一样的功能将通过返回的常量发生(或只读?)HashSet的成员。

事后看来,这让人想起了Python的的frozenset 。 有谁知道一个精心设计的方式在C#来实现这一点?

如果我能失去ISet功能,那么我会简单地创建一个有序ImmutableList ,但后来我想快结合,快速路口和子线(大约为O(log(n))的)设置会员与丧失功能Contains

编辑:基类的HashSet 具有虚拟AddRemove的方法,所以它们覆盖将派生类内工作,但如果执行将无法正常工作HashSet<int> set = new ContentHashableHashSet<int>(); 。 铸造基类将允许编辑。

编辑2:感谢@xanatos用于推荐简单GetHashCode实现:

计算GetHashCode的最简单的方法是简单地异或(^)元素的所有gethashcodes。 XOR运算符是可交换的,所以排序是无关紧要的。 为了比较,你可以使用SetEquals

编辑3:最近有人分享有关信息ImmutableHashSet ,但由于这个类是密封的,这是不可能从中派生并重写GetHashCode

我还被告知, HashSet接受一个IEqualityComparer作为参数,所以这可以被用来提供一个不可变的,内容哈希的集合,而不从ImmutableHashSet获得; 然而,这是不是一个非常面向对象的解决方案:我想使用每次ContentHashableHashSet ,我就需要通过相同的(非平凡)的说法。 正如我敢肯定,你知道,这真的可以肆虐你的编码禅宗,并在那里我会沿着蟒蛇同飞myDictionary[ frozenset(mySet) ] = myValue ,我会被卡住做同样的事情一而再,再而再次 。

感谢您的任何帮助,您可以提供。 我有一个临时的解决方法(其问题在上面编辑1被提及),但我大多喜欢了解设计这样的事情的最好方法。

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


文章来源: Implementing a content-hashable HashSet in C# (like python's `frozenset`)