407 lines
14 KiB
C
407 lines
14 KiB
C
/* @license 2021 Neil Edelman, distributed under the terms of the
|
|
[MIT License](https://opensource.org/licenses/MIT).
|
|
|
|
@subtitle Recur trait
|
|
|
|
Included by `list.h`.
|
|
|
|
@param[LC_]
|
|
A one-argument macro producing a name that is responsible for the name of the
|
|
functions. The caller is responsible for undefining `LC_`.
|
|
|
|
@param[COMPARE_NAME, COMPARE_IS_EQUAL, COMPARE]
|
|
Optional unique name that satisfies `C` naming conventions when mangled,
|
|
and a function implementing, for `COMPARE_IS_EQUAL`,
|
|
<typedef:<PLC>bipredicate_fn> that establishes an equivalence relation, or
|
|
for `COMPARE`, <typedef:<PLC>compare_fn> that establishes a total
|
|
order. There can be multiple comparable functions, but only one can omit
|
|
`COMPARE_NAME`.
|
|
|
|
@std C89 */
|
|
|
|
#if !defined(LC_) || !(defined(LIST_IS_EQUAL) ^ defined(LIST_COMPARE)) \
|
|
|| !defined(L_) || !defined(PL_)
|
|
#error Unexpected preprocessor symbols.
|
|
#endif
|
|
|
|
#ifndef LIST_CODA_H /* <!-- idempotent */
|
|
#define LIST_CODA_H
|
|
#if defined(PLC_)
|
|
#error Unexpected defines.
|
|
#endif
|
|
#define PLC_(n) LIST_CAT(list_coda, LC_(n))
|
|
/* <fn:<PLC>boolean> operations bit-vector; dummy ensures closed. */
|
|
enum list_operation {
|
|
LIST_SUBTRACTION_AB = 1,
|
|
LIST_SUBTRACTION_BA = 2, RECURA,
|
|
LIST_INTERSECTION = 4, RECURB, RECURC, RECURD,
|
|
LIST_DEFAULT_A = 8, RECURE, RECURF, RECURG, RECURH, RECURI, RECURJ,
|
|
RECURK,
|
|
LIST_DEFAULT_B = 16, RECURL, RECURM, RECURN, RECURO, RECURP, RECURQ,
|
|
RECURR, RECURS, RECURT, RECURU, RECURV, RECURW, RECURX, RECURY, RECURZ
|
|
};
|
|
#endif /* idempotent --> */
|
|
|
|
/** Returns a boolean given two read-only <tag:<L>listlink>. */
|
|
typedef int (*PLC_(bipredicate_fn))(const struct L_(listlink) *,
|
|
const struct L_(listlink) *);
|
|
|
|
#ifdef LIST_COMPARE /* <!-- compare */
|
|
|
|
/** Three-way comparison on a totally order set of <tag:<L>listlink>;
|
|
returns an integer value less then, equal to, greater then zero, if
|
|
`a < b`, `a == b`, `a > b`, respectively. */
|
|
typedef int (*PLC_(compare_fn))(const struct L_(listlink) *a,
|
|
const struct L_(listlink) *b);
|
|
|
|
/* Check that `LIST_COMPARE` is a function implementing
|
|
<typedef:<PLC>compare_fn>. */
|
|
static const PLC_(compare_fn) PLC_(compare) = (LIST_COMPARE);
|
|
|
|
/** Lexicographically compares `alist` to `blist`. Null values are before
|
|
everything.
|
|
@return `a < b`: negative; `a == b`: zero; `a > b`: positive.
|
|
@implements <typedef:<PLC>compare_fn> (one can `qsort` an array of lists, as
|
|
long as one calls <fn:<L>list_self_correct> on it's elements)
|
|
@order \Theta(min(|`alist`|, |`blist`|)) @allow */
|
|
static int LC_(compare)(const struct L_(list) *const alist,
|
|
const struct L_(list) *const blist) {
|
|
struct L_(listlink) *a, *b;
|
|
int diff;
|
|
/* Null counts as `-\infty`. */
|
|
if(!alist) {
|
|
return blist ? -1 : 0;
|
|
} else if(!blist) {
|
|
return 1;
|
|
}
|
|
/* Compare element by element. */
|
|
for(a = alist->u.flat.next, b = blist->u.flat.next; ;
|
|
a = a->next, b = b->next) {
|
|
if(!a->next) {
|
|
return b->next ? -1 : 0;
|
|
} else if(!b->next) {
|
|
return 1;
|
|
} else if((diff = PLC_(compare)(a, b))) {
|
|
return diff;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Merges `from` into `to`, preferring elements from `to` go in the front.
|
|
@order \O(|`from`| + |`to`|). */
|
|
static void LC_(merge)(struct L_(list) *const to, struct L_(list) *const from) {
|
|
struct L_(listlink) *head, **x = &head, *prev = &to->u.as_head.head, *t, *f;
|
|
assert(to && to->u.flat.next && to->u.flat.prev
|
|
&& from && from->u.flat.next && from->u.flat.prev && from != to);
|
|
/* Empty. */
|
|
if(!(f = from->u.flat.next)->next) return;
|
|
if(!(t = to->u.flat.next)->next)
|
|
{ PL_(move)(from, &to->u.as_tail.tail); return; }
|
|
/* Exclude sentinel. */
|
|
from->u.flat.prev->next = to->u.flat.prev->next = 0;
|
|
/* Merge. */
|
|
for( ; ; ) {
|
|
if(PLC_(compare)(t, f) <= 0) {
|
|
t->prev = prev, prev = *x = t, x = &t->next;
|
|
if(!(t = t->next)) { *x = f; goto from_left; }
|
|
} else {
|
|
f->prev = prev, prev = *x = f, x = &f->next;
|
|
if(!(f = f->next)) { *x = t; break; }
|
|
}
|
|
}
|
|
if(0) {
|
|
from_left:
|
|
f->prev = prev;
|
|
/* Switch sentinels. */
|
|
f = from->u.flat.prev;
|
|
to->u.flat.prev = f;
|
|
f->next = &from->u.as_tail.tail;
|
|
} else {
|
|
t->prev = prev;
|
|
}
|
|
/* Erase `from`. */
|
|
from->u.flat.next = &from->u.as_tail.tail;
|
|
from->u.flat.prev = &from->u.as_head.head;
|
|
}
|
|
|
|
/** Merges the two top runs referenced by `head_ptr` in stack form. */
|
|
static void PLC_(merge_runs)(struct L_(listlink) **const head_ptr) {
|
|
struct L_(listlink) *head = *head_ptr, **x = &head, *b = head, *a = b->prev,
|
|
*const prev = a->prev;
|
|
assert(head_ptr && a && b);
|
|
for( ; ; ) {
|
|
if(PLC_(compare)(a, b) <= 0) {
|
|
*x = a, x = &a->next;
|
|
if(!(a = a->next)) { *x = b; break; }
|
|
} else {
|
|
*x = b, x = &b->next;
|
|
if(!(b = b->next)) { *x = a; break; }
|
|
}
|
|
}
|
|
head->prev = prev, *head_ptr = head; /* `prev` is the previous run. */
|
|
}
|
|
|
|
/** The list form of `list` is restored from `head` in stack form with two
|
|
runs. */
|
|
static void PLC_(merge_final)(struct L_(list) *const list,
|
|
struct L_(listlink) *head) {
|
|
struct L_(listlink) *prev = 0, **x = &list->u.flat.next,
|
|
*b = head, *a = head->prev;
|
|
assert(list && b && a && !a->prev);
|
|
for( ; ; ) {
|
|
if(PLC_(compare)(a, b) <= 0) {
|
|
a->prev = prev, prev = *x = a, x = &a->next;
|
|
if(!(a = a->next)) { a = *x = b; break; }
|
|
} else {
|
|
b->prev = prev, prev = *x = b, x = &b->next;
|
|
if(!(b = b->next)) { *x = a; break; }
|
|
}
|
|
}
|
|
do; while(a->prev = prev, prev = a, a = a->next);
|
|
prev->next = &list->u.as_tail.tail, list->u.flat.prev = prev;
|
|
/* Not empty. */
|
|
assert(list->u.flat.next && list->u.flat.next != &list->u.as_tail.tail);
|
|
list->u.flat.next->prev = &list->u.as_head.head;
|
|
}
|
|
|
|
/** Natural merge sort `list`; the requirement for \O(\log |`list`|) space is
|
|
satisfied by converting it to a singly-linked with `prev` as a stack of
|
|
increasing lists, which are merged. */
|
|
static void PLC_(sort)(struct L_(list) *const list) {
|
|
/* Add `[-1,0,1]`: unique identifier for nine weakly-ordered transitions. */
|
|
enum { DEC = 1, EQ = 4, INC = 7 };
|
|
int mono = EQ, cmp;
|
|
struct L_(listlink) *a, *b, *c, *dec_iso = /* Unused. */0;
|
|
struct { size_t count; struct L_(listlink) *head, *prev; } run;
|
|
/* Closed sentinel list. */
|
|
assert(list
|
|
&& list->u.flat.next && !list->u.flat.zero && list->u.flat.prev);
|
|
if(a = list->u.flat.next, !(b = a->next)) return; /* Empty. */
|
|
/* Identify runs of monotonicity until `b` sentinel. */
|
|
run.count = 0, run.prev = 0, run.head = a;
|
|
for(c = b->next; c; a = b, b = c, c = c->next) {
|
|
cmp = PLC_(compare)(b, a);
|
|
switch(mono + (0 < cmp) - (cmp < 0)) {
|
|
/* Valley and mountain inflection. */
|
|
case INC - 1: a->next = 0; /* _Sic_. */
|
|
case DEC + 1: break;
|
|
/* Decreasing more and levelled off from decreasing. */
|
|
case DEC - 1: b->next = dec_iso; dec_iso = run.head = b; continue;
|
|
case DEC + 0: b->next = a->next; a->next = b; continue;
|
|
/* Turning down and up. */
|
|
case EQ - 1: a->next = 0; b->next = run.head; dec_iso = run.head = b;
|
|
mono = DEC; continue;
|
|
case EQ + 1: mono = INC; continue;
|
|
case EQ + 0: /* Same. _Sic_. */
|
|
case INC + 0: /* Levelled off from increasing. _Sic_. */
|
|
case INC + 1: continue; /* Increasing more. */
|
|
}
|
|
/* Binary carry sequence, <https://oeis.org/A007814>, one delayed so
|
|
always room for final merge. */
|
|
if(run.count) {
|
|
size_t rc;
|
|
for(rc = run.count - 1; rc & 1; rc >>= 1)
|
|
PLC_(merge_runs)(&run.prev);
|
|
}
|
|
/* Add to runs, advance; `b` becomes `a` forthwith. */
|
|
run.head->prev = run.prev, run.prev = run.head, run.count++;
|
|
run.head = b, mono = EQ;
|
|
}
|
|
/* Last run; go into an accepting state. */
|
|
if(mono != DEC) {
|
|
if(!run.count) return; /* Sorted already. */
|
|
a->next = 0; /* Last one of increasing or equal. */
|
|
} else { /* Decreasing. */
|
|
assert(dec_iso);
|
|
run.head = dec_iso;
|
|
if(!run.count) { /* Restore the pointers without having two runs. */
|
|
list->u.flat.next = dec_iso, dec_iso->prev = &list->u.as_head.head;
|
|
for(a = dec_iso, b = a->next; b; a = b, b = b->next) b->prev = a;
|
|
list->u.flat.prev = a, a->next = &list->u.as_tail.tail;
|
|
return; /* Reverse sorted; now good as well. */
|
|
}
|
|
}
|
|
assert(run.count);
|
|
/* (Actually slower to merge the last one eagerly. So do nothing.) */
|
|
run.head->prev = run.prev, run.count++;
|
|
/* Merge leftovers from the other direction, saving one for final. */
|
|
while(run.head->prev->prev) PLC_(merge_runs)(&run.head);
|
|
PLC_(merge_final)(list, run.head);
|
|
}
|
|
|
|
/** Performs a stable, adaptive sort of `list` according to `compare`.
|
|
@order \Omega(|`list`|), \O(|`list`| log |`list`|) @allow */
|
|
static void LC_(sort)(struct L_(list) *const list) { PLC_(sort)(list); }
|
|
|
|
/** Private: `alist` `mask` `blist` -> `result`. Prefers `a` to `b` when equal.
|
|
Either could be null.
|
|
@order \O(|`a`| + |`b`|) */
|
|
static void PLC_(boolean)(struct L_(list) *const alist,
|
|
struct L_(list) *const blist,
|
|
const enum list_operation mask, struct L_(list) *const result) {
|
|
struct L_(listlink) *temp,
|
|
*a = alist ? alist->u.flat.next : 0,
|
|
*b = blist ? blist->u.flat.next : 0;
|
|
int comp;
|
|
assert((!result || (result != alist && result != blist))
|
|
&& (!alist || (alist != blist)));
|
|
if(a && b) {
|
|
while(a->next && b->next) {
|
|
comp = PLC_(compare)(a, b);
|
|
if(comp < 0) {
|
|
temp = a, a = a->next;
|
|
if(mask & LIST_SUBTRACTION_AB) {
|
|
PL_(remove)(temp);
|
|
if(result) PL_(push)(result, temp);
|
|
}
|
|
} else if(comp > 0) {
|
|
temp = b, b = b->next;
|
|
if(mask & LIST_SUBTRACTION_BA) {
|
|
PL_(remove)(temp);
|
|
if(result) PL_(push)(result, temp);
|
|
}
|
|
} else {
|
|
temp = a, a = a->next, b = b->next;
|
|
if(mask & LIST_INTERSECTION) {
|
|
PL_(remove)(temp);
|
|
if(result) PL_(push)(result, temp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(a && mask & LIST_DEFAULT_A) {
|
|
while((temp = a, a = a->next)) {
|
|
PL_(remove)(temp);
|
|
if(result) PL_(push)(result, temp);
|
|
}
|
|
}
|
|
if(b && mask & LIST_DEFAULT_B) {
|
|
while((temp = b, b = b->next)) {
|
|
PL_(remove)(temp);
|
|
if(result) PL_(push)(result, temp);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Subtracts `a` from `b`, as sequential sorted individual elements, and moves
|
|
it to `result`. All elements are removed from `a`. All parameters must be
|
|
unique or can be null.
|
|
|
|
For example, if `a` contains `(A, B, D)` and `b` contains `(B, C)` then
|
|
`(a:A, a:D)` would be moved to `result`.
|
|
@order \O(|`a`| + |`b`|) @allow */
|
|
static void LC_(subtraction_to)(struct L_(list) *const a,
|
|
struct L_(list) *const b, struct L_(list) *const result) {
|
|
PLC_(boolean)(a, b, LIST_SUBTRACTION_AB | LIST_DEFAULT_A, result);
|
|
}
|
|
|
|
/** Moves the union of `a` and `b` as sequential sorted individual elements to
|
|
`result`. Equal elements are moved from `a`. All parameters must be unique or
|
|
can be null.
|
|
|
|
For example, if `a` contains `(A, B, D)` and `b` contains `(B, C)` then
|
|
`(a:A, a:B, b:C, a:D)` would be moved to `result`.
|
|
@order \O(|`a`| + |`b`|) @allow */
|
|
static void LC_(union_to)(struct L_(list) *const a,
|
|
struct L_(list) *const b, struct L_(list) *const result) {
|
|
PLC_(boolean)(a, b, LIST_SUBTRACTION_AB | LIST_SUBTRACTION_BA
|
|
| LIST_INTERSECTION | LIST_DEFAULT_A | LIST_DEFAULT_B, result);
|
|
}
|
|
|
|
/** Moves the intersection of `a` and `b` as sequential sorted individual
|
|
elements to `result`. Equal elements are moved from `a`. All parameters must
|
|
be unique or can be null.
|
|
|
|
For example, if `a` contains `(A, B, D)` and `b` contains `(B, C)` then
|
|
`(a:B)` would be moved to `result`.
|
|
@order \O(|`a`| + |`b`|) @allow */
|
|
static void LC_(intersection_to)(struct L_(list) *const a,
|
|
struct L_(list) *const b, struct L_(list) *const result) {
|
|
PLC_(boolean)(a, b, LIST_INTERSECTION, result);
|
|
}
|
|
|
|
/** Moves `a` exclusive-or `b` as sequential sorted individual elements to
|
|
`result`. Equal elements are moved from `a`. All parameters must be unique or
|
|
can be null.
|
|
|
|
For example, if `a` contains `(A, B, D)` and `b` contains `(B, C)` then
|
|
`(a:A, b:C, a:D)` would be moved to `result`.
|
|
@order O(|`a`| + |`b`|) @allow */
|
|
static void LC_(xor_to)(struct L_(list) *const a, struct L_(list) *const b,
|
|
struct L_(list) *const result) {
|
|
PLC_(boolean)(a, b, LIST_SUBTRACTION_AB | LIST_SUBTRACTION_BA
|
|
| LIST_DEFAULT_A | LIST_DEFAULT_B, result);
|
|
}
|
|
|
|
/** !compare(`a`, `b`) == equals(`a`, `b`).
|
|
@implements <typedef:<PLC>bipredicate_fn> */
|
|
static int PLC_(is_equal)(const struct L_(listlink) *const a,
|
|
const struct L_(listlink) *const b) { return !PLC_(compare)(a, b); }
|
|
|
|
#else /* compare --><!-- is equal */
|
|
|
|
/* Check that `LIST_IS_EQUAL` is a function implementing
|
|
<typedef:<PLC>bipredicate_fn>. */
|
|
static const PLC_(bipredicate_fn) PLC_(is_equal) = (LIST_IS_EQUAL);
|
|
|
|
#endif /* is equal --> */
|
|
|
|
/** @return If `lista` piecewise equals `listb`, which both can be null.
|
|
@order \O(min(|`lista`|, |`listb`|)) @allow */
|
|
static int LC_(is_equal)(const struct L_(list) *const lista,
|
|
const struct L_(list) *const listb) {
|
|
const struct L_(listlink) *a, *b;
|
|
if(!lista) return !listb;
|
|
if(!listb) return 0;
|
|
for(a = lista->u.flat.next, b = listb->u.flat.next; ;
|
|
a = a->next, b = b->next) {
|
|
if(!a->next) return !b->next;
|
|
if(!b->next) return 0;
|
|
if(!PLC_(is_equal)(a, b)) return 0;
|
|
}
|
|
}
|
|
|
|
/** Moves all local-duplicates of `from` to the end of `to`.
|
|
|
|
For example, if `from` is `(A, B, B, A)`, it would concatenate the second
|
|
`(B)` to `to` and leave `(A, B, A)` in `from`. If one <fn:<LC>sort> `from`
|
|
first, `(A, A, B, B)`, the global duplicates will be transferred, `(A, B)`.
|
|
@order \O(|`from`|) @allow */
|
|
static void LC_(duplicates_to)(struct L_(list) *const from,
|
|
struct L_(list) *const to) {
|
|
struct L_(listlink) *a = from->u.flat.next, *b, *temp;
|
|
assert(from);
|
|
if(!(b = a->next)) return;
|
|
while(b->next) {
|
|
if(!PLC_(is_equal)(a, b)) {
|
|
a = b, b = b->next;
|
|
} else {
|
|
temp = b, b = b->next;
|
|
PL_(remove)(temp);
|
|
if(to) PL_(push)(to, temp);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void PLC_(unused_coda_coda)(void);
|
|
static void PLC_(unused_coda)(void) {
|
|
#ifdef LIST_COMPARE /* <!-- compare */
|
|
LC_(compare)(0, 0); LC_(merge)(0, 0); LC_(sort)(0);
|
|
LC_(subtraction_to)(0, 0, 0); LC_(union_to)(0, 0, 0);
|
|
LC_(intersection_to)(0, 0, 0); LC_(xor_to)(0, 0, 0);
|
|
#endif /* compare --> */
|
|
LC_(is_equal)(0, 0); LC_(duplicates_to)(0, 0);
|
|
PLC_(unused_coda_coda)();
|
|
}
|
|
static void PLC_(unused_coda_coda)(void) { PLC_(unused_coda)(); }
|
|
|
|
#ifdef BOX_COMPARE
|
|
#undef BOX_COMPARE
|
|
#endif
|
|
#ifdef BOX_IS_EQUAL
|
|
#undef BOX_IS_EQUAL
|
|
#endif
|
|
#ifdef BOX_COMPARE_NAME
|
|
#undef BOX_COMPARE_NAME
|
|
#endif
|