【问题标题】:Implementing a content-hashable HashSet in C# (like python's `frozenset`)在 C# 中实现内容可散列的 HashSet(如 python 的`frozenset`)
【发布时间】:2013-09-25 14:21:22
【问题描述】:

简要总结

我想在 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!

据我所知,我有两个选择:我可以从HashSet 派生ContentHashableHashSet,但是我需要让它让所有修饰符都抛出异常。缺少一个修饰符可能会导致一个潜在的错误。

或者,我可以使用封装和类ContentHashableHashSet 可以包含readonly HashSet。但是我需要重新实现所有设置方法(修饰符除外),以便ContentHashableHashSet 的行为类似于HashSet。据我所知,扩展不适用。

最后,我可以像上面那样封装,然后通过返回 const(或只读?)HashSet 成员来实现所有类似集合的功能。

事后看来,这让人想起 python 的frozenset。有谁知道在 C# 中实现这一点的设计良好的方法?

如果我能够失去ISet 功能,那么我将简单地创建一个排序的ImmutableList,但是我会失去快速联合、快速交叉和次线性等功能(大约 O(log(n) ) ) 使用Contains 设置成员资格。

编辑: 基类 HashSet 有虚拟的 AddRemove 方法,因此覆盖它们将在派生类中起作用,但 >不如果你执行HashSet&lt;int&gt; set = new ContentHashableHashSet&lt;int&gt;();。转换为基类将允许编辑。

编辑 2:感谢 @xanatos 推荐一个简单的 GetHashCode 实现:

计算 GetHashCode 的最简单方法是简单地异或 (^) 元素的所有 gethashcode。 xor 运算符是可交换的,因此排序无关紧要。对于比较,您可以使用 SetEquals

编辑 3: 最近有人分享了有关 ImmutableHashSet 的信息,但由于此类是密封的,因此无法从它派生并覆盖 GetHashCode

我还被告知HashSetIEqualityComparer 作为参数,因此这可用于提供不可变的内容哈希集,而无需派生自 ImmutableHashSet;但是,这不是一个非常面向对象的解决方案:每次我想使用ContentHashableHashSet 时,我都需要传递相同的(重要的)参数。我相信你知道,这真的会对你的编码禅宗造成严重破坏,而我将在 python 中与myDictionary[ frozenset(mySet) ] = myValue 一起飞行,我将被困在做同样的事情again and again and again

感谢您提供的任何帮助。我有一个临时的解决方法(上面的 EDIT 1 中提到了它的问题),但我主要想了解设计这样的东西的最佳方法。

【问题讨论】:

  • 我在这种情况下所做的是创建IReadOnlySet&lt;T&gt; : IReadOnlyCollection&lt;T&gt; 接口,我将在其中重新声明非可变集合方法,以及更改某些方法的合同以不修改集合,但返回一个新的(例如IReadOnlySet&lt;T&gt; SymmetricExceptWith(IEnumerable&lt;T&gt; other); )。它并不完全“向后兼容”并且有很多委托,但界面很简单。
  • 哇。可能值得重新检查您的架构,看看您是否可以找到一种更简单、更清洁的方法来完成您需要做的任何事情。但是在这种情况下,我什至不确定问题到底是什么。您不只是在为哈希密钥生成而苦苦挣扎吗?
  • @dr.mo 这不是GetHashCode 的问题(这很容易——使用ToString().GetHashCode()),而是在插入集合后项目集可以更改项目,并且此更改可能会导致重复的项目集。
  • 如果你对开销没问题,只需在外部集合周围有一个包装类,它在 Add 上复制你的哈希集,并复制任何检索到的值。然后没有人可以修改已添加到您的外部集的哈希集。您需要对外部集进行哪些操作(在您的示例中为x)?我只看到Add,但想必你想要containsget等?
  • @oliver 你能重写这个类,让它不可变吗?即,将整数列表作为构造函数的参数?

标签: c# set containers immutability


【解决方案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);
    }
}

【讨论】:

  • +1 优雅。这与我的想法一致。它确实解决了特定的集合问题,但我可以在制作 Dictionary&lt;HashSet, ...&gt; 时重用代码吗?
  • 你可以用同样的思路来实现IDictionary&lt;HashSet&lt;T&gt;,U&gt;。您可以将 HashSetComparer 移到类之外并共享它 - 它没有理由在 SetOfSets 类中是私有的。不知道还有什么可以重用 - 您只是通过将调用传递给私有的Dictionary 来实现IDictionary 成员,但要确保在输入(例如添加、[])或输出的过程中复制任何哈希集(例如 Keys、Trygetvalue、[])
  • 啊,这就是我所缺少的:如果不重新实现所有这些方法并使用 setOfSets 调用适当的方法,我将无法使用 .UnionWith 等?
  • 你想在你的哈希集(内部集)上使用 UnionWith 吗?还是您的 SetOfSets/DictionaryOfSets(外集)?您只需要提供外部集合的实现。您可以在内部哈希集上使用所有现有哈希集功能不变。但是请注意,如果您将 X 添加到 SetOfSets,然后通过执行 UnionWith 修改 X,则 SetOfSets 中的 X 副本不会更改。您只修改了您持有的版本。从这个意义上说,SetOfSets 中 X 的副本实际上是不可变的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-07-14
  • 2010-12-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-15
  • 2019-07-31
相关资源
最近更新 更多