8

In my PriorityQueue I have 2 types of customers, VIP and regular. I want to serve VIP first, then regular.

If CustomerID < 100 it is considered to be VIP.

If a customer is VIP he goes at the end of VIP part of the queue

If a customer is regular he goes at the end of the entire queue.

In other words, I want to sort by boolean VIP value, while preserving the order in which customers came in.

Here's my Order class

public class Order implements Comparable<Order> {
    private final int customerID;
    private final int amount;
    private final boolean vip_status;

    public Order(int customerID, int amount) { 
        this.customerID = customerID;
        this.amount = amount;
        this.vip_status = customerID < 100 ? true : false;

    }

    @Override
    public int compareTo(Order o) {
        if (vip_status && !o.vip_status) {
            return -1;
        }
        if (!vip_status && o.vip_status)
            return 1;
        return 0;
    }

    public int getCustomerID() {
        return customerID;
    }

    public int getAmount() {
        return amount;
    }

    public boolean isVip_status() {
        return vip_status;
    }
}

Here's my attempt to fill the queue:

import java.util.PriorityQueue;

public class MyPriorityQueue {
    public static void main(String[] args) {
        PriorityQueue<Order> queue = new PriorityQueue<>();
        Order o1 = new Order(1, 50);
        Order o2 = new Order(5, 30);
        Order o3 = new Order(4, 10);
        Order o4 = new Order(150, 5);
        Order o5 = new Order(2, 5);
        Order o6 = new Order(200, 5);

        queue.add(o1);
        queue.add(o2);
        queue.add(o3);
        queue.add(o4);
        queue.add(o5);
        queue.add(o6);

        while(!queue.isEmpty()){
            Order s = queue.poll();
            System.out.printf("VIP Status: %s CustomerID: %s Amount: %s%n", 
                        s.isVip_status(), s.getCustomerID(), s.getAmount());
        }
    }
}

RESULT that I'm getting (which is wrong):

VIP Status: true CustomerID: 1 Amount: 50
VIP Status: true CustomerID: 5 Amount: 30
VIP Status: true CustomerID: 2 Amount: 5
VIP Status: true CustomerID: 4 Amount: 10
VIP Status: false CustomerID: 150 Amount: 5
VIP Status: false CustomerID: 200 Amount: 5

This is what I expected to see (CustomerID 2 and 4 should be in the same order they came in):

VIP Status: true CustomerID: 1 Amount: 50
VIP Status: true CustomerID: 5 Amount: 30
VIP Status: true CustomerID: 4 Amount: 10
VIP Status: true CustomerID: 2 Amount: 5
VIP Status: false CustomerID: 150 Amount: 5
VIP Status: false CustomerID: 200 Amount: 5

UPDATE: I don't want sort by any other column except VIP. I don't want add "date" because it feels like a hack, rather than understanding how Java works.

B.shruti
  • 1,589
  • 1
  • 21
  • 41
Vladimir S.
  • 483
  • 6
  • 16
  • Your compareTo does not compare amounts, and it must do this to succeed. – Hovercraft Full Of Eels May 07 '17 at 16:25
  • 1
    Change the last return from `return 0;` to `return Integer.compare(amount, o.amount);` – Hovercraft Full Of Eels May 07 '17 at 16:26
  • 1
    @HovercraftFullOfEels OP wants the order to be the order of insertion into the queue, and the amount seems to be just irrelevant data. – RealSkeptic May 07 '17 at 16:28
  • @RealSkeptic: are you sure? Look at the expected result above. He appears to want a secondary sort on amount. – Hovercraft Full Of Eels May 07 '17 at 16:29
  • This is just a side-note, but your ordering by VIP status exclusively probably shouldn't be the natural order of the class. It should probably be implemented in a `Comparator`. Usually the natural order should consider all variables, as if e.g. by `equals`. See the docs for [`Comparable`](http://docs.oracle.com/javase/8/docs/api/java/lang/Comparable.html) for a more extended explanation. – Radiodef May 07 '17 at 16:33
  • You can change the method add (for example) or write something own, where you will input the next element in the queue using rating score. From another way, you can create method print, where you will print the customer with so big rating, etc. – Vasyl Lyashkevych May 07 '17 at 16:35
  • Do you need a `PriorityQueue`? – fps May 07 '17 at 16:44
  • 2
    *"I don't want add "date" because it feels like a hack"* Orders generally have dates, so this is not a hack. Also see [here](http://stackoverflow.com/a/15731988/2891664), which shows an excerpt from `PriorityBlockingQueue` which officially recommends something like this. – Radiodef May 07 '17 at 16:45

2 Answers2

4

It appears that the PriorityQueue class that java comes with out of the box feels free to reorder items if they compare as equal to each other.

(This is not "how java works", it is just a little perversion on behalf of a certain class that comes with the Java Runtime.)

So, here is something that will probably work:

  1. Introduce a new OrderPlacement class, containing a) an Order and b) an int priority.

  2. In your PriorityQueue add OrderPlacement objects instead of Order objects.

  3. When you create a new OrderPlacement object, issue a new priority for it, by incrementing a counter.

Then, your OrderPlacement object can have a compareTo() method that looks like this:

@Override
public int compareTo( OrderPlacement o ) 
{
    int d = -Boolean.compare( order.vip_status, o.order.vip_status );
    if( d != 0 )
        return d;
    return Integer.compare( priority, o.priority );
}
Mike Nakis
  • 56,297
  • 11
  • 110
  • 142
  • Well, if you reverse the ordering (`return -Boolean.compare(...`) then it will be a lot closer to what you need. But still not quite there. That's why I amended my answer. – Mike Nakis May 07 '17 at 16:58
  • Looks like you're right about "java is free to reorder items if they compare as equal to each other". Could you please highlight that in your answer? – Vladimir S. May 07 '17 at 17:00
  • 2
    Indeed, there is even an example in the JavaDocs suggesting this approach (not for `PriorityQueue`, but for the related [`PriorityBlockingQueue`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/PriorityBlockingQueue.html) (using an `static final AtomicLong` as counter) – Hulk May 08 '17 at 10:08
2

If you must do it using priority queue, below code will solve your problem. Do note that I am using a static counter to maintain correct order of elements with same VIP status as equal elements are maintained in random order inside priority queue. This is because priority queue uses a min / max heap data structure and it only cares about placing min / max element at top of heap and does not bother about ordering of same elements.

import java.util.PriorityQueue;

public class Order implements Comparable<Order> {
  private final int customerID;
  private final int amount;
  private final int vip_status;
  private final int score;
  private static int counter = 0;

  public Order(int customerID, int amount) {
    this.customerID = customerID;
    this.amount = amount;
    this.vip_status = customerID < 100 ? 0 : 1;
    this.score = counter++;
  }

  @Override
  public String toString() {
    return customerID + " : " + amount + " : " + vip_status;
  }

  @Override
  public int compareTo(Order o) {
    int status = ((Integer) this.vip_status).compareTo(o.vip_status);
    status = status == 0 ? ((Integer) this.score).compareTo(o.score) : status;
    return status;
  }

  public static void main(String[] args) {
    Order o1 = new Order(1000, 100);
    Order o2 = new Order(500, 100);
    Order o3 = new Order(99, 100);
    Order o4 = new Order(10, 100);
    Order o5 = new Order(200, 100);
    Order o6 = new Order(1, 100);

    PriorityQueue<Order> orderQueue = new PriorityQueue<>();
    orderQueue.offer(o1);
    orderQueue.offer(o2);
    orderQueue.offer(o3);
    orderQueue.offer(o4);
    orderQueue.offer(o5);
    orderQueue.offer(o6);

    System.out.println(orderQueue.poll());
    System.out.println(orderQueue.poll());
    System.out.println(orderQueue.poll());
    System.out.println(orderQueue.poll());
    System.out.println(orderQueue.poll());
    System.out.println(orderQueue.poll());
  }
}

`

Sample Output:

99 : 100 : 0
10 : 100 : 0
1 : 100 : 0
1000 : 100 : 1
500 : 100 : 1
200 : 100 : 1

Note: you need to be aware that score may eventually reach Integer.MAX_VALUE

Nilesh Rajani
  • 538
  • 6
  • 16
  • It is a bad idea to pollute the design of `Order` by introducing an additional field into it, for the sole purpose of remembering its order in a queue. What if you wanted to simultaneously add `Order` in two different queues, with a different position in each queue? That's why I suggested an extra class in my answer. – Mike Nakis May 07 '17 at 20:45