【问题标题】:Using C# HashSet to solve problems where equal is not equal使用C#HashSet解决equal不等于的问题
【发布时间】:2012-02-04 15:37:25
【问题描述】:

我基于我最近发现的关于Dictionary 的性能特征,所以我使用Dictionary<type, bool>,其中bool 被忽略,但据说我可以使用HashSet

例如:

Dictionary<bounds, bool> overlap;

class bounds
{
    public float top_left_x, top_left_y, width, height;

    public bool equal(bounds other)
    {
        return upper_left_x + width > other.upper_left_x &&
        upper_left_x < other.upper_left_x + other.width &&
        upper_left_y + height > other.upper_left_y &&
        upper_left_y < other.upper_left_y + other.height;
    }

    public ... GetHashCode()
    {
        ...;
    }
}

这里我没有使用 equal 来检查是否相等,而是用于重叠,这在其他地方肯定会很烦人,但我这样做是有原因的。

我假设如果一个值可以在 O(1) 时间内从一个键中查找到,那么一个键也可以从它自身中查找到。

所以我大概可以将数千个边界重叠并这样做:

overlap.ContainsKey(new bounds(...));

在 O(1) 时间内找出给定边界是否与集合中的任何其他边界重叠。

我也想知道如果我改变边界的 (x, y) 位置会发生什么,大概这就像删除然后再次将其添加到集合中一样,性能明智,非常昂贵?

我在 GetHashCode 函数中添加了什么?

目标

如果这可行,那么我在使用这种机制来找出给定边界重叠的其他边界之后。

在这个系统中移动的边界很少,并且在填充集合后不会添加新的边界。新添加的边界需要能够与旧边界重叠。

结论

有关详细信息,请参阅下面的反馈。

总而言之,不可能达到 O(1) 的性能,因为与默认的 equals 不同,重叠检查不是传递性的。

然而,区间树是一个很好的解决方案。

【问题讨论】:

  • 如果您尝试添加与任何已插入的边界重叠的边界,这将不起作用。但是,如果您创建一个执行重叠检查的派生类(假设您创建了一个好的哈希码)
  • 谢谢。他们确实需要能够做到。你能给出一个例子吗?

标签: c# object hash dictionary hashset


【解决方案1】:

您不能使用DictionaryHashSet 来检查边界是否重叠。为了能够使用字典(或哈希集),您需要满足以下属性的 Equals()GetHashCode() 方法:

  1. Equals() 方法是 equivalence relation
  2. a.Equals(b) 必须暗示 a.GetHashCode() == b.GetHashCode()

你不能满足这两个要求,所以你必须使用另一个数据结构:An Interval tree

【讨论】:

    【解决方案2】:

    相等关系是完全错误的关系,因为相等关系必须是等价关系。也就是说,它必须是自反的——对于任何 A,A == A。它必须是对称的——A == B 意味着 B == A。而且它必须是可传递的 -- 如果 A == B 且 B == C 则 A == C。

    您提议违反传递性; “重叠”不是传递关系,因此“重叠”不是等价关系,因此 您不能将相等定义为重叠

    与其尝试做这种危险的事情,不如解决真正的问题。您的目标显然是采用一组区间,然后快速确定给定区间是否与这些区间中的任何一个重叠。您想要的数据结构称为区间树专门针对解决该问题进行了优化,因此请使用它在任何情况下,您都不应尝试将哈希集用作区间树。使用正确的工具来完成这项工作:

    http://wikipedia.org/wiki/Interval_tree

    【讨论】:

    • 我不喜欢创建广告与行为不同的东西,例如称为 equals 但实际上做不同的事情。我这样做只是为了利用 O(1) 性能方面,在短时间内有很多界限需要检查。问题是,无论如何,这可能是不可能的。最终目标是知道哪些 2D 区间与给定的 2d 区间重叠。感谢间隔树的想法。我以前没听说过这些。有没有内置在 C# 中?
    • @alan2here:O(1) 行为为基础,相等是一种等价关系。哈希集算法的全部意义在于它利用了相等性始终是等价关系这一事实来获得良好的性能。如果您违反了该要求,那么结果将是错误的或缓慢的。
    • @alan2here:二维矩形重叠是一维区间重叠的直接扩展。您可以使用嵌套区间树解决二维问题(或 n-d 问题);有关草图,请参见维基百科页面。
    • @alan2here:顺便说一下,如果您有兴趣解决一个相关问题——如何在高维向量空间中与一组点进行最接近的匹配——请参阅我的文章,其中概述了如何就这样:blogs.msdn.com/b/ericlippert/archive/tags/…。如果您对违反传递性或其他相等性和排序关系的属性可能导致错误的方式感兴趣,请参阅我从这里开始的系列文章:blogs.msdn.com/b/ericlippert/archive/2011/01/20/…
    • 我来晚了,但如果有人对高维空间中的最接近匹配感到好奇......想想高维立方体:)
    【解决方案3】:

    如果您使用我上面提到的派生类方法,则需要以下内容:

    public class Bounds
    {
        public Point position;
        public Point size; // I know the width and height don't really compose
                           // a point, but this is just for demonstration
    
        public override int GetHashCode(){...}
    }
    
    public class OverlappingBounds : Bounds
    {
        public override bool Equals(object other)
        {
            // your implementation here
        }
    }
    
    // Usage:
    if (_bounds.ContainsKey(new OverlappingBounds(...))){...}
    

    但由于 GetHashCode() 方法需要始终返回相同的值,因此运行时复杂度很可能为 O(n) 而不是 O(1)。

    【讨论】:

    • 哈哈,好吧,那对我没那么有用。感谢您提供答案。我希望有人会觉得它有帮助。
    【解决方案4】:

    您无法保证 O(1) 在您自定义 hashcode calculation 的字典上的性能。如果我在 GetHashCode() 方法中放入一些 WebService 请求,它应该为我控制两个提供的项目的相等性,很明显时间永远不会像预期的那样是 O(1)。好的,这是一种“边缘案例”,但只是为了给出一个想法。

    通过以您认为可行的方式进行操作(假设这甚至是可能的),imo,您否定了Dictionary&lt;K,V&gt; 提供的好处,因此在大集合上也有恒定的密钥恢复时间.

    需要根据你拥有的合理数量的物体来衡量,但我会先尝试使用 List&lt;T&gt; 像一个对象持有者,并制作这样的东西:

    var bounds = new List<Bound> {.... initialization... }
    Bound providedBound = //something. Some data filled in it. 
    var overlappedany = bounds.Any<Bound>(b=>return b.Equals(providedBound));
    

    【讨论】:

    • 您给出的示例是否不必检查列表中的每个边界,将 O(1) 时间分解为最多 O(n)?另外我不知道我的哈希码计算是什么,但相等计算是一个常数时间,与默认相等检查相同。
    • O(1) 表示“恒定时间”;这并不意味着“快”。所以 O(1) 通常比 O(N) 好,但并非总是如此。您的示例就是这样一种情况,但这不是字典未能提供 O(1) 性能的示例。一个更好的例子是始终返回相同值的 GetHashCode() 实现。
    • @alan2here:我说的是,当您覆盖 Equality/HashCodeCalc 时,您基本上会破坏来自 Dictionary 的 O(1) 恢复规则,在这种情况下,值得查看像这样的集合占位符。那是。这不是关于“好或坏”,而是关于“更好和更坏”。
    【解决方案5】:

    这里我没有使用 equal 来检查相等性,而是用于重叠,这在其他地方肯定会很烦人,但我这样做是有原因的。

    我假设这意味着您将遇到 A.Equals(B) 为真,B.Equals(C) 为真,但 A.Equals(C) 为假的情况。换句话说,你的 Equals 不是传递的。

    这违反了 Equals() 的规则,因此 Dictionary 将不适合您。 Equals/GetHashCode的规则是(来自http://msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx):

    如果两个对象比较相等,则每个对象的 GetHashCode 方法必须返回相同的值。

    如果您的 Equals 不具有传递性,那么您不可能编写有效的 GetHashCode。

    【讨论】:

    • 你可以写一个 valid (如果你只返回 0 一切都会很好)...但是一个 good 是完全不可能的.
    • @Tobias o.O,这不会导致 Dictionary/HashSet 成为一个美化的列表,它可能在做它想做的事情时甚至更不优化?
    • :¬( 是否有某种方法可以使用类似的方法实现相同的行为和性能特征,但与我描述的不太一样?
    • @Alxandr,是的,它会导致哈希集作为链表执行。结论:OP可以做他想做的事,但他不应该
    • @alan2here:遵循逻辑。 (1) 如果 A 等于 B 则 A.GHC()==B.GHC() 根据 GetHashCode 的规则必须为真。您可能会遇到 A 等于 B,B 等于 C 但 A 不等于 C 的情况。根据刚才所述的规则,A.GHC()==B.GHC() 和 B.GHC() == C.GHC( ),因此 A.GHC()==C.GHC(),即使 A 不等于 C。遵循这一点得出其合乎逻辑的结论:对 GHC 的每次调用都必须返回相同的数字。由于每次调用都返回相同的数字,而哈希表的性能取决于结果是否分布良好,因此性能会很差。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-10-10
    • 2016-08-17
    • 2019-03-27
    • 2016-04-25
    • 1970-01-01
    • 1970-01-01
    • 2021-10-19
    相关资源
    最近更新 更多