java comparator on multidimensional array - java

Is there a better way to write this comparator? I have an equivalent of multidimensional array where columns are Object's. The actual objects are String and BigDecimal 99% of the time. I am sorting "rows" on a given column index.
I want to avoid instanceof.
protected static class MyComparator implements Comparator<DataRow> {
private int idx;
public MyComparator(int idx) {
this.idx = idx;
}
#Override
public int compare(DataRow r1, DataRow r2) {
Object o1 = r1.getColumns()[idx];
Object o2 = r2.getColumns()[idx];
if (o1 instanceof String){
return ((String)o1).compareTo((String)o2);
}else if (o1 instanceof BigDecimal){
return ((BigDecimal)o1).compareTo((BigDecimal)o2);
}else{
throw new UnsupportedOperationException("comparison cannot be performed");
}
}

Since both String and BigDecimal are Comparables:
return ((Comparable)o1).compareTo(o2);

I think since you only depend on the type Comparable you could rewrite it as:
public int compare(DataRow r1, DataRow r2) {
Comparable o1 = (Comparable) r1.getColumns()[idx];
Comparable o2 = (Comparable) r2.getColumns()[idx];
return o1.compareTo(o2);
}
If you carefully populate the table you shouldn't face the UnsupportedOperationException situation.

If the colum types are not comparable, I would implement separate comparators and make the Column class a factory for the correct comparator that should be used.

Related

Custom Comparator sort with multiple fields

Because this question is related to my last one, I will link it here.
Suppose I have a class TestB with two integers. I would be able to sort List<TestB> list on a and then on b like this:
list.sort(Comparator.comparing(TestB::getA).thenComparing(TestB::getB));
Now I want to know how to do that with the custom comparator in the last answer.
The custom Comparator version of list.sort(Comparator.comparing(TestB::getA).thenComparing(TestB::getB)); is:
list.sort(new Comparator<>() {
#Override
public int compare(TestB b1, TestB b2) {
int cmp = b1.getA().compareTo(b2.getA());
if (cmp == 0)
cmp = b1.getB().compareTo(b2.getB());
return cmp;
}
});
One option is to use what I call a custom generic multi-comparator:
list2.sort(getComparator( p -> p.getTestB().getA(),
p -> p.getTestB().getB() ));
private <T> Comparator<T> getComparator( Function<T, ? extends Comparable<?>>... functions ) {
return new Comparator<T>() {
#Override
public int compare(T obj1, T obj2) {
for (Function<T, ? extends Comparable<?>> function : functions) {
Comparable<T> res1 = (Comparable<T>) function.apply(obj1);
Comparable<T> res2 = (Comparable<T>) function.apply(obj2);
int result = res1.compareTo((T) res2);
if ( result != 0 ) {
return result;
}
}
return 0;
}
};
}
It will sort from left to right regarding the order which function parameters are placed. Warnings will be raised although. Because it's very generic.
Keep in mind that the types of the final values to be compared must implement Comparator (which primitive types like Integer already do) and you should deal with null problems (I didn't do it here to keep it short).

Reversing part of the CompareTo method from custom objects

I have a object called project, I want to sort this project by 2 of its fields:
First: by Date(Gregorian Callander);
Second: by Name(String);
I want to sort the project by date form new to old. The only way I know to do this is to reverse the collection. However I want to sort the project with same date on name(alphabetically), where reverse also reverses this part of the sort.
Is there a way to reverse only part of the sort method, or any other way to get this sorted first by a date(reverse) and then a string(normal order a-z) ?
At the moment I am overriding the object compareTo method like so:
#Override
public int compareTo(Project project) {
int i = this.projectDate.compareTo(project.projectDate);
if(i != 0) return i;
return this.projectName.compareTo(project.projectName);
}
Date#compareTo returns a value < 0 if this Date is before the Date argument and a value > 0 otherwise.
If you want to reverse the sort from new to old, you can just return the negative compare result:
#Override
public int compareTo(Project project) {
int i = this.projectDate.compareTo(project.projectDate);
if(i != 0) return -i; // reverse sort
return this.projectName.compareTo(project.projectName);
}
In Java 8, the Comparator interface has a method thenComparing. You can use this method to create a comparator that compare by more than one field.
If you have a comparator to compare alphabetically and other to compare by dates, you can combine the comparator to sort by the field you want:
Comparator<Project> nameComparator = ...
Comparator<Project> dateComparator = ...
You can mix the comparator, using the reverse comparator if needed. These are some examples:
Comparator<Project> nameAndDateComparator = nameComparator.thenComparing(dateComparator);
Comparator<Project> nameAndReversedDateComparator = nameComparator.thenComparing(dateComparator.reversed());
Then, you can use the method sort as usual with the comparator that matches your needs.
If you are not using Java 8, you can create an utility class to combine your comparators:
public class CombinedComparator<T> implements Comparator<T> {
Comparator<T> firstComparator;
Comparator<T> secondComparator;
public CombinedComparator(Comparator<T> firstComparator, Comparator<T> secondComparator) {
this.firstComparator = firstComparator;
this.secondComparator = secondComparator;
}
#Override
public int compare(T o1, T o2) {
int result = firstComparator.compare(o1, o2);
return (result != 0) ? result : secondComparator.compare(o1, o2);
}
}
And you could create multiple fields comparators this way:
Comparator<Project> nameAndDateComparator = new CombinedComparator<Project>(nameComparator, dateComparator);
Comparator<Project> nameAndReversedDateComparator = new CombinedComparator<Project>(nameComparator, Collections.reverseOrder(dateComparator));

How to combine two Collections.sort functions

I have a program where i am have a list of Names, and how many people have that name. I want to put the names in alphabetical order while also putting the counts from greatest to least. If the name has the same count it puts the name in alphabetical order. I figured out how to put the names in abc order and figured out how to put the counts in greatest to least but i cant figure out how to combine the two to get list of names greatest to least and if they have the same count in alphabetical order.
Collections.sort(oneName, new OneNameCompare());
for(OneName a: oneName)
{
System.out.println(a.toString());
}
Collections.sort(oneName, new OneNameCountCompare());
for(OneName a: oneName)
{
System.out.println(a.toString());
}
You can make another Comparator that combines the effects of the two other Comparators. If one comparator compares equal, then you can call the second comparator and use its value.
public class CountNameComparator implements Comparator<Name>
{
private OneNameCompare c1 = new OneNameCompare();
private OneNameCountCompare c2 = new OneNameCountCompare();
#Override
public int compare(Name n1, Name n2)
{
int comp = c1.compare(n1, n2);
if (comp != 0) return comp;
return c2.compare(n1, n2);
}
}
Then you can call Collections.sort just once.
Collections.sort(oneName, new CountNameComparator());
This can be generalized for any number of comparators.
You can combine comparators like this
public static <T> Comparator<T> combine(final Comparator<T> c1, final Comparator<T> c2) {
return new Comparator<T>() {
public int compare(T t1, T t2) {
int cmp = c1.compare(t1, t2);
if (cmp == 0)
cmp = c2.compare(t1, t2);
return cmp;
}
};
}
BTW Comparators are a good example of when to use a stateless singleton. All comparators or a type are the same so you only ever need one of them.
public enum OneNameCompare implements Comparator<OneName> {
INSTANCE;
public int compare(OneName o1, OneName o2) {
int cmp = // compare the two objects
return cmp;
}
}
This avoid creating new instances or cache copies. You only ever need one of each type.
Assuming you're using the Apache Commons Collections API, you might want to check out ComparatorUtils.chainedComparator:
Collections.sort(oneName, ComparatorUtils.chainedComparator(new OneNameCompare(), new OneNameCountCompare());
Using lambdas from Java 8:
Collections.sort(Arrays.asList(""),
(e1, e2) -> e1.getName().compareTo(e2.getName()) != 0 ?
e1.getName().compareTo(e2.getName()) :
e1.getCount().compareTo(e2.getCount()));

TableSorter numeric values sorting

Since I'm using Java 1.4.2, this means that I cannot use Java's implementation of the table sorter. Instead, I've been using the TableSorter.java class from an earlier reply to my previous post:
Heads up on implementing rowsorter and rowfilter java 1.4
It's working perfectly with one problem however, which is that it doesn't sort numeric values correctly. For instance, I have the following sequence of numbers in my table:
5,18,9,7,2,33
A increasing order sorting would display them like this in my JTable:
18,2,33,5,7,9
A decreasing order sorting would display them like this in my JTable:
9,7,5,33,2,18
I don't know if you have realized it, but apparently, the sorting of numeric values is only happening based on the first digit.
Do you have any quick fix for the problem? Please bare in mind that those numeric values are used as strings in my JTable as suggested by the getValue() method.
Verify that getColumnClass() returns a numeric type, such as Number.
Addendum: MyTableModel in TableSorterDemo is an example. Data for the third column is of type Integer, a subclass of Number; as a result, getColumnClass() returns Integer.class. Because Integer implements Comparable, the column is sorted numerically.
You need to implement a Comparator that performs a numeric comparison rather than the default lexicographical one. So something like (taken from http://crossedlogic.blogspot.com/2008/11/java-custom-comparators-sorting-text.html):
Comparator c1 = new java.util.Comparator() {
/**
* Custom compare to sort numbers as numbers.
* Strings as strings, with numbers ordered before strings.
*
* #param o1
* #param o2
* #return
*/
#Override
public int compare(Object oo1, Object oo2) {
boolean isFirstNumeric, isSecondNumeric;
String o1 = oo1.toString(), o2 = oo2.toString();
isFirstNumeric = o1.matches("\\d+");
isSecondNumeric = o2.matches("\\d+");
if (isFirstNumeric) {
if (isSecondNumeric) {
return Integer.valueOf(o1).compareTo(Integer.valueOf(o2));
} else {
return -1; // numbers always smaller than letters
}
} else {
if (isSecondNumeric) {
return 1; // numbers always smaller than letters
} else {
isFirstNumeric = o1.split("[^0-9]")[0].matches("\\d+");
isSecondNumeric = o2.split("[^0-9]")[0].matches("\\d+");
if (isFirstNumeric) {
if (isSecondNumeric) {
int intCompare = Integer.valueOf(o1.split("[^0-9]")[0]).compareTo(Integer.valueOf(o2.split("[^0-9]")[0]));
if (intCompare == 0) {
return o1.compareToIgnoreCase(o2);
}
return intCompare;
} else {
return -1; // numbers always smaller than letters
}
} else {
if (isSecondNumeric) {
return 1; // numbers always smaller than letters
} else {
return o1.compareToIgnoreCase(o2);
}
}
}
}
}
};
then pass that in as the comparator for the column:
tblSorter.setColumnComparator(String.class, c1);
Let me know how that works.
To update you guys on this, what I did was to replace the LEXICAL_COMPARATOR in the TableSorter.java from my original post with this:
public static final Comparator LEXICAL_COMPARATOR = new Comparator() {
public int compare(Object o1, Object o2) {
if(o1 instanceof Integer)
{
Integer firstNumeric, secondNumeric;
firstNumeric =(Integer)o1;
secondNumeric= (Integer)o2;
return firstNumeric.compareTo(secondNumeric);
}
else
return o1.toString().compareTo(o2.toString());
}
};
Now, in the getValueAt, make sure that your "int" values are cast into Integer by simply doing:
new Integer(intValue);
I hope this will save people some time.

Sort an ArrayList base on multiple attributes

I have an ArrayList of object. The object contain attributes date and value. So I want to sort the objects on the date, and for all objects in the same date I want to sort them on value. How can I do that?
Implement a custom Comparator, then use Collections.sort(List, Comparator). It will probably look something like this:
public class FooComparator implements Comparator<Foo> {
public int compare(Foo a, Foo b) {
int dateComparison = a.date.compareTo(b.date);
return dateComparison == 0 ? a.value.compareTo(b.value) : dateComparison;
}
}
Collections.sort(foos, new FooComparator());
public static <T> void sort(List<T> list, final List<Comparator<T>> comparatorList) {
if (comparatorList.isEmpty()) {//Always equals, if no Comparator.
throw new IllegalArgumentException("comparatorList is empty.");
}
Comparator<T> comparator = new Comparator<T>() {
public int compare(T o1, T o2) {
for (Comparator<T> c:comparatorList) {
if (c.compare(o1, o2) > 0) {
return 1;
} else if (c.compare(o1, o2) < 0) {
return -1;
}
}
return 0;
}
};
Collections.sort(list, comparator);
}
Java-8 solution using Stream API:
List<Foo> sorted = list.stream()
.sorted(Comparator.comparing(Foo::getDate)
.thenComparing(Foo::getValue))
.collect(Collectors.toList());
If you want to sort the original list itself:
list.sort(Comparator.comparing(Foo::getDate)
.thenComparing(Foo::getValue));
If you want sample code looks like, you can use following:
Collections.sort(foos, new Comparator<Foo>{
public int compare(Foo a, Foo b) {
int dateComparison = a.date.compareTo(b.date);
return dateComparison == 0 ? a.value.compareTo(b.value) : dateComparison;
}
});
If the class of the object implements Comparable, then all you need to do is properly code the compareTo method to first compare dates, and then if dates are equal, compare values, and then return the appropriate int result based on the findings.

Categories

Resources