Beka Bekeri
Beka Bekeri

Reputation: 13

How to use Comparator with a multiple field object?

Im trying to use the interface comparator to order a priority queue so the oorder of the passengers depends in first levell if they have disability, then depending of the type of ticket they have and lastly the time of arrival.

import java.util.*;

public static void main(String[] args){
    Random rand = new Random(System.nanoTime());
    Comparator<Passenger> comparator;
    PriorityQueue<Passenger> queue = new PriorityQueue<Passenger>(10, comparator);
    Passenger pass[] = new Passenger [10];


    for (int i=0; i<10;i++){
        int time1 = 0;
        pass[i] = new Passenger(rand.nextInt(100000000), rand.nextInt(3) , rand.nextBoolean(), time1);
        time1 = time1 + 15;
    }   

}

There i initialize the array of passengers, here is the class Passenger and the compare method:

public class Passenger implements Comparator<Passenger>{

private int ID;
private int clase;
private boolean disability;
private int arrivalTime;

public Passenger(int ID, int clase, boolean disability, int arrivalTime) {

    this.ID = ID;
    this.clase = clase; // 0-vip 1-economy 2-economy
    this.disability = disability;
    this.arrivalTime = arrivalTime;
}
public int getID() {
    return ID;
}
public void setID(int iD) {
    ID = iD;
}
public int getClase() {
    return clase;
}
public void setClase(int clase) {
    this.clase = clase;
}
public boolean isDisability() {
    return disability;
}
public void setDisability(boolean disability) {
    this.disability = disability;
}
public int getArrivalTime() {
    return arrivalTime;
}
public void setArrivalTime(int arrivalTime) {
    this.arrivalTime = arrivalTime;
}

public int compare(Passenger pas1, Passenger pas2) {
    if((pas1.isDisability()) && (!pas2.isDisability())){
        return 1;                       //passenger 1 has disability
    }else if((!pas1.isDisability()) && (pas2.isDisability())){
        return -1;                          //passenger 2 has disability 
    }
    else{                                   //both have disability or no one has disability 
        if(pas1.getClase() < pas2.getClase()){
            return 1;                   // passenger 1 has better class
        }else if(pas1.getClase() > pas2.getClase()){
            return -1;                      // passenger 2 has better class
        }
        else{                               //both have disability and same class
            if(pas1.getArrivalTime() < pas2.getArrivalTime()){
                return 1;               //passenger 1 arrived before passenger 2
            }
            else return -1;                 //passenger 2 arrived before passenger 1 
        }
    }
}

How can I handle these multi level comparisons in a better way ?

Upvotes: 1

Views: 1640

Answers (4)

Beka Bekeri
Beka Bekeri

Reputation: 13

Hey I just get the right answer, just by adding this to the class Passenger it works out the exact way i wanted so the preference order is: disability, class and arrivalTime, thank you so much. :)

public int compareTo(Passenger other) {
    if (this.isDisability() ^ other.isDisability()) { // use an XOR so it only enters if one of them is true
        return this.isDisability() ? -1 : 1; 
    }
     int clase = -Integer.compare(this.getClase(), other.getClase());
     if (clase == 0) {
        return this.getArrivalTime() < other.getArrivalTime() ? -1 : 1;
     }
     return clase > 0 ? -1 : 1;
}

Upvotes: 0

camickr
camickr

Reputation: 324108

How can I handle these multi level comparisons in a better way ?

  1. Create separate Comparators for each property.
  2. Combine individual Comparators into a multi level comparison. Check out Group Comparator for an example of this approach.

So know you have reusable code that allows you to do sorting in any order you wish without writing a complex multi level Comparator.

You may also want to check out the Bean Comparator which makes it easy to create individual Comparators in a single line of code.

Upvotes: 0

Rogue
Rogue

Reputation: 11483

It seems your question is in regards to simplifying your comparison, but I think you'd rather implement Comparable<Passenger> rather than Comparator, and use the #compareTo method. As for cleanup, that's a bit easy too if you simply abstract the actual boolean logic:

public int compareTo(Passenger other) {
    if (this.isDisability() ^ other.isDisability()) { //use an XOR
        return this.isDisability() ? 1 : -1; //1 for us, -1 for other
    }
    //compare #getClase
    int clase = -Integer.compare(this.getClase(), other.getClase()); //invert
    if (clase == 0) {
        //compare arrival times if clase is equal
        //normalize to -1, 1 (0 excluded in OP)
        return this.getArrivalTime() < other.getArrivalTime() ? 1 : -1;
    }
    return clase > 0 ? 1 : -1; //normalize to -1, 0, 1
}

This allows you to define a natural ordering to a Passenger, and is encapsulated/internal to your class implementation (doesn't require as much exposure).

This also makes operations like sorting much easier:

List<Passenger> passengers = /* some list */;
Collections.sort(passengers);

If you want to provide a Comparator which can accomplish alternative sorts, you can also do that inside your class:

public class Passenger {

    //...

    public static class ArrivalComparator implements Comparator<Passenger> {

        public int compare(Passenger one, Passenger two) {
            return Integer.compare(one.getArrivalTime(), two.getArrivalTime());
        }
    }

    //...

}

Using our previous example, this would let you sort all the passengers based on arrival time:

Collections.sort(passengers, new Passenger.ArrivalComparator());

Additionally, this can just be inlined using Java 8:

//Sort by arrival time
Collections.sort(passengers, (one, two) -> Integer.compare(one.getArrivalTime(), two.getArrivalTime());

But overall, keep in mind a Comparator is mostly for defining a specific sort, while Comparable defines a general/natural ordering.

Upvotes: 1

Vasu
Vasu

Reputation: 22422

I think what you are looking for is refactoring your code, I would recommend separating compare logic into a separate PassengerComparator class (SRP) for better maintenance and readability as shown below.

public class PassengerComparator implements Comparator<Passenger> {

      public int compare(Passenger pas1, Passenger pas2) {    
        //check the comparison of all
        if(disabilityComparator(Passenger pas1, Passenger pas2) 
              && arrivalTimeComparator(Passenger pas1, Passenger pas2) 
              && claseComparator(Passenger pas1, Passenger pas2)) {
            return 1;
        } else {
            return -1;
        }
      }

     //compares only disability 
     private int disabilityComparator(Passenger pas1, Passenger pas2) {
            return pas1.isDisability() - pas2.isDisability();
     }

     //compares only arrivalTime 
     private int arrivalTimeComparator(Passenger pas1, Passenger pas2) {
            return pas1.getArrivalTime() - pas2.getArrivalTime();
     }  

     //compares only clase
     private int claseComparator(Passenger pas1, Passenger pas2) {
            return pas1.getClase() - pas2.getClase();
     }
}

Usage:

PriorityQueue<Book> queue = new PriorityQueue<Book>(10, new PassengerComparator());

Upvotes: 1

Related Questions