我有一个Comparator<Foo>
,具有以下比较功能:
float d = o1.bar - o2.bar;
if (Math.abs(d) <= 0.001) {
return 0;
} else {
return d < 0 ? -1 : 1; // inline Math.copySign
}
基本上,这应该基于它们的Foo
属性来比较两个bar
,除非这些值足够接近,在这种情况下它们应该被声明为相等。 (这很重要,因为在此之后,我在另一个属性上进行另一种排序。)
但显然,这不是一个传递性比较器。如果Foo
s f1
f2
,f3
和bar
的值为1.999
2.000
,2.001
和{ {1}}分别根据我的比较器,f1==f2
和f2==f3
,但f1 != f3
。
调用sort(myListOfFoo, myFooComparator)
会产生“比较方法违反其总合同!”错误很少,但确定性。
如何使用Collections.sort(List, Comparator)
这样的比较器而不会产生此错误?
或者,有什么方法可以存储我的数据,让比较器正常工作?将每个浮点运算到最近的0.001
构造将是最简单的解决方案,除了Foo.bar
字段实际上是基于任意距离度量计算的,所以它并不那么简单。
实际代码是:
float d = metric.distance(vertex, o1)
- metric.distance(vertex, o2);
if (Math.abs(d) < threshold) {
return 0;
} else {
return d < 0 ? -1 : 1; // inline Math.copySign
}
其中o1
,o2
和vertex
是class Point { float x; float y; }
的实例,metric
是interface DistanceMetric { float distance(Point p1, Point p2); }
的实例。值得注意的是,即使按照标准欧几里德度量标准,这也是失败的。
答案 0 :(得分:2)
我担心Java 7排序实现不会容忍表现出不敏感的比较器。如果你想使用标准的Java SE排序API,你无能为力......
但实际上,在排序中使用阈值比较实际上在数学上是不正确的。
比较浮点值时的问题是它们通常不精确,然后计算通常会在结果中引入更多的小错误。当两个结果足够接近时,累积误差可能大于值之间的差异......意味着我们无法判断理想数字(没有误差)是否小于,等于或大于每个误差。我们通过比较使用阈值来将“接近等于”视为“平等”来解决这个问题。
当我们对值进行排序(即按顺序排列)时,需要以不同方式处理值中的错误问题。假设
我们有两个号码v1 ± e1
和v2 ± e2
,以及
当我们使用阈值比较比较数字时,阈值大于mod(e1) + mod(e2)
如果事实证明v1
和v2
足够接近mod(v1 - v2) < (mod(e1) + mod(e2))
那么无关紧要如果我们先放v1 ± e1
或者在订购中v2 ± e2
之后。如果我们观察两个数字的排序(在排序完成之后)通过使用阈值进行比较,它们将显示为“相等”,这是我们将它们放入的顺序。
因此,如果我们忽略错误并简单地使用精确比较对数字进行排序,我们就不会在不正确的顺序中放置任何数字对,只要我们在使用基于阈值的比较时能够辨别
现在假设我们有v1 ± e1
,v2 ± e2
和v3 ± e3
...和
mod(e1) + mod(e3)
比我们的门槛更高:
如果我们按上述顺序(使用精确比较),我们仍然会以正确的顺序结束数字。
如果我们使用“与阈值进行比较”来排序值(并允许排序实现!),我们最终可能会按照v3 ± e3
,v2 ± e2
和v1 ± e1
的顺序排列数字。 {v1 ± e1, v2 ± e2}
。我们{v2 ± e2, v3 ± e3}
和{v1 ± e3, v3 ± e3}
成对相等,但即使我们使用基于阈值的比较进行比较,我们也可能会错误地排序Comparator
!
最重要的是,你应该简单地实现你的sort
(用于排序目的!)以使用精确的比较。阈值比较对于此上下文是错误的。无论{{1}}算法如何编码,这都适用
答案 1 :(得分:0)
我猜你真正想要做的是删除重复值(按你的门槛),然后对其余值进行排序。为什么不首先根据非舍入值进行自然排序,然后根据阈值使用过滤。