/** * */ package com.trendrr.oss; import java.util.Collection; import java.util.Date; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * A priority queue with an efficient 'update' function. * * update from code posted: http://stackoverflow.com/questions/714796/priorityqueue-heap-update * * Synchronized. * * @author Dustin Norlander * @created Feb 15, 2012 * */ public class PriorityUpdateQueue <T>{ protected static Log log = LogFactory.getLog(PriorityUpdateQueue.class); private List<T> heap = new ArrayList<T>(); /** * TODO: this implementation is broken for T is a Map since hashcode for that * is apparently contingent on the mappings in the map.. * investigate.. */ private Map<T, Integer> indexes; //map of T to int private Comparator<T> comparator; /** * Pass in an appropriate comparator. * @param comparator */ public PriorityUpdateQueue(Comparator<T> comparator) { this(comparator, new HashMap<T, Integer>(), new ArrayList<T>()); } /** * allows better map and collection implementation (i.e. trove, hppc, ect) * * map should be <T,Integer> * collection should be T, where get(int) is fast. * * @param comparator * @param map * @param collection */ public PriorityUpdateQueue(Comparator<T> comparator, Map map, List collection) { this.comparator = comparator; this.heap = collection; this.indexes = map; } public PriorityUpdateQueue(Comparator<T> comparator, Map map) { this(comparator, map, new ArrayList<T>()); } public synchronized int getSize() { return heap.size(); } /** * drains and discards elements until the size of the heap is <= size * @param size */ public void drainToSize(int size) { while(this.size() > size) { this.pop(); } } /** * push an item into the queue. * * note that you can NOT add the same element twice. Sameness meaning exactly the same reference. Same by comparable works fine. * @param obj */ public synchronized void push(T obj) { if (this.indexes.containsKey(obj)) { log.warn("Already contains: " + obj); return; } heap.add(obj); indexes.put(obj, heap.size()-1); pushUp(heap.size()-1); } /** * does a (FAST) removal of the specified element. * * * @param obj */ public synchronized boolean remove(T obj) { Integer index = this.indexes.remove(obj); if (index == null) { return false; } if (index == heap.size()-1) { //removing the last element heap.remove((int)index); //cast is necessary here return true; } T item = heap.remove(heap.size()-1); this.indexes.put(item, index); this.heap.set(index, item); this.pushDown(item); return true; } /** * pops the top of the heap and returns it. returns null on empty. * @return */ public synchronized T pop() { if (heap.size() > 0) { swap(0, heap.size()-1); T result = heap.remove(heap.size()-1); this.indexes.remove(result); pushDownByIndex(0); return result; } else { return null; } } /** * returns the head of the queue without removing it. * @return */ public synchronized T peek() { if (heap.size() < 1) return null; return heap.get(0); } /** * item has been updated, so move in the heap. update is meant to be fast. usually only a few compares. * @param obj */ public synchronized void update(T obj) { int i = this.indexes.get(obj); T parent = this.heap.get(this.parent(i)); if (i > 0 && this.isGreaterOrEqual(obj, parent)) { this.pushUp(i); } else { this.pushDownByIndex(i); } } /** * inserts the item if it does not already exist, else updates it if it does. * @param obj * @returns true if the item was pushed, false if it was updated. */ public synchronized boolean pushOrUpdate(T obj) { if (this.indexes.containsKey(obj)) { this.update(obj); return false; } else { this.push(obj); return true; } } protected Object get(int index) { return heap.get(index); } public synchronized int size() { return heap.size(); } protected int parent(int i) { return (i - 1) / 2; } protected int left(int i) { return 2 * i + 1; } protected int right(int i) { return 2 * i + 2; } protected void swap(int i, int j) { T tmp = heap.get(i); T tmpj = heap.get(j); this.indexes.put(tmpj, i); this.indexes.put(tmp, j); heap.set(i, tmpj); heap.set(j, tmp); } /** * push the object lower in the heap. * @param obj */ private void pushDown(T obj) { int i = this.indexes.get(obj); this.pushDownByIndex(i); } private void pushDownByIndex(int i) { int left = left(i); int right = right(i); int largest = i; if (left < heap.size() && !isGreaterOrEqual(this.heap.get(largest), this.heap.get(left))) { largest = left; } if (right < heap.size() && !isGreaterOrEqual(this.heap.get(largest), this.heap.get(right))) { largest = right; } if (largest != i) { swap(largest, i); pushDownByIndex(largest); } } private boolean isGreaterOrEqual(T i, T j) { return this.comparator.compare(i, j) >= 0; } private void pushUp(int i) { // System.out.println("PUSH UP: " + i); // // System.out.println(parent(i)); // System.out.println(this.heap); while (i > 0 && !isGreaterOrEqual(this.heap.get(parent(i)), this.heap.get(i))) { swap(parent(i), i); i = parent(i); } } public String toString() { StringBuffer s = new StringBuffer("Heap:\n"); int rowStart = 0; int rowSize = 1; for (int i = 0; i < heap.size(); i++) { if (i == rowStart+rowSize) { s.append('\n'); rowStart = i; rowSize *= 2; } s.append(get(i)); s.append(" "); } return s.toString(); } public static void main(String[] args){ PriorityUpdateQueue<DynMap> h = new PriorityUpdateQueue<DynMap>(new Comparator<DynMap>() { @Override public int compare(DynMap o1, DynMap o2) { return o1.getInteger("val").compareTo(o2.getInteger("val")); } }); DynMap item = new DynMap(); item.put("name","testing"); item.put("val",1000); h.push(item); item.put("val",50); h.update(item); Date start = new Date(); for (int i = 0; i < 10; i++) { DynMap it = new DynMap(); int v = (int)(100 * Math.random()); it.put("name", "" + v); it.put("val", v); h.push(it); if (i > 100000) { h.pop(); } if (i % 10000 == 0) { System.out.println(i); } } System.out.println("Added 1 million in : " + (new java.util.Date().getTime()-start.getTime())); System.out.println("**************** DONE ADDING *************"); item.put("val",50); h.update(item); start = new Date(); while (h.size() > 0) { // System.out.println(h.pop()); h.pop(); } System.out.println("Removed 1 million in : " + (new java.util.Date().getTime()-start.getTime())); } }