package org.limewire.collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* Provides a discrete-case priority queue. <code>BucketQueue</code> is designed
* to be a replacement for a binary heap for the special case when there
* are only a small number of positive priorities. (Priorities are zero based
* and therefore, when priority is set to 3, the values are [0, 1, 2]. Also, larger
* numbers are higher priority.)
* <p>
* You determine how many element cases per priority are allowed in the queue.
* When the queue attempts to add an element more than the maximum capacity for
* the priority, the first element is removed upon {@link #insert(Object, int)}.
* <p>
* This class is not thread-safe.
*
<pre>
//priorities, capacity
BucketQueue<String> bq = new BucketQueue<String>(3,2);
bq.insert("Abby", 1);
bq.insert("Bob", 1);
System.out.println(bq);
String returnFromInsert ;
returnFromInsert = bq.insert("Chris", 1);
if(returnFromInsert != null)
System.out.println("Element " + returnFromInsert + " popped because there are already 2 elements of priority 1.");
System.out.println(bq);
bq.insert("Dan", 2);
bq.insert("Eric", 2);
bq.insert("Fred", 0);
System.out.println("Max: " + bq.getMax().toString() + " and bq is " + bq);
Output:
[[], [Bob, Abby], []]
Element Abby popped because there are already 2 elements of priority 1.
[[], [Chris, Bob], []]
Max: Eric and bq is [[Eric, Dan], [Chris, Bob], [Fred]]
</pre>
*
*/
public class BucketQueue<E> implements Cloneable, Iterable<E> {
/**
* Within each bucket, elements at the FRONT are newer then the back. It is
* assumed that buckets is very small; otherwise additional state could
* speed up some of the operations.
*/
private Buffer<E>[] buckets;
/**
* The size, stored for efficiency reasons.
* INVARIANT: size=buckets[0].size()+...+buckets[buckets.length-1].size()
*/
private int size=0;
/**
* @effects a new queue with the given number of priorities, and
* the given number of entries PER PRIORITY. Hence 0 through
* priorities-1 are the legal priorities, and there are up to
* capacityPerPriority*priorities elements in the queue.
* @exception IllegalArgumentException priorities or capacityPerPriority
* is non-positive.
*/
@SuppressWarnings("unchecked")
public BucketQueue(int priorities, int capacityPerPriority)
throws IllegalArgumentException {
if (priorities<=0)
throw new IllegalArgumentException(
"Bad priorities: "+priorities);
if (capacityPerPriority<=0)
throw new IllegalArgumentException(
"Bad capacity: "+capacityPerPriority);
this.buckets = new Buffer[priorities];
for (int i=0; i<buckets.length; i++) {
buckets[i] = new Buffer<E>(capacityPerPriority);
}
}
/**
* @effects makes a new queue that will hold up to capacities[i]
* elements of priority i. Hence the legal priorities are 0
* through capacities.length-1
* @exception IllegalArgumentException capacities.length<=0 or
* capacities[i]<=0 for any i
*/
@SuppressWarnings("unchecked")
public BucketQueue(int[] capacities) throws IllegalArgumentException {
if (capacities.length<=0)
throw new IllegalArgumentException();
this.buckets = new Buffer[capacities.length];
for (int i=0; i<buckets.length; i++) {
if (capacities[i]<=0)
throw new IllegalArgumentException(
"Non-positive capacity: "+capacities[i]);
buckets[i]=new Buffer<E>(capacities[i]);
}
}
/** "Copy constructor": constructs a a new shallow copy of other. */
@SuppressWarnings("unchecked")
public BucketQueue(BucketQueue<? extends E> other) {
//Note that we can't just shallowly clone other.buckets
this.buckets = new Buffer[other.buckets.length];
for (int i=0; i<this.buckets.length; i++) {
this.buckets[i]=new Buffer<E>(other.buckets[i]); //clone
}
this.size=other.size;
}
/**
* Removes all elements from the queue.
*/
public void clear() {
repOk();
for (Buffer<E> bucket : buckets) {
bucket.clear();
}
size=0;
repOk();
}
/**
* @modifies this.
* @effects adds o to this, removing and returning some older element of
* same or lesser priority as needed
* @exception IllegalArgumentException priority is not a legal priority,
* as determined by this' constructor
*/
public E insert(E o, int priority) {
repOk();
if(priority < 0 || priority >= buckets.length) {
throw new IllegalArgumentException("Bad priority: "+priority);
}
E ret = buckets[priority].addFirst(o);
if (ret == null)
size++; //Maintain invariant
repOk();
return ret;
}
/**
* @modifies this.
* @effects removes all o' such that o'.equals(o). Note that p's
* priority is ignored. Returns true if any elements were removed.
*/
public boolean removeAll(Object o) {
repOk();
//1. For each bucket, remove o, noting if any elements were removed.
boolean ret=false;
for (Buffer<E> bucket : buckets) {
ret = ret | bucket.removeAll(o);
}
//2. Maintain size invariant. The problem is that removeAll() can
//remove multiple elements from this. As a slight optimization, we
//could incrementally update size by looking at buckets[i].getSize()
//before and after the call to removeAll(..). But I favor simplicity.
if (ret) {
this.size=0;
for (Buffer<E> bucket : buckets) {
this.size += bucket.getSize();
}
}
repOk();
return ret;
}
public E extractMax() throws NoSuchElementException {
repOk();
try {
for (int i=buckets.length-1; i>=0 ;i--) {
if (! buckets[i].isEmpty()) {
size--;
return buckets[i].removeFirst();
}
}
throw new NoSuchElementException();
} finally {
repOk();
}
}
public E getMax() throws NoSuchElementException {
//TODO: we can optimize this by storing the position of the first
//non-empty bucket.
for (int i=buckets.length-1; i>=0 ;i--) {
if (! buckets[i].isEmpty()) {
return buckets[i].first();
}
}
throw new NoSuchElementException();
}
public int size() {
return size;
}
/**
* @effects returns the number of entries with the given priority.
* @exception IllegalArgumentException priority is not a legal priority,
* as determined by this' constructor
*/
public int size(int priority) throws IllegalArgumentException {
if(priority < 0 || priority >= buckets.length) {
throw new IllegalArgumentException("Bad priority: "+priority);
}
return buckets[priority].getSize();
}
public boolean isEmpty() {
return size()==0;
}
/**
* @requires this not modified while iterator in use
* @effects yields the elements of this exactly once, from highest priority
* to lowest priority. Within each priority level, newer elements are
* yielded before older ones.
*/
public Iterator<E> iterator() {
return new BucketQueueIterator(buckets.length-1, this.size());
}
/**
* @requires this not modified while iterator in use
* @effects yields the best n elements from startPriority down to to lowest
* priority. Within each priority level, newer elements are yielded before
* older ones, and each element is yielded exactly once. May yield fewer
* than n elements.
* @exception IllegalArgumentException startPriority is not a legal priority
* as determined by this' constructor
*/
public Iterator<E> iterator(int startPriority, int n)
throws IllegalArgumentException {
if (startPriority<0 || startPriority>=buckets.length)
throw new IllegalArgumentException("Bad priority: "+startPriority);
return new BucketQueueIterator(startPriority, n);
}
private class BucketQueueIterator extends UnmodifiableIterator<E> {
private Iterator<E> currentIterator;
private int currentBucket;
private int left;
/**
* @requires buckets.length>0
* @effects creates an iterator that yields the best
* n elements.
*/
public BucketQueueIterator(int startPriority, int n) {
this.currentBucket=startPriority;
this.currentIterator=buckets[currentBucket].iterator();
this.left=n;
}
public synchronized boolean hasNext() {
if (left<=0)
return false;
if (currentIterator.hasNext())
return true;
if (currentBucket<0)
return false;
//Find non-empty bucket. Note the "benevolent side effect".
//(Changes internal state, but not visible to caller.)
for (currentBucket-- ; currentBucket>=0 ; currentBucket--) {
currentIterator=buckets[currentBucket].iterator();
if (currentIterator.hasNext())
return true;
}
return false;
}
public synchronized E next() {
//This relies on the benevolent side effects of hasNext.
if (! hasNext())
throw new NoSuchElementException();
left--;
return currentIterator.next();
}
}
/** Returns a shallow copy of this, of type BucketQueue. */
@Override
public BucketQueue<E> clone() throws CloneNotSupportedException {
return new BucketQueue<E>(this);
}
private void repOk() {
/*
int count=0;
for (int i=0; i<buckets.length; i++) {
count+=buckets[i].getSize();
}
Assert.that(count==size);
*/
}
@Override
public String toString() {
StringBuilder buf=new StringBuilder();
buf.append("[");
for (int i=buckets.length-1; i>=0; i--) {
if (i!=buckets.length-1)
buf.append(", ");
buf.append(buckets[i].toString());
}
buf.append("]");
return buf.toString();
}
}