diff --git a/lib/std/sort/sorted.c3 b/lib/std/sort/sorted.c3 new file mode 100644 index 000000000..8718e3ca4 --- /dev/null +++ b/lib/std/sort/sorted.c3 @@ -0,0 +1,59 @@ +module std::sort; + +<* +Returns true if list is sorted in either ascending or descending order. + @require @is_sortable(list) "The list must be indexable and support .len or .len()" + @require @is_valid_cmp_fn(cmp, list, ctx) "Expected a comparison function which compares values" + @require @is_valid_context(cmp, ctx) "Expected a valid context" +*> +macro bool is_sorted(list, cmp = EMPTY_MACRO_SLOT, ctx = EMPTY_MACRO_SLOT) @builtin +{ + var $Type = $typeof(list); + usz len = sort::@len_from_list(list); + if (len <= 1) return true; + var check_sort = fn bool($Type list, usz start, usz end, $typeof(cmp) cmp, $typeof(ctx) ctx) + { + usz i; + int sort_order; + + // determine sort order (ascending or descending) + for (i = start; i < end && sort_order == 0; i++) + { + sort_order = @sort_cmp(list, i, cmp, ctx); + } + + // no sort order found, all elements are the same, consider list sorted + if (sort_order == 0) return true; + + // compare adjacent elements to the sort order + for (; i < end; i++) + { + if (sort_order * @sort_cmp(list, i, cmp, ctx) < 0) return false; + } + return true; + }; + return check_sort(list, 0, len - 1, cmp, ctx); +} + +macro int @sort_cmp(list, pos, cmp, ctx) @local +{ + var $has_cmp = @is_valid_macro_slot(cmp); + var $has_context = @is_valid_macro_slot(ctx); + var $cmp_by_value = $has_cmp &&& $assignable(list[0], $typefrom($typeof(cmp).paramsof[0].type)); + + var a = list[pos]; + var b = list[pos+1]; + + $switch + $case $cmp_by_value && $has_context: + return cmp(a, b); + $case $cmp_by_value: + return cmp(a, b); + $case $has_cmp && $has_context: + return cmp(&a, &b, ctx); + $case $has_cmp: + return cmp(&a, &b); + $default: + return compare_to(a,b); + $endswitch +} diff --git a/releasenotes.md b/releasenotes.md index 967d75e92..f6c40601c 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -40,6 +40,7 @@ - Add `file::save`. - Add `memcpy` / `memset` / `memcmp` to nolibc. - Add `sort::quickselect` to find the k-th smallest element in an unordered list. +- Add `sort::is_sorted` to determine if a list is sorted. ## 0.6.4 Change list diff --git a/test/unit/stdlib/sort/sorted.c3 b/test/unit/stdlib/sort/sorted.c3 new file mode 100644 index 000000000..b82019bfc --- /dev/null +++ b/test/unit/stdlib/sort/sorted.c3 @@ -0,0 +1,92 @@ +module sort_test @test; +import std::sort; +import std::collections::list; + +struct TestCase @local +{ + int[] input; + bool want; +} + +fn void sorted() +{ + TestCase[] tcases = { + { + .input = {}, + .want = true, + }, + { + .input = {1}, + .want = true, + }, + { + .input = {1,2}, + .want = true, + }, + { + .input = {2,1}, + .want = true, + }, + { + .input = {1,2,3}, + .want = true, + }, + { + .input = {1,2,1}, + .want = false, + }, + { + .input = {2,1,2}, + .want = false, + }, + { + .input = {3,2,1}, + .want = true, + }, + { + .input = {1,1,1,1,1,2}, + .want = true, + }, + { + .input = {2,2,2,2,2,1}, + .want = true, + }, + { + .input = {1,1,1,1,2,1}, + .want = false, + }, + }; + + bool got; + foreach (tc : tcases) + { + // default + got = is_sorted(tc.input); + assert(got == tc.want, "default: %s, got: %s, want: %s", + tc.input, got, tc.want); + + // with list + List() list; + list.temp_init(); + list.add_array(tc.input); + + got = is_sorted(list); + assert(got == tc.want, "list: %s, got: %s, want: %s", + list.to_tstring(), got, tc.want); + + // with lambda + got = is_sorted(tc.input, fn int(int a, int b) => a - b); + assert(got == tc.want, "lambda: %s, got: %s, want: %s", + tc.input, got, tc.want); + + // with value + got = is_sorted(tc.input, &sort::cmp_int_value); + assert(got == tc.want, "value: %s, got: %s, want: %s", + tc.input, got, tc.want); + + // with ref + got = is_sorted(tc.input, &sort::cmp_int_ref); + assert(got == tc.want, "ref: %s, got: %s, want: %s", + tc.input, got, tc.want); + } +}