Comparison method violates its general contract when sorting - java

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.

Related

Comparison method violates its general contract, sorting by different properties

Need to sort some items based on a time stamp/ current hour and/or the name property of the item's person object in alphabetical order. How would I correct the IllegalArgumentException which is thrown in some cases?
public static final Comparator<Item> sortByTimeAndName = new Comparator<Item>() {
#Override
public int compare(Item lhs, Item rhs) {
if(lhs.getDate() != null && rhs.getDate != null){
if (lhs.getDate().getTime() < rhs.getDate().getTime()) {
return -1;
} else if (lhs.getDate().getTime() == rhs.getDate().getTime()) {
if (lhs.getHour() < rhs.getHour()) {
return -1;
} else if (lhs.getHour() == rhs.getHour()) {
if(lhs.getPerson().getName().compareTo(rhs.getPerson().getName()) == 0){
// if two persons have same name, sort by person id
if (lhs.getPerson().getID() < rhs.getPerson().getID()){
return -1;
}
}
else{
return lhs.getPatient().getName().compareTo(rhs.getPatient().getName());
}
}
}
}else{
if (lhs.getHour() < rhs.getHour()) {
return -1;
} else if (lhs.getHour() == rhs.getHour()) {
if(lhs.getPerson().getName().compareTo(rhs.getPerson().getName()) == 0){
if (lhs.getPerson().getID() < rhs.getPerson().getID()){
return -1;
}
}
else{
return lhs.getPerson().getName().compareTo(rhs.getPerson().getName());
}
}
}
return 1;
}
};
Stack trace:
Caused by java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.TimSort.mergeLo(TimSort.java:761)
at java.util.TimSort.mergeAt(TimSort.java:497)
at java.util.TimSort.mergeCollapse(TimSort.java:421)
at java.util.TimSort.sort(TimSort.java:210)
at java.util.Arrays.sort(Arrays.java:1998)
at java.util.Collections.sort(Collections.java:1900)
....
Assume there are 3 objects of type Item such as:
for a: assume getDate() == null
for b: assume getDate() != null
So the comperartor's result is
assume if (lhs.getHour() < rhs.getHour()) is true then a < b
also
for b: assume getDate() != null
for c: assume getDate() != null
So the comperartor's result is
assume if (lhs.getDate().getTime() < rhs.getDate().getTime()) is true then b < c
also
for a: assume getDate() == null
for c: assume getDate() != null
So the comperartor's result is
assume if (lhs.getHour() > rhs.getHour()) is true then a > c (because of the final return 1)
As you can see there is a contradiction:
a < b and b < c but a > c
I believe that cases like this violate the comparison method's general contract.

JavaFX TableView Ignore empty cells when sorting

I have problems when sorting columns when there are empty cells.
I created a new Comparator for my codeMed column:
codeMed.setComparator(new Comparator<Integer>() {
#Override
public int compare(Integer o1, Integer o2) {
if (o1 == null)return -1;
if (o2 == null) return -1;
return o1 < o2 ? -1 : o1 == o2 ? 0 :1;
}
});
At first, it seems to work fine:
But if I decide to sort a different column then sort the codeMed column this happens:
I imagine the error is in the Comparator but I don't know what is the problem.
EDIT: I want that the null values will always be at the bottom of the column. I tried something like that:
if (codeMed.getSortType() == TableColumn.SortType.DESCENDING) {
return (o1 != null ? o1 : Integer.MAX_VALUE) - (o2 != null ? o2 : Integer.MAX_VALUE);
} else if (codeMed.getSortType()==TableColumn.SortType.ASCENDING){
return (o1 != null ? o1 : Integer.MIN_VALUE) - (o2 != null ? o2 : Integer.MIN_VALUE);
}
return 0;
But it doesn't work :/ (Maybe because of the problem that Slaw suggests)
My solution:
Thank you very much Jai, I adapt your code just because I want to use it for 2 different columns:
Comparator<Integer> integerComparator = new Comparator<>() {
#Override
public int compare(Integer o1, Integer o2) {
final boolean isDesc = tabInfosPatient.getSortOrder().get(0).getSortType() == TableColumn.SortType.DESCENDING;
if (o1 == null && o2 == null) return 0;
else if (o1 == null) return isDesc ? -1 : 1;
else if (o2 == null) return isDesc ? 1 : -1;
else return Integer.compare(o1, o2);
}
};
I'm surprised it even worked once. This is what I think would work well:
codeMed.setComparator(new Comparator<Integer>() {
#Override
public int compare(Integer o1, Integer o2) {
return (o1 != null ? o1 : Integer.MAX_VALUE) - (o2 != null ? o2 : Integer.MAX_VALUE);
}
});
This would assume any null value is treated as if it represents the largest possible int value. This would force it to move to the bottom of the list in an ascending sorted column, or at the top if it is in a descending sorted column. If the reverse is desired, switch Integer.MAX_VALUE to Integer.MIN_VALUE.
What you did doesn't work because you have violated this:
The implementor must ensure that sgn(compare(x, y)) == -sgn(compare(y,
x)) for all x and y. (This implies that compare(x, y) must throw an
exception if and only if compare(y, x) throws an exception.)
An example of this violation is when o1 is 5 and o2 is null. In this case, compare(o1, o2) returns -1 and compare(o2, o1) returns -1 as well. One of them should return positive value while the other negative value, or both must return 0.
Update
This is what you need.
public class Model {
private final ObjectProperty<Integer> v = new SimpleObjectProperty<>();
public Model(Integer v) {
this.v.setValue(v);
}
public final ObjectProperty<Integer> vProperty() {
return this.v;
}
public final Integer getV() {
return this.vProperty().get();
}
public final void setV(final Integer v) {
this.vProperty().set(v);
}
}
ObservableList<Model> list = FXCollections.observableArrayList();
list.addAll(new Model(20), new Model(-30), new Model(null), new Model(10));
TableView<Model> tv = new TableView<>();
TableColumn<Model, Integer> tc = new TableColumn<>();
tc.setCellValueFactory(new PropertyValueFactory<>("v"));
tv.getColumns().add(tc);
tv.setItems(list);
Comparator<Integer> ascComparator = (o1, o2) ->
(o1 != null ? o1 : Integer.MAX_VALUE) -
(o2 != null ? o2 : Integer.MAX_VALUE);
Comparator<Integer> descComparator = (o1, o2) ->
(o1 != null ? o1 : Integer.MIN_VALUE) -
(o2 != null ? o2 : Integer.MIN_VALUE);
#SuppressWarnings("unchecked")
Comparator<Integer> defaultComparator = TableColumn.DEFAULT_COMPARATOR;
tc.comparatorProperty().bind(
Bindings.when(tc.sortTypeProperty().isEqualTo(SortType.ASCENDING))
.then(ascComparator)
.otherwise(
Bindings.when(tc.sortTypeProperty().isEqualTo(SortType.DESCENDING))
.then(descComparator)
.otherwise(defaultComparator)));
Also, I would want to point out that while using Integer.MIN_VALUE and Integer.MAX_VALUE should work most of the time, there is a much higher risk of integer underflow and overflow problem, which I'm not sure if it's a problem for using comparators and comparables.
If you want to be more safe, then do a bunch of if-else:
Comparator<Integer> ascComparator = (o1, o2) -> {
if (o1 == null && o2 == null) return 0;
else if (o1 == null && o2 != null) return -1;
else if (o1 != null && o2 == null) return 1;
else return Integer.compare(o1, o2);
};
Update again
After looking at what you attempted, I realized that this works:
Comparator<Integer> comparator = (o1, o2) -> {
final boolean isDesc = tc.getSortType() == SortType.DESCENDING;
if (o1 == null && o2 == null) return 0;
else if (o1 == null && o2 != null) return isDesc ? -1 : 1;
else if (o1 != null && o2 == null) return isDesc ? 1 : -1;
else return Integer.compare(o1, o2);
};

Getting error: Comparison method violates its general contract

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.

Java: why equal method is not called when searching for same variable in hashmap

Here is my test class..
import java.util.HashMap;
public class Test {
public static void main(String[] args) {
A a = new A(0, 1);
HashMap<A, Integer> map = new HashMap<A, Integer>();
map.put(a, (a.x + a.y));
System.out.println(map.containsKey(a));
System.out.println("----------------- ");
System.out.println(map.containsKey(new A(0, 1)));
}
}
and here is my class A with hashcode and equal method generated by eclipse.
class A {
int x, y;
public A(int x, int y) {
super();
this.x = x;
this.y = y;
}
#Override
public int hashCode() {
System.out.println(" in hashcode");
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
#Override
public boolean equals(Object obj) {
System.out.println(" in equal");
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
A other = (A) obj;
if (x != other.x)
return false;
if (y != other.y)
return false;
return true;
}
}
The output of program is
in hashcode
in hashcode
true
-----------------
in hashcode
in equal
true
My questions are: (I know the contract of hashcode and equal and why it is used)
Why in first case hashcode method is called twise ?
Why in first case equal does not called ? How JVM know that it is the same variable we are searching?
1) getHashCode is called one when you call put, then again when you call contains.
2) in the first case, the hashmap contains a reference to a, i.e. the address of a in memory, so there is no need to call equals. In the second case, the table lookup finds a, but this is a different object from the new A that you gave as a parameter, so there is a need to call equals() to find out if they are equal (they could be different and have the same hash code, this would be a collision).
If you take a look at the source code of HashMap.containsKey you'll find the following (taken from here: http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/HashMap.java#HashMap.containsKey%28java.lang.Object%29 )
public boolean containsKey(Object key) {
return getEntry(key) != null;
}
final Entry<K,V> getEntry(Object key) {
int hash = (key == null) ? 0 : hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
The important part is this (k = e.key) == key || (key != null && key.equals(k)). The metod first compares key objects by reference and then uses equals method for comparison but only if the references are different.
In your first call to containsKey key references will be the same (the same object). In second call references will be different (different instances of an "equal" object), hence call to equals.
As you can view in #kresimir-nesek pasted code,
First get the hashCode: int hash = (key == null) ? 0 : hash(key.hashCode()); and prints " in hashcode"
Then compare the java object identifier, (k = e.key) == key
1) in the first case is the same and return true. Calling to map.put you get the second " in hashcode"
2) but in the second case the object identifier is different, so call to equals (...(key != null && key.equals(k))...)
" in equal"

Check if all values in a map are equal

I need to check if all values in a map are equal. I have a method to perform this task but would like to use a library or native methods. Limitations: Java 5 + Apache Commons libraries.
public static boolean isUnique(Map<Dboid,?> aMap){
boolean isUnique = true;
Object currValue = null;
int iteration = 0;
Iterator<?> it = aMap.entrySet().iterator();
while(it.hasNext() && isUnique){
iteration++;
Object value = it.next();
if(iteration > 1){
if (value != null && currValue == null ||
value == null && currValue != null ||
value != null && currValue != null & !value.equals(currValue)) {
isUnique = false;
}
}
currValue = value;
}
return isUnique;
}
What about this something like this:
Set<String> values = new HashSet<String>(aMap.values());
boolean isUnique = values.size() == 1;
how about
return (new HashSet(aMap.values()).size() == 1)
I know the original questions asks for solutions in Java 5, but in case someone else searching for an answer to this question is not limited to Java 5 here is a Java 8 approach.
return aMap.values().stream().distinct().limit(2).count() < 2
You could store the values in a Bidirectional Map and always have this property.
public static boolean isUnique(Map<Dboid,?> aMap) {
Set<Object> values = new HashSet<Object>();
for (Map.Entry<Dboid,?> entry : aMap.entrySet()) {
if (!values.isEmpty() && values.add(entry.getValue())) {
return false;
}
}
return true;
}
This solution has the advantage to offer a memory-saving short cut if there are many differences in the map. For the special case of an empty Map you might choose false as return value, change it appropriately for your purpose.
Or even better without a Set (if your Map does not contain null-values):
public static boolean isUnique(Map<Dboid,?> aMap) {
Object value = null;
for (Object entry : aMap.values()) {
if (value == null) {
value = entry;
} else if (!value.equals(entry)) {
return false;
}
}
return true;
}
As my comment above:
//think in a more proper name isAllValuesAreUnique for example
public static boolean isUnique(Map<Dboid,?> aMap){
if(aMap == null)
return true; // or throw IlegalArgumentException()
Collection<?> c = aMap.getValues();
return new HashSet<>(c).size() <= 1;
}

Categories

Resources