package edu.berkeley.nlp.util; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; /** * PriorityQueue with explicit double priority values. Larger doubles are higher * priorities. BinaryHeap-backed. * * @author Dan Klein * @author Christopher Manning For each entry, uses ~ 24 (entry) + 16? * (Map.Entry) + 4 (List entry) = 44 bytes? */ public class GeneralPriorityQueue<E> implements PriorityQueueInterface<E>, Serializable { /** * An <code>Entry</code> stores an object in the queue along with its * current location (array position) and priority. uses ~ 8 (self) + 4 (key * ptr) + 4 (index) + 8 (priority) = 24 bytes? */ public static final class Entry<E> implements Serializable { public E key; public int index; public double priority; @Override public String toString() { return key + " at " + index + " (" + priority + ")"; } } public boolean hasNext() { return size() > 0; } public E next() { final E removeFirst = removeFirst(); return removeFirst; } public void remove() { throw new UnsupportedOperationException(); } /** * <code>indexToEntry</code> maps linear array locations (not priorities) to * heap entries. */ private List<Entry<E>> indexToEntry; /** * <code>keyToEntry</code> maps heap objects to their heap entries. */ private Map<E, Entry<E>> keyToEntry; public GeneralPriorityQueue<E> deepCopy() { GeneralPriorityQueue<E> pq = new GeneralPriorityQueue<E>(); for (Entry<E> entry : indexToEntry) { pq.setPriority(entry.key, entry.priority); } return pq; } private Entry<E> parent(Entry<E> entry) { int index = entry.index; return (index > 0 ? getEntry((index - 1) / 2) : null); } private Entry<E> leftChild(Entry<E> entry) { int leftIndex = entry.index * 2 + 1; return (leftIndex < size() ? getEntry(leftIndex) : null); } private Entry<E> rightChild(Entry<E> entry) { int index = entry.index; int rightIndex = index * 2 + 2; return (rightIndex < size() ? getEntry(rightIndex) : null); } private int compare(Entry<E> entryA, Entry<E> entryB) { return compare(entryA.priority, entryB.priority); } protected int compare(double a, double b) { double diff = a - b; if (diff > 0.0) { return 1; } if (diff < 0.0) { return -1; } return 0; } /** * Structural swap of two entries. * * @param entryA * @param entryB */ private void swap(Entry<E> entryA, Entry<E> entryB) { int indexA = entryA.index; int indexB = entryB.index; entryA.index = indexB; entryB.index = indexA; indexToEntry.set(indexA, entryB); indexToEntry.set(indexB, entryA); } /** * Remove the last element of the heap (last in the index array). */ private void removeLastEntry() { Entry entry = indexToEntry.remove(size() - 1); keyToEntry.remove(entry.key); } /** * Get the entry by key (null if none). */ protected Entry<E> getEntry(Object key) { Entry<E> entry = keyToEntry.get(key); return entry; } /** * Get entry by index, exception if none. */ private Entry<E> getEntry(int index) { Entry<E> entry = indexToEntry.get(index); return entry; } protected Entry<E> makeEntry(E key) { Entry<E> entry = new Entry<E>(); entry.index = size(); entry.key = key; entry.priority = Double.NEGATIVE_INFINITY; indexToEntry.add(entry); keyToEntry.put(key, entry); return entry; } /** * iterative heapify up: move item o at index up until correctly placed */ protected void heapifyUp(Entry<E> entry) { while (true) { if (entry.index == 0) { break; } Entry<E> parentEntry = parent(entry); if (compare(entry, parentEntry) <= 0) { break; } swap(entry, parentEntry); } } /** * On the assumption that leftChild(entry) and rightChild(entry) satisfy the * heap property, make sure that the heap at entry satisfies this property * by possibly percolating the element o downwards. I've replaced the * obvious recursive formulation with an iterative one to gain (marginal) * speed */ private void heapifyDown(Entry<E> entry) { Entry<E> currentEntry = entry; Entry<E> bestEntry = null; do { bestEntry = currentEntry; Entry<E> leftEntry = leftChild(currentEntry); if (leftEntry != null) { if (compare(bestEntry, leftEntry) < 0) { bestEntry = leftEntry; } } Entry<E> rightEntry = rightChild(currentEntry); if (rightEntry != null) { if (compare(bestEntry, rightEntry) < 0) { bestEntry = rightEntry; } } if (bestEntry != currentEntry) { // Swap min and current swap(bestEntry, currentEntry); // at start of next loop, we set currentIndex to largestIndex // this indexation now holds current, so it is unchanged } } while (bestEntry != currentEntry); // System.err.println("Done with heapify down"); // verify(); } private void heapify(Entry<E> entry) { heapifyUp(entry); heapifyDown(entry); } /** * Finds the object with the highest priority, removes it, and returns it. * * @return the object with highest priority */ public E removeFirst() { E first = getFirst(); removeKey(first); return first; } /** * Finds the object with the highest priority and returns it, without * modifying the queue. * * @return the object with minimum key */ public E getFirst() { if (isEmpty()) throw new NoSuchElementException(); return getEntry(0).key; } /** * Searches for the object in the queue and returns it. May be useful if you * can create a new object that is .equals() to an object in the queue but * is not actually identical, or if you want to modify an object that is in * the queue. * * @return null if the object is not in the queue, otherwise returns the * object. */ public E getObject(E key) { if (!containsKey(key)) return null; Entry<E> e = getEntry(key); return e.key; } /** * Get the priority of a key -- if the key is not in the queue, * Double.NEGATIVE_INFINITY is returned. * * @param key * @return */ public double getPriority(E key) { Entry entry = getEntry(key); if (entry == null) { return Double.NEGATIVE_INFINITY; } return entry.priority; } public double removeKey(E key) { Entry<E> entry = getEntry(key); if (entry == null) { return Double.NEGATIVE_INFINITY; } removeEntry(entry); return entry.priority; } private void removeEntry(Entry<E> entry) { Entry<E> lastEntry = getLastEntry(); if (entry != lastEntry) { swap(entry, lastEntry); removeLastEntry(); heapify(lastEntry); } else { removeLastEntry(); } return; } private Entry<E> getLastEntry() { return getEntry(size() - 1); } /** * Promotes a key in the queue, adding it if it wasn't there already. If the * specified priority is worse than the current priority, nothing happens. * Faster than add if you don't care about whether the key is new. * * @param key * an <code>Object</code> value * @return whether the priority actually improved. */ public boolean relaxPriority(E key, double priority) { Entry<E> entry = getEntry(key); if (entry == null) { entry = makeEntry(key); } if (compare(priority, entry.priority) <= 0) { return false; } entry.priority = priority; heapifyUp(entry); return true; } /** * Demotes a key in the queue, adding it if it wasn't there already. If the * specified priority is better than the current priority, nothing happens. * If you decrease the priority on a non-present key, it will get added, but * at its old implicit priority of Double.NEGATIVE_INFINITY. * * @param key * an <code>Object</code> value * @return whether the priority actually improved. */ public boolean decreasePriority(E key, double priority) { Entry<E> entry = getEntry(key); if (entry == null) { entry = makeEntry(key); } if (compare(priority, entry.priority) >= 0) { return false; } entry.priority = priority; heapifyDown(entry); return true; } /** * Changes a priority, either up or down, adding the key it if it wasn't * there already. * * @param key * an <code>Object</code> value */ public void setPriority(E key, double priority) { Entry<E> entry = getEntry(key); if (entry == null) { entry = makeEntry(key); } else { if (entry.key != key) { entry.key = key; keyToEntry.put(key, entry); } } if (compare(priority, entry.priority) == 0) { return; } entry.priority = priority; heapify(entry); // isValid(entry); } /** * @param entry * @param count */ private boolean isValid(Entry<E> entry) { int count = 0; for (int i = 0; i < indexToEntry.size(); ++i) { if (indexToEntry.get(i).key == entry.key) count++; } assert count == 1; return count == 1; } /** * Checks if the queue is empty. * * @return a <code>boolean</code> value */ public boolean isEmpty() { return indexToEntry.isEmpty(); } /** * Get the number of elements in the queue. * * @return queue size */ public int size() { return indexToEntry.size(); } public List<E> toSortedList() { List<E> sortedList = new ArrayList<E>(size()); GeneralPriorityQueue<E> queue = deepCopy(); while (queue.hasNext()) { sortedList.add(queue.next()); } return sortedList; } public Iterator<E> iterator() { return Collections.unmodifiableCollection(toSortedList()).iterator(); } /** * Clears the queue. */ public void clear() { indexToEntry.clear(); keyToEntry.clear(); } // private void verify() { // for (int i = 0; i < indexToEntry.size(); i++) { // if (i != 0) { // // check ordering // if (compare(getEntry(i), parent(getEntry(i))) < 0) { // System.err.println("Error in the ordering of the heap! ("+i+")"); // System.exit(0); // } // } // // check placement // if (i != ((Entry)indexToEntry.get(i)).index) // System.err.println("Error in placement in the heap!"); // } // } @Override public String toString() { List<E> sortedKeys = toSortedList(); StringBuffer sb = new StringBuffer("["); for (Iterator<E> keyI = sortedKeys.iterator(); keyI.hasNext();) { E key = keyI.next(); sb.append(key); sb.append("="); sb.append(getPriority(key)); if (keyI.hasNext()) { sb.append(", "); } } sb.append("]"); return sb.toString(); } public String toVerticalString() { List<E> sortedKeys = toSortedList(); StringBuffer sb = new StringBuffer(); for (Iterator<E> keyI = sortedKeys.iterator(); keyI.hasNext();) { E key = keyI.next(); sb.append(key); sb.append(" : "); sb.append(getPriority(key)); if (keyI.hasNext()) { sb.append("\n"); } } return sb.toString(); } public double getPriority() { return getPriority(getFirst()); } public boolean containsKey(E e) { return keyToEntry.containsKey(e); } public String toString(int maxKeysToPrint) { GeneralPriorityQueue<E> pq = deepCopy(); StringBuilder sb = new StringBuilder("["); int numKeysPrinted = 0; while (numKeysPrinted < maxKeysToPrint && !pq.isEmpty()) { double priority = pq.getPriority(); E element = pq.removeFirst(); sb.append(element.toString()); sb.append(" : "); sb.append(priority); if (numKeysPrinted < size() - 1) // sb.append("\n"); sb.append(", "); numKeysPrinted++; } if (numKeysPrinted < size()) sb.append("..."); sb.append("]"); return sb.toString(); } public GeneralPriorityQueue() { this(new MapFactory.HashMapFactory<E, Entry<E>>()); } public GeneralPriorityQueue(MapFactory<E, Entry<E>> mapFactory) { indexToEntry = new ArrayList<Entry<E>>(); keyToEntry = mapFactory.buildMap(); } public static void main(String[] args) { GeneralPriorityQueue<String> queue = new GeneralPriorityQueue<String>(); queue.setPriority("a", 1.0); System.out.println("Added a:1 " + queue); queue.setPriority("b", 2.0); System.out.println("Added b:2 " + queue); queue.setPriority("c", 1.5); System.out.println("Added c:1.5 " + queue); queue.setPriority("a", 3.0); System.out.println("Increased a to 3 " + queue); queue.setPriority("b", 0.0); System.out.println("Decreased b to 0 " + queue); System.out.println("removeFirst()=" + queue.next()); System.out.println("queue=" + queue); System.out.println("removeFirst()=" + queue.next()); System.out.println("queue=" + queue); System.out.println("removeFirst()=" + queue.next()); System.out.println("queue=" + queue); } public void put(E key, double priority) { setPriority(key, priority); } public E peek() { return getFirst(); } }