Getting error: Comparison method violates its general contract - java

I have tried many possible solution given on the net like to set System property and to convert in double but still getting same error:
java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.ComparableTimSort.mergeHi(ComparableTimSort.java:835)
at java.util.ComparableTimSort.mergeAt(ComparableTimSort.java:453)
at java.util.ComparableTimSort.mergeForceCollapse(ComparableTimSort.java:392)
at java.util.ComparableTimSort.sort(ComparableTimSort.java:191)
at java.util.ComparableTimSort.sort(ComparableTimSort.java:146)
at java.util.Arrays.sort(Arrays.java:472)
at java.util.Collections.sort(Collections.java:155)
Here is my code:
System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");
Collections.sort(docs, new Comparator<FeedDocument>() {
public int compare(FeedDocument o1, FeedDocument o2) {
int year1 = 0;
int year2 = 0;
int returnResult = 0;
if (o1.containsKey(FeedConstants.PUBLICATION_YEAR)
&& o2.containsKey(FeedConstants.PUBLICATION_YEAR)
&& o1.get(FeedConstants.PUBLICATION_YEAR) != null
&& (o1.get(FeedConstants.PUBLICATION_YEAR) instanceof String)
&& o2.get(FeedConstants.PUBLICATION_YEAR) != null
&& (o2.get(FeedConstants.PUBLICATION_YEAR) instanceof String)) {
String firstyear = (String) o1.get((FeedConstants.PUBLICATION_YEAR));
String secondyear = (String) o2.get((FeedConstants.PUBLICATION_YEAR));
if (firstyear.equals(secondyear)) {
return 0;
} else if (firstyear != null && !firstyear.isEmpty() && secondyear != null
&& !secondyear.isEmpty()) {
year1 = Integer.parseInt(firstyear.trim());
year2 = Integer.parseInt(secondyear.trim());
// int result = year2 - year1;
// if (result > 0) {
// returnResult = 1;
// } else if (result < 0) {
// returnResult = -1;
// }
return Double.compare(year2, year1);
}
} else {
returnResult = 0;
}
return returnResult;
}
});

Pretty sure I know what's happening here...
Suppse:
o1.get(FeedConstants.PUBLICATION_YEAR) != null
o2.get(FeedConstants.PUBLICATION_YEAR) == null
o3.get(FeedConstants.PUBLICATION_YEAR) != null
Then:
compare (o1, o2); //returns 0
compare (o2, o3); //returns 0
compare (o1, o3); //returns not 0
So you're claiming o1 == o2 == o3 but o1 != o3

Edward Peters' answer is correct in diagnosing your problem, as your compare method doesn't produce consistent (transitive) results.
The best way to resolve this is something like the following in your compare method:
if (o1.get(FeedConstants.PUBLICATION_YEAR) instanceof String) {
if (o2.get(FeedConstants.PUBLICATION_YEAR) instanceof String) {
// Perform the comparison here like you are
} else {
/*
* This could also be 1, the key is to have it consistent
* so the final sorted list clearly separates the FeedDocuments
* with a String PUBLICATION_YEAR and those without one.
*/
return -1;
}
} else if (o2.get(FeedConstants.PUBLICATION_YEAR) instanceof String) {
/*
* In this case, o1 doesn't have a String PUBLICATION_YEAR and o2
* does, so this needs to be the opposite of the return value
* 6 lines up to be consistent.
*/
return 1;
} else {
/*
* Consider all FeedDocuments without a String PUBLICATION_YEAR
* to be equivalent, otherwise you could do some other comparison
* on them here if you wanted.
*/
return 0;
}
The key is, if you only care about a subset of the list being sorted (the FeedDocuments with a String publication year, then you need to first separate them from the rest of the list that you don't care about being sorted (by returning either 1 or -1 when one of the FeedDocuments has a String publication year and the other doesn't). Then, you are free to sort the desired subset without inconsistent results.

Related

"java.lang.IllegalArgumentException: Comparison method violates its general contract!" during comparison [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

java.lang.IllegalArgumentException in java.util.TimSort.mergeLo() [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 - Java [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 when sorting

I keep getting: Comparison method violates its general contract! Exception for the below compare function when I call Arrays.sort(ScreenItems)
One assumption I have is that the ParseInt below is throwing an exception for the left object but not to the right object
Could that be the case?
public int compare(Object o1, Object o2) {
if (o2 == null || o1 == null)
return 0;
if (!(o1 instanceof ScreenItem) || !(o2 instanceof ScreenItem))
return 0;
ScreenItem item1 = (ScreenItem) o1;
ScreenItem item2 = (ScreenItem) o2;
String subSystem1 = item1.getSubSystem();
String subSystem2 = item2.getSubSystem();
if(subSystem1.equals(subSystem2)) {
return 0;
} else if(subSystem1.startsWith(subSystem2)) {
return 1;
} else if (subSystem2.startsWith(subSystem1)) {
return -1;
}
String order1 = item1.getOrder();
String order2 = item2.getOrder();
if (order1 == null || order2 == null){
String name1 = item1.getName();
String name2 = item2.getName();
if(name1 == null || name2 == null)
return 0;
return name1.compareToIgnoreCase(name2);
}
try {
return Integer.parseInt(order1) - Integer.parseInt(order2);
} catch (Exception ex) {
return 0;
}
}
This is one example of the sort of change that I think is needed. As #CommuSoft pointed out in a comment, the current treatment of null for o1 and o2 breaks transitivity.
I would replace:
if (o2 == null || o1 == null)
return 0;
with:
if (o2 == null && o1 == null)
return 0;
if (o1 == null)
return -1;
if (o2 == null)
return 1;
This treats null as equal to itself, but less than all non-null references. Of course, you could also choose to treat null as greater than all non-null references, as long as you are consistent. Treating it as equal to everything, as is done in the current code, is not consistent if there are any two non-null references for which you return a non-zero value.
More generally, I suggest writing a set of rules for the ordering, ensuring they meet the Comparator contract, then writing both code and tests to match those rules.

"Comparison method violates its general contract!"

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

Categories

Resources