我在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()方法?
答案 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方法。
然后对于任何点a
和b
,无论它们之间的距离是什么,a.GetHashCode() == b.GetHashCode()
。
证明:假设a < b
。将a
和b
之间的距离拆分为小于0.001的细分。即a0 = a
,a1 = a0 + 0.0005
,a2 = 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 */