Comparing Using 2 Properties in Ascending and Descending Orders - java

I have an object that has multiple properties, two of which the user can choose to order by, both can be Ascending, Descending, or neither (Normal), and they are independent of each other. So my cases are:
Case 1
propA - Normal
propB - Normal
Case 2
propA - Asc
propB - Normal
Case 3
propA - Desc
propB - Normal
And you get the idea. I'm using a Comparator to do this, and so far I have been able to get it to sort when one or both values are set to Normal. The part that I'm unsure of is what to do when I have chosen to sort by both methods. For example, if I want to order by propA ascending and propB descending, it should look a little like this
propA propB
A Z
A D
B M
B A
R Q
Z Z
Z A
Here is how I'm sorting now
#Override
public int compare(Field lhs, Field rhs) {
switch (growerSort) {
case NORMAL:
switch (fieldSort) {
case NORMAL:
return ((Integer) lhs.getID()).compareTo(rhs.getID());
case ASC:
return lhs.getPropB().toLowerCase().compareTo(rhs.getPropB().toLowerCase());
default:
return rhs.getPropB().toLowerCase().compareTo(lhs.getPropB().toLowerCase());
}
case ASC:
switch (fieldSort) {
case NORMAL:
return lhs.getPropA().toLowerCase().compareTo(rhs.getPropA().toLowerCase());;
case ASC:
return 0; // 0 used as placeholder
default:
return 0; // 0 used as placeholder
}
default:
switch (fieldSort) {
case NORMAL:
return rhs.getPropA().toLowerCase().compareTo(lhs.getPropA().toLowerCase());
case ASC:
return 0; // 0 used as placeholder
default:
return 0; // 0 used as placeholder
}
}
}
How can I sort with two different fields, each with their own order of sorting?

I'm a little bit confused of your Comparator. It's not easy to understand what switch triggers what event.
However I'll describe the standard procedure.
You'll need a priority order over your fields you want to compare. In your example above, I assume it first must be sorted by propA, then by propB.
Then you first sort by propA. If it returns "equals" (zero), then you want to sort by the next field, propB, and so on.
Let me show you an example:
#Override
public int compare(final Field lhs, final Field rhs) {
int firstCompareValue = lhs.getPropA().compareTo(rhs.getPropA());
if (firstCompareValue == 0) {
// lhs and rhs are equals in propA, use propB
int secondCompareValue = lhs.getPropB().compareTo(rhs.getPropB());
return secondCompareValue;
} else {
return firstCompareValue;
}
}
Of course you can also do this iterative if you have multiple fields, as long as you have specified an order (e.g. by using an ordered list over your property fields).
Now you need to add your switches to this showcase :) I'll recommend doing a PropertyComparator for that.
public final class PropertyComparator extends Comparator<Comparable<?>> {
private final boolean mUseDscOrder = false;
public void setUseDscOrder(final boolean useDscOrder) {
mUseDscOrder = useDscOrder;
}
public int compare(final Comparable<?> o1, final Comparable<?> o2) {
if (!mUseDscOrder) {
return o1.compareTo(o2);
} else {
// Reverses the logic, results in DscOrder
return o2.compareTo(o1)
}
}
}
And now use it in the above Comparator.
#Override
public int compare(final Field lhs, final Field rhs, final boolean firstUseDscOrder, final boolean secondUseDcsOrder) {
PropertyComparator firstComparator = new PropertyComparator();
firstComparator.setUseDscOrder(firstUseDscOrder);
int firstCompareValue = firstComparator.compare(lhs.getPropA(), rhs.getPropA());
if (firstCompareValue == 0) {
// lhs and rhs are equals in propA, use propB
PropertyComparator secondComparator = new PropertyComparator();
secondComparator.setUseDscOrder(secondUseDscOrder);
int secondCompareValue = secondComparator.compare(lhs.getPropB(), rhs.getPropB());
return secondCompareValue;
} else {
return firstCompareValue;
}
}
I've not tested it but I think you get the idea :)

Create 1 comparator that sorts on the "first field". If the values of the first field are equal, sort on the "second field".
if(object1.f1.equals(object2.f1)){
object1.f2.compareTo(object2.f2);
} else {
object1.f1.compareTo(object2.f1);
}

Related

Collections.sort for multiple conditions [duplicate]

This question already has answers here:
How to compare objects by multiple fields
(23 answers)
Closed 3 years ago.
I have a list of objects which I want to sort. But I have three different conditions. That is why I have this code:
Collections.sort(myList, new Comparator<MyObject>() {
#Override
public int compare(MyObject o1, MyObject o2) {
// my code
}
});
Three times. First sorting all elements with condition x to the bottom of the list. Then a second time sorting all elements with condition y to the bottom and again for condition z.
Now I wonder how I could combine multiple conditions in one compare-method. So I don't have to do this three times.
Edit: To be more clear about the conditions. I want to sort all objects that have the criteria x to the bottom of the list. If an element fulfills criteria y it should be even below x and the same applies analog for z.
You can use Java Streams. This is also used when using Collection.sort():
myList.sort(Comparator.comparing(MyObject::getAttributeX)
.thenComparing(i -> i.getSomething().getSubValue())
.thenComparing((a, b) -> a.getInt() - b.getInt()));
If you are using a lower version than Java 8 you have to implement the sort logic yourself in a Comparator or use an external library:
Collections.sort(myList, new Comparator<MyObject>() {
#Override
public int compare(MyObject a, MyObject b) {
int cmp0 = a.getAttributeX().compareTo(b.getAttributeX());
if (cmp0 != 0) {
return cmp0;
}
int cmp1 = a.getSomething().getSubValue().compareTo(b.getSomething().getSubValue());
if (cmp1 != 0) {
return cmp1;
}
return a.getInt() - b.getInt();
}
});
I am assuming that MyObject has methods like
public boolean isX() {
return // either false or true;
}
Then sorting is easiest in this way:
Collections.sort(myList,
Comparator.comparing(MyObject::isX)
.thenComparing(MyObject::isY)
.thenComparing(MyObject::isZ));
Since isX etc. return boolean values, these values are sorted, false comes before true. So the sorting will make sure that all the objects that fulfil the x condition (isX returns true) will come at the end of the list. Among the remaining objects, those that fulfil y will be moved last, just before the x-s. Similarly for z.
What if instead x, y and z are determined by a method in the class doing the sorting? Let’s call it the Sorter class in this example. Such methods may look like:
public static boolean isY(MyObject obj) {
return // either false or true;
}
All you need to do is replace MyObject::isX with Sorter::isX:
Collections.sort(myList,
Comparator.comparing(Sorter::isX)
.thenComparing(Sorter::isY)
.thenComparing(Sorter::isZ));
You may also mix, define some methods in Sorter and some in MyMethod.
What really happens is that the boolean values returned are boxed into Boolean objects that are then compared, but you need not be concerned with this detail.
EDIT: Version for lower Android API levels:
Comparator<MyObject> xyzComparator = new Comparator<MyObject>() {
#Override
public int compare(MyObject o1, MyObject o2) {
int diff = Boolean.compare(o1.isX(), o2.isX());
if (diff != 0) {
return diff;
}
diff = Boolean.compare(o1.isY(), o2.isY());
if (diff != 0) {
return diff;
}
diff = Boolean.compare(o1.isZ(), o2.isZ());
// return whether 0 or not
return diff;
}
};
Collections.sort(myList, xyzComparator);
It even saves the auto-boxing mentioned above.
You could use a comparison chain for example:
public int compareTo(Foo that) {
return ComparisonChain.start()
.compare(this.aString, that.aString)
.compare(this.anInt, that.anInt)
.compare(this.anEnum, that.anEnum, Ordering.natural().nullsLast())
.result();
}
See here: link to docs

Comparator general contract violation

I have read all thread about transitive comparator, I don't get why this comparator function is violating a rule. If someone can clean my eyes, it is quite simple I think but I cannot get it
Stack is:
java.util.TimSort.mergeLo(TimSort.java:747)
java.util.TimSort.mergeAt(TimSort.java:483)
java.util.TimSort.mergeCollapse(TimSort.java:410)
my object (simplified)
public class SleepDetails {
private DateTime time;
private SleepEnum type;
[...]
}
public enum SleepEnum {
DEEP(0), LIGHT(1), AWAKE(2), BEGIN(16), END(17);
[...]
}
The comparator a static into a class
Comparator<SleepDetails> comparator = new Comparator<SleepDetails>(){
public int compare(SleepDetails arg0, SleepDetails arg1) {
int res = arg0.getTime().compareTo(arg1.getTime());
if (res != 0)
return res;
if (arg0.getType() == arg1.getType())
return 0;
switch(arg0.getType()) {
case BEGIN:
return -1;
case END:
return 1;
default:
return 0;
}
}
};
Mainly I want to sort the events by date, in case of two events with the same datetime put begin event first and end event as last.
I don't have the collection triggering the bug
If you compare two SleepDetails instances having the same getTime(), and one of them has getType() BEGIN and the other AWAKE.
compare (one, two)
would give -1
while
compare (two, one)
would give 0
This violates the contract :
The implementor must ensure that sgn(compare(x, y)) == -sgn(compare(y, x)) for all x and y.
You must also check arg1.getType() in your compare method (whenever arg0.getType() is neither BEGIN nor END).
public int compare(SleepDetails arg0, SleepDetails arg1) {
int res = arg0.getTime().compareTo(arg1.getTime());
if (res != 0)
return res;
if (arg0.getType() == arg1.getType())
return 0;
switch(arg0.getType()) {
case BEGIN:
return -1;
case END:
return 1;
default:
switch(arg1.getType()) {
case BEGIN:
return 1;
case END:
return -1;
default:
return 0;
}
}
}
The problem is that your code does not distinguish enum values for the type other than BEGIN and END. In particular, it returns zero when the first type is neither BEGIN nor END, regardless of the second type.
However, this behavior is not symmetrical: if you swap the two items in a pair BEGIN and LIGHT, you would get -1 and 0, breaking the symmetry.
You can treat all types other than BEGIN and END as equal to each other, but then you need to use both sides when deciding the equality.

Comparable interface with many conditions

The Question is how can use comparable interface and collections.sort to do the sorting with model , production and price. Can i do these three sorting in ascending order within "public int compareto(car other)"?
For example, It will be sorted with model in alphabetical order. If model is same, then sorted with production in alphabetical order. if production is also same , then finally sorted with price in ascending order.
Thank you for attention, i stuck with this problem many days. Please help me.
public static void main(String[] args) {
ArrayList<Car> car = new ArrayList<car>();
// something ignored//
Collections.sort(car); <----------------------Problem
for (Car c : car) {
System.out.println(c);
}
}
class car implements Comparable<car>{
protected String model;
protected String production;
protected int price;
public Tablet(String model ,String production , int price)
{
this.model=model;
this.price=price;
this.production = production;
}
public int compareTo (car other)
{
?????????????????
}
}
class mini-bus extends car
{
private door;
public Tablet(String model ,String production , int price ,int door)
{
super(model , production , price);
this.door = door;
}
}
The principle is quite straightforward:
Compare the first pair of properties. If they are different, return the negative/positive compare value; otherwise...
Compare the second pair of properties. If they are different, return the negative/positive compare value; otherwise...
... (repeat for as many pairs of properties as you have) ...
Compare the last pair of properties. This is the last property, so return the compare value.
For example:
int compareModels = this.model.compareTo(that.model);
if (compareModels != 0) {
return compareModels;
}
int compareProd = this.production.compareTo(that.production);
if (compareProd != 0) {
return compareProd;
}
return Integer.compare(this.price, that.price);
Note that there is also a nice class in Guava called ComparisonChain which reduces a lot of this boilerplate logic:
return ComparisonChain.start()
.compare(this.model, that.model)
.compare(this.production, that.production)
.compare(this.price, that.price)
.result();
This stops comparing once a difference is found between any pair of properties. It will still access the subsequent properties, but that should hopefully be an irrelevantly cheap thing to do anyway.
Here is the general approach to the problem of multi-attribute sorting:
Decide on the ordered list of attributes by which you sort
For each attribute on your list, compare the values on both sides
If the result is not zero, return it right away
If the result is zero, go to the next attribute on your list
If you ran out of attributes, return zero
If the number of attributes is fixed, the "loop" on the ordered list of attributes is unrolled, i.e. each individual attribute is compared separately:
int res;
res = this.getA().compareTo(other.getA()); // e.g. model
if (res != 0) return res;
res = this.getB().compareTo(other.getB()); // e.g. production
if (res != 0) return res;
res = this.getC().compareTo(other.getC());
if (res != 0) return res;
...
// For the last attribute return the result directly
return this.getZ().compareTo(other.getZ()); // e.g. price
This should do:
public int compareTo(Car other){
if(this.getModel().compareTo(other.getModel()) != 0){
return this.getModel().compareTo(other.getModel());
}else if(this.getProduction().compareTo(other.getProduction()) != 0){
return this.getProduction().compareTo(other.getProduction());
}else{
return Integer.compare(this.getPrice(), other.getPrice());
}
}

Can you sort this ArrayList in two different ways writing your own Comparator?

I have an ArrayList of object called Course and I'm trying to sort it in 2 ways, by courseID and courseStartTime.
Edit: to clarify I mean I want to sort it by courseID at some point in time, and at another time later sort it by courseStartTime.
class Course implements Comparable<Course> {
private int courseID;
private String courseBeginTime;
#Override
public int compareTo(Course course) {
//what to return?
}
If I wrote 2 of my own comparators, one to compare courseID and the other for courseStarTime, then the compareTo() method in the class isn't used and I don't know what to return.
If I want to use the compareTo() method, I'm not sure how to write it so I can compare courseID and courseStartTime.
You can implement two different comparators.
public class CourseComparatorById implements Comparator<Course> {
#Override
public int compare(Course o1, Course o2) {
// for example - sort ascending by ID
return o1.getId() - o2.getId();
}
}
public class CourseComparatorByStartTime implements Comparator<Course> {
#Override
public int compare(Course o1, Course o2) {
// for example - sort ascending by start time
return o1.getStartTime() - o2.getStartTime();
}
}
And then use them to sort the array.
List<Course> courses = ...
Collections.sort(courses, new CourseComparatorById());
// now it's sorted by ID
Collections.sort(courses, new CourseComparatorByStartTime());
// now it's sorted by start time
You can also try the Java 8 Lambda way:
// this sorts by courseID
courseList.sort((c1, c2) -> Integer.valueOf(c1.courseID).compareTo(c2.courseID));
// this sorts by String courseBeginTime
courseList.sort((c1, c2) -> c1.courseBeginTime.compareTo(c2.courseBeginTime));
Note that is Java 8 you don't have to use Collections.sort, because the new List interface also provides a sort method
I have a feeling that this is being used for an online registration web app ...
you will probably be fetching the data source from a RDB ... It wouldnt be wise to put ALL courses in one list (one entity) and save that. I would create an object (containing courseID and courseBeginTime) for EVERY course and save them all. Then when querying, add hints to sort your entities based on whatever root parameters you have in them (like courseID or courseBeginTime), ending with a List containing objects sorted the way you want :) :)
May be you should do something like this
public class Course implements Comparator<Course> {
private int compareTime(int lhsTime, int rhsTime) {
if (lhsTime > rhsTime) {
return 1;
} else if (lhsTime == rhsTime) {
return 0;
} else {
return -1;
}
}
#Override
public int compare(Course lhs, Course rhs) {
if (lhs.id > rhs.id) {
return 1;
//Get the time object from course obj and pass to compaerTime
} else if (lhs.courseStarTime == rhs.courseStarTime) {
return compareTime(lhs, rhs);
} else {
return -1;
}
}
}

Why does my compare method violate its general contract?

public static Comparator<Container> DEPARTURE = new Comparator<Container>() {
#Override
public int compare(Container container1, Container container2) {
if (container1.departure.time.isBefore(container2.departure.time))
return -1;
else if (container1.departure.time.equals(container2.departure.time) &&
container1.departure.maxDuration == container2.departure.maxDuration &&
container1.departure.transportCompany.equals(container2.departure.transportCompany) &&
container1.departure.transportType == container2.departure.transportType)
return 0;
else
return +1;
}
};
the departure variable is just an instance of an object containing the following fields:
public DateTime time;
public int maxDuration;
public TransportType transportType;
public String transportCompany;
P.S. the time object is an instance of DateTime from the Joda-Time library and TransportType is an enumeration containing the constants Train, Seaship, Barge and Truck.
EDIT:
Ok, so, I edited my comparator to the following:
public static Comparator<Container> DEPARTURE = new Comparator<Container>() {
#Override
public int compare(Container container1, Container container2) {
if (container1.departure.time.isBefore(container2.departure.time))
return -1;
else if (container1.departure.time.isBefore(container2.departure.time))
return +1;
else {
if (container1.departure.maxDuration == container2.departure.maxDuration && container1.departure.transportType == container2.departure.transportType && container1.departure.transportCompany.equals(container2.departure.transportCompany))
return 0;
else
return +1;
}
}
};
but this obviously violates the general contract. How do I make it so it sorts by time and then sort those objects that have equivalent times by their other attributes only caring if they're equal or not? Hope this makes sense ...
EDIT: SOLUTION
Thank you all for answering my question! After studying your comments I came up with the following solution that seems to work (not thoroughly tested though):
I actually moved the comparing part to departure his class because I also need to compare by arrival. I decided to simply sort by all attributes (consecutively time, maxDuration, transportCompany and transportType) and the solution I came up with is:
public static Comparator<Container> ARRIVAL = new Comparator<Container>() {
#Override
public int compare(Container container1, Container container2) {
return container1.arrival.compareTo(container2.arrival);
}
};
public static Comparator<Container> DEPARTURE = new Comparator<Container>() {
#Override
public int compare(Container container1, Container container2) {
return container1.departure.compareTo(container2.departure);
}
};
And then the compareTo method:
#Override
public int compareTo(LocationMovement lm) {
if (this.time.isBefore(lm.time))
return -1;
else if (this.time.isAfter(lm.time))
return +1;
else {
int c = this.maxDuration - lm.maxDuration;
if (c != 0) return c;
c = this.transportCompany.compareTo(lm.transportCompany);
if (c != 0) return c;
c = this.transportType.ordinal() - lm.transportType.ordinal();
return c;
}
}
The general contract is that
COMPARATOR.compare(a, b) = - COMPARATOR.compare(b, a)
In your case, the code which returns -1 one way could return 0 the other.
In order to implement compare, all of the things you check must have the concept of being "lesser," "greater," or "equal" to one another, and then you must decide the order in which to check them, returning lesser/greater for the first of the items that isn't equal. That way, you satisfy the contract that compare(a, b) must be the converse of compare(b, a). If all of the parts of what you're comparing don't have the concept of "greater" or "lesser" (for instance, transport type), then either you can't implement compare or you must force an arbitrary (but reliable) greater/lesser interpretation on them.
Here's a conceptual example of doing that. In this case, the order I've chosen (arbitrarily) is: The time, the duration, the company, and the type. But a different order may be more reasonable. This is just an example. Also, you haven't said what the type of transportType is, so I've assumed it has a compareTo method; obviously it may not and you may have to adjust that.
public static Comparator<Container> DEPARTURE = new Comparator<Container>() {
#Override
public int compare(Container container1, Container container2) {
int rv;
// Times
rv = container1.departure.time.compareTo(container2.departure.time);
if (rv == 0) {
// Duration
if (container1.departure.maxDuration < container2.departure.maxDuration) {
rv = -1;
}
else if (container1.departure.maxDuration > container2.departure.maxDuration) {
rv = 1;
}
else {
// Transport company
rv = container1.departure.transportCompany.compareTo(container2.departure.transportCompany);
if (rv == 0) {
// Transport type
rv = container1.departure.transportType.compareTo(container2.departure.transportType);
}
}
}
return rv;
}
};
Note that if two containers c1 and c2 have equal departure.time, but differ in the other attributes, then both compare(c1, c2) and compare(c2, c1) will return +1, i.e., c1>c2 and c2>c1.
Instead, you should either drop the other fields entirely, or compare them separately in nested or sequential if-elses in case the departure time is equal.
Take a look at this answer to a related question for a clean way to compare objects by multiple attributes.

Categories

Resources