什么是适用于 2D 点结构的“GetHashCode()”算法(避免冲突)

Posted

技术标签:

【中文标题】什么是适用于 2D 点结构的“GetHashCode()”算法(避免冲突)【英文标题】:What is an appropriate `GetHashCode()` algorithm for a 2D point struct (avoiding ***es) 【发布时间】:2011-07-10 10:25:27 【问题描述】:

考虑以下代码:

struct Vec2 : IEquatable<Vec2>

    double X,Y;

    public bool Equals(Vec2 other)
    
        return X.Equals(other.X) && Y.Equals(other.Y);
    

    public override bool Equals(object obj)
    
        if (obj is Vec2)
        
            return Equals((Vec2)obj);
        
        return false;
    

    // this will return the same value when X, Y are swapped
    public override int GetHashCode()
    
        return X.GetHashCode() ^ Y.GetHashCode();
    


除了比较双精度值是否相等(这只是演示代码)之外,我关心的是交换 X、Y 值时会出现哈希冲突。例如:

Vec2 A = new Vec2()  X=1, Y=5 ;
Vec2 B = new Vec2()  X=5, Y=1 ;

bool test1 = A.Equals(B);  // returns false;
bool test2 = A.GetHashCode() == B.GetHashCode() // returns true !!!!!

这应该会破坏字典集合。所以问题是如何为 2,3 甚至 4 个浮点值设置 GetHashCode() 函数的属性,以使结果不对称并且哈希不会冲突。

编辑 1:

Point 实现了不合适的x ^ y 解决方案,而PointF 包装了ValueType.GetHashCode()

Rectangle 有一个非常奇特的 (((X ^ ((Y &lt;&lt; 13) | (Y &gt;&gt; 19))) ^ ((Width &lt;&lt; 26) | (Width &gt;&gt; 6))) ^ ((Height &lt;&lt; 7) | (Height &gt;&gt; 25))) 哈希码表达式,它似乎按预期执行。

编辑 2:

'System.Double' 有一个很好的实现,因为它并不认为每一位都同等重要

public override unsafe int GetHashCode() //from System.Double

    double num = this;
    if (num == 0.0)
    
        return 0;
    
    long num2 = *((long*) &num);
    return (((int) num2) ^ ((int) (num2 >> 32)));

【问题讨论】:

可以尽量减少冲突,但是你的代码必须期待它们;它们总会发生 哈希 冲突 - int 的可能值范围比 double 小,与 doublexdouble 相比更小。此外,您可能需要考虑更宽松的相等比较 - 由于四舍五入,两个浮点值可能非常接近(任何人“通过眼睛”进行比较都会认为相等),但仍然不会完全相等。 不仅仅是 (A,B) 与 (B,A) 发生碰撞。由于 X^X --> 0 all (C,C) 与 all (D,D) 碰撞,这是一个更大的碰撞空间。 Create a hashcode of two numbers的可能重复 【参考方案1】:

Jon skeet 对此进行了介绍:

What is the best algorithm for an overridden System.Object.GetHashCode?

   public override int GetHashCode()
   
       unchecked // Overflow is fine, just wrap
       
           int hash = 17;
           // Suitable nullity checks etc, of course :)
           hash = hash * 23 + X.GetHashCode();
           hash = hash * 23 + Y.GetHashCode();
           return hash;
       
   

另外,将您的 Equals(object) 实现更改为:

return Equals(obj as FVector2);

但是请注意,这可能会认为派生类型是相等的。如果您不希望这样,则必须将运行时类型 other.GetType()typeof(FVector2) 进行比较(并且不要忘记空值检查)感谢您指出它是一个结构,LukH

Resharper 为相等和哈希码生成了很好的代码,所以如果你有 resharper,你可以让它做它的事情

【讨论】:

所讨论的类型是一个结构体,因此不能将Equals(object) 实现更改为使用as。 OP 当前的Equals(object) 方法没有任何问题。 这可能效果很好,但是如果您查看Double.GetHashCode() 的计算,您会注意到使用了对重要位的更仔细考虑,我想效仿。 感谢您的回复,因为我已经为许多结构实现了这个版本。 很高兴为您提供帮助!【参考方案2】:

哈希冲突不会对字典集合造成严重破坏。如果你不幸得到它们,它们会降低效率,但字典必须处理它们。

如果可能的话,冲突应该很少见,但这并不意味着实现不正确。由于您给出的原因(高冲突),XOR 通常很糟糕 - ohadsc 发布了我之前提供的示例作为替代方案,这应该没问题。

请注意,实现Vec2no 冲突是不可能的——GetHashCode 只有 232 个可能的返回值,但还有更多可能的 X 和 Y 值,即使在您删除了 NaN 和无限值之后...

Eric Lippert 在GetHashCode 上有一个recent blog post,您可能会发现它很有用。

【讨论】:

请注意,当字段是浮点数时,位不密集,但遵循某些模式,给定合理的数字以使结构保持。因此,如果 xor 操作避免了最高有效数字的对称性,它就足够了。这有意义吗? @ja72:可能。这取决于现实世界的数据。如果它们实际上往往是整数,那仍然很糟糕。【参考方案3】:

坐标的合理界限是什么?

除非它可以是所有可能的整数值,否则你可以简单地:

常量 SOME_LARGE_NUMBER=100000; 返回 SOME_LARGE_NUMBER * x + y;

【讨论】:

如果你知道界限,这真的是最好的。您也可以将它用于 ints 和 int.MaxValueunchecked 子句【参考方案4】:

如果哈希码的大小小于结构的大小,那么冲突是不可避免的。

【讨论】:

但是例如与 (X=1, Y=5) 和 (X=5, Y=1) 发生冲突是不可接受的。与 (3.287e308, -7,.228e175) 及其对称对的冲突更容易接受。【参考方案5】:

哈希码方法适用于整数坐标,但不建议用于浮点值。使用浮点坐标,可以通过使用排序的序列结构来创建点集/池。

有序序列是叶子版本的平衡二叉树。

这里的键是点坐标。

【讨论】:

您能否展示一些代码来解释您提到的内容。我很难想象它。我知道浮点精度会使GetHashCode() 不确定,但我不明白您打算如何解决这个问题。 忘掉 GetHashCode(),你不使用散列。刚刚使用了带有 xy 比较器的平衡二叉树。 我经常需要实现IEquatable&lt;&gt;,因此需要哈希来覆盖。能否请您显示一些代码,以便我们了解您在说什么。 顺便说一句,您可以使用快照网格转换为整数,然后使用这些函数之一获得没有冲突的散列。 dmauro.com/post/77011214305/… 以独特且确定的方式将两个整数映射为一个。 ***.com/questions/919612/…

以上是关于什么是适用于 2D 点结构的“GetHashCode()”算法(避免冲突)的主要内容,如果未能解决你的问题,请参考以下文章

在 GLM 中旋转、缩放和平移 2d 点

gl.texImage2D适用于Image,但不适用于ImageData

适用于 Android 和 iOS 的 2D 跨平台游戏引擎? [关闭]

寻找适用于 iOS 和 Android 的基本 2D/3D 图形的跨平台方法

使用适用于 iOS 的 Google Vision API 添加 2D 或 3D 人脸过滤器,例如 MSQRD/SnapChat

适用于移动设备的跨平台工具:什么时候有用?