Files
c3c/lib/std/sort/quicksort.c3
Christoffer Lerno cdabe8fd9e - Create optional with ~ instead of ?. return io::EOF?; becomes return io::EOF~.
- Deprecated use of `?` to create optional.
2026-01-20 16:10:28 +01:00

163 lines
5.1 KiB
Plaintext

module std::sort;
import std::sort::qs;
<*
Sort list using the quick sort algorithm.
@require @list_is_by_ref(list) : "Expected a list passed by reference or be a slice"
@require @is_sortable(list) : "The list must be indexable and support .len or .len()"
@require @is_valid_cmp_fn(cmp, list, context) : "Expected a comparison function which compares values"
@require @is_valid_context(cmp, context) : "Expected a valid context"
*>
macro void quicksort(list, cmp = EMPTY_MACRO_SLOT, context = EMPTY_MACRO_SLOT) @builtin
{
$if $kindof(list) == SLICE:
qs::qsort{$typeof(list), $typeof(cmp), $typeof(context)}(list, 0, (isz)list.len - 1, cmp, context);
$else
qs::qsort{$typeof(*list), $typeof(cmp), $typeof(context)}(list, 0, (isz)lengthof(*list) - 1, cmp, context);
$endif
}
<*
Select the (k+1)th smallest element in an unordered list using Hoare's
selection algorithm (Quickselect). k should be between 0 and len-1. The data
list will be partially sorted.
@require @list_is_by_ref(list) : "Expected a list passed by reference or be a slice"
@require @is_sortable(list) : "The list must be indexable and support .len or .len()"
@require @is_valid_cmp_fn(cmp, list, context) : "expected a comparison function which compares values"
@require @is_valid_context(cmp, context) : "Expected a valid context"
*>
macro quickselect(list, isz k, cmp = EMPTY_MACRO_SLOT, context = EMPTY_MACRO_SLOT) @builtin
{
$if $kindof(list) == SLICE:
return qs::qselect{$typeof(list), $typeof(cmp), $typeof(context)}(list, 0, (isz)list.len - 1, k, cmp, context);
$else
return qs::qselect{$typeof(*list), $typeof(cmp), $typeof(context)}(list, 0, (isz)lengthof(*list) - 1, k, cmp, context);
$endif
}
module std::sort::qs <Type, CmpFn, Context>;
alias ElementType = $typeof(((Type){})[0]);
const bool IS_SLICE = Type.kindof == SLICE;
alias ListType = $typefrom(IS_SLICE ??? Type : Type*);
macro list_get(ListType l, i) @if(!IS_SLICE) => (*l)[i];
macro list_get(ListType l, i) @if(IS_SLICE) => l[i];
macro list_get_ref(ListType l, i) @if(!IS_SLICE) => &(*l)[i];
macro list_get_ref(ListType l, i) @if(IS_SLICE) => &l[i];
macro list_set(ListType l, i, v) @if(!IS_SLICE) => (*l)[i] = v;
macro list_set(ListType l, i, v) @if(IS_SLICE) => l[i] = v;
struct StackElementItem @private
{
isz low;
isz high;
}
alias Stack @private = StackElementItem[64];
// Based on https://alienryderflex.com/quicksort by Darel Rex Finley, Public Domain.
fn void qsort(ListType list, isz low, isz high, CmpFn cmp, Context context)
{
if (low >= 0 && high >= 0 && low < high)
{
Stack stack;
stack[0].low = low;
stack[0].high = high;
isz i;
isz l;
isz h;
while (i >= 0)
{
l = stack[i].low;
h = stack[i].high;
if (l < h)
{
l = @partition(list, l, h, cmp, context);
stack[i + 1].low = l + 1;
stack[i + 1].high = stack[i].high;
stack[i++].high = l;
if (stack[i].high - stack[i].low > stack[i - 1].high - stack[i - 1].low)
{
@swap(stack[i], stack[i - 1]);
}
}
else
{
i--;
}
}
}
}
<*
@require low <= k : "kth smallest element is smaller than lower bounds"
@require k <= high : "kth smallest element is larger than upper bounds"
*>
fn ElementType? qselect(ListType list, isz low, isz high, isz k, CmpFn cmp, Context context)
{
if (low >= 0 && high >= 0 && low < high)
{
isz l = low;
isz h = high;
isz pivot;
usz max_retries = 64;
while (l <= h && max_retries--)
{
pivot = @partition(list, l, h, cmp, context);
if (k == pivot) return list_get(list, k);
if (k < pivot)
{
h = pivot - 1;
}
else
{
l = pivot + 1;
}
}
}
return NOT_FOUND~;
}
macro isz @partition(ListType list, isz l, isz h, CmpFn cmp, Context context)
{
var $has_cmp = @is_valid_macro_slot(cmp);
var $has_context = @is_valid_macro_slot(context);
var $cmp_by_value = $has_cmp &&& $defined($typefrom(CmpFn.paramsof[0].type) v = list_get(list, 0));
ElementType pivot = list_get(list, l);
while (l < h)
{
$switch:
$case $cmp_by_value && $has_context:
while (cmp(list_get(list, h), pivot, context) >= 0 && l < h) h--;
if (l < h) list_set(list, l++, list_get(list, h));
while (cmp(list_get(list, l), pivot, context) <= 0 && l < h) l++;
$case $cmp_by_value:
while (cmp(list_get(list, h), pivot) >= 0 && l < h) h--;
if (l < h) list_set(list, l++, list_get(list, h));
while (cmp(list_get(list, l), pivot) <= 0 && l < h) l++;
$case $has_cmp && $has_context:
while (cmp(list_get_ref(list, h), &pivot, context) >= 0 && l < h) h--;
if (l < h) list_set(list, l++, list_get(list, h));
while (cmp(list_get_ref(list, l), &pivot, context) <= 0 && l < h) l++;
$case $has_cmp:
while (cmp(list_get_ref(list, h), &pivot) >= 0 && l < h) h--;
if (l < h) list_set(list, l++, list_get(list, h));
while (cmp(list_get_ref(list, l), &pivot) <= 0 && l < h) l++;
$default:
while (greater_eq(list_get(list, h), pivot) && l < h) h--;
if (l < h) list_set(list, l++, list_get(list, h));
while (less_eq(list_get(list, l), pivot) && l < h) l++;
$endswitch
if (l < h) list_set(list, h--, list_get(list, l));
}
list_set(list, l, pivot);
return l;
}