Comparator error : Comparison method violates its general contract [duplicate] - java

Can someone explain me in simple terms, why does this code throw an exception, "Comparison method violates its general contract!", and how do I fix it?
private int compareParents(Foo s1, Foo s2) {
if (s1.getParent() == s2) return -1;
if (s2.getParent() == s1) return 1;
return 0;
}

Your comparator is not transitive.
Let A be the parent of B, and B be the parent of C. Since A > B and B > C, then it must be the case that A > C. However, if your comparator is invoked on A and C, it would return zero, meaning A == C. This violates the contract and hence throws the exception.
It's rather nice of the library to detect this and let you know, rather than behave erratically.
One way to satisfy the transitivity requirement in compareParents() is to traverse the getParent() chain instead of only looking at the immediate ancestor.

Just because this is what I got when I Googled this error, my problem was that I had
if (value < other.value)
return -1;
else if (value >= other.value)
return 1;
else
return 0;
the value >= other.value should (obviously) actually be value > other.value so that you can actually return 0 with equal objects.

The violation of the contract often means that the comparator is not providing the correct or consistent value when comparing objects. For example, you might want to perform a string compare and force empty strings to sort to the end with:
if ( one.length() == 0 ) {
return 1; // empty string sorts last
}
if ( two.length() == 0 ) {
return -1; // empty string sorts last
}
return one.compareToIgnoreCase( two );
But this overlooks the case where BOTH one and two are empty - and in that case, the wrong value is returned (1 instead of 0 to show a match), and the comparator reports that as a violation. It should have been written as:
if ( one.length() == 0 ) {
if ( two.length() == 0 ) {
return 0; // BOth empty - so indicate
}
return 1; // empty string sorts last
}
if ( two.length() == 0 ) {
return -1; // empty string sorts last
}
return one.compareToIgnoreCase( two );

Even if your compareTo is holds transitivity in theory, sometimes subtle bugs mess things up... such as floating point arithmetic error. It happened to me. this was my code:
public int compareTo(tfidfContainer compareTfidf) {
//descending order
if (this.tfidf > compareTfidf.tfidf)
return -1;
else if (this.tfidf < compareTfidf.tfidf)
return 1;
else
return 0;
}
The transitive property clearly holds, but for some reason I was getting the IllegalArgumentException. And it turns out that due to tiny errors in floating point arithmetic, the round-off errors where causing the transitive property to break where they shouldn't! So I rewrote the code to consider really tiny differences 0, and it worked:
public int compareTo(tfidfContainer compareTfidf) {
//descending order
if ((this.tfidf - compareTfidf.tfidf) < .000000001)
return 0;
if (this.tfidf > compareTfidf.tfidf)
return -1;
else if (this.tfidf < compareTfidf.tfidf)
return 1;
return 0;
}

Editing VM Configuration worked for me.
-Djava.util.Arrays.useLegacyMergeSort=true

In our case were were getting this error because we had accidentally flipped the order of comparison of s1 and s2. So watch out for that. It was obviously way more complicated than the following but this is an illustration:
s1 == s2
return 0;
s2 > s1
return 1;
s1 < s2
return -1;

In my case I was doing something like the following:
if (a.someField == null) {
return 1;
}
if (b.someField == null) {
return -1;
}
if (a.someField.equals(b.someField)) {
return a.someOtherField.compareTo(b.someOtherField);
}
return a.someField.compareTo(b.someField);
What I forgot to check was when both a.someField and b.someField are null.

I've seen this happen in a piece of code where the often recurring check for null values was performed:
if(( A==null ) && ( B==null )
return +1;//WRONG: two null values should return 0!!!

Java does not check consistency in a strict sense, only notifies you if it runs into serious trouble. Also it does not give you much information from the error.
I was puzzled with what's happening in my sorter and made a strict consistencyChecker, maybe this will help you:
/**
* #param dailyReports
* #param comparator
*/
public static <T> void checkConsitency(final List<T> dailyReports, final Comparator<T> comparator) {
final Map<T, List<T>> objectMapSmallerOnes = new HashMap<T, List<T>>();
iterateDistinctPairs(dailyReports.iterator(), new IPairIteratorCallback<T>() {
/**
* #param o1
* #param o2
*/
#Override
public void pair(T o1, T o2) {
final int diff = comparator.compare(o1, o2);
if (diff < Compare.EQUAL) {
checkConsistency(objectMapSmallerOnes, o1, o2);
getListSafely(objectMapSmallerOnes, o2).add(o1);
} else if (Compare.EQUAL < diff) {
checkConsistency(objectMapSmallerOnes, o2, o1);
getListSafely(objectMapSmallerOnes, o1).add(o2);
} else {
throw new IllegalStateException("Equals not expected?");
}
}
});
}
/**
* #param objectMapSmallerOnes
* #param o1
* #param o2
*/
static <T> void checkConsistency(final Map<T, List<T>> objectMapSmallerOnes, T o1, T o2) {
final List<T> smallerThan = objectMapSmallerOnes.get(o1);
if (smallerThan != null) {
for (final T o : smallerThan) {
if (o == o2) {
throw new IllegalStateException(o2 + " cannot be smaller than " + o1 + " if it's supposed to be vice versa.");
}
checkConsistency(objectMapSmallerOnes, o, o2);
}
}
}
/**
* #param keyMapValues
* #param key
* #param <Key>
* #param <Value>
* #return List<Value>
*/
public static <Key, Value> List<Value> getListSafely(Map<Key, List<Value>> keyMapValues, Key key) {
List<Value> values = keyMapValues.get(key);
if (values == null) {
keyMapValues.put(key, values = new LinkedList<Value>());
}
return values;
}
/**
* #author Oku
*
* #param <T>
*/
public interface IPairIteratorCallback<T> {
/**
* #param o1
* #param o2
*/
void pair(T o1, T o2);
}
/**
*
* Iterates through each distinct unordered pair formed by the elements of a given iterator
*
* #param it
* #param callback
*/
public static <T> void iterateDistinctPairs(final Iterator<T> it, IPairIteratorCallback<T> callback) {
List<T> list = Convert.toMinimumArrayList(new Iterable<T>() {
#Override
public Iterator<T> iterator() {
return it;
}
});
for (int outerIndex = 0; outerIndex < list.size() - 1; outerIndex++) {
for (int innerIndex = outerIndex + 1; innerIndex < list.size(); innerIndex++) {
callback.pair(list.get(outerIndex), list.get(innerIndex));
}
}
}

If compareParents(s1, s2) == -1 then compareParents(s2, s1) == 1 is expected. With your code it's not always true.
Specifically if s1.getParent() == s2 && s2.getParent() == s1.
It's just one of the possible problems.

In my case, it was an infinite sort.
That is, at first the line moved up according to the condition, and then the same line moved down to the same place.
I added one more condition at the end that unambiguously established the order of the lines.

You can't compare object data like this:s1.getParent() == s2 - this will compare the object references. You should override equals function for Foo class and then compare them like this s1.getParent().equals(s2)

I faced the same issue and I solved it.
//This this your code
private int compareParents(Foo s1, Foo s2) {
if (s1.getParent() == s2) return -1;
if (s2.getParent() == s1) return 1;
return 0;
}
The violation is comparing different things with each other.
//acceptable
compare between s1.getParent() and s2.getParent()
//acceptable
compare between s1 and s2
//NOT acceptable
compare between s1 and s2.getParent()
//NOT acceptable
compare between s1.getParent() and s2
In my code, I wanted to sort addresses by their coordination. In the comparator, I compared between X and Y (by mistake), instead of X and X.
//My code:
private void sortBasedOnX(){
//addresses is a list of addresses where each address has X and Y
addresses.sort((o1, o2) -> {
String a = o1.getAddress().getX();
String b = o2.getAddress().getY(); //<-- this is supposed to be getX
return Integer.parseInt(a)-Integer.parseInt(b);
});
}
//acceptable
compare between o1.getAddress().getX() and o1.getAddress().getX()
//acceptable
compare between o1.getAddress().getY() and o1.getAddress().getY()
//NOT acceptable
compare between o1.getAddress().getX() and o1.getAddress().getY()
//NOT acceptable
compare between o1.getAddress().getX() and o1.getAddress()
//NOT acceptable
compare between o1.getAddress().getX() and o1

Related

Java Streams illegalargumentexception Comparison method violates its general contract [duplicate]

Can someone explain me in simple terms, why does this code throw an exception, "Comparison method violates its general contract!", and how do I fix it?
private int compareParents(Foo s1, Foo s2) {
if (s1.getParent() == s2) return -1;
if (s2.getParent() == s1) return 1;
return 0;
}
Your comparator is not transitive.
Let A be the parent of B, and B be the parent of C. Since A > B and B > C, then it must be the case that A > C. However, if your comparator is invoked on A and C, it would return zero, meaning A == C. This violates the contract and hence throws the exception.
It's rather nice of the library to detect this and let you know, rather than behave erratically.
One way to satisfy the transitivity requirement in compareParents() is to traverse the getParent() chain instead of only looking at the immediate ancestor.
Just because this is what I got when I Googled this error, my problem was that I had
if (value < other.value)
return -1;
else if (value >= other.value)
return 1;
else
return 0;
the value >= other.value should (obviously) actually be value > other.value so that you can actually return 0 with equal objects.
The violation of the contract often means that the comparator is not providing the correct or consistent value when comparing objects. For example, you might want to perform a string compare and force empty strings to sort to the end with:
if ( one.length() == 0 ) {
return 1; // empty string sorts last
}
if ( two.length() == 0 ) {
return -1; // empty string sorts last
}
return one.compareToIgnoreCase( two );
But this overlooks the case where BOTH one and two are empty - and in that case, the wrong value is returned (1 instead of 0 to show a match), and the comparator reports that as a violation. It should have been written as:
if ( one.length() == 0 ) {
if ( two.length() == 0 ) {
return 0; // BOth empty - so indicate
}
return 1; // empty string sorts last
}
if ( two.length() == 0 ) {
return -1; // empty string sorts last
}
return one.compareToIgnoreCase( two );
Even if your compareTo is holds transitivity in theory, sometimes subtle bugs mess things up... such as floating point arithmetic error. It happened to me. this was my code:
public int compareTo(tfidfContainer compareTfidf) {
//descending order
if (this.tfidf > compareTfidf.tfidf)
return -1;
else if (this.tfidf < compareTfidf.tfidf)
return 1;
else
return 0;
}
The transitive property clearly holds, but for some reason I was getting the IllegalArgumentException. And it turns out that due to tiny errors in floating point arithmetic, the round-off errors where causing the transitive property to break where they shouldn't! So I rewrote the code to consider really tiny differences 0, and it worked:
public int compareTo(tfidfContainer compareTfidf) {
//descending order
if ((this.tfidf - compareTfidf.tfidf) < .000000001)
return 0;
if (this.tfidf > compareTfidf.tfidf)
return -1;
else if (this.tfidf < compareTfidf.tfidf)
return 1;
return 0;
}
Editing VM Configuration worked for me.
-Djava.util.Arrays.useLegacyMergeSort=true
In our case were were getting this error because we had accidentally flipped the order of comparison of s1 and s2. So watch out for that. It was obviously way more complicated than the following but this is an illustration:
s1 == s2
return 0;
s2 > s1
return 1;
s1 < s2
return -1;
In my case I was doing something like the following:
if (a.someField == null) {
return 1;
}
if (b.someField == null) {
return -1;
}
if (a.someField.equals(b.someField)) {
return a.someOtherField.compareTo(b.someOtherField);
}
return a.someField.compareTo(b.someField);
What I forgot to check was when both a.someField and b.someField are null.
I've seen this happen in a piece of code where the often recurring check for null values was performed:
if(( A==null ) && ( B==null )
return +1;//WRONG: two null values should return 0!!!
Java does not check consistency in a strict sense, only notifies you if it runs into serious trouble. Also it does not give you much information from the error.
I was puzzled with what's happening in my sorter and made a strict consistencyChecker, maybe this will help you:
/**
* #param dailyReports
* #param comparator
*/
public static <T> void checkConsitency(final List<T> dailyReports, final Comparator<T> comparator) {
final Map<T, List<T>> objectMapSmallerOnes = new HashMap<T, List<T>>();
iterateDistinctPairs(dailyReports.iterator(), new IPairIteratorCallback<T>() {
/**
* #param o1
* #param o2
*/
#Override
public void pair(T o1, T o2) {
final int diff = comparator.compare(o1, o2);
if (diff < Compare.EQUAL) {
checkConsistency(objectMapSmallerOnes, o1, o2);
getListSafely(objectMapSmallerOnes, o2).add(o1);
} else if (Compare.EQUAL < diff) {
checkConsistency(objectMapSmallerOnes, o2, o1);
getListSafely(objectMapSmallerOnes, o1).add(o2);
} else {
throw new IllegalStateException("Equals not expected?");
}
}
});
}
/**
* #param objectMapSmallerOnes
* #param o1
* #param o2
*/
static <T> void checkConsistency(final Map<T, List<T>> objectMapSmallerOnes, T o1, T o2) {
final List<T> smallerThan = objectMapSmallerOnes.get(o1);
if (smallerThan != null) {
for (final T o : smallerThan) {
if (o == o2) {
throw new IllegalStateException(o2 + " cannot be smaller than " + o1 + " if it's supposed to be vice versa.");
}
checkConsistency(objectMapSmallerOnes, o, o2);
}
}
}
/**
* #param keyMapValues
* #param key
* #param <Key>
* #param <Value>
* #return List<Value>
*/
public static <Key, Value> List<Value> getListSafely(Map<Key, List<Value>> keyMapValues, Key key) {
List<Value> values = keyMapValues.get(key);
if (values == null) {
keyMapValues.put(key, values = new LinkedList<Value>());
}
return values;
}
/**
* #author Oku
*
* #param <T>
*/
public interface IPairIteratorCallback<T> {
/**
* #param o1
* #param o2
*/
void pair(T o1, T o2);
}
/**
*
* Iterates through each distinct unordered pair formed by the elements of a given iterator
*
* #param it
* #param callback
*/
public static <T> void iterateDistinctPairs(final Iterator<T> it, IPairIteratorCallback<T> callback) {
List<T> list = Convert.toMinimumArrayList(new Iterable<T>() {
#Override
public Iterator<T> iterator() {
return it;
}
});
for (int outerIndex = 0; outerIndex < list.size() - 1; outerIndex++) {
for (int innerIndex = outerIndex + 1; innerIndex < list.size(); innerIndex++) {
callback.pair(list.get(outerIndex), list.get(innerIndex));
}
}
}
If compareParents(s1, s2) == -1 then compareParents(s2, s1) == 1 is expected. With your code it's not always true.
Specifically if s1.getParent() == s2 && s2.getParent() == s1.
It's just one of the possible problems.
In my case, it was an infinite sort.
That is, at first the line moved up according to the condition, and then the same line moved down to the same place.
I added one more condition at the end that unambiguously established the order of the lines.
You can't compare object data like this:s1.getParent() == s2 - this will compare the object references. You should override equals function for Foo class and then compare them like this s1.getParent().equals(s2)
I faced the same issue and I solved it.
//This this your code
private int compareParents(Foo s1, Foo s2) {
if (s1.getParent() == s2) return -1;
if (s2.getParent() == s1) return 1;
return 0;
}
The violation is comparing different things with each other.
//acceptable
compare between s1.getParent() and s2.getParent()
//acceptable
compare between s1 and s2
//NOT acceptable
compare between s1 and s2.getParent()
//NOT acceptable
compare between s1.getParent() and s2
In my code, I wanted to sort addresses by their coordination. In the comparator, I compared between X and Y (by mistake), instead of X and X.
//My code:
private void sortBasedOnX(){
//addresses is a list of addresses where each address has X and Y
addresses.sort((o1, o2) -> {
String a = o1.getAddress().getX();
String b = o2.getAddress().getY(); //<-- this is supposed to be getX
return Integer.parseInt(a)-Integer.parseInt(b);
});
}
//acceptable
compare between o1.getAddress().getX() and o1.getAddress().getX()
//acceptable
compare between o1.getAddress().getY() and o1.getAddress().getY()
//NOT acceptable
compare between o1.getAddress().getX() and o1.getAddress().getY()
//NOT acceptable
compare between o1.getAddress().getX() and o1.getAddress()
//NOT acceptable
compare between o1.getAddress().getX() and o1

Could not found solution for "java.lang.IllegalArgumentException: Comparison method violates its general contract!" [duplicate]

Can someone explain me in simple terms, why does this code throw an exception, "Comparison method violates its general contract!", and how do I fix it?
private int compareParents(Foo s1, Foo s2) {
if (s1.getParent() == s2) return -1;
if (s2.getParent() == s1) return 1;
return 0;
}
Your comparator is not transitive.
Let A be the parent of B, and B be the parent of C. Since A > B and B > C, then it must be the case that A > C. However, if your comparator is invoked on A and C, it would return zero, meaning A == C. This violates the contract and hence throws the exception.
It's rather nice of the library to detect this and let you know, rather than behave erratically.
One way to satisfy the transitivity requirement in compareParents() is to traverse the getParent() chain instead of only looking at the immediate ancestor.
Just because this is what I got when I Googled this error, my problem was that I had
if (value < other.value)
return -1;
else if (value >= other.value)
return 1;
else
return 0;
the value >= other.value should (obviously) actually be value > other.value so that you can actually return 0 with equal objects.
The violation of the contract often means that the comparator is not providing the correct or consistent value when comparing objects. For example, you might want to perform a string compare and force empty strings to sort to the end with:
if ( one.length() == 0 ) {
return 1; // empty string sorts last
}
if ( two.length() == 0 ) {
return -1; // empty string sorts last
}
return one.compareToIgnoreCase( two );
But this overlooks the case where BOTH one and two are empty - and in that case, the wrong value is returned (1 instead of 0 to show a match), and the comparator reports that as a violation. It should have been written as:
if ( one.length() == 0 ) {
if ( two.length() == 0 ) {
return 0; // BOth empty - so indicate
}
return 1; // empty string sorts last
}
if ( two.length() == 0 ) {
return -1; // empty string sorts last
}
return one.compareToIgnoreCase( two );
Even if your compareTo is holds transitivity in theory, sometimes subtle bugs mess things up... such as floating point arithmetic error. It happened to me. this was my code:
public int compareTo(tfidfContainer compareTfidf) {
//descending order
if (this.tfidf > compareTfidf.tfidf)
return -1;
else if (this.tfidf < compareTfidf.tfidf)
return 1;
else
return 0;
}
The transitive property clearly holds, but for some reason I was getting the IllegalArgumentException. And it turns out that due to tiny errors in floating point arithmetic, the round-off errors where causing the transitive property to break where they shouldn't! So I rewrote the code to consider really tiny differences 0, and it worked:
public int compareTo(tfidfContainer compareTfidf) {
//descending order
if ((this.tfidf - compareTfidf.tfidf) < .000000001)
return 0;
if (this.tfidf > compareTfidf.tfidf)
return -1;
else if (this.tfidf < compareTfidf.tfidf)
return 1;
return 0;
}
Editing VM Configuration worked for me.
-Djava.util.Arrays.useLegacyMergeSort=true
In our case were were getting this error because we had accidentally flipped the order of comparison of s1 and s2. So watch out for that. It was obviously way more complicated than the following but this is an illustration:
s1 == s2
return 0;
s2 > s1
return 1;
s1 < s2
return -1;
In my case I was doing something like the following:
if (a.someField == null) {
return 1;
}
if (b.someField == null) {
return -1;
}
if (a.someField.equals(b.someField)) {
return a.someOtherField.compareTo(b.someOtherField);
}
return a.someField.compareTo(b.someField);
What I forgot to check was when both a.someField and b.someField are null.
I've seen this happen in a piece of code where the often recurring check for null values was performed:
if(( A==null ) && ( B==null )
return +1;//WRONG: two null values should return 0!!!
Java does not check consistency in a strict sense, only notifies you if it runs into serious trouble. Also it does not give you much information from the error.
I was puzzled with what's happening in my sorter and made a strict consistencyChecker, maybe this will help you:
/**
* #param dailyReports
* #param comparator
*/
public static <T> void checkConsitency(final List<T> dailyReports, final Comparator<T> comparator) {
final Map<T, List<T>> objectMapSmallerOnes = new HashMap<T, List<T>>();
iterateDistinctPairs(dailyReports.iterator(), new IPairIteratorCallback<T>() {
/**
* #param o1
* #param o2
*/
#Override
public void pair(T o1, T o2) {
final int diff = comparator.compare(o1, o2);
if (diff < Compare.EQUAL) {
checkConsistency(objectMapSmallerOnes, o1, o2);
getListSafely(objectMapSmallerOnes, o2).add(o1);
} else if (Compare.EQUAL < diff) {
checkConsistency(objectMapSmallerOnes, o2, o1);
getListSafely(objectMapSmallerOnes, o1).add(o2);
} else {
throw new IllegalStateException("Equals not expected?");
}
}
});
}
/**
* #param objectMapSmallerOnes
* #param o1
* #param o2
*/
static <T> void checkConsistency(final Map<T, List<T>> objectMapSmallerOnes, T o1, T o2) {
final List<T> smallerThan = objectMapSmallerOnes.get(o1);
if (smallerThan != null) {
for (final T o : smallerThan) {
if (o == o2) {
throw new IllegalStateException(o2 + " cannot be smaller than " + o1 + " if it's supposed to be vice versa.");
}
checkConsistency(objectMapSmallerOnes, o, o2);
}
}
}
/**
* #param keyMapValues
* #param key
* #param <Key>
* #param <Value>
* #return List<Value>
*/
public static <Key, Value> List<Value> getListSafely(Map<Key, List<Value>> keyMapValues, Key key) {
List<Value> values = keyMapValues.get(key);
if (values == null) {
keyMapValues.put(key, values = new LinkedList<Value>());
}
return values;
}
/**
* #author Oku
*
* #param <T>
*/
public interface IPairIteratorCallback<T> {
/**
* #param o1
* #param o2
*/
void pair(T o1, T o2);
}
/**
*
* Iterates through each distinct unordered pair formed by the elements of a given iterator
*
* #param it
* #param callback
*/
public static <T> void iterateDistinctPairs(final Iterator<T> it, IPairIteratorCallback<T> callback) {
List<T> list = Convert.toMinimumArrayList(new Iterable<T>() {
#Override
public Iterator<T> iterator() {
return it;
}
});
for (int outerIndex = 0; outerIndex < list.size() - 1; outerIndex++) {
for (int innerIndex = outerIndex + 1; innerIndex < list.size(); innerIndex++) {
callback.pair(list.get(outerIndex), list.get(innerIndex));
}
}
}
If compareParents(s1, s2) == -1 then compareParents(s2, s1) == 1 is expected. With your code it's not always true.
Specifically if s1.getParent() == s2 && s2.getParent() == s1.
It's just one of the possible problems.
In my case, it was an infinite sort.
That is, at first the line moved up according to the condition, and then the same line moved down to the same place.
I added one more condition at the end that unambiguously established the order of the lines.
You can't compare object data like this:s1.getParent() == s2 - this will compare the object references. You should override equals function for Foo class and then compare them like this s1.getParent().equals(s2)
I faced the same issue and I solved it.
//This this your code
private int compareParents(Foo s1, Foo s2) {
if (s1.getParent() == s2) return -1;
if (s2.getParent() == s1) return 1;
return 0;
}
The violation is comparing different things with each other.
//acceptable
compare between s1.getParent() and s2.getParent()
//acceptable
compare between s1 and s2
//NOT acceptable
compare between s1 and s2.getParent()
//NOT acceptable
compare between s1.getParent() and s2
In my code, I wanted to sort addresses by their coordination. In the comparator, I compared between X and Y (by mistake), instead of X and X.
//My code:
private void sortBasedOnX(){
//addresses is a list of addresses where each address has X and Y
addresses.sort((o1, o2) -> {
String a = o1.getAddress().getX();
String b = o2.getAddress().getY(); //<-- this is supposed to be getX
return Integer.parseInt(a)-Integer.parseInt(b);
});
}
//acceptable
compare between o1.getAddress().getX() and o1.getAddress().getX()
//acceptable
compare between o1.getAddress().getY() and o1.getAddress().getY()
//NOT acceptable
compare between o1.getAddress().getX() and o1.getAddress().getY()
//NOT acceptable
compare between o1.getAddress().getX() and o1.getAddress()
//NOT acceptable
compare between o1.getAddress().getX() and o1

Comparison method violates its general contract on generic comparator [duplicate]

Can someone explain me in simple terms, why does this code throw an exception, "Comparison method violates its general contract!", and how do I fix it?
private int compareParents(Foo s1, Foo s2) {
if (s1.getParent() == s2) return -1;
if (s2.getParent() == s1) return 1;
return 0;
}
Your comparator is not transitive.
Let A be the parent of B, and B be the parent of C. Since A > B and B > C, then it must be the case that A > C. However, if your comparator is invoked on A and C, it would return zero, meaning A == C. This violates the contract and hence throws the exception.
It's rather nice of the library to detect this and let you know, rather than behave erratically.
One way to satisfy the transitivity requirement in compareParents() is to traverse the getParent() chain instead of only looking at the immediate ancestor.
Just because this is what I got when I Googled this error, my problem was that I had
if (value < other.value)
return -1;
else if (value >= other.value)
return 1;
else
return 0;
the value >= other.value should (obviously) actually be value > other.value so that you can actually return 0 with equal objects.
The violation of the contract often means that the comparator is not providing the correct or consistent value when comparing objects. For example, you might want to perform a string compare and force empty strings to sort to the end with:
if ( one.length() == 0 ) {
return 1; // empty string sorts last
}
if ( two.length() == 0 ) {
return -1; // empty string sorts last
}
return one.compareToIgnoreCase( two );
But this overlooks the case where BOTH one and two are empty - and in that case, the wrong value is returned (1 instead of 0 to show a match), and the comparator reports that as a violation. It should have been written as:
if ( one.length() == 0 ) {
if ( two.length() == 0 ) {
return 0; // BOth empty - so indicate
}
return 1; // empty string sorts last
}
if ( two.length() == 0 ) {
return -1; // empty string sorts last
}
return one.compareToIgnoreCase( two );
Even if your compareTo is holds transitivity in theory, sometimes subtle bugs mess things up... such as floating point arithmetic error. It happened to me. this was my code:
public int compareTo(tfidfContainer compareTfidf) {
//descending order
if (this.tfidf > compareTfidf.tfidf)
return -1;
else if (this.tfidf < compareTfidf.tfidf)
return 1;
else
return 0;
}
The transitive property clearly holds, but for some reason I was getting the IllegalArgumentException. And it turns out that due to tiny errors in floating point arithmetic, the round-off errors where causing the transitive property to break where they shouldn't! So I rewrote the code to consider really tiny differences 0, and it worked:
public int compareTo(tfidfContainer compareTfidf) {
//descending order
if ((this.tfidf - compareTfidf.tfidf) < .000000001)
return 0;
if (this.tfidf > compareTfidf.tfidf)
return -1;
else if (this.tfidf < compareTfidf.tfidf)
return 1;
return 0;
}
Editing VM Configuration worked for me.
-Djava.util.Arrays.useLegacyMergeSort=true
In our case were were getting this error because we had accidentally flipped the order of comparison of s1 and s2. So watch out for that. It was obviously way more complicated than the following but this is an illustration:
s1 == s2
return 0;
s2 > s1
return 1;
s1 < s2
return -1;
In my case I was doing something like the following:
if (a.someField == null) {
return 1;
}
if (b.someField == null) {
return -1;
}
if (a.someField.equals(b.someField)) {
return a.someOtherField.compareTo(b.someOtherField);
}
return a.someField.compareTo(b.someField);
What I forgot to check was when both a.someField and b.someField are null.
I've seen this happen in a piece of code where the often recurring check for null values was performed:
if(( A==null ) && ( B==null )
return +1;//WRONG: two null values should return 0!!!
Java does not check consistency in a strict sense, only notifies you if it runs into serious trouble. Also it does not give you much information from the error.
I was puzzled with what's happening in my sorter and made a strict consistencyChecker, maybe this will help you:
/**
* #param dailyReports
* #param comparator
*/
public static <T> void checkConsitency(final List<T> dailyReports, final Comparator<T> comparator) {
final Map<T, List<T>> objectMapSmallerOnes = new HashMap<T, List<T>>();
iterateDistinctPairs(dailyReports.iterator(), new IPairIteratorCallback<T>() {
/**
* #param o1
* #param o2
*/
#Override
public void pair(T o1, T o2) {
final int diff = comparator.compare(o1, o2);
if (diff < Compare.EQUAL) {
checkConsistency(objectMapSmallerOnes, o1, o2);
getListSafely(objectMapSmallerOnes, o2).add(o1);
} else if (Compare.EQUAL < diff) {
checkConsistency(objectMapSmallerOnes, o2, o1);
getListSafely(objectMapSmallerOnes, o1).add(o2);
} else {
throw new IllegalStateException("Equals not expected?");
}
}
});
}
/**
* #param objectMapSmallerOnes
* #param o1
* #param o2
*/
static <T> void checkConsistency(final Map<T, List<T>> objectMapSmallerOnes, T o1, T o2) {
final List<T> smallerThan = objectMapSmallerOnes.get(o1);
if (smallerThan != null) {
for (final T o : smallerThan) {
if (o == o2) {
throw new IllegalStateException(o2 + " cannot be smaller than " + o1 + " if it's supposed to be vice versa.");
}
checkConsistency(objectMapSmallerOnes, o, o2);
}
}
}
/**
* #param keyMapValues
* #param key
* #param <Key>
* #param <Value>
* #return List<Value>
*/
public static <Key, Value> List<Value> getListSafely(Map<Key, List<Value>> keyMapValues, Key key) {
List<Value> values = keyMapValues.get(key);
if (values == null) {
keyMapValues.put(key, values = new LinkedList<Value>());
}
return values;
}
/**
* #author Oku
*
* #param <T>
*/
public interface IPairIteratorCallback<T> {
/**
* #param o1
* #param o2
*/
void pair(T o1, T o2);
}
/**
*
* Iterates through each distinct unordered pair formed by the elements of a given iterator
*
* #param it
* #param callback
*/
public static <T> void iterateDistinctPairs(final Iterator<T> it, IPairIteratorCallback<T> callback) {
List<T> list = Convert.toMinimumArrayList(new Iterable<T>() {
#Override
public Iterator<T> iterator() {
return it;
}
});
for (int outerIndex = 0; outerIndex < list.size() - 1; outerIndex++) {
for (int innerIndex = outerIndex + 1; innerIndex < list.size(); innerIndex++) {
callback.pair(list.get(outerIndex), list.get(innerIndex));
}
}
}
If compareParents(s1, s2) == -1 then compareParents(s2, s1) == 1 is expected. With your code it's not always true.
Specifically if s1.getParent() == s2 && s2.getParent() == s1.
It's just one of the possible problems.
In my case, it was an infinite sort.
That is, at first the line moved up according to the condition, and then the same line moved down to the same place.
I added one more condition at the end that unambiguously established the order of the lines.
You can't compare object data like this:s1.getParent() == s2 - this will compare the object references. You should override equals function for Foo class and then compare them like this s1.getParent().equals(s2)
I faced the same issue and I solved it.
//This this your code
private int compareParents(Foo s1, Foo s2) {
if (s1.getParent() == s2) return -1;
if (s2.getParent() == s1) return 1;
return 0;
}
The violation is comparing different things with each other.
//acceptable
compare between s1.getParent() and s2.getParent()
//acceptable
compare between s1 and s2
//NOT acceptable
compare between s1 and s2.getParent()
//NOT acceptable
compare between s1.getParent() and s2
In my code, I wanted to sort addresses by their coordination. In the comparator, I compared between X and Y (by mistake), instead of X and X.
//My code:
private void sortBasedOnX(){
//addresses is a list of addresses where each address has X and Y
addresses.sort((o1, o2) -> {
String a = o1.getAddress().getX();
String b = o2.getAddress().getY(); //<-- this is supposed to be getX
return Integer.parseInt(a)-Integer.parseInt(b);
});
}
//acceptable
compare between o1.getAddress().getX() and o1.getAddress().getX()
//acceptable
compare between o1.getAddress().getY() and o1.getAddress().getY()
//NOT acceptable
compare between o1.getAddress().getX() and o1.getAddress().getY()
//NOT acceptable
compare between o1.getAddress().getX() and o1.getAddress()
//NOT acceptable
compare between o1.getAddress().getX() and o1

Verify if an object exist in a List

What I need is: Verify if an object exist in a List comparing some attributes.
I'm in a trouble here with Collections and Comparator. I'm trying to do the verify with this Binary Search:
Collections.binarySearch(listFuncionarioObs2, formFuncionarioObsIns, formFuncionarioObsIns.objectComparator);//Binary search of an object in a List of this Object.
With this comparator:
public int compare(FuncionarioObs func, FuncionarioObs funcToCompare) {
int testCodigo = -1;
if(null != func2.getCodigo()){
testCodigo = func.getCodigo().compareTo(funcToCompare.getCodigo());
}
int testData = func.getData().compareTo(funcToCompare.getData());
int testEvento = func.getEvento().compareTo(funcToCompare.getEvento());
int testAndamento = func.getAndamento().compareTo(funcToCompare.getAndamento());
if(testCodigo == 0 && testData == 0 && testEvento == 0 && testAndamento == 0){
return 0;
}else if(testData == 0 && testEvento == 0 && testAndamento == 0) {
return 0;
}
return -1;
}
But I'm a little bit lost, this is not working and I don't know the best way to do this. Someone can turn on a light for me?
Best regards,
Edited.
I'm sorting the List before the Binary Search with this code:
List<FuncionarioObs> listFuncionarioObsBD = funcionarioObsDAO.getFuncionarioObsById(sigla);
Collections.sort(listFuncionarioObsBD);
The comparator to the sort is:
#Override
public int compareTo(FuncionarioObs func) {
if(this.getCodigo() > func.getCodigo()){
return 1;
}else if(this.getCodigo() == func.getCodigo() ) {
return 0;
}else{
return -1;
}
}
CompareTo
Your compare wont work correctly. Right now it is only comparing the references of the objects. You will have to change this to compare the objects values:
#Override public int compareTo(Account aThat) {
final int BEFORE = -1;
final int EQUAL = 0;
final int AFTER = 1;
//this optimization is usually worthwhile, and can
//always be added
if (this == aThat) return EQUAL;
//primitive numbers follow this form
if (this.fAccountNumber < aThat.fAccountNumber) return BEFORE;
if (this.fAccountNumber > aThat.fAccountNumber) return AFTER;
//booleans follow this form
if (!this.fIsNewAccount && aThat.fIsNewAccount) return BEFORE;
if (this.fIsNewAccount && !aThat.fIsNewAccount) return AFTER;
.
.
.
//all comparisons have yielded equality
//verify that compareTo is consistent with equals (optional)
assert this.equals(aThat) : "compareTo inconsistent with equals.";
return EQUAL;
}
from here
Finding the object
Now comes the next part. As CrtlAltDelete has hinted it dependends of whether your list is sorted or not.
If its sorted ascending: iterate through the objects till you either find one which compareTo returns a Zero (== success) or a One ( == fail).
For an unsorted list you will have to iterate through all objects in search for one that returns a Zero.

Sorting a custom type array by a string attribute?

I have an array of a custom type that I want to sort by one of its String attributes. For some reason, the following code is producing wrong results. Could you point out where I might have made a mistake?
class PatientLNComparator implements Comparator<Patient>{
#Override
public int compare(Patient p1, Patient p2) {
String p1_LN = (p1 == null) ? null : p1.last_name;
String p2_LN = (p2 == null) ? null : p2.last_name;
if(p2_LN == null)
return -1;
else if(p1_LN == null)
return +1;
else if(p1_LN.equals(p2_LN))
return 0;
else if(p1_LN.compareTo(p2_LN) > 0)
return -1;
else
return +1;
}
}
One problem to start with - your comparator is inconsistent if you give it two patients with null names, or two null patient references. In particular:
Patient p1 = null;
Patient p2 = null;
int x = comparator.compare(p1, p2);
int y = comparator.compare(p2, p1);
The signs of x and y ought to be different - but they'll both be -1.
After that, it depends on how you want to compare the names. I would usually use
return p1_LN.compareTo(p2_LN);
if you want to sort in ascending order. Note that to sort in descending order you shouldn't just return -p1_LN.compareTo(p2_LN), as if the comparison returns the Integer.MIN_VALUE, the negation won't work. Instead you'd want to return p2_LN.compareTo(p1_LN);.
Note that if you're using this scheme, you don't need to call p1_LN.equals(p2_LN) either - that will be handled by the compareTo call.
You want patient to be ordered by alphabetical by last name, null patients and null last names up front?
class PatientLNComparator implements Comparator<Patient>{
#Override
public int compare(Patient p1, Patient p2) {
String p1_LN = (p1 == null) ? null : p1.last_name;
String p2_LN = (p2 == null) ? null : p2.last_name;
if (p1_LN == null && p2_LN == null)
return 0;
else if (p2_LN == null)
return -1;
else if(p1_LN == null)
return +1;
else
return p1_LN.compareTo(p2_LN);
}
}
To be stable, it really should order by some other fields, like first name, when last names are equal.
I'm assuming you want natural string ordering for this.
First of all, as it is, your compareTo branch is giving inversed results. Don't know if that's what you intended or not (as in you're saying p1 is greater than p2 when the p1's string is lower than p2's).
Furthermore, you can ditch the .equals branch of the if. The compareTo already handles this case.
Therefore a simple
if(p2_LN == null && p1_LN == null)
return 0;
else if(p1_LN == null)
return +1;
else if(p2_LN == null)
return -1;
else return p1_LN.compareTo(p2_LN)
would suffice.
I would use Guava's Ordering class for this:
class Patient {
// ...
public static final Function<Patient, String> GET_LAST_NAME =
new Function<Patient, String>() {
public String apply(Patient from) {
if (from == null) return null;
return from.last_name;
}
};
public static final Comparator<Patient> BY_LAST_NAME =
Ordering.natural()
.onResultOf(GET_LAST_NAME)
.nullsFirst();
}
This will resolve the issue with inconsistent comparison of nulls. It also makes it easy to add a secondary order (e.g. first name):
public static final Comparator<Patient> BY_LAST_NAME =
Ordering.natural()
.onResultOf(GET_LAST_NAME)
.compound(Ordering.natural().onResultOf(GET_FIRST_NAME))
.nullsFirst();

Categories