From f8a3e4f6f0cf16f36b0e6b44a4b7b7436db74db9 Mon Sep 17 00:00:00 2001 From: Pierre Curto Date: Tue, 4 Jul 2023 20:15:03 +0200 Subject: [PATCH] add basic quicksort support (#816) * lib/std/sort: refactor binarysearch namespace to prepare for sorting Signed-off-by: Pierre Curto * std/lib/sort: add basic quicksort support Signed-off-by: Pierre Curto * lib/std/hash: use method first parameter inferred type Signed-off-by: Pierre Curto * lib/std/hash: add fnv64a support Signed-off-by: Pierre Curto --------- Signed-off-by: Pierre Curto --- lib/std/hash/crc32.c3 | 8 +-- lib/std/hash/crc64.c3 | 8 +-- lib/std/hash/fnv32a.c3 | 6 +- lib/std/hash/fnv64a.c3 | 41 ++++++++++++ lib/std/sort/binarysearch.c3 | 30 ++------- lib/std/sort/quicksort.c3 | 66 +++++++++++++++++++ lib/std/sort/sort.c3 | 21 ++++++ test/unit/stdlib/sort/binarysearch.c3 | 31 +++------ test/unit/stdlib/sort/quicksort.c3 | 92 +++++++++++++++++++++++++++ test/unit/stdlib/sort/sort.c3 | 9 +++ 10 files changed, 255 insertions(+), 57 deletions(-) create mode 100644 lib/std/hash/fnv64a.c3 create mode 100644 lib/std/sort/quicksort.c3 create mode 100644 lib/std/sort/sort.c3 create mode 100644 test/unit/stdlib/sort/quicksort.c3 create mode 100644 test/unit/stdlib/sort/sort.c3 diff --git a/lib/std/hash/crc32.c3 b/lib/std/hash/crc32.c3 index edaba5817..7b265deb6 100644 --- a/lib/std/hash/crc32.c3 +++ b/lib/std/hash/crc32.c3 @@ -8,17 +8,17 @@ struct Crc32 uint result; } -fn void Crc32.init(Crc32* this, uint seed = 0) +fn void Crc32.init(&this, uint seed = 0) { this.result = ~seed; } -fn void Crc32.updatec(Crc32* this, char c) +fn void Crc32.updatec(&this, char c) { this.result = (this.result >> 8) ^ CRC32_TABLE[(this.result ^ c) & 0xFF]; } -fn void Crc32.update(Crc32* this, char[] data) +fn void Crc32.update(&this, char[] data) { uint result = this.result; foreach (char x : data) @@ -28,7 +28,7 @@ fn void Crc32.update(Crc32* this, char[] data) this.result = result; } -fn uint Crc32.final(Crc32* this) +fn uint Crc32.final(&this) { return ~this.result; } diff --git a/lib/std/hash/crc64.c3 b/lib/std/hash/crc64.c3 index 2796e2c12..4726ab40a 100644 --- a/lib/std/hash/crc64.c3 +++ b/lib/std/hash/crc64.c3 @@ -8,17 +8,17 @@ struct Crc64 ulong result; } -fn void Crc64.init(Crc64* this, uint seed = 0) +fn void Crc64.init(&this, uint seed = 0) { this.result = seed; } -fn void Crc64.updatec(Crc64* this, char c) +fn void Crc64.updatec(&this, char c) { this.result = (this.result << 8) ^ CRC64_TABLE[(char)((this.result >> 56) ^ c)]; } -fn void Crc64.update(Crc64* this, char[] data) +fn void Crc64.update(&this, char[] data) { ulong result = this.result; foreach (char x : data) @@ -28,7 +28,7 @@ fn void Crc64.update(Crc64* this, char[] data) this.result = result; } -fn ulong Crc64.final(Crc64* this) +fn ulong Crc64.final(&this) { return this.result; } diff --git a/lib/std/hash/fnv32a.c3 b/lib/std/hash/fnv32a.c3 index 073850f49..443aaf5f3 100644 --- a/lib/std/hash/fnv32a.c3 +++ b/lib/std/hash/fnv32a.c3 @@ -10,12 +10,12 @@ const FNV32A_MUL @private = 0x01000193; macro void @update(uint &h, char x) @private => h = (h * FNV32A_MUL) ^ x; -fn void Fnv32a.init(Fnv32a* this) +fn void Fnv32a.init(&this) { *this = FNV32A_START; } -fn void Fnv32a.update(Fnv32a* this, char[] data) +fn void Fnv32a.update(&this, char[] data) { uint h = (uint)*this; foreach (char x : data) @@ -25,7 +25,7 @@ fn void Fnv32a.update(Fnv32a* this, char[] data) *this = (Fnv32a)h; } -macro void Fnv32a.update_char(Fnv32a* this, char c) +macro void Fnv32a.update_char(&this, char c) { @update(*this, x); } diff --git a/lib/std/hash/fnv64a.c3 b/lib/std/hash/fnv64a.c3 new file mode 100644 index 000000000..838520bcb --- /dev/null +++ b/lib/std/hash/fnv64a.c3 @@ -0,0 +1,41 @@ +// Copyright (c) 2021 Christoffer Lerno. All rights reserved. +// Use of this source code is governed by the MIT license +// a copy of which can be found in the LICENSE_STDLIB file. +module std::hash::fnv64a; + +def Fnv64a = distinct ulong; + +const FNV64A_START @private = 0xcbf29ce484222325; +const FNV64A_MUL @private = 0x00000100000001b3; + +macro void @update(ulong &h, char x) @private => h = (h * FNV64A_MUL) ^ x; + +fn void Fnv64a.init(&this) +{ + *this = FNV64A_START; +} + +fn void Fnv64a.update(&this, char[] data) +{ + ulong h = (ulong)*this; + foreach (char x : data) + { + @update(h, x); + } + *this = (Fnv64a)h; +} + +macro void Fnv64a.update_char(&this, char c) +{ + @update(*this, x); +} + +fn ulong encode(char[] data) +{ + ulong h = FNV64A_START; + foreach (char x : data) + { + @update(h, x); + } + return h; +} \ No newline at end of file diff --git a/lib/std/sort/binarysearch.c3 b/lib/std/sort/binarysearch.c3 index 0ffd93631..dd89125a9 100644 --- a/lib/std/sort/binarysearch.c3 +++ b/lib/std/sort/binarysearch.c3 @@ -1,12 +1,12 @@ -module std::sort::binarysearch; +module std::sort; /** * Perform a binary search over the sorted array and return the smallest index * in [0, array.len) where cmp(i) is true and cmp(j) is true for j in [i, array.len). * @require is_searchable(list) "The list must be indexable and support .len or .len()" - * @require is_comparer(cmp, list) "Expeced a comparison function which compares values" + * @require is_comparer(cmp, list) "Expected a comparison function which compares values" **/ -macro usz cmp_search(list, x, cmp) +macro usz binarysearch_with(list, x, cmp) { usz i; usz len = @len_from_list(list); @@ -34,7 +34,7 @@ macro usz cmp_search(list, x, cmp) * @require is_searchable(list) "The list must be indexable and support .len or .len()" * @checked less(list[0], x) "The values must be comparable" **/ -macro usz search(list, x) +macro usz binarysearch(list, x) { usz i; usz len = @len_from_list(list); @@ -48,24 +48,4 @@ macro usz search(list, x) } } return i; -} - -macro bool is_searchable(list) @local -{ - return $checks(list[0]) && ($checks(list.len) || $checks(list.len())); -} - -macro usz @len_from_list(&list) @local -{ - $if $checks(list.len()): - return list.len(); - $else - return list.len; - $endif -} - -macro bool is_comparer(cmp, list) -{ - return $checks(int i = cmp(list[0], list[0])) - || $checks(int i = cmp(&list[0], &list[0])); -} +} \ No newline at end of file diff --git a/lib/std/sort/quicksort.c3 b/lib/std/sort/quicksort.c3 new file mode 100644 index 000000000..151e42b3a --- /dev/null +++ b/lib/std/sort/quicksort.c3 @@ -0,0 +1,66 @@ +module std::sort; + +/** + * @require is_searchable(list) "The list must be indexable and support .len or .len()" + **/ +macro quicksort(list, $Type) +{ + (($Type)(list)).sort(null); +} + +/** + * @require is_searchable(list) "The list must be indexable and support .len or .len()" + * @require is_comparer(cmp, list) "Expected a comparison function which compares values" + **/ +macro quicksort_with(list, $Type, cmp) +{ + (($Type)(list)).sort(cmp); +} + +module std::sort::quicksort; +import std::sort; + +def Quicksort = distinct Type[]; + +fn void Quicksort.sort(qs, Comparer cmp) +{ + usz len = sort::@len_from_list(qs); + qs.qsort(0, (isz)len - 1, cmp); +} + +fn void Quicksort.qsort(Quicksort qs, isz low, isz high, Comparer cmp) @private +{ + if (low < high) + { + isz p = qs.partition(low, high, cmp); + qs.qsort(low, p - 1, cmp); + qs.qsort(p + 1, high, cmp); + } +} + +fn isz Quicksort.partition(qs, isz low, isz high, Comparer cmp) @inline @private +{ + Type pivot = qs[high]; + isz i = low - 1; + for (isz j = low; j < high; j++) + { + $if $checks(cmp(qs[0], qs[0])): + int res = cmp(qs[j], pivot); + $else + $if $checks(cmp(&qs[0], &qs[0])): + int res = cmp(&qs[j], &pivot); + $else + int res; + if (greater(qs[j], pivot)) res = 1; + $endif + $endif + if (res <= 0) + { + i++; + @swap(qs[i], qs[j]); + } + } + i++; + @swap(qs[i], qs[high]); + return i; +} \ No newline at end of file diff --git a/lib/std/sort/sort.c3 b/lib/std/sort/sort.c3 new file mode 100644 index 000000000..7cb8f32fb --- /dev/null +++ b/lib/std/sort/sort.c3 @@ -0,0 +1,21 @@ +module std::sort; + +macro bool is_searchable(list) +{ + return $checks(list[0]) && ($checks(list.len) || $checks(list.len())); +} + +macro usz @len_from_list(&list) +{ + $if $checks(list.len()): + return list.len(); + $else + return list.len; + $endif +} + +macro bool is_comparer(cmp, list) +{ + return $checks(int i = cmp(list[0], list[0])) + || $checks(int i = cmp(&list[0], &list[0])); +} \ No newline at end of file diff --git a/test/unit/stdlib/sort/binarysearch.c3 b/test/unit/stdlib/sort/binarysearch.c3 index e5ac62313..11033c345 100644 --- a/test/unit/stdlib/sort/binarysearch.c3 +++ b/test/unit/stdlib/sort/binarysearch.c3 @@ -1,16 +1,16 @@ -module binarysearch_test @test; -import std::sort::binarysearch; +module sort_test @test; +import std::sort; -struct SearchTest +struct BinarySearchTest { int[] data; int x; int index; } -fn void search() +fn void binarysearch() { - SearchTest[] tcases = { + BinarySearchTest[] tcases = { { {}, 0, 0 }, { {1, 2, 3}, 1, 0 }, { {1, 2, 3}, 2, 1 }, @@ -22,28 +22,17 @@ fn void search() foreach (tc : tcases) { - usz idx = binarysearch::search(tc.data, tc.x); + usz idx = sort::binarysearch(tc.data, tc.x); assert(idx == tc.index, "%s: got %d; want %d", tc.data, idx, tc.index); - usz cmp_idx = binarysearch::cmp_search(tc.data, tc.x, &cmp_int); + usz cmp_idx = sort::binarysearch_with(tc.data, tc.x, &sort::cmp_int); assert(cmp_idx == tc.index, "%s: got %d; want %d", tc.data, cmp_idx, tc.index); - usz cmp_idx2 = binarysearch::cmp_search(tc.data, tc.x, &cmp_int2); + usz cmp_idx2 = sort::binarysearch_with(tc.data, tc.x, &sort::cmp_int2); assert(cmp_idx2 == tc.index, "%s: got %d; want %d", tc.data, cmp_idx2, tc.index); - usz cmp_idx3 = binarysearch::cmp_search(tc.data, tc.x, fn int(int a, int b) => a - b); + usz cmp_idx3 = sort::binarysearch_with(tc.data, tc.x, fn int(int a, int b) => a - b); assert(cmp_idx3 == tc.index, "%s: got %d; want %d", tc.data, cmp_idx2, tc.index); } -} - -module binarysearch_test; - -fn int cmp_int(void* x, void* y) { - return *(int*)x - *(int*)y; -} - -fn int cmp_int2(int x, int y) { - return x - y; -} - +} \ No newline at end of file diff --git a/test/unit/stdlib/sort/quicksort.c3 b/test/unit/stdlib/sort/quicksort.c3 new file mode 100644 index 000000000..ccf0e674c --- /dev/null +++ b/test/unit/stdlib/sort/quicksort.c3 @@ -0,0 +1,92 @@ +module sort_test @test; +import std::sort; +import std::sort::quicksort; + +def QSInt = quicksort::Quicksort; + +fn void quicksort() +{ + int[][] tcases = { + {}, + {10, 3}, + {3, 2, 1}, + {1, 2, 3}, + {2, 1, 3}, + }; + + foreach (tc : tcases) + { + sort::quicksort(tc, QSInt); + assert(sort::check_int_sort(tc)); + } +} + +def Cmp = fn int (void*, void*); +def QSIntCmp = quicksort::Quicksort; + +fn void quicksort_with() +{ + int[][] tcases = { + {}, + {10, 3}, + {3, 2, 1}, + {1, 2, 3}, + {2, 1, 3}, + }; + + foreach (tc : tcases) + { + sort::quicksort_with(tc, QSIntCmp, &sort::cmp_int); + assert(sort::check_int_sort(tc)); + } +} + +def Cmp2 = fn int (int, int); +def QSIntCmp2 = quicksort::Quicksort; + +fn void quicksort_with2() +{ + int[][] tcases = { + {}, + {10, 3}, + {3, 2, 1}, + {1, 2, 3}, + {2, 1, 3}, + }; + + foreach (tc : tcases) + { + sort::quicksort_with(tc, QSIntCmp2, &sort::cmp_int2); + assert(sort::check_int_sort(tc)); + } +} + +fn void quicksort_with_lambda() +{ + int[][] tcases = { + {}, + {10, 3}, + {3, 2, 1}, + {1, 2, 3}, + {2, 1, 3}, + }; + + foreach (tc : tcases) + { + sort::quicksort_with(tc, QSIntCmp2, fn int(int a, int b) => a - b); + assert(sort::check_int_sort(tc)); + } +} + +module std::sort; + +fn bool check_int_sort(int[] list) +{ + int prev = int.min; + foreach (x : list) + { + if (prev > x) return false; + prev = x; + } + return true; +} \ No newline at end of file diff --git a/test/unit/stdlib/sort/sort.c3 b/test/unit/stdlib/sort/sort.c3 new file mode 100644 index 000000000..90ab431ae --- /dev/null +++ b/test/unit/stdlib/sort/sort.c3 @@ -0,0 +1,9 @@ +module std::sort; + +fn int cmp_int(void* x, void* y) { + return *(int*)x - *(int*)y; +} + +fn int cmp_int2(int x, int y) { + return x - y; +} \ No newline at end of file