为大致相似的数字生成相同的Hash码

时间:2010-02-12 09:11:31

标签: c# math coordinates autocad

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

我使用AutoCAD中每条线的起点和终点坐标创建这些形状,但有些坐标并不完全匹配。例如,一个点可以在0.69912839(在一个轴上),但是从同一点开始的线可以是0.69990821。这些都是mm,所以距离是微小的(0.00078mm!)

我已经创建了自己的类(称之为MyPoint,类似于PointF),因为我需要为它添加一些额外的逻辑。在那个类中,我创建了一个方法,它接受两个双精度并返回true或false,具体取决于两个点是否在彼此的0.001mm之内。我已经覆盖了Equals方法,==和!=运算符,所以我可以做(point1 == point2或point1.Equals(point2)),检查所有轴是否在彼此的0.001mm之内 - 如果它们是,我把它归类为同一点。

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

但是,上面的示例失败了,因为它们显然是不同的值,因此生成不同的哈希码。有两种方法可以生成相同的哈希码吗? (注意数字彼此不了解,因为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()方法?

7 个答案:

答案 0 :(得分:3)

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

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

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

所以过度这样:

public override int GetHashCode()
{
    return 0;
}

并实现你的平等。

答案 1 :(得分:3)

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

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

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

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

修改

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

if(P1 == P2 && P2 == P3 && P1 != P3)
{
    // Code here gets executed
}

答案 2 :(得分:2)

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

答案 3 :(得分:1)

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

但无论如何,我建议你为这个领域使用更合适的结构和算法,例如Binary Splitting Partition(BSP)树(例如)。

答案 4 :(得分: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都不能在任何其他存储桶中。

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

答案 5 :(得分:1)

这应该更清楚地解释Steck和Paolo所说的内容。

假设您确实设法以您希望的方式编写GetHashCode方法。

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

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

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

答案 6 :(得分: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 */