/* * 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 #include #include #include "lib.h" #include "allocate.h" #undef PARANOIA #undef COVERAGE #ifdef PARANOIA #include #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; } }