JavaFX TableView Ignore empty cells when sorting - java

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);
};

Related

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.

GWT CellTable does not sort correctly if the column contains NULL cells

I am using a GWT CellTable and the following code does not sort correctly if the column contains NULL cells as well as cells that are not NULL:
columnSortHandler.setComparator(sixColumn, new Comparator<SectDtlsString>() {
#Override
public int compare(SectDtlsString o1, SectDtlsString o2) {
if (o1 == o2) {
return 0;
}
// Compare the Six columns.
if (o1 != null) {
return (o2 != null) ? o1.getsixPatrol().compareTo(o2.getsixPatrol()) : 1;
}
return -1;
}
});
table.addColumnSortHandler(columnSortHandler);
For example:
Black, null, null, Red - does nothing. It should return - null, null, Black, Red on first select and Red, Black, null, null - on second select
Black, Red, Brown, Tawney - returns - Black, Brown, Red, Tawney on first select and - Tawney, Red, Brown, Black - on the second select (i.e., no nulls works).
I have near identical code that refers to columns that do not contain NULLs and they sort perfectly well. I copied this code from a tutorial.
This is the result after the advice:
// Compare the Six columns.
if (o1 != null) {
if (o1 == o2) {
return 0;
}
if (o1.getsixPatrol() != null) {
if (o1.getsixPatrol() == o2.getsixPatrol()) {
return 0;
}
return o2 != null ? o1.getsixPatrol().compareTo(o2.getsixPatrol()) : 1;
}
}
return -1;
There are two problems in your code.
First, null check is in the wrong place: o1 == o2 will through exception if o1 is null. It should be:
// Compare the Six columns.
if (o1 != null) {
if (o1 == o2) {
return 0;
}
return o2 != null ? o1.getsixPatrol().compareTo(o2.getsixPatrol()) : 1;
}
Second, it's not enough to check that o1 and o2 are not null. You also need to check if o1.getsixPatrol() and o2.getsixPatrol() are not null before comparing them.

List<List<Object>> sorting

I am facing an issue while sorting List<List<Object>>. I have created a Custom Comparator where i have written the code to sort the data.
public class CustomComparator implements Comparator<List<Object>>
{
static int i = 0;
public int compare(List<Object> o1, List<Object> o2) {
if (i < o1.size()) {
System.out.println(i);
Object obj1 = o1.get(i);
Object obj2 = o2.get(i);
if (obj1 != null && obj2 != null) {
int value = compareTo(obj1.toString(), obj2.toString());
if (value == 0) {
i++;
compare(o1, o2);
}
return value;
}
if (obj1 == null && obj2 != null) {
return -1;
}
if (obj1 != null && obj2 == null) {
return 1;
}
if (obj1 == null && obj2 == null) {
i++;
compare(o1, o2);
}
}
else{
i=0;
}
return 0;
}
public int compareTo(String value1, String value2) {
return value1.compareTo(value2);
}
}
Logic that i am trying to implement is that first it will try to sort the using the Object at 0th position. If the values at 0th position is equal or null then it will sort using the Object at 2nd position.etc.
If either of the values are null then that element will be shifted downward.
However when i try to sort using the code that I have written it gets sort but neither in ascending or descending order. It is just shuffled.
It makes no sense to use recursion here. Just iterate over the two Lists in a loop to compare their elements.
Your recursive implementation doesn't work since you ignore that value returned by the recursive calls. The use of a static variable for keeping the index is also a bad idea. What will happen if your Comparator instance is used by two threads concurrently?
Try this,
public int compare(List<Object> o1, List<Object> o2) {
public int compare(List<Object> o1, List<Object> o2) {
int i=0;
//TODO validation here for null check
while(i< o1.size()){
Object obj1 = o1.get(i);
Object obj2 = o2.get(i);
if (obj1 == null && obj2 != null) {
return -1;
}else if (obj1 != null && obj2 == null) {
return 1;
}else if(obj1 != null && obj2 != null){
int value = compareTo(obj1.toString(), obj2.toString());
if(value!=0){
return value;
}
}
i++;
}
return 0;
}
}
public static void main(String args[]) {
List<List<String>> list = new ArrayList<>();
List<String> a = new ArrayList<>();
a.add("c");
a.add("a");
List<String> b = new ArrayList<>();
b.add("h");
b.add("b");
list.add(a);
list.add(b);
Collections.sort(list,new Comparator<List<String>>() {
#Override
public int compare(List<String> o1, List<String> o2) {
Collections.sort(o1);
Collections.sort(o2);
//Compare your list based on your criteria
}
});
}

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.

How to #Override Comparator to make the Column in CellTable sort based on Integer not based on String (GWT)?

Ok, I got a Table with a list of No.
No
1
2
5
10
20
If i set columnSortHandler for the No column using String like this:
columnSortHandler.setComparator(noColumn, new Comparator<String[]>() {
#Override
public int compare(String[] o1, String[] o2) {
if (o1==o2) {
return 0;
}
if (o1 != null) {
return (o2 != null) ? o1[0].compareTo(o2[0]) : 1;
}
return -1;
}
});
Then it won't sort like integer but like String. Ex: it will sort like this:
No
1
10
2
20
5
Then it is not correct.
So tried:
columnSortHandler.setComparator(noColumn, new Comparator<String[]>() {
#Override
public int compare(String[] o1, String[] o2) {
if (o1==o2) {
return 0;
}
if (o1 != null) {
return (o2 != null) ? Integer.parseInt(o1[0]).compareTo(Integer.parseInt(o2[0]) : 1;
}
return -1;
}
});
But compareTo does not apply for Integer.
So my question is
How to #Override Comparator to make the Column in CellTable sort based on Integer not based on String (GWT)?
Use Integer.valueOf() as shown below
return (o2 != null) ? Integer.valueOf(o1[0]).compareTo(Integer.valueOf(o2[0])) : 1;
instead of
return (o2 != null) ? Integer.parseInt(o1[0]).compareTo(Integer.parseInt(o2[0]) : 1;
Integer.parseInt() returns primitive int not wrapper Integer class.
Cannot invoke compareTo(int) on the primitive type int
There are lots of ways to convert Stringinto Integer wrapper class.
Try any one:
Integer.valueOf(String)
new Integer(String)
Integer.getInteger(String)

Categories

Resources