3

我在 C# 3.5 中创建一个应用程序,它使用 AutoCAD API 读取 2D AutoCAD 绘图,使用定义的业务逻辑对绘图进行更改,然后在 AutoCAD 中对其进行调整。由于逻辑的性质,必须重新构造图形的形状——例如,一个矩形由 4 条连接直线组成。

我正在使用 AutoCAD 中每条线的开始和结束坐标创建这些形状,但有些坐标并不完全匹配。例如,一个点可能在 0.69912839(在一个轴上),但从同一点开始的线可能是 0.69990821。这些以毫米为单位,因此距离很短(0.00078 毫米!)

我创建了自己的类(称为 MyPoint,类似于 PointF),因为我需要向它添加一些额外的逻辑。在那个类中,我创建了一个方法,它接受两个双精度值并根据两个点是否在 0.001 毫米内返回真或假。然后,我重写了 Equals 方法、== 和 != 运算符,因此我可以执行 (point1 == point2 或 point1.Equals(point2)) 检查所有轴是否彼此相距 0.001mm - 如果是,我将其归类为同一点。

这很好,工作出色。现在,我需要检查这些点类的集合以消除所有重复项,因此我在我的集​​合上使用 LINQ 的 Distinct() 方法。但是,此方法使用 GetHashcode(),而不是 Equals() 来确定实例是否相等。所以,我已经覆盖了 GetHashcode(),它使用了 double 类的 GetHashcode。

但是,上面的示例失败了,因为显然它们是不同的值,因此会生成不同的哈希码。有什么方法可以让两个相差在 0.001 以内的数字生成相同的哈希码?(请注意,这些数字彼此不知道,因为 GetHashcode 在不同的类实例上分别调用。)我尝试了许多适用于某些示例但不适用于其他示例的方法。

一个示例是将数字截断为 3dp(将其乘以 10^3,然后截断它)并在结果上创建哈希码 - 这适用于上述示例(699 == 699。)但这不适用于 0.69990821 和0.70000120 (699 != 700.) 我试过四舍五入,它适用于第二组数字 (0.700 == 0.700) 但不适用于第一组 (0.699 != 0.700。) 我什至尝试将数字截断为 3dp然后将其调整为下一个偶数,这适用于前面的两个示例,但不适用于 12.9809 和 12.9818 (12980 != 12982.)

有没有其他方法,或者我应该废弃 Equals、==、!= 和 GetHashcode 覆盖,并创建我自己的 MyPoint.IsEqualTo() 和 MyPointCollection.Distinct() 方法?

4

7 回答 7

3

我认为你不应该覆盖Equals(), ==,!=GetHashCode()

如果您覆盖其中任何一个,您应该确保它们的语义不会改变。在你的例子中,他们这样做了。

例如==,如果 P1 距离 P2 0.001 毫米,P2 距离 P3 0.001 毫米,P1 距离 P3 0.002 毫米,那么你不能传递它,那么 P1 == P2,P2 == P3 和 P1 == P3,即不是你想要的。一般来说,你最终得到的所有点都等于所有其他点。

我会坚持使用单独的方法来确定点是否足够接近。

编辑

通过您的覆盖,==您现在可以编写如下代码:

if(P1 == P2 && P2 == P3 && P1 != P3)
{
    // Code here gets executed
}
于 2010-02-12T09:37:05.320 回答
3

它无法编写正确的哈希码。证明一下:我们有 2 分。var a = point1.GetHashCode(); var b = point2.GetHashCode();

如果 a!= b 让在 point1 和 point2 之间创建点。等等。

在这样的操作之后,我们将创建线,其中每个点都靠近其他点,并且它们的哈希码将相同。所以 point1 和 point2 的哈希码应该相等。

所以像这样覆盖:

public override int GetHashCode()
{
    return 0;
}

并实施你平等。

于 2010-02-12T09:23:02.567 回答
2

删除对 Distinct 方法的依赖会更容易。实现一个 System.Collections.IComparer (或通用的等价物)并使用一个简单的集合,如列表。然后使用比较器确定该项目是否在列表中,如果已包含则不添加。

于 2010-02-12T09:45:22.700 回答
1

我应该废弃 Equals、==、!= 和 GetHashcode 覆盖,并创建自己的 MyPoint.IsEqualTo() 和 MyPointCollection.Distinct() 方法吗?

是的。

不过,它不一定是一些完全不同的数据结构。在检查重复项时,您需要检查相邻的哈希码,例如 (x+0.001,y)、(x,y-0.001) 等的哈希值。与通常的重复数据删除查找相比,这只是一个常数因子的减速,而且并不复杂,所以它可能是要走的路。(很明显的一点,但我还没有看到它在这里明确说明。)

更新:为了澄清,让我们看一下问题的一维版本。“点”是单个数字,x。我们认为 x1 和 x2 在 时匹配abs(x1 - x2) < .001。现在我们想知道 x 是否匹配 {x_0, ..., x_n} 中的任何一个。x_i 保存在哈希表中,其中hash(x) = h(floor(1000*x))一些函数 h() 用于传播事物。要查看 x 是否已经在表中,我们计算hash(x-.001)hash(x)hash(x+.001),然后我们测试 x 是否与三个桶中的任何一个 x_i 匹配。任何匹配的 x_i 都不能在任何其他存储桶中。

在二维变体中,有 9 个相邻的桶要检查(计算中间);在 3 维中,27。

于 2010-02-12T10:43:24.893 回答
1

我猜如果你总是返回相同的哈希值(比如 0),LinQ 会尝试将所有元素与equals. 毕竟,散列有助于证明两个元素是不同的,而不是相等的。

但无论如何,我建议您为此域使用更适合的结构和算法,例如二进制拆分分区 (BSP) 树(例如)。

于 2010-02-12T09:23:51.340 回答
1

这应该是对 Steck 和 Paolo 所说的更清晰的解释。

假设您确实设法以您想要的方式编写 GetHashCode 方法。

然后对于任何点ab,无论它们之间的距离如何,a.GetHashCode() == b.GetHashCode()

证明:假设a < b. a将之间的距离拆分b为小于 0.001 的段。即a0 = a,等a1 = a0 + 0.0005a2 = a1 + 0.0005,直到您到达b.

然后a.GetHashCode() == a1.GetHashCode() == a2.GetHashCode() == ... == b.GetHashCode()

于 2010-02-12T14:36:28.597 回答
0

这是一些代码来显示我在做什么。“原始”中的每对数字都应返回相同的值。

int tolerance = 3;
double[] original = new double[] {
0.69912839,
0.69990821,

0.69990821,
0.70000120,

12.980984087,
12.981808908
};
double[] modified = new double[original.Length];

for (int i = 0; i < original.Length; i++)
{
modified[i] = original[i];

/* Begin number adjustment logic */
modified[i] *= Math.Pow(10, tolerance);
modified[i] = Math.Truncate(modified[i]);

if (modified[i] % 2 != 0)
{
modified[i]++;
}
/* End number adjustment logic */

Console.WriteLine(modified[i]);

if (i % 2 != 0)
{
Console.WriteLine(string.Empty);
}
}

上述方法是“截断到 3dp 然后调整到最接近的偶数”方法。接下来是 truncate 方法(替换开始/结束注释之间的代码):

/* Begin number adjustment logic */
modified[i] *= Math.Pow(10, tolerance);
modified[i] = Math.Truncate(modified[i]);
/* End number adjustment logic */

这是圆形方法:

/* Begin number adjustment logic */
modified[i] = Math.Round(modified[i], tolerance);
/* End number adjustment logic */
于 2010-02-12T09:18:02.730 回答