IEquatable<Point3D>在容差范围内,如何实现 GetHashCode() [英] IEquatable&lt;Point3D&gt; within a tolerance, how to implement GetHashCode()

查看:22
本文介绍了IEquatable<Point3D>在容差范围内,如何实现 GetHashCode()的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个 Point3d 结构,它以下列方式实现 IEquatable:

public override bool Equals(object obj) {返回 obj 是 Point3d p &&等于(p);}公共布尔等于(Point3d 其他){返回等于(其他,Tolerance.ToleranceDecimal);}公共布尔等于(Point3d 其他,双容差){if (tolerance <0) throw new ArgumentOutOfRangeException(nameof(tolerance), tolerance, "预期容差大于或等于 0");返回 Math.Abs​​(X - other.X) <= 容差 &&Math.Abs​​(Y - other.Y) <= 公差 &&Math.Abs​​(Z - other.Z) <= 公差;}公共覆盖 int GetHashCode() {无功哈希= 17;散列 = 散列 * 23 + X.GetHashCode();hash = hash * 23 + Y.GetHashCode();hash = hash * 23 + Z.GetHashCode();返回哈希值;}public static bool operator ==(Point3d firstPoint, Point3d secondPoint) {返回 firstPoint.Equals(secondPoint);}公共静态布尔运算符 !=(Point3d firstPoint, Point3d secondPoint) {返回 !(firstPoint == secondPoint);}

这已经在应用程序中大量使用,期望检查两点之间的相等性允许容差(这是实现正确工作所必需的).

如果我注意到 EqualsGetHashCode 方法不一致,并且确实不可能将 GetHashCode 写入一种可以产生良好和一致结果的形式.这个问题在使用 Linq 查询的情况下尤其成问题,例如 points.Distinct() 因为如果直接比较,结果点可能被认为是相等的,例如 points[0] == 点[1]

我个人认为最好的选择是将 Equals 更改如下,使其行为与 GetHashCode 一致:

public bool Equals(Point3d other) {返回等于(其他,0);}

然而,由于这已经在应用程序中大量使用,这将是一个重大的突破性变化.我认为这是错误的做法,但我正在考虑将 GetHashCode 更改为:

public override int GetHashCode() {返回0;}

我的理解是,以上将强制使用 Equals 方法,这会导致性能下降,但也允许在 Linq 查询中将容差内的点视为相等.我想知道这是否会让我面临任何其他潜在的陷阱.

我不确定还有哪些其他途径可供我使用,因此我非常想寻求有关解决此问题的最佳方法的建议.

提前致谢!

解决方案

苦涩的事实是,你不能以任意的容忍度实现正确的Equals.等于(参见https://msdn.microsoft.com/en-us/library/336aedhh(v=vs.100).aspx 了解详情)必须是 transitive(x.Equals(y) && y.Equals(z)) 返回 true 如果且仅if x.Equals(z) 返回 true.

这里我们可以为给定的Tolerance.ToleranceDecimal创建一个反例:

 Point3d x = new Point3d(-Tolerance.ToleranceDecimal * 2.0/3.0, 0, 0);Point3d y = 新 Point3d(0, 0, 0);Point3d z = new Point3d(Tolerance.ToleranceDecimal * 2.0/3.0, 0, 0);

如你所见

 x.Equals(y) == truey.Equals(z) == 真

但是

 x.Equals(z) == false

由于Equals 实现不正确,我们无法创建相应的GetHashCode,除了degeneration(和无用)

 public override int GetHashCode() {返回0;}

因为如果 x.Equals(y) == 真.在我们的例子中: let x y = x + N * 容差

 x 等于x + 容差/2.0 等于x + 容差/2.0 * 2 等于x + 容差/2.0 * 3 等于...x + 容差/2.0 * 2 * N 等于是

这意味着对于任何任意 xy 和非零容忍度 GetHashCode 必须为任何参数返回相同的值.>

I have a Point3d struct which implements IEquatable<Point3d> in the following manner:

public override bool Equals(object obj) {
    return obj is Point3d p && Equals(p);
}

public bool Equals(Point3d other) {
    return Equals(other, Tolerance.ToleranceDecimal);
}

public bool Equals(Point3d other, double tolerance) {
    if (tolerance < 0) throw new ArgumentOutOfRangeException(nameof(tolerance), tolerance, "Expected a tolerance greater than or equal to 0");
    return Math.Abs(X - other.X) <= tolerance && Math.Abs(Y - other.Y) <= tolerance && Math.Abs(Z - other.Z) <= tolerance;
}

public override int GetHashCode() {
    var hash = 17;
    hash = hash * 23 + X.GetHashCode();
    hash = hash * 23 + Y.GetHashCode();
    hash = hash * 23 + Z.GetHashCode();
    return hash;
}

public static bool operator ==(Point3d firstPoint, Point3d secondPoint) {
    return firstPoint.Equals(secondPoint);
}

public static bool operator !=(Point3d firstPoint, Point3d secondPoint) {
    return !(firstPoint == secondPoint);
}

This is already heavily used within an application with the expectation that checking equality between two points allows for a tolerance (which is necessary for the implementation to work correctly).

If has come to my attention that the Equals and GetHashCode methods are not consistent, and indeed it is not possible to write GetHashCode in a form that would produce good and consistent results. This issue is particularly problematic in situations where a Linq query is used, such as points.Distinct() as the resulting points may be considered Equal if comparing directly, such as points[0] == points[1]

Personally I believe the best option would be to change Equals as follows, such that it's behaviour is consistent with GetHashCode:

public bool Equals(Point3d other) {
    return Equals(other, 0);
}

However, as this is already heavily utilised within an application this would be a significant breaking change. I believe it is the wrong thing to do, but I'm considering changing GetHashCode to:

public override int GetHashCode() {
    return 0;
}

My understanding is that the above would force the Equals method to be utilised which would result in a performance hit, but also allow for points within a tolerance to be considered equal within Linq queries. I would like to know if this opens me up to any other potential pitfalls.

I'm unsure as to what other avenues may be available to me, so am very much looking for advice as to the best approach to resolve this issue.

Thanks in advance!

解决方案

The bitter truth is that you can't implement correct Equals with arbitrary tolerance. Equals (see https://msdn.microsoft.com/en-us/library/336aedhh(v=vs.100).aspx for details) must be transitive i.e. (x.Equals(y) && y.Equals(z)) returns true if and only if x.Equals(z) returns true.

And here we can create a counter example for given Tolerance.ToleranceDecimal:

 Point3d x = new Point3d(-Tolerance.ToleranceDecimal * 2.0 / 3.0, 0, 0);
 Point3d y = new Point3d(0, 0, 0);
 Point3d z = new Point3d(Tolerance.ToleranceDecimal * 2.0 / 3.0, 0, 0);

As you can see

 x.Equals(y) == true
 y.Equals(z) == true

But

 x.Equals(z) == false

Since Equals implementation is incorrect we can't create corresponding GetHashCode, except degenerated (and useless)

 public override int GetHashCode() {
   return 0;
 }

because GetHashCode must return the same value for x and y if x.Equals(y) == true. In our case: let x < y and y = x + N * tolerance

 x equals to 
 x + tolerance / 2.0 equals to
 x + tolerance / 2.0 * 2 equals to
 x + tolerance / 2.0 * 3 equals to
 ...
 x + tolerance / 2.0 * 2 * N equals to
 y

which means that for any arbitrary x and y and non-zero tolerance GetHashCode must return the same value for any argument.

这篇关于IEquatable<Point3D>在容差范围内,如何实现 GetHashCode()的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆