#include <stdlib.h>
#include <memory.h>
// these two values are stored together
// to improve processor cache hits
typedef struct {
unsigned prefix, offset;
} KeyPrefix;
// construct an offset/key prefix
// for qsort to use
KeyPrefix *Keys;
unsigned *Rank;
#define WORK_blk 512
typedef struct {
unsigned start, size;
} WorkUnit;
typedef struct {
void *prev;
unsigned top;
WorkUnit unit[WORK_blk];
} WorkGrp;
WorkGrp *Work;
// set the sort key from the ranking of the offsets
void bwtkeygroup (unsigned from, unsigned cnt, unsigned offset)
{
unsigned off;
while( cnt-- ) {
off = Keys[from].offset + offset;
Keys[from++].prefix = Rank[off];
}
}
// set the offset rankings, create
// new work for unsorted groups
void bwtsetranks (unsigned from, unsigned cnt)
{
unsigned idx = 0;
WorkGrp *work;
// all members of a group get the same rank
while( idx < cnt )
Rank[Keys[from+idx++].offset] = from;
// is this a new group?
if( cnt < 2 )
return; // final ranking set
if( Work->top == WORK_blk )
work = Work, Work = malloc (sizeof(WorkGrp)), Work->prev = work, Work->top = 0;
// add this group to work units for next round
Work->unit[Work->top].start = from;
Work->unit[Work->top++].size = cnt;
}
// the standard qsort partitioning
// creates two sets of pivot valued
// elements from [0:leq] and [heq:size]
// while partitioning a segment of the Keys
void bwtpartition (unsigned start, unsigned size)
{
KeyPrefix tmp, pvt, *lo;
unsigned loguy, higuy;
unsigned leq, heq;
while( size > 7 ) {
// find median-of-three element to use as a pivot
// and swap it to the beginning of the array
// to begin the leq group of pivot equals.
// the larger-of-three element goes to higuy
// the smallest-of-three element goes to middle
lo = Keys + start;
higuy = size - 1;
leq = loguy = 0;
// move larger of lo and hi to tmp,hi
tmp = lo[higuy];
if( tmp.prefix < lo->prefix )
lo[higuy] = *lo, *lo = tmp, tmp = lo[higuy];
// move larger of tmp,hi and mid to hi
if( lo[size >> 1].prefix > tmp.prefix )
lo[higuy] = lo[size >> 1], lo[size >> 1] = tmp;
// move larger of mid and lo to pvt,lo
// and the smaller into the middle
pvt = *lo;
if( pvt.prefix < lo[size >> 1].prefix )
*lo = lo[size >> 1], lo[size >> 1] = pvt, pvt = *lo;
// start the high group of equals
// with a pivot valued element, or not
if( pvt.prefix == lo[higuy].prefix )
heq = higuy;
else
heq = size;
for( ; ; ) {
// both higuy and loguy are already in position
// loguy leaves .le. elements beneath it
// and swaps equal to pvt elements to leq
while( ++loguy < higuy )
if( pvt.prefix < lo[loguy].prefix )
break;
else if( pvt.prefix == lo[loguy].prefix )
if( ++leq < loguy )
tmp = lo[loguy], lo[loguy] = lo[leq], lo[leq] = tmp;
// higuy leaves .ge. elements above it
// and swaps equal to pvt elements to heq
while( --higuy > loguy )
if( pvt.prefix > lo[higuy].prefix )
break;
else if( pvt.prefix == lo[higuy].prefix )
if( --heq > higuy )
tmp = lo[higuy], lo[higuy] = lo[heq], lo[heq] = tmp;
// quit when they finally meet at the empty middle
if( higuy <= loguy )
break;
// element loguy is .gt. element higuy
// swap them around (the pivot)
tmp = lo[higuy];
lo[higuy] = lo[loguy];
lo[loguy] = tmp;
}
// create an empty middle
higuy = loguy;
// swap the group of pivot equals into the middle from
// the leq and heq sets. Include original pivot in
// the leq set. higuy will be the lowest pivot
// element; loguy will be one past the highest.
// the heq set might be empty or completely full.
if( loguy < heq )
while( heq < size )
tmp = lo[loguy], lo[loguy++] = lo[heq], lo[heq++] = tmp;
else
loguy = size; // no high elements, they're all pvt valued
// the leq set always has the original pivot, but might
// also be completely full of pivot valued elements.
if( higuy > ++leq )
while( leq )
tmp = lo[--higuy], lo[higuy] = lo[--leq], lo[leq] = tmp;
else
higuy = 0; // no low elements, they're all pvt valued
// The partitioning around pvt is done.
// ranges [0:higuy-1] .lt. pivot and [loguy:size-1] .gt. pivot
// set the new group rank of the middle range [higuy:loguy-1]
// (the .lt. and .gt. ranges get set during their selection sorts)
bwtsetranks (start + higuy, loguy - higuy);
// pick the smaller group to partition first,
// then loop with larger group.
if( higuy < size - loguy ) {
bwtpartition (start, higuy);
size -= loguy;
start += loguy;
} else {
bwtpartition (start + loguy, size - loguy);
size = higuy;
}
}
// do a selection sort for small sets by
// repeately selecting the smallest key to
// start, and pulling a leq group together
// for it
while( size ) {
for( leq = loguy = 0; ++loguy < size; )
if( Keys[start].prefix > Keys[start + loguy].prefix )
tmp = Keys[start], Keys[start] = Keys[start + loguy], Keys[start + loguy] = tmp, leq = 0;
else if( Keys[start].prefix == Keys[start + loguy].prefix )
if( ++leq < loguy )
tmp = Keys[start + leq], Keys[start + leq] = Keys[start + loguy], Keys[start + loguy] = tmp;
// now set the rank for the group
bwtsetranks (start, ++leq);
start += leq;
size -= leq;
}
}
// the main entry point
KeyPrefix* bwtsort (unsigned char *buff, unsigned size)
{
unsigned offset = 0, off;
WorkGrp *work, *next;
unsigned prefix[1];
// the Key and Rank arrays include stopper elements
Keys = malloc (size * sizeof(KeyPrefix));
memset (prefix, 0xff, sizeof(prefix));
// construct the suffix sorting key for each offset
for( off = size; off--; ) {
*prefix >>= 8;
*prefix |= buff[off] << sizeof(prefix) * 8 - 8;
Keys[off].prefix = *prefix;
Keys[off].offset = off;
}
// the ranking of each suffix offset,
// plus extra ranks for the stopper elements
Rank = malloc ((size + sizeof(prefix)) * sizeof(unsigned));
// fill in the extra stopper ranks
for( off = 0; off < sizeof(prefix); off++ )
Rank[size + off] = size + off;
// the queue of work to do on each pass
// starts with the initial sorting job
work = malloc (sizeof(WorkGrp));
work->unit[0].size = size;
work->unit[0].start = 0;
work->prev = NULL;
work->top = 1;
// continue until there are no undifferentiated suffix groups
// created during a sorting pass
while( work->top ) {
Work = malloc (sizeof(WorkGrp));
Work->prev = NULL;
Work->top = 0;
// consume the work units created last round
// while preparing new work for next pass
// (units are created in bwtsetranks)
while( next = work ) {
for( off = 0; off < work->top; off++ ) {
if( offset )
bwtkeygroup (next->unit[off].start, next->unit[off].size, offset);
bwtpartition (next->unit[off].start, next->unit[off].size);
}
work = work->prev;
free (next);
}
work = Work;
// each pass doubles the range of suffix considered
// thus achieving Order(n * log(n)) comparisons
// the first pass used prefix keys constructed above,
// subsequent passes use the offset rankings as keys
if( offset )
offset <<= 1;
else
offset = sizeof(prefix);
}
free (work);
free (Rank);
return Keys;
}
#ifdef SORTSTANDALONE
int main (int argc, char **argv)
{
unsigned size, nxt;
unsigned char *buff;
KeyPrefix *keys;
FILE *in, *out;
in = fopen(argv[1], "rb");
out = fopen (argv[2], "wb");
fseek(in, 0, 2);
size = ftell(in);
fseek (in, 0, 0);
buff = malloc (size);
for( nxt = 0; nxt < size; nxt++ )
buff[nxt] = getc(in);
keys = bwtsort (buff, size);
for( nxt = 0; nxt < size; nxt++ )
putc(buff[keys[nxt].offset], out);
}
#endif