blob: 101e0f0e4196970d4357c261af2f1b181a3c1fb3 [file] [log] [blame]
/*
* sort_list: a stable sort for lists.
*
* Time complexity: O(n*log n)
* [assuming limited zero-element fragments]
*
* Space complexity: O(1).
*
* Stable: yes.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "lib.h"
#include "allocate.h"
#undef PARANOIA
#undef COVERAGE
#ifdef PARANOIA
#include <assert.h>
#else
#define assert(x)
#endif
#ifdef COVERAGE
static unsigned char been_there[256];
#define BEEN_THERE(_c) \
do { \
if (!been_there[_c]) { \
been_there[_c] = 1; \
printf ("Been there: %c\n", _c); \
} \
} while (0)
#else
#define BEEN_THERE(_c) do { } while (0)
#endif
// Sort one fragment. LIST_NODE_NR (==29) is a bit too high for my
// taste for something this simple. But, hey, it's O(1).
//
// I would use libc qsort for this, but its comparison function
// gets a pointer indirection extra.
static void array_sort(void **ptr, int nr, int (*cmp)(const void *, const void *))
{
int i;
for (i = 1; i < nr; i++) {
void *p = ptr[i];
if (cmp(ptr[i-1],p) > 0) {
int j = i;
do {
ptr[j] = ptr[j-1];
if (!--j)
break;
} while (cmp(ptr[j-1], p) > 0);
ptr[j] = p;
}
}
}
#ifdef PARANOIA
static void verify_seq_sorted (struct ptr_list *l, int n,
int (*cmp)(const void *, const void *))
{
int i = 0;
const void *a;
struct ptr_list *head = l;
while (l->nr == 0) {
l = l->next;
if (--n == 0)
return;
assert (l != head);
}
a = l->list[0];
while (n > 0) {
const void *b;
if (++i >= l->nr) {
i = 0;
l = l->next;
n--;
assert (l != head || n == 0);
continue;
}
b = l->list[i];
assert (cmp (a, b) <= 0);
a = b;
}
}
#endif
#define FLUSH_TO(b) \
do { \
int nr = (b)->nr; \
assert (nbuf >= nr); \
memcpy ((b)->list, buffer, nr * sizeof (void *)); \
nbuf -= nr; \
memmove (buffer, buffer + nr, nbuf * sizeof (void *)); \
} while (0)
#define DUMP_TO(b) \
do { \
assert (nbuf <= (b)->nr); \
memcpy ((b)->list, buffer, nbuf * sizeof (void *)); \
} while (0)
// Merge two already-sorted sequences of blocks:
// (b1_1, ..., b1_n) and (b2_1, ..., b2_m)
// Since we may be moving blocks around, we return the new head
// of the merged list.
static struct ptr_list *
merge_block_seqs (struct ptr_list *b1, int n,
struct ptr_list *b2, int m,
int (*cmp)(const void *, const void *))
{
int i1 = 0, i2 = 0;
const void *buffer[2 * LIST_NODE_NR];
int nbuf = 0;
struct ptr_list *newhead = b1;
// printf ("Merging %d blocks at %p with %d blocks at %p\n", n, b1, m, b2);
// Skip empty blocks in b2.
while (b2->nr == 0) {
BEEN_THERE('F');
b2 = b2->next;
if (--m == 0) {
BEEN_THERE('G');
return newhead;
}
}
// Do a quick skip in case entire blocks from b1 are
// already less than smallest element in b2.
while (b1->nr == 0 ||
cmp (PTR_ENTRY_NOTAG(b1, b1->nr - 1), PTR_ENTRY_NOTAG(b2,0)) < 0) {
// printf ("Skipping whole block.\n");
BEEN_THERE('H');
b1 = b1->next;
if (--n == 0) {
BEEN_THERE('I');
return newhead;
}
}
while (1) {
const void *d1 = PTR_ENTRY_NOTAG(b1,i1);
const void *d2 = PTR_ENTRY_NOTAG(b2,i2);
assert (i1 >= 0 && i1 < b1->nr);
assert (i2 >= 0 && i2 < b2->nr);
assert (b1 != b2);
assert (n > 0);
assert (m > 0);
if (cmp (d1, d2) <= 0) {
BEEN_THERE('J');
buffer[nbuf++] = d1;
// Element from b1 is smaller
if (++i1 >= b1->nr) {
BEEN_THERE('L');
FLUSH_TO(b1);
do {
b1 = b1->next;
if (--n == 0) {
BEEN_THERE('O');
while (b1 != b2) {
BEEN_THERE('P');
FLUSH_TO(b1);
b1 = b1->next;
}
assert (nbuf == i2);
DUMP_TO(b2);
return newhead;
}
} while (b1->nr == 0);
i1 = 0;
}
} else {
BEEN_THERE('K');
// Element from b2 is smaller
buffer[nbuf++] = d2;
if (++i2 >= b2->nr) {
struct ptr_list *l = b2;
BEEN_THERE('M');
// OK, we finished with b2. Pull it out
// and plug it in before b1.
b2 = b2->next;
b2->prev = l->prev;
b2->prev->next = b2;
l->next = b1;
l->prev = b1->prev;
l->next->prev = l;
l->prev->next = l;
if (b1 == newhead) {
BEEN_THERE('N');
newhead = l;
}
FLUSH_TO(l);
b2 = b2->prev;
do {
b2 = b2->next;
if (--m == 0) {
BEEN_THERE('Q');
assert (nbuf == i1);
DUMP_TO(b1);
return newhead;
}
} while (b2->nr == 0);
i2 = 0;
}
}
}
}
void sort_list(struct ptr_list **plist, int (*cmp)(const void *, const void *))
{
struct ptr_list *head = *plist, *list = head;
int blocks = 1;
if (!head)
return;
// Sort all the sub-lists
do {
array_sort(list->list, list->nr, cmp);
#ifdef PARANOIA
verify_seq_sorted (list, 1, cmp);
#endif
list = list->next;
} while (list != head);
// Merge the damn things together
while (1) {
struct ptr_list *block1 = head;
do {
struct ptr_list *block2 = block1;
struct ptr_list *next, *newhead;
int i;
for (i = 0; i < blocks; i++) {
block2 = block2->next;
if (block2 == head) {
if (block1 == head) {
BEEN_THERE('A');
*plist = head;
return;
}
BEEN_THERE('B');
goto next_pass;
}
}
next = block2;
for (i = 0; i < blocks; ) {
next = next->next;
i++;
if (next == head) {
BEEN_THERE('C');
break;
}
BEEN_THERE('D');
}
newhead = merge_block_seqs (block1, blocks,
block2, i,
cmp);
#ifdef PARANOIA
verify_seq_sorted (newhead, blocks + i, cmp);
#endif
if (block1 == head) {
BEEN_THERE('E');
head = newhead;
}
block1 = next;
} while (block1 != head);
next_pass:
blocks <<= 1;
}
}