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
Related
I have implemented a TreeMap that contains blueprints (to simplify it).
private TreeMap<BuildingFloorKey, Blueprint> blueprints = new TreeMap<>((o1, o2) -> {
int value = o1.compareTo(o2);
return value;
});
To use building (in my case called complex) and floor as a tuple key, I wrote the following class:
public static class BuildingFloorKey {
private Complex mComplex;
private int mFloor;
public BuildingFloorKey(Complex complex, int floor){
mComplex = complex;
mFloor = floor;
}
#Override
public boolean equals(Object other) {
if (!(other instanceof BuildingFloorKey)) return false;
BuildingFloorKey that = (BuildingFloorKey) other;
return mFloor == that.mFloor && mComplex.equals(that.mComplex);
}
#Override
public int hashCode() {
return Arrays.hashCode(new Object[]{mComplex, mFloor});
}
public int compareTo(BuildingFloorKey otherKey){
if(this.equals(otherKey)) return 0;
//same complex -> compare floors
else if (this.getComplex().equals(otherKey.getComplex())){
return otherKey.getFloorInt() - this.getFloorInt();
}
//different complexes (incl. some modification for special cases)
else return -(Math.abs(otherKey.mFloor + 2) + 100);
}
}
I am working on an Android App and I want to click through the blueprints via buttons. For that I make use of the methods TreeMap.lowerKey(otherKey) and TreeMap.higherKey(otherKey). Like so:
#Override
public void onNextPlanClicked() {
nextFloorPlan = blueprints.higherKey(currentlyDisplayedPlan);
drawFloorPlan(nextFloorPlan);
}
As an example, I have a usecase where the set of blueprints is
04|02
03|03
04|-1
03|00
(format: complex|floor). Unfortunately, it is not sorted properly in the TreeMap (as you can see - the list above is sorted like the entries of the TreeMap in the debugger).
I read something about TreeMap Sorting using case-sensitive Strings. But I'm actually using integers. So I don't get why sorting and using lowerKey() and higherKey() not working properly. Did I mess up with the comparator? Can someone help please?
I think your issues is a very simple one, your compareTo method should have an override. You need to add implements Comparable to your BuildingFloorKey definition, which will then take your compareTo argument as a comparable that TreeMap can recognize.
I have a question about java collections such as Set or List. More generally objects that you can use in a for-each loop. Is there any requirement that the elements of them actually has to be stored somewhere in a data structure or can they be described only from some sort of requirement and calculated on the fly when you need them? It feels like this should be possible to be done, but I don't see any of the java standard collection classes doing anything like this. Am I breaking any sort of contract here?
The thing I'm thinking about using these for is mainly mathematics. Say for example I want to have a set representing all prime numbers under 1 000 000. It might not be a good idea to save these in memory but to instead have a method check if a particular number is in the collection or not.
I'm also not at all an expert at java streams, but I feel like these should be usable in java 8 streams since the objects have very minimal state (the objects in the collection doesn't even exist until you try to iterate over them or check if a particular object exists in the collection).
Is it possible to have Collections or Iterators with virtually infinitely many elements, for example "all numbers on form 6*k+1", "All primes above 10" or "All Vectors spanned by this basis"? One other thing I'm thinking about is combining two sets like the union of all primes below 1 000 000 and all integers on form 2^n-1 and list the mersenne primes below 1 000 000. I feel like it would be easier to reason about certain mathematical objects if it was done this way and the elements weren't created explicitly until they are actually needed. Maybe I'm wrong.
Here's two mockup classes I wrote to try to illustrate what I want to do. They don't act exactly as I would expect (see output) which make me think I am breaking some kind of contract here with the iterable interface or implementing it wrong. Feel free to point out what I'm doing wrong here if you see it or if this kind of code is even allowed under the collections framework.
import java.util.AbstractSet;
import java.util.Iterator;
public class PrimesBelow extends AbstractSet<Integer>{
int max;
int size;
public PrimesBelow(int max) {
this.max = max;
}
#Override
public Iterator<Integer> iterator() {
return new SetIterator<Integer>(this);
}
#Override
public int size() {
if(this.size == -1){
System.out.println("Calculating size");
size = calculateSize();
}else{
System.out.println("Accessing calculated size");
}
return size;
}
private int calculateSize() {
int c = 0;
for(Integer p: this)
c++;
return c;
}
public static void main(String[] args){
PrimesBelow primesBelow10 = new PrimesBelow(10);
for(int i: primesBelow10)
System.out.println(i);
System.out.println(primesBelow10);
}
}
.
import java.util.Iterator;
import java.util.NoSuchElementException;
public class SetIterator<T> implements Iterator<Integer> {
int max;
int current;
public SetIterator(PrimesBelow pb) {
this.max= pb.max;
current = 1;
}
#Override
public boolean hasNext() {
if(current < max) return true;
else return false;
}
#Override
public Integer next() {
while(hasNext()){
current++;
if(isPrime(current)){
System.out.println("returning "+current);
return current;
}
}
throw new NoSuchElementException();
}
private boolean isPrime(int a) {
if(a<2) return false;
for(int i = 2; i < a; i++) if((a%i)==0) return false;
return true;
}
}
Main function gives the output
returning 2
2
returning 3
3
returning 5
5
returning 7
7
Exception in thread "main" java.util.NoSuchElementException
at SetIterator.next(SetIterator.java:27)
at SetIterator.next(SetIterator.java:1)
at PrimesBelow.main(PrimesBelow.java:38)
edit: spotted an error in the next() method. Corrected it and changed the output to the new one.
Well, as you see with your (now fixed) example, you can easily do it with Iterables/Iterators. Instead of having a backing collection, the example would've been nicer with just an Iterable that takes the max number you wish to calculate primes to. You just need to make sure that you handle the hasNext() method properly so you don't have to throw an exception unnecessarily from next().
Java 8 streams can be used easier to perform these kinds of things nowadays, but there's no reason you can't have a "virtual collection" that's just an Iterable. If you start implementing Collection it becomes harder, but even then it wouldn't be completely impossible, depending on the use cases: e.g. you could implement contains() that checks for primes, but you'd have to calculate it and it would be slow for large numbers.
A (somewhat convoluted) example of a semi-infinite set of odd numbers that is immutable and stores no values.
public class OddSet implements Set<Integer> {
public boolean contains(Integer o) {
return o % 2 == 1;
}
public int size() {
return Integer.MAX_VALUE;
}
public boolean add(Integer i) {
throw new OperationNotSupportedException();
}
public boolean equals(Object o) {
return o instanceof OddSet;
}
// etc. etc.
}
As DwB stated, this is not possible to do with Java's Collections API, as every element must be stored in memory. However, there is an alternative: this is precisely why Java's Stream API was implemented!
Streams allow you to iterate across an infinite amount of objects that are not stored in memory unless you explicitly collect them into a Collection.
From the documentation of IntStream#iterate:
Returns an infinite sequential ordered IntStream produced by iterative application of a function f to an initial element seed, producing a Stream consisting of seed, f(seed), f(f(seed)), etc.
The first element (position 0) in the IntStream will be the provided seed. For n > 0, the element at position n, will be the result of applying the function f to the element at position n - 1.
Here are some examples that you proposed in your question:
public class Test {
public static void main(String[] args) {
IntStream.iterate(1, k -> 6 * k + 1);
IntStream.iterate(10, i -> i + 1).filter(Test::isPrime);
IntStream.iterate(1, n -> 2 * n - 1).filter(i -> i < 1_000_000);
}
private boolean isPrime(int a) {
if (a < 2) {
return false;
}
for(int i = 2; i < a; i++) {
if ((a % i) == 0) {
return false;
}
return true;
}
}
}
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;
}
}
}
class Obj{
int x;
int y;
Date z;
public int compareTo(Obj other) {
if(this.z.getTime() > other.getZ().getTime())
return 1;
else if(this.z.getTime() < other.getZ().getTime())
return -1;
else
return 0;
}
boolean equals(Obj other) {
if(x== other.x && y == other.y)
return true;
else
return false;
}
}
Now I have a list<Obj> and I have to remove duplicate and only pick the latest one (latest z) when there are multiple object with same id.
sortedSet = new TreeSet(objList);
reversedSortedList = new ArrayList(sortedSet); //This will not be needed if we reverse the comparator logic. However it is not good.
uniqueSet = new HashSet(reverseSortedList);
return uniqueSet;
Is this a good way of doing things. Or there is a cleaner and better way of doing things. Also the number of element in the list for me lies between 1000-10000
Thanks
You can write a separate Comparator to compare your object which will have the capability to sort in reverse(just opposite logic what you have implemented) instead of implementing compareTo method in our object(while I can see that your class doesn't implement Comparable interface).
In that way you will be able to directly get the reversely sorted Set and you can change the logic easily anytime you want or can use many different comparators at the different places.
For 1000-10000 using TreeSet is a good option with Compartors.
You comparator could be optimized to:
public int compareTo(Obj other) {
return (int)(this.z.getTime() - other.getZ().getTime());
}
So I've been struggling with a problem for a while now, figured I might as well ask for help here.
I'm adding Ticket objects to a TreeSet, Ticket implements Comparable and has overridden equals(), hashCode() and CompareTo() methods. I need to check if an object is already in the TreeSet using contains(). Now after adding 2 elements to the set it all checks out fine, yet after adding a third it gets messed up.
running this little piece of code after adding a third element to the TreeSet, Ticket temp2 is the object I'm checking for(verkoopLijst).
Ticket temp2 = new Ticket(boeking, TicketType.STANDAARD, 1,1);
System.out.println(verkoop.getVerkoopLijst().first().hashCode());
System.out.println(temp2.hashCode());
System.out.println(verkoop.getVerkoopLijst().first().equals(temp2));
System.out.println(verkoop.getVerkoopLijst().first().compareTo(temp2));
System.out.println(verkoop.getVerkoopLijst().contains(temp2));
returns this:
22106622
22106622
true
0
false
Now my question would be how this is even possible?
Edit:
public class Ticket implements Comparable{
private int rijNr, stoelNr;
private TicketType ticketType;
private Boeking boeking;
public Ticket(Boeking boeking, TicketType ticketType, int rijNr, int stoelNr){
//setters
}
#Override
public int hashCode(){
return boeking.getBoekingDatum().hashCode();
}
#Override
#SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
public boolean equals(Object o){
Ticket t = (Ticket) o;
if(this.boeking.equals(t.getBoeking())
&&
this.rijNr == t.getRijNr() && this.stoelNr == t.getStoelNr()
&&
this.ticketType.equals(t.getTicketType()))
{
return true;
}
else return false;
}
/*I adjusted compareTo this way because I need to make sure there are no duplicate Tickets in my treeset. Treeset seems to call CompareTo() to check for equality before adding an object to the set, instead of equals().
*/
#Override
public int compareTo(Object o) {
int output = 0;
if (boeking.compareTo(((Ticket) o).getBoeking())==0)
{
if(this.equals(o))
{
return output;
}
else return 1;
}
else output = boeking.compareTo(((Ticket) o).getBoeking());
return output;
}
//Getters & Setters
On compareTo contract
The problem is in your compareTo. Here's an excerpt from the documentation:
Implementor must ensure sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) for all x and y.
Your original code is reproduced here for reference:
// original compareTo implementation with bug marked
#Override
public int compareTo(Object o) {
int output = 0;
if (boeking.compareTo(((Ticket) o).getBoeking())==0)
{
if(this.equals(o))
{
return output;
}
else return 1; // BUG!!!! See explanation below!
}
else output = boeking.compareTo(((Ticket) o).getBoeking());
return output;
}
Why is the return 1; a bug? Consider the following scenario:
Given Ticket t1, t2
Given t1.boeking.compareTo(t2.boeking) == 0
Given t1.equals(t2) return false
Now we have both of the following:
t1.compareTo(t2) returns 1
t2.compareTo(t1) returns 1
That last consequence is a violation of the compareTo contract.
Fixing the problem
First and foremost, you should have taken advantage of the fact that Comparable<T> is a parameterizable generic type. That is, instead of:
// original declaration; uses raw type!
public class Ticket implements Comparable
it'd be much more appropriate to instead declare something like this:
// improved declaration! uses parameterized Comparable<T>
public class Ticket implements Comparable<Ticket>
Now we can write our compareTo(Ticket) (no longer compareTo(Object)). There are many ways to rewrite this, but here's a rather simplistic one that works:
#Override public int compareTo(Ticket t) {
int v;
v = this.boeking.compareTo(t.boeking);
if (v != 0) return v;
v = compareInt(this.rijNr, t.rijNr);
if (v != 0) return v;
v = compareInt(this.stoelNr, t.stoelNr);
if (v != 0) return v;
v = compareInt(this.ticketType, t.ticketType);
if (v != 0) return v;
return 0;
}
private static int compareInt(int i1, int i2) {
if (i1 < i2) {
return -1;
} else if (i1 > i2) {
return +1;
} else {
return 0;
}
}
Now we can also define equals(Object) in terms of compareTo(Ticket) instead of the other way around:
#Override public boolean equals(Object o) {
return (o instanceof Ticket) && (this.compareTo((Ticket) o) == 0);
}
Note the structure of the compareTo: it has multiple return statements, but in fact, the flow of logic is quite readable. Note also how the priority of the sorting criteria is explicit, and easily reorderable should you have different priorities in mind.
Related questions
What is a raw type and why shouldn't we use it?
How to sort an array or ArrayList ASC first by x and then by y?
Should a function have only one return statement?
This could happen if your compareTo method isn't consistent. I.e. if a.compareTo(b) > 0, then b.compareTo(a) must be < 0. And if a.compareTo(b) > 0 and b.compareTo(c) > 0, then a.compareTo(c) must be > 0. If those aren't true, TreeSet can get all confused.
Firstly, if you are using a TreeSet, the actual behavior of your hashCode methods won't affect the results. TreeSet does not rely on hashing.
Really we need to see more code; e.g. the actual implementations of the equals and compareTo methods, and the code that instantiates the TreeSet.
However, if I was to guess, it would be that you have overloaded the equals method by declaring it with the signature boolean equals(Ticket other). That would lead to the behavior that you are seeing. To get the required behavior, you must override the method; e.g.
#Override
public boolean equals(Object other) { ...
(It is a good idea to put in the #Override annotation to make it clear that the method overrides a method in the superclass, or implements a method in an interface. If your method isn't actually an override, then you'll get a compilation error ... which would be a good thing.)
EDIT
Based on the code that you have added to the question, the problem is not overload vs override. (As I said, I was only guessing ...)
It is most likely that the compareTo and equals are incorrect. It is still not entirely clear exactly where the bug is because the semantics of both methods depends on the compareTo and equals methods of the Boeking class.
The first if statement of the Ticket.compareTo looks highly suspicious. It looks like the return 1; could cause t1.compareTo(t2) and t2.compareTo(t1) to both return 1 for some tickets t1 and t2 ... and that would definitely be wrong.