package btools.util; import java.util.Random; /** * Memory efficient and lightning fast heap to get the lowest-key value of a set of key-object pairs * * @author ab */ public final class SortedHeap<V> { private int size; private int peaksize; private SortedBin first; private SortedBin second; private SortedBin firstNonEmpty; public SortedHeap() { clear(); } /** * @return the lowest key value, or null if none */ public V popLowestKeyValue() { SortedBin bin = firstNonEmpty; if ( bin == null ) { return null; } size--; int minId = bin.lv; SortedBin minBin = bin; while( ( bin = bin.nextNonEmpty ) != null ) { if ( bin.lv < minId ) { minId = bin.lv; minBin = bin; } } return (V) minBin.dropLowest(); } private static final class SortedBin { SortedHeap parent; SortedBin next; SortedBin nextNonEmpty; int binsize; int[] al; // key array Object[] vla; // value array int lv; // low value int lp; // low pointer SortedBin( int binsize, SortedHeap parent ) { this.binsize = binsize; this.parent = parent; al = new int[binsize]; vla = new Object[binsize]; lp = binsize; } SortedBin next() { if ( next == null ) { next = new SortedBin( binsize << 1, parent ); } return next; } Object dropLowest() { int lpOld = lp; if ( ++lp == binsize ) { unlink(); } else { lv = al[lp]; } Object res = vla[lpOld]; vla[lpOld] = null; return res; } void unlink() { SortedBin neBin = parent.firstNonEmpty; if ( neBin == this ) { parent.firstNonEmpty = nextNonEmpty; return; } for(;;) { SortedBin next = neBin.nextNonEmpty; if ( next == this ) { neBin.nextNonEmpty = nextNonEmpty; return; } neBin = next; } } void add( int key, Object value ) { int p = lp; for(;;) { if ( p == binsize || key < al[p] ) { al[p-1] = key; vla[p-1] = value; lv = al[--lp]; return; } al[p-1] = al[p]; vla[p-1] = vla[p]; p++; } } } /** * add a key value pair to the heap * * @param id * the key to insert * @param value * the value to insert object */ public void add( int key, V value ) { size++; if ( first.lp == 0 && second.lp == 0) // both full ? { sortUp(); } if ( first.lp > 0 ) { first.add( key, value ); if ( firstNonEmpty != first ) { first.nextNonEmpty = firstNonEmpty; firstNonEmpty = first; } } else // second bin not full { second.add( key, value ); if ( first.nextNonEmpty != second ) { second.nextNonEmpty = first.nextNonEmpty; first.nextNonEmpty = second; } } } private void sortUp() { if ( size > peaksize ) { peaksize = size; } // determine the first array big enough to take them all int cnt = 8; // value count of first 2 bins is always 8 SortedBin tbin = second; // target bin SortedBin lastNonEmpty = second; do { tbin = tbin.next(); int nentries = tbin.binsize - tbin.lp; if ( nentries > 0 ) { cnt += nentries; lastNonEmpty = tbin; } } while( cnt > tbin.binsize ); int[] al_t = tbin.al; Object[] vla_t = tbin.vla; int tp = tbin.binsize-cnt; // target pointer // unlink any higher, non-empty arrays SortedBin otherNonEmpty = lastNonEmpty.nextNonEmpty; lastNonEmpty.nextNonEmpty = null; // now merge the content of these non-empty bins into the target bin while( firstNonEmpty != null ) { SortedBin ne = firstNonEmpty; SortedBin minBin = ne; int minId = minBin.lv; while ( ( ne = ne.nextNonEmpty ) != null ) { if ( ne.lv < minId ) { minBin = ne; minId = minBin.lv; } } // current minimum found, copy to target array al_t[tp] = minId; vla_t[tp++] = minBin.dropLowest(); } tp = tbin.binsize-cnt; tbin.lp = tp; // new target low pointer tbin.lv = tbin.al[tp]; tbin.nextNonEmpty = otherNonEmpty; firstNonEmpty = tbin; } public void clear() { size = 0; first = new SortedBin( 4, this ); second = new SortedBin( 4, this ); firstNonEmpty = null; } public int getSize() { return size; } public int getPeakSize() { return peaksize; } public int getExtract( Object[] targetArray ) { int tsize = targetArray.length; int div = size / tsize + 1; int tp = 0; int lpi = 0; SortedBin bin = firstNonEmpty; while( bin != null ) { lpi += bin.lp; Object[] vlai = bin.vla; int n = bin.binsize; while (lpi < n) { targetArray[tp++] = vlai[lpi]; lpi += div; } lpi -= n; bin = bin.nextNonEmpty; } return tp; } public static void main(String[] args) { SortedHeap<String> sh = new SortedHeap<String>(); Random rnd = new Random(); for( int i = 0; i< 1000; i++ ) { int val = rnd.nextInt( 1000000 ); sh.add( val, "" + val ); val = rnd.nextInt( 1000000 ); sh.add( val, "" + val ); sh.popLowestKeyValue(); } int cnt = 0; int lastval = 0; for(;;) { String s = sh.popLowestKeyValue(); if ( s == null ) break; cnt ++; int val = Integer.parseInt( s ); System.out.println( "popLowestKeyValue: " + val); // Assert.assertTrue( "sorting test", val >= lastval ); lastval = val; } // Assert.assertTrue( "total count test", cnt == 100000 ); } }