为双打创建一个HashSet

时间:2013-04-14 09:34:56

标签: java double hashset

我希望使用定义的容差(HashSet)为实数(目前Double s)创建epsilon,(cf Assert.assertEquals(double, double, double)
由于使用Double.equals()仅适用于完全相等,Double是最终类,我无法使用它。我最初的想法是使用HashSet方法扩展DoubleHashSet(例如转到setEpsilon(double))并创建一个新的类ComparableDouble,其中equals()使用DoubleHashSet中的此值{1}}。但是,我想检查是否已有现有的解决方案和现有的F / OSS库。

(将来我想把它扩展到实数的元组 - 例如矩形和立方体 - 所以最好采用通用的方法

注意:@NPE表示这是不可能的。不幸的是我怀疑这是正式正确:-)所以我想知道是否有近似的方法......其他人一定有这个问题并且大致解决了。 (我已经经常使用工具Real.isEqual(a, b, epsilon)并且它非常有用。)我准备接受一些不常见的传递性错误。

注意:我将使用TreeSet,因为它解决了“几乎等于()”的问题。稍后我将比较complexNumbers,矩形(和更复杂的对象),并且能够设置两个相等的限制是非常有用的。 complexNumbers没有简单的自然排序(也许Cantor方法可行),但我们可以判断它们是否几乎相等。

3 个答案:

答案 0 :(得分:4)

这种方法存在一些根本性缺陷。

HashSet使用equals()检查两个元素是否相等。 The contract on equals() has the following among its requirements

  

transitive :对于任何非空引用值xyz,如果x.equals(y)返回{{1} }和true返回y.equals(z),然后true应该返回x.equals(z)

现在考虑以下示例:

true

很明显,您提出的比较方案会打破传递性要求(x = 0.0 y = 0.9 * epsilon z = 1.8 * epsilon 等于xy等于y,但z没有&#39 ; t等于x)。在这些情况下,z无法正常运行。

此外,HashSet会产生额外的挑战,原因如下requirement

  

如果两个对象根据hashCode()方法相等,则在两个对象中的每一个上调用equals(Object)方法必须产生相同的整数结果。

使用hashCode代替hashCode()可以回避TreeSet要求。

答案 1 :(得分:1)

我会做的是在使用它们之前围绕双打(假设这是合适的)

e.g。

public static double roundByFactor(double d, long factor) {
    return (double) Math.round(d * factor) / factor;
}

TDoubleHashSet set = new TDoubleHashSet(); // more efficient than HashSet<Double>
set.add(roundByFactor(1.001, 100));
set.add(roundByFactor(1.005, 100));
set.add(roundByFactor(1.01, 100));
// set has two elements.

您可以在自己的DoubleHashSet中包装此行为。如果要保留原始值,可以使用HashMap或TDoubleDoubleHashMap,其中键是舍入值,值是原始值。

答案 2 :(得分:0)

我实施了@ NPE的方法(我接受了他/她的答案,所以他/她得到了点数:-)并在这里给出了代码

//Create a comparator:
public class RealComparator implements Comparator<Double> {

    private double epsilon = 0.0d;

    public RealComparator(double eps) {
        this.setEpsilon(eps);
    }

    /**
     * if Math.abs(d0-d1) <= epsilon  
     * return -1 if either arg is null
     */
    public int compare(Double d0, Double d1) {
        if (d0 == null || d1 == null) {
            return -1;
        }
        double delta = Math.abs(d0 - d1);
        if (delta <= epsilon) {
            return 0;
        }
        return (d0 < d1) ? -1 : 1;
    }

    /** set the tolerance
     * negative values are converted to positive
     * @param epsilon
     */
    public void setEpsilon(double epsilon) {
        this.epsilon = Math.abs(epsilon);
    }

并测试它

public final static Double ONE = 1.0;
public final static Double THREE = 3.0;

@Test
public void testTreeSet(){
    RealComparator comparator = new RealComparator(0.0);
    Set<Double> set = new TreeSet<Double>(comparator);
    set.add(ONE);
    set.add(ONE);
    set.add(THREE);
    Assert.assertEquals(2, set.size());
}
@Test
public void testTreeSet1(){
    RealComparator comparator = new RealComparator(0.0);
    Set<Double> set = new TreeSet<Double>(comparator);
    set.add(ONE);
    set.add(ONE-0.001);
    set.add(THREE);
    Assert.assertEquals(3, set.size());
}
@Test
public void testTreeSet2(){
    RealComparator comparator = new RealComparator(0.01);
    Set<Double> set = new TreeSet<Double>(comparator);
    set.add(ONE);
    set.add(ONE - 0.001);
    set.add(THREE);
    Assert.assertEquals(2, set.size());
}
@Test
public void testTreeSet3(){
    RealComparator comparator = new RealComparator(0.01);
    Set<Double> set = new TreeSet<Double>(comparator);
    set.add(ONE - 0.001);
    set.add(ONE);
    set.add(THREE);
    Assert.assertEquals(2, set.size());
}