2

I know it has been asked and answered millions of times but still I am unable to figure out why I am receiving with the violation during sort. Here is my code:

Collections.sort(sorted, new Comparator<MyObject>() {
    @Override
    public int compare(MyObject m1, MyObject m2) {
        // Actual energy comparison :-
        // THE higher the energy, the earlier in the list
        float delta = m1.getTotalEnergy() - m2.getTotalEnergy();

        if (delta > 0) {
            return 1;
        } else if (delta < 0) {
            return -1;
        } else {
            return 0;
        }
    }
});

and I receive this error

java.lang.IllegalArgumentException: Comparison method violates its general contract!  
        at java.util.TimSort.mergeHi(TimSort.java:895)  
        at java.util.TimSort.mergeAt(TimSort.java:512)  
        at java.util.TimSort.mergeForceCollapse(TimSort.java:453)  
        at java.util.TimSort.sort(TimSort.java:250)  
        at java.util.Arrays.sort(Arrays.java:1512)  
        at java.util.ArrayList.sort(ArrayList.java:1454)  
        at java.util.Collections.sort(Collections.java:175)

Any ideas ?

user2864740
  • 60,010
  • 15
  • 145
  • 220
math_law
  • 391
  • 1
  • 4
  • 16
  • 2
    Are any other threads changing the value of totalEnergy while the sort is running? – David Xu Jan 17 '15 at 21:03
  • possible duplicate of ["Comparison method violates its general contract!"](http://stackoverflow.com/questions/8327514/comparison-method-violates-its-general-contract) – Mr. Polywhirl Jan 17 '15 at 21:07
  • @DavidXu nope single Thread is processing the during this exception. – math_law Jan 17 '15 at 21:07
  • 6
    You could just use: return Float.compare(m1.getTotalEnergy(), m2.getTotalEnergy()); – Atuos Jan 17 '15 at 21:08
  • I would advise you check for **`null`** objects, before you start comparing properties. – Mr. Polywhirl Jan 17 '15 at 21:11
  • @Mr.Polywhirl I suspect there are NaN values. Do I have to use the following code then ? if (value < other.value) return -1; else if (value >= other.value) return 1; else return 0; – math_law Jan 17 '15 at 21:13
  • @Mr.Polywhirl , without null check , would not I get "NullPointerException" instead of sort violation ? – math_law Jan 17 '15 at 21:15
  • 1
    I have copied this code and it does its job without any problems. Can you please show us the input data and type of the "sorted" collection? Like a minimum reproducible sample? – Rafal G. Jan 17 '15 at 21:19
  • @R4J Thanks. the collection being sorted is ArrayList I cant give you a small data. The reason is I also dont get any errors during a small set. The problem occurs when I am trying to sort millions of records. I suspect some floats have NaN , do you think it could cause problem. – math_law Jan 17 '15 at 21:22
  • By the way, is it possible to print out which objects (2 or 3...) are violating the "Contract" , how can I print them just before the StackTrace so have an idea to cope with the compareTo method. – math_law Jan 17 '15 at 22:45

5 Answers5

6

Assuming getTotalEnergy() return(s) float, you could use

return new Float(m1.getTotalEnergy()).compareTo(m2.getTotalEnergy());

Using Float.valueOf(float) is probably slightly more efficient, and hopefully this is easier to read.

Float f1 = Float.valueOf(m1.getTotalEnergy());
Float f2 = Float.valueOf(m2.getTotalEnergy());
return f1.compareTo(f2);
Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
Elliott Frisch
  • 198,278
  • 20
  • 158
  • 249
  • 1
    @Mr.Polywhirl I'm not. It's returning the result of [`Float.compareTo(Float)`](http://docs.oracle.com/javase/7/docs/api/java/lang/Float.html#compareTo%28java.lang.Float%29) with [autoboxing](http://docs.oracle.com/javase/tutorial/java/data/autoboxing.html). – Elliott Frisch Jan 17 '15 at 21:14
  • Yeah, but where in the code is the `return`? Hopefully not in the `compare()`. – Mr. Polywhirl Jan 17 '15 at 21:15
  • Perhaps the code could be written to be more obvious. This code sure looks like it returns a floating-point number to me. [This](http://stackoverflow.com/questions/28004269/java-collections-sort-comparison-method-violates-its-general-contract#comment44397154_28004269) makes more sense. – Robert Harvey Jan 17 '15 at 21:15
  • 2
    Prefer `return Float.valueOf(m1.getTotalEnergy()).compareTo(m2.getTotalEnergy());` to allow it to use the `Float` from cache rather than always creating a new one. – pickypg Jan 17 '15 at 21:21
  • Ah, never mind. I missed the `CompareTo`. In other news, don't use "EDIT" in your answers. Every Stack Overflow post has a comprehensive edit history that anyone can review. The edit history for this answer is located [here](http://stackoverflow.com/posts/28004369/revisions). – Robert Harvey Jan 17 '15 at 21:43
  • Just use the static `Float.compare()`. – shmosel Feb 13 '17 at 21:43
1

Without reference to MyObject. My guess is that the comparator is inconsistent with MyObject.equal.

That is, the contract you are violating is:

(comparator.compare(mo1, mo2) == 0) == mo1.equals(mo2)

Your comparator will compare objects with the same float value as being equal, where a more complex comparator would give an ordering, whilst the equals method would say the objects were not equal. Or you could have the reverse problem -- the equals method says the objects are equal and the compare method says they are different.

The following should work.

public int compare(MyObject m1, MyObject m2) {
    if (m1 == m2) return 0;
    if (m1 == null) return -1;
    if (m2 == null) return 1;
    if (m1.equals(m2)) return 0;

    int value = Float.compare(m1.getTotalEnergy(), m2.getTotalEnergy());
    if (value != 0) return value;

    // Warning, this line is not fool proof as unequal objects can have identical hash 
    // codes.
    return m1.hashCode() - m2.hashCode();
}
Dunes
  • 37,291
  • 7
  • 81
  • 97
  • Yeah, that is a good guess since NaN does not causes Collections.sort to fail either. – Rafal G. Jan 17 '15 at 21:28
  • @Dunes thanks , after trying Atuos' suggestion with Float.compare ( hoping it handles NaNs) I will try your suggestion. BR. – math_law Jan 17 '15 at 21:34
  • @math_law I have checked - NaN's are not the issue here. Can you show us MyObject.equals and MyObject.hashCode please? – Rafal G. Jan 17 '15 at 21:35
  • @R4J I have not overridden these methods. Anything i can supply additionally ? – math_law Jan 17 '15 at 21:37
  • @R4J It could be a NaN. An object would compare differently to itself if its total energy was NaN, despite being equal to itself. – Dunes Jan 17 '15 at 21:38
  • @Dunes I have copied the code and put some NaN's (0.0f / 0.0f) to the array - it still sorts :/ – Rafal G. Jan 17 '15 at 21:42
  • I checked the specs of `Float.compare` It compares `NaN` as equal to itself and greater than all other floats. It does however, compare `0f` as greater than `-0f`. So you may want to test that corner case. – Dunes Jan 17 '15 at 21:57
0

Maybe this will make the change:

public int compare(Object m1, Object m2) {
    // Actual energy comparison :-
    // THE higher the energy, the earlier in the list
    float delta = ((MyObject)m1).getTotalEnergy() - ((MyObject)m2).getTotalEnergy();
....
}
roeygol
  • 4,908
  • 9
  • 51
  • 88
0

The following code has been tested with float, NaN, and null values. All cases are handled correctly. I created a hashCode() and equals() method to the MyObject class.

Result: [null, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, NaN]

SortObjectFloatProperty

package q28004269;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class SortObjectFloatProperty {
    public static void main(String[] args) {
        List<MyObject> sorted;

        sorted = create(1f, 4f, 6f, null, 8f, 5f, Float.NaN, 3f, 2f, 7f, 0f, 9f);

        Collections.sort(sorted, new Comparator<MyObject>() {
            @Override
            public int compare(MyObject m1, MyObject m2) {
                if (m1 == m2) return 0;
                if (m1 == null) return -1;
                if (m2 == null) return 1;
                if (m1.equals(m2)) return 0;
                int value = Float.compare(m1.getTotalEnergy(), m2.getTotalEnergy());
                if (value != 0) return value;
                return m1.hashCode() - m2.hashCode();
            }
        });

        System.out.println(sorted);
    }

    public static MyObject create(float totalEnergy) {
        return new MyObject(totalEnergy);
    }

    public static List<MyObject> create(Object ...values) {
        List<MyObject> objs = new ArrayList<SortObjectFloatProperty.MyObject>();
        for (int i = 0; i < values.length; i++) {
            if (values[i] instanceof Float) {
                objs.add(create((float) values[i]));
            } else {
                objs.add(null);
            }
        }
        return objs;
    }
}

MyObject

package q28004269;

public static class MyObject {
    private float totalEnergy;

    public float getTotalEnergy() {
        return totalEnergy;
    }

    public void setTotalEnergy(float totalEnergy) {
        this.totalEnergy = totalEnergy;
    }

    public MyObject() {
        this(0.0f);
    }

    public MyObject(float totalEnergy) {
        this.totalEnergy = totalEnergy;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + Float.floatToIntBits(totalEnergy);
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null) return false;
        if (getClass() != obj.getClass()) return false;
        MyObject other = (MyObject) obj;
        return !(Float.floatToIntBits(totalEnergy) != Float.floatToIntBits(other.totalEnergy));
    }

    @Override
    public String toString() {
        return Float.toString(totalEnergy);
    }
}
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132
  • I just used implemented the compareTo method proposed here and it worked. Not sure if my object is Null or has some NaN value inside the totalEnergy method. While I would prefer single line of solutions (Stuart's or Elliot's) the only solution that worked in this thread is this. Thanks all. – math_law Jan 18 '15 at 20:42
0

There was some discussion of NaN values in the comments. This is important, because NaN violates our typical expectations of comparisons of floating point numbers. For example, Double.NaN > 0.0 and Double.NaN < 1.0 are both false!

This may cause the sort to throw the exception as you encountered. Even if the exception isn't thrown, it may cause the list to end up sorted in the wrong order. Therefore, your sort comparator must deal with NaN values. Fortunately, the library's built-in comparisons such as Double.compare() do this for you. This has the same semantics as Double.compareTo(), except that boxed Double values aren't necessary. See the documentation for details. Briefly, NaN values are considered greater than all other values (including +Inf), and negative zero is less than positive zero.

To use Double.compare() in a Comparator for sort, do this:

Collections.sort(data, new Comparator<MyObject>() {
    public int compare(MyObject m1, MyObject m2) {
        return Double.compare(m1.getTotalEnergy(), m2.getTotalEnergy());
    }
});

If you're using Java 8, you could do this:

data.sort(Comparator.comparing(MyObject::getTotalEnergy));

To handle nulls, wrap the comparator with nullsFirst or nullsLast:

data.sort(Comparator.nullsFirst(Comparator.comparing(MyObject::getTotalEnergy)));
Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
  • @math_law Still the same IAE? Try instrumenting the comparator, i.e. print out (or log) the inputs and result of each call, and then look at the last entry prior to getting the exception. – Stuart Marks Jan 18 '15 at 21:27
  • @math_law If your input does have nulls, try wrapping the comparator with `Comparator.nullFirst` or `Comparator.nullsLast` (Java 8 only). – Stuart Marks Jan 18 '15 at 21:30
  • @math_law Also, does `getTotalEnergy` always return the same value for any given instance of `MyObject`? – Stuart Marks Jan 18 '15 at 21:41