performance patches

This commit is contained in:
Arndt 2014-12-28 08:03:27 +01:00
parent 176beba6f6
commit 46db0104e5
13 changed files with 469 additions and 280 deletions

View file

@ -1,172 +0,0 @@
/**
* Implementation for the open-set
* that should be somewhat faster
* and memory-efficient than the original
* version based on java.util.TreeSet
*
* It relies on the two double-linked
* lists implemented in OsmPath
*
* @author ab
*/
package btools.router;
import btools.mapaccess.OsmNode;
public class OpenSet
{
private OsmPath start = new OsmPath();
private OsmPath index2 = new OsmPath();
private int addCount = 0;
private int size = 0;
public void clear()
{
start.nextInSet = null;
start.nextInIndexSet = null;
index2.nextInIndexSet = null;
size = 0;
addCount = 0;
}
public void add( OsmPath path )
{
int ac = path.adjustedCost;
OsmPath p1 = index2;
// fast forward along index2
while( p1.nextInIndexSet != null && p1.nextInIndexSet.adjustedCost < ac )
{
p1 = p1.nextInIndexSet;
}
if ( p1 == index2 )
{
p1 = start;
}
// search using index1
for(;;)
{
if ( p1.nextInIndexSet != null && p1.nextInIndexSet.adjustedCost < ac )
{
p1 = p1.nextInIndexSet;
}
else if ( p1.nextInSet != null && p1.nextInSet.adjustedCost < ac )
{
p1 = p1.nextInSet;
}
else
{
break;
}
}
OsmPath p2 = p1.nextInSet;
p1.nextInSet = path;
path.prevInSet = p1;
path.nextInSet = p2;
if ( p2 != null ) { p2.prevInSet = path; }
size++;
addCount++;
// feed random samples to the indices
if ( (addCount & 31) == 0 )
{
addIndex( path, start );
}
else if ( (addCount & 1023) == 1023 )
{
addIndex( path, index2 );
}
}
public void remove( OsmPath path )
{
OsmPath p1 = path.prevInSet;
OsmPath p2 = path.nextInSet;
if ( p1 == null )
{
return; // not in set
}
path.prevInSet = null;
path.nextInSet = null;
if ( p2 != null )
{
p2.prevInSet = p1;
}
p1.nextInSet = p2;
removeIndex( path );
size--;
}
public OsmPath first()
{
return start.nextInSet;
}
public int size()
{
return size;
}
public int[] getExtract()
{
int div = size / 1000 + 1;
int[] res = new int[size/div * 2];
int i = 0;
int cnt = 0;
for( OsmPath p = start.nextInSet; p != null; p = p.nextInSet )
{
if ( (++cnt) % div == 0 )
{
OsmNode n = p.getLink().targetNode;
res[i++] = n.ilon;
res[i++] = n.ilat;
}
}
return res;
}
// index operations
private void addIndex( OsmPath path, OsmPath index )
{
int ac = path.adjustedCost;
OsmPath p1 = index;
OsmPath p2 = p1.nextInIndexSet;
while( p2 != null && p2.adjustedCost < ac )
{
p1 = p2;
p2 = p2.nextInIndexSet;
}
p1.nextInIndexSet = path;
path.prevInIndexSet = p1;
path.nextInIndexSet = p2;
if ( p2 != null ) { p2.prevInIndexSet = path; }
}
private void removeIndex( OsmPath path )
{
OsmPath p1 = path.prevInIndexSet;
OsmPath p2 = path.nextInIndexSet;
if ( p1 == null )
{
return; // not in set
}
path.prevInIndexSet = null;
path.nextInIndexSet = null;
if ( p2 != null )
{
p2.prevInIndexSet = p1;
}
p1.nextInIndexSet = p2;
}
}

View file

@ -9,12 +9,6 @@ import btools.mapaccess.*;
final class OsmPath implements OsmLinkHolder
{
// double-linked lists for the openSet
public OsmPath nextInSet;
public OsmPath prevInSet;
public OsmPath nextInIndexSet;
public OsmPath prevInIndexSet;
/**
* The cost of that path (a modified distance)
*/
@ -30,12 +24,7 @@ final class OsmPath implements OsmLinkHolder
// if the corresponding node has not
public short selev;
public int adjustedCost = 0;
public void setAirDistanceCostAdjustment( int costAdjustment )
{
adjustedCost = cost + costAdjustment;
}
public int airdistance = 0; // distance to endpos
private OsmNode sourcenode;
private OsmLink link;

View file

@ -18,6 +18,7 @@ import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import btools.mapaccess.OsmPos;
import btools.util.CompactLongMap;
@ -219,7 +220,7 @@ public final class OsmTrack
sb.append( " xmlns=\"http://www.topografix.com/GPX/1/1\" \n" );
sb.append( " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" \n" );
sb.append( " xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\" \n" );
sb.append( " creator=\"BRouter-1.0.4\" version=\"1.1\">\n" );
sb.append( " creator=\"BRouter-1.1\" version=\"1.1\">\n" );
sb.append( " <trk>\n" );
sb.append(" <name>").append(name).append("</name>\n");
sb.append( " <trkseg>\n" );
@ -286,6 +287,8 @@ public final class OsmTrack
return sb.toString();
}
public List<String> iternity;
public String formatAsGeoJson()
{
StringBuilder sb = new StringBuilder(8192);
@ -296,13 +299,24 @@ public final class OsmTrack
sb.append( " {\n" );
sb.append( " \"type\": \"Feature\",\n" );
sb.append( " \"properties\": {\n" );
sb.append( " \"creator\": \"BRouter-1.0.4\",\n" );
sb.append( " \"creator\": \"BRouter-1.1\",\n" );
sb.append( " \"name\": \"" ).append( name ).append( "\",\n" );
sb.append( " \"track-length\": \"" ).append( distance ).append( "\",\n" );
sb.append( " \"filtered ascend\": \"" ).append( ascend ).append( "\",\n" );
sb.append( " \"plain-ascend\": \"" ).append( plainAscend ).append( "\",\n" );
sb.append( " \"cost\": \"" ).append( cost ).append( "\"\n" );
sb.append( " },\n" );
if ( iternity != null )
{
sb.append( " \"iternity\": [\n" );
for( String s : iternity )
{
sb.append( " \"").append( s ).append( "\",\n" );
}
sb.deleteCharAt( sb.lastIndexOf( "," ) );
sb.append( " ],\n" );
}
sb.append( " \"geometry\": {\n" );
sb.append( " \"type\": \"LineString\",\n" );
sb.append( " \"coordinates\": [\n" );

View file

@ -40,6 +40,8 @@ public final class RoutingContext implements DistanceChecker
public BExpressionContext expctxWay;
public BExpressionContext expctxNode;
public boolean serversizing = false;
public int downhillcostdiv;
public int downhillcutoff;
public int uphillcostdiv;
@ -51,6 +53,12 @@ public final class RoutingContext implements DistanceChecker
public int elevationpenaltybuffer;
public int elevationmaxbuffer;
public int elevationbufferreduce;
public double cost1speed;
public double additionalcostfactor;
public double changetime;
public double buffertime;
public double waittimeadjustment;
public void readGlobalConfig( BExpressionContext expctxGlobal )
{
@ -67,6 +75,12 @@ public final class RoutingContext implements DistanceChecker
elevationpenaltybuffer = (int)(expctxGlobal.getVariableValue( "elevationpenaltybuffer", 5.f )*1000000);
elevationmaxbuffer = (int)(expctxGlobal.getVariableValue( "elevationmaxbuffer", 10.f )*1000000);
elevationbufferreduce = (int)(expctxGlobal.getVariableValue( "elevationbufferreduce", 0.f )*10000);
cost1speed = expctxGlobal.getVariableValue( "cost1speed", 22.f );
additionalcostfactor = expctxGlobal.getVariableValue( "additionalcostfactor", 1.5f );
changetime = expctxGlobal.getVariableValue( "changetime", 180.f );
buffertime = expctxGlobal.getVariableValue( "buffertime", 120.f );
waittimeadjustment = expctxGlobal.getVariableValue( "waittimeadjustment", 0.9f );
}
public RoutingMessageHandler messageHandler = new RoutingMessageHandler();

View file

@ -15,32 +15,33 @@ import btools.mapaccess.OsmLink;
import btools.mapaccess.OsmLinkHolder;
import btools.mapaccess.OsmNode;
import btools.mapaccess.OsmNodesMap;
import btools.util.SortedHeap;
public class RoutingEngine extends Thread
{
private OsmNodesMap nodesMap;
private NodesCache nodesCache;
private OpenSet openSet = new OpenSet();
private SortedHeap<OsmPath> openSet = new SortedHeap<OsmPath>();
private boolean finished = false;
private List<OsmNodeNamed> waypoints = null;
protected List<OsmNodeNamed> waypoints = null;
private int linksProcessed = 0;
private OsmTrack foundTrack = new OsmTrack();
protected OsmTrack foundTrack = new OsmTrack();
private OsmTrack foundRawTrack = null;
private int alternativeIndex = 0;
private String errorMessage = null;
protected String errorMessage = null;
private volatile boolean terminated;
private File profileDir;
private String segmentDir;
protected File profileDir;
protected String segmentDir;
private String outfileBase;
private String logfileBase;
private boolean infoLogEnabled;
private Writer infoLogWriter;
private RoutingContext routingContext;
protected RoutingContext routingContext;
private double airDistanceCostFactor;
private OsmTrack guideTrack;
@ -80,8 +81,8 @@ public class RoutingEngine extends Thread
BExpressionMetaData meta = new BExpressionMetaData();
BExpressionContext expctxGlobal = new BExpressionContext( "global", meta );
rc.expctxWay = new BExpressionContext( "way", meta );
rc.expctxNode = new BExpressionContext( "node", 1024, meta );
rc.expctxWay = new BExpressionContext( "way", rc.serversizing ? 262144 : 4096, meta );
rc.expctxNode = new BExpressionContext( "node", rc.serversizing ? 16384 : 1024, meta );
meta.readMetaData( new File( profileDir, "lookups.dat" ) );
@ -504,7 +505,6 @@ public class RoutingEngine extends Thread
if ( pe.cost >= costdelta )
{
pe.cost -= costdelta;
pe.adjustedCost -= costdelta;
if ( guideTrack != null )
{
@ -559,7 +559,7 @@ public class RoutingEngine extends Thread
wp.radius = 1e9;
OsmPath testPath = new OsmPath( null, startPath, link, null, guideTrack != null, routingContext );
testPath.setAirDistanceCostAdjustment( (int)( nextNode.calcDistance( endPos ) * airDistanceCostFactor ) );
testPath.airdistance = nextNode.calcDistance( endPos );
if ( wp.radius < minradius )
{
bestPath = testPath;
@ -593,10 +593,7 @@ public class RoutingEngine extends Thread
if ( wp != null ) wp.radius = 1e-5;
OsmPath testPath = new OsmPath( n1, startPath, link, null, guideTrack != null, routingContext );
testPath.setAirDistanceCostAdjustment( 0 );
return testPath;
return new OsmPath( n1, startPath, link, null, guideTrack != null, routingContext );
}
finally
{
@ -636,8 +633,6 @@ public class RoutingEngine extends Thread
OsmPath startPath1 = getStartPath( start1, start2, startWp, endWp, sameSegmentSearch );
OsmPath startPath2 = getStartPath( start2, start1, startWp, endWp, sameSegmentSearch );
int maxAdjCostFromQueue = 0;
// check for an INITIAL match with the cost-cutting-track
if ( costCuttingTrack != null )
{
@ -655,8 +650,8 @@ public class RoutingEngine extends Thread
synchronized( openSet )
{
openSet.clear();
if ( startPath1.cost >= 0 ) openSet.add( startPath1 );
if ( startPath2.cost >= 0 ) openSet.add( startPath2 );
addToOpenset( startPath1 );
addToOpenset( startPath2 );
}
while(!terminated)
{
@ -671,16 +666,10 @@ public class RoutingEngine extends Thread
OsmPath path = null;
synchronized( openSet )
{
if ( openSet.size() == 0 ) break;
path = openSet.first();
openSet.remove( path );
path = openSet.popLowestKeyValue();
}
if ( path.adjustedCost < maxAdjCostFromQueue && airDistanceCostFactor == 0.)
{
throw new RuntimeException( "assertion failed: path.adjustedCost < maxAdjCostFromQueue: " + path.adjustedCost + "<" + maxAdjCostFromQueue );
}
maxAdjCostFromQueue = path.adjustedCost;
if ( path == null ) break;
if ( path.airdistance == -1 ) continue;
if ( matchPath != null && fastPartialRecalc && firstMatchCost < 500 && path.cost > 30L*firstMatchCost )
{
@ -740,8 +729,7 @@ public class RoutingEngine extends Thread
}
// recheck cutoff before doing expensive stuff
int airDistance2 = currentNode.calcDistance( endPos );
if ( path.cost + airDistance2 > maxTotalCost + 10 )
if ( path.cost + path.airdistance > maxTotalCost + 10 )
{
continue;
}
@ -824,18 +812,14 @@ public class RoutingEngine extends Thread
}
if ( otherPath != path )
{
synchronized( openSet )
{
openSet.remove( otherPath );
}
otherPath.airdistance = -1; // invalidate the entry in the open set
}
}
if ( bestPath != null )
{
int airDistance = isFinalLink ? 0 : nextNode.calcDistance( endPos );
bestPath.setAirDistanceCostAdjustment( (int)( airDistance * airDistanceCostFactor ) );
bestPath.airdistance = isFinalLink ? 0 : nextNode.calcDistance( endPos );
if ( isFinalLink || bestPath.cost + airDistance <= maxTotalCost + 10 )
if ( isFinalLink || bestPath.cost + bestPath.airdistance <= maxTotalCost + 10 )
{
// add only if this may beat an existing path for that link
OsmLinkHolder dominator = link.firstlinkholder;
@ -854,7 +838,7 @@ public class RoutingEngine extends Thread
link.addLinkHolder( bestPath );
synchronized( openSet )
{
openSet.add( bestPath );
addToOpenset( bestPath );
}
}
}
@ -869,6 +853,14 @@ public class RoutingEngine extends Thread
}
return null;
}
private void addToOpenset( OsmPath path )
{
if ( path.cost >= 0 )
{
openSet.add( path.cost + (int)(path.airdistance*airDistanceCostFactor), path );
}
}
private void preloadPosition( OsmNode n, int minRingWidth, int minCount )
{
@ -991,7 +983,16 @@ public class RoutingEngine extends Thread
{
synchronized( openSet )
{
return openSet.getExtract();
List<OsmPath> extract = openSet.getExtract();
int[] res = new int[extract.size() * 2];
int i = 0;
for( OsmPath p : extract )
{
OsmNode n = p.getLink().targetNode;
res[i++] = n.ilon;
res[i++] = n.ilat;
}
return res;
}
}
@ -1044,4 +1045,10 @@ public class RoutingEngine extends Thread
{
terminated = true;
}
public boolean isTerminated()
{
return terminated;
}
}

View file

@ -50,8 +50,12 @@ public final class BExpressionContext
// hash-cache for function results
private byte[][] _arrayBitmap;
private boolean[] _arrayInverse;
private int[] _arrayCrc;
private int currentHashBucket = -1;
private byte[] currentByteArray = null;
private boolean currentInverseDirection= false;
public List<BExpression> expressionList;
@ -104,7 +108,9 @@ public final class BExpressionContext
if ( Boolean.getBoolean( "disableExpressionCache" ) ) hashSize = 1;
_arrayBitmap = new byte[hashSize][];
_arrayBitmap = new byte[hashSize][];
_arrayInverse = new boolean[hashSize];
_arrayCrc = new int[hashSize];
_arrayCostfactor = new float[hashSize];
_arrayTurncost = new float[hashSize];
@ -163,7 +169,7 @@ public final class BExpressionContext
// crosscheck: decode and compare
int[] ld2 = new int[lookupValues.size()];
decode( ld2, ab );
decode( ld2, false, ab );
for( int inum = 0; inum < lookupValues.size(); inum++ ) // loop over lookup names
{
if ( ld2[inum] != ld[inum] ) throw new RuntimeException( "assertion failed encoding " + getKeyValueDescription(false, ab) );
@ -208,21 +214,21 @@ public final class BExpressionContext
*/
public void decode( byte[] ab )
{
decode( lookupData, ab );
decode( lookupData, false, ab );
lookupDataValid = true;
}
/**
* decode a byte-array into a lookup data array
*/
public void decode( int[] ld, byte[] ab )
private void decode( int[] ld, boolean inverseDirection, byte[] ab )
{
if ( !meta.readVarLength ) { decodeFix( ld, ab ); return; }
BitCoderContext ctx = new BitCoderContext(ab);
// start with first bit hardwired ("reversedirection")
ld[0] = ctx.decodeBit() ? 2 : 0;
ld[0] = inverseDirection ^ ctx.decodeBit() ? 2 : 0;
// all others are generic
int inum = 1;
@ -276,13 +282,10 @@ public final class BExpressionContext
public String getKeyValueDescription( boolean inverseDirection, byte[] ab )
{
int inverseBitByteIndex = meta.readVarLength ? 0 : 7;
int abLen = ab.length;
byte[] ab_copy = new byte[abLen];
System.arraycopy( ab, 0, ab_copy, 0 , abLen );
if ( inverseDirection ) ab_copy[inverseBitByteIndex] ^= 1;
// int abLen = ab.length;
StringBuilder sb = new StringBuilder( 200 );
decode( lookupData, ab_copy );
decode( lookupData, inverseDirection, ab );
for( int inum = 0; inum < lookupValues.size(); inum++ ) // loop over lookup names
{
BExpressionLookupValue[] va = lookupValues.get(inum);
@ -344,7 +347,9 @@ public final class BExpressionContext
}
}
public long requests;
public long requests2;
public long cachemisses;
/**
* evaluates the data in the given byte array
@ -353,55 +358,53 @@ public final class BExpressionContext
*/
public boolean evaluate( boolean inverseDirection, byte[] ab, BExpressionReceiver receiver )
{
requests ++;
lookupDataValid = false; // this is an assertion for a nasty pifall
int inverseBitByteIndex = meta.readVarLength ? 0 : 7;
int abLen = ab.length;
boolean equalsCurrent = currentHashBucket >= 0 && abLen == currentByteArray.length;
if ( equalsCurrent )
{
for( int i=0; i<abLen; i++ )
{
byte b = ab[i];
if ( i == inverseBitByteIndex && inverseDirection ) b ^= 1;
if ( b != currentByteArray[i] ) { equalsCurrent = false; break; }
}
}
int inverseBitByteIndex = meta.readVarLength ? 0 : 7;
if ( equalsCurrent )
{
return true;
}
else
{
// calc hash bucket from crc
int crc = Crc32.crc( abBuf, 0, abLen );
int hashSize = _arrayBitmap.length;
currentHashBucket = (crc & 0xfffffff) % hashSize;
currentByteArray = new byte[abLen];
System.arraycopy( ab, 0, currentByteArray, 0 , abLen );
if ( inverseDirection ) currentByteArray[inverseBitByteIndex] ^= 1;
}
boolean hashBucketEquals = false;
// calc hash bucket from crc
int lastHashBucket = currentHashBucket;
int crc = Crc32.crcWithInverseBit(ab, inverseDirection ? inverseBitByteIndex : -1 );
int hashSize = _arrayBitmap.length;
currentHashBucket = (crc & 0xfffffff) % hashSize;
currentByteArray = ab;
currentInverseDirection = inverseDirection;
byte[] abBucket = _arrayBitmap[currentHashBucket];
if ( abBucket != null && abBucket.length == abLen )
boolean inverseBucket = _arrayInverse[currentHashBucket];
if ( ab == abBucket && inverseBucket == inverseDirection ) // fast identity check
{
return lastHashBucket == currentHashBucket;
}
requests2++;
// compare input value to hash bucket content
boolean hashBucketEquals = false;
if ( crc == _arrayCrc[currentHashBucket] )
{
int abLen = ab.length;
if ( abBucket != null && abBucket.length == ab.length )
{
hashBucketEquals = true;
boolean isInverse = inverseDirection ^ inverseBucket;
for( int i=0; i<abLen; i++ )
{
if ( abBucket[i] != currentByteArray[i] ) { hashBucketEquals = false; break; }
byte b = ab[i];
if ( isInverse && i == inverseBitByteIndex ) b ^= 1;
if ( abBucket[i] != b ) { hashBucketEquals = false; break; }
}
}
}
if ( hashBucketEquals ) return false;
if ( hashBucketEquals ) return lastHashBucket == currentHashBucket;
cachemisses++;
_arrayBitmap[currentHashBucket] = currentByteArray;
_arrayInverse[currentHashBucket] = currentInverseDirection;
_arrayCrc[currentHashBucket] = crc;
_receiver = receiver;
decode( lookupData, currentByteArray );
decode( lookupData, currentInverseDirection, currentByteArray );
evaluate( lookupData );
_arrayCostfactor[currentHashBucket] = variableData[costfactorIdx];

View file

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="4"
android:versionName="1.0.2" package="btools.routingapp">
android:versionCode="5"
android:versionName="1.1" package="btools.routingapp">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".BRouterActivity"
android:label="@string/app_name"

View file

@ -536,7 +536,7 @@ private long startTime = 0L;
}
else
{
String result = "version = BRouter-1.0.4\n"
String result = "version = BRouter-1.1\n"
+ "distance = " + cr.getDistance()/1000. + " km\n"
+ "filtered ascend = " + cr.getAscend() + " m\n"
+ "plain ascend = " + cr.getPlainAscend();

View file

@ -2,19 +2,16 @@ package btools.server;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.StringTokenizer;
import java.util.TreeMap;
import btools.router.OsmNodeNamed;
import btools.router.OsmTrack;
@ -129,7 +126,7 @@ public class RouteServer extends Thread
public static void main(String[] args) throws Exception
{
System.out.println("BRouter 1.0.4 / 28092014");
System.out.println("BRouter 1.1 / 27122014");
if ( args.length != 5 )
{
System.out.println("serve BRouter protocol");

View file

@ -41,6 +41,7 @@ public class ServerHandler extends RequestHandler {
public RoutingContext readRoutingContext()
{
rc = new RoutingContext();
rc.serversizing = true;
String profile = params.get( "profile" );
// when custom profile replace prefix with directory path
@ -123,7 +124,7 @@ public class ServerHandler extends RequestHandler {
System.out.println("unknown track format '" + format + "', using default");
result = track.formatAsGpx();
}
return result;
}

View file

@ -30,6 +30,19 @@ public class Crc32
return crc;
}
public static int crcWithInverseBit( byte[] ab, int inverseBitByteIndex )
{
int crc = 0xFFFFFFFF;
int end = ab.length;
for( int j=0; j<end; j++ )
{
byte b = ab[j];
if ( j == inverseBitByteIndex ) b ^= 1;
crc = (crc >>> 8) ^ crctable[(crc ^ b) & 0xff];
}
return crc;
}
private static int[] crctable = {
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,

View file

@ -0,0 +1,258 @@
package btools.util;
import java.util.ArrayList;
import java.util.List;
/**
* Memory efficient Heap to get the lowest-key value
* of a set of key-object pairs
*
* @author ab
*/
public class SortedHeap<V>
{
private int[][] al;
private int[] pa;
private int[] lp; // the low pointers
private Object[][] vla; // value list array
protected static final int MAXLISTS = 31; // enough for size Integer.MAX_VALUE
private int size;
private boolean isClear = false;
public SortedHeap()
{
clear();
}
/**
* @return the lowest key value, or null if none
*/
public V popLowestKeyValue()
{
int minId = 0;
int minIdx = -1;
for ( int i=1;; i++ )
{
int[] ali = al[i];
if ( ali == null ) break;
int lpi = lp[i];
if ( lpi < ali.length )
{
int currentId = ali[lpi];
if ( minIdx < 0 || currentId < minId )
{
minIdx = i;
minId = currentId;
}
}
}
if ( minIdx == -1 ) return null;
int lp_minIdx = lp[minIdx]++;
Object[] vla_minIdx = vla[minIdx];
V res =(V)vla_minIdx[lp_minIdx];
vla_minIdx[lp_minIdx] = null;
size--;
return res;
}
/**
* 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 )
{
isClear = false;
size++;
// trivial shortcut if first array empty
if ( lp[1] == 1)
{
al[1][0] = key;
vla[1][0] = value;
lp[1] = 0;
return;
}
// trivial shortcut if second array empty
if ( lp[2] > 0 )
{
int[] al2 = al[2];
Object[] vla2 = vla[2];
int key1;
Object val1;
if ( lp[2] == 2 )
{
key1 = al[1][0];
val1 = vla[1][0];
lp[1] = 1;
}
else // == 1
{
key1 = al2[1];
val1 = vla2[1];
}
lp[2] = 0;
if ( key1 < key )
{
al2[0] = key1;
vla2[0] = val1;
al2[1] = key;
vla2[1] = value;
}
else
{
al2[1] = key1;
vla2[1] = val1;
al2[0] = key;
vla2[0] = value;
}
return;
}
// put the new entry in the first array
al[0][0] = key;
vla[0][0] = value;
pa[0] = 1;
pa[1] = 1;
pa[2] = 2;
// determine the first array big enough to take them all
int cnt = 4; // value count up to idx
int idx = 3;
int n = 4;
for(;;)
{
cnt += n-lp[idx];
if ( cnt <= n ) break;
pa[idx++] = n;
n <<= 1;
}
if ( idx == MAXLISTS )
{
throw new IllegalArgumentException( "overflow" );
}
// create it if not existant
if ( al[idx] == null )
{
al[idx] = new int[n];
vla[idx] = new Object[n];
}
int[] al_t = al[idx];
Object[] vla_t = vla[idx];
int lp_t = lp[idx];
// shift down content if any
if ( lp_t < n )
{
System.arraycopy(al_t, lp_t, al_t, 0, n-lp_t);
System.arraycopy(vla_t, lp_t, vla_t, 0, n-lp_t);
}
lp[idx] = 0;
pa[idx] = n - lp_t;
// now merge the contents of arrays 0...idx-1 into idx
while ( cnt > 0 )
{
int maxId = 0;
int maxIdx = -1;
for ( int i=0; i<=idx; i++ )
{
int p = pa[i];
if ( p > lp[i] )
{
int currentId = al[i][p-1];
if ( maxIdx < 0 || currentId > maxId )
{
maxIdx = i;
maxId = currentId;
}
}
}
// current maximum found, copy to target array
--n;
al[idx][n] = maxId;
vla[idx][n] = vla[maxIdx][pa[maxIdx]-1];
--cnt;
--pa[maxIdx];
}
lp[idx] = n;
while(--idx > 0) lp[idx] = al[idx].length;
}
public void clear()
{
if ( !isClear )
{
isClear = true;
size = 0;
// pointer array
pa = new int[MAXLISTS];
lp = new int[MAXLISTS];
// allocate key lists
al = new int[MAXLISTS][];
al[0] = new int[1]; // make the first arrays
al[1] = new int[1];
al[2] = new int[2];
// same for the values
vla = new Object[MAXLISTS][];
vla[0] = new Object[1];
vla[1] = new Object[1];
vla[2] = new Object[2];
int n = 1;
lp[0] = 0;
for( int idx=1; idx < MAXLISTS; idx++ )
{
lp[idx] = n;
n <<= 1;
}
}
}
public List<V> getExtract()
{
int div = size / 1000 + 1;
ArrayList<V> res = new ArrayList<V>(size / div );
int cnt = 0;
for ( int i=1;; i++ )
{
int[] ali = al[i];
if ( ali == null ) break;
int lpi = lp[i];
Object[] vlai = vla[i];
int n = ali.length;
while ( lpi < n )
{
if ( (++cnt) % div == 0 )
{
res.add( (V)vla[i][lpi] );
}
lpi++;
}
}
return res;
}
}

View file

@ -0,0 +1,65 @@
package btools.util;
import java.util.Random;
import java.util.HashMap;
import java.util.HashSet;
import org.junit.Assert;
import org.junit.Test;
public class SortedHeapTest
{
@Test
public void sortedHeapTest1()
{
SortedHeap<String> sh = new SortedHeap<String>();
Random rnd = new Random();
for( int i = 0; i< 100000; 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 );
Assert.assertTrue( "sorting test", val >= lastval );
lastval = val;
}
Assert.assertTrue( "total count test", cnt == 100000 );
}
@Test
public void sortedHeapTest2()
{
SortedHeap<String> sh = new SortedHeap<String>();
Random rnd = new Random();
for( int i = 0; i< 100000; i++ )
{
sh.add( i, "" + i );
}
int cnt = 0;
int expected = 0;
for(;;)
{
String s = sh.popLowestKeyValue();
if ( s == null ) break;
cnt ++;
int val = Integer.parseInt( s );
Assert.assertTrue( "sequence test", val == expected );
expected++;
}
Assert.assertTrue( "total count test", cnt == 100000 );
}
}