从HashSet中获取原始值

4

更新: 从 .Net 4.7.2 开始,HashSet.TryGetValue - 文档 可用。
HashSet.TryGetValue - SO 帖子


我有一个与HashSet有关的问题,因为它不提供类似于Dictionary中已知的TryGetValue方法。而我需要这样的方法——传递要在集合中查找的元素,集合返回其集合中的元素(如果找到)。
旁注——“为什么你需要来自集合的元素,你已经有那个元素了吗?”不,我没有,相等和身份是两个不同的概念。
HashSet没有被密封,但它的所有字段都是私有的,所以从中派生是毫无意义的。我不能使用Dictionary,因为我需要SetEquals方法。我考虑过获取HashSet的源代码并添加所需的方法,但许可证并不真正是开源的(我可以查看,但我不能分发/修改)。我可以使用反射,但HashSet中的数组不是只读的,这意味着我不能将其绑定到每个实例生命周期一次的字段。
我不想为了单个类而使用完整的库。

目前我卡在了LINQ的SingleOrDefault上。所以问题是如何解决这个问题--是否使用带有TryGetValueHashSet


提供一个代码示例,展示你在哈希集合中的内容以及你想如何使用 TryGetValue 可能会有所帮助! - Trevor Pilley
3
如果TryGetValue在集合中找到了特定项,它会返回true/false,同时还会返回该项在集合中的值。 - Lasse V. Karlsen
在一个私有成员的HashSet周围定义自己的ISet<T>门面。 - Jodrell
为什么你想使用哈希集作为关键字,而标识很重要呢?你是要编写自己的内部机制或类似的东西吗?你是在使用 SetEquals 来与另一个集合或另一个集合进行比较吗? - Luaan
@Luaan,身份本身并不重要,我想指出的是,相等并不等同于身份。换句话说,我发现的元素与手头的元素相等,并不意味着这些元素是相同的。我需要使用SetEquals来检查这两个集合是否相等。 - greenoldman
显示剩余2条评论
5个回答

4

建议您将HashSet替换为SortedSet

SortedSet有一个简单的TryGetValue()方法:

public bool TryGetValue(ref T element)
{
    var foundSet = sortedSet.GetViewBetween(element, element);
    if(foundSet.Count == 1)
    {
        element = foundSet.First();
        return true;
    }
    return false;       
}

当调用该元素时,需要设置所有在比较器中使用的属性。它会返回在集合中找到的元素。


谢谢,这种方法也很好,可以加入我的“工具箱”中 :)。 - greenoldman

3

我同意这是基本缺失的功能。虽然它只在极少数情况下有用,但我认为这些情况很重要,尤其是关键规范化。

目前我只能想到一个建议,而且真的很糟糕。

您可以在创建HashSet<T>时指定自己的IEqualityComparer<T> - 因此创建一个记住执行最后一次正(即返回true)Equals比较的参数的相等性比较器。然后可以调用Contains,并查看要求相等比较器进行比较的内容。

注意事项:

  • 这会不必要地保留引用,因此可能会防止对象被垃圾回收
  • 您可能需要按线程对此进行操作(例如,如果您拥有一组在初始化后未经修改但随后由多个线程读取的集合)
  • 它假设 HashSet<T>不使用任何优化,例如“如果引用相等,则不要费心请教相等比较器”
  • 从根本上讲,这是一种可怕的滥用

我一直在努力考虑其他交集方案的替代方案,但迄今为止我还没有想到...

正如评论中所指出的那样,将其尽可能封装起来是值得的 - 我认为您只需要非常有限的一组操作,因此我会在自己的类中包装一个HashSet<T>并仅公开您真正需要的操作 - 这样您可以在每个操作后清除“缓存”,从而消除我上面提到的第一个问题。

对我来说,它仍然感觉像是一种可怕的滥用,但是...

正如其他人建议的那样,另一种选择是使用Dictionary<TKey, TValue>并自己实现SetEquals。这很容易做到 - 再次,您希望将其封装在自己的类型中。无论哪种方式,您都应该首先设计类型本身,然后使用HashSet<>Dictionary<,>作为实现细节进行实现。


谢谢!!!幸运的是,我不需要将这个暴露给其他人,并且我有单线程应用程序(到目前为止)。第三点很容易解决——如果有匹配项,并且比较器没有被触发,那么这意味着已经找到了传递给该方法的元素。我会保持我的问题一段时间,希望你不介意。 - greenoldman
1
如果您选择这条路,我建议将其封装在一个新的哈希集类型类中,以便公开有意义的方法。这将使未来更容易发现您当前的假设也会造成限制,例如不支持线程安全。您还可以通过始终要求比较器在使用后清除其缓存来减轻“保留不必要引用”的问题。换句话说,不要将 ask-hashset-check-comparer 代码散布在各个地方,将其封装起来。 - Lasse V. Karlsen
@LasseV.Karlsen:非常好的观点。我仍然不确定是否会真正提出使用这个方法,但至少封装它是一个很好的开始。我会编辑我的回答。 - Jon Skeet
@greenoldman:当然要保持问题的开放,并查看我的编辑和Lasse的评论以获得改进。它仍然是一个非常丑陋的hack :( - Jon Skeet
如果内存和CPU性能至关重要,这是一个不错的解决方案。否则最好远离它。虽然公平地说,HashSet本身也不是线程安全的,如果你将其封装在自己的类型中以隐藏滥用,它就是相对安全的。只是不要忘记修复所有那些对于HashSet参数具有不同行为的方法,否则你仍会失去性能。组合和继承总是让人头疼 :/ - Luaan
显示剩余2条评论

3
听起来你正在尝试使用错误的工具。确实,使用HashSet可以节省一些内存,但我觉得你正在尝试实现一个不同的目标:获取与表示相等的实际元素
因此,它们实际上是两个不同的元素。只有备忘录(唯一的表示)是相等的。
因此,最好使用一个字典,将您的元素添加为。这样,您就能够获得它(相同的元素),但会错过您的SetEquals...。
我想SetEquals在其实现中与顺序比较两个哈希集没有什么不同,并且在第一个非相等时失败。
因此,您可以同样好地使用一个简单的SequenceEqual()(LINQ)来比较两个Keys集合。
因此,这个扩展方法可以做到
public static SetEqual<T,G>(this IDictionary<T,G> d, IDictionary<T,G> e)
{
    return d.Keys.SequenceEqual(e.Keys);
}

这应该可行,因为Dictionary基本上是一个带有关联值的哈希集合。对于您的问题更加适用。(好吧,要准确的话,代码应该使用Dictionary<>而不是IDictionary<>,因为键的顺序很重要)
如果您需要在第二个参数上使用IEnumerable<>,尝试进行排序以获得定义好的顺序(效率不高)。

当你比较一个集合和另一个集合时,实现方式会有所不同——在这种情况下,它可以使用更直接和高效的比较方法。否则,SequenceEqual 或多或少是等价的。 - Luaan
4
SequenceEqual也会强制排序吗?两个包含相同元素数和容量的字典可能具有相同的键顺序,但如果它们具有不同的容量,则肯定不是这种情况。字典键的顺序是未记录的行为。 - Lasse V. Karlsen
你只是把一个问题换成了另一个问题,最终我仍然有问题。如何在字典的键(或值,因为在这种情况下它们是一对孪生)上高效地实现SetEquals - greenoldman
如上所述,集合中键或元素的顺序不保证也未记录。确实,容量会影响它(没有考虑过这一点)。因此,为了安全起见,唯一有效的检查将是ORDER + SequenceEquals。按哈希值排序可能是一个提示,但没有它就不太可能。我敢打赌,即使您实现自己的HashSet,验证Set-Identity也将是相同复杂性的问题。由于等式的N x N组合。因此,CS引入了一个顺序并进行检查的解决方案。=> O(N) - Robetto
根据您最后的评论,修改您的答案以显示需要对两组键施加某种排序:直接比较两个键序列是不正确的。 - ToolmakerSteve

2

1
希望不是盲目的,但我还没有在任何地方看到这个答案。如果你想要字典的 TryGetValue,你可以直接借鉴它。
theHashset.ToDictionary(item => item.ID).TryGetValue(key, out value)

你只需要一个快速的lambda表达式来确定唯一键。

整个主题都涉及到“字典”——Ctrl+F是你的好朋友。 - greenoldman
显然。我说过我没见过有人使用字典吗?没有。我说的是我没见过这个 ^^^ 的答案。啊,计算机科学的人们,一如既往地傲慢和自大。不得不爱他们。 - user7706940
你能否提供一些情况,说明这个答案可能是 OP 已经说过的“备用”解决方案 LINQ 的 SingleOrDefault 的有用替代品?编辑:我刚想到一个:如果在集合不被修改的时候,想要进行大量的 TryGetValue 测试,那么创建这个字典可能是有意义的,因为创建它的成本将分摊到所有这些使用中。 - ToolmakerSteve
@ greenoldman - 在我看来,其他答案使用字典的方式有所不同。在这里,建议是暂时创建一个字典以执行TryGetValue。因此,该字典不需要SetEquals。这可能是一个有用的建议(我怀疑任何人都会真正这样做),但它确实是一个新的建议。[一个相关的方法是同时维护HashSet和字典。] - ToolmakerSteve
@ToolmakerSteve,为什么你编辑了答案而不是在评论中添加内容呢?评论正是为此而设。或者直接回答问题。你不能被点赞、被问及,编辑的目的是为了修正错别字、澄清问题等。 - greenoldman
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接