From 267c745ac70754952f65a19f4b50e5d8970ea0df Mon Sep 17 00:00:00 2001 From: TheRadischen Date: Sun, 16 Aug 2026 12:07:09 +0200 Subject: [PATCH 01/10] rotate_merge --- core/slice/sort_private.odin | 104 ++++++++++++++++++++++++++++++++--- 1 file changed, 96 insertions(+), 8 deletions(-) diff --git a/core/slice/sort_private.odin b/core/slice/sort_private.odin index efa9c596b..35886c8c4 100644 --- a/core/slice/sort_private.odin +++ b/core/slice/sort_private.odin @@ -13,7 +13,7 @@ Sort_Kind :: enum { Cmp, } -_stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (ORD(E) && KIND == .Ordered) || (KIND != .Ordered) #no_bounds_check { +_stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) { less :: #force_inline proc(a, b: E, call: P) -> bool { when KIND == .Ordered { return a < b @@ -25,14 +25,102 @@ _stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (O #panic("unhandled Sort_Kind") } } - - // insertion sort - // TODO(bill): use a different algorithm as insertion sort is O(n^2) - n := len(data) - for i in 1.. 0 && less(data[j], data[j-1], call); j -= 1 { - swap(data, j, j-1) + + merge_rotate(data, call) + + insertion_sort :: proc(data: T, call: P){ + for i in 1.. 0 && less(temp, data[j - 1], call); j -= 1 { + data[j] = data[j - 1] + } + data[j] = temp + } + } + + merge_rotate :: proc(data: T, call: P){ + if len(data) <= 64 { + insertion_sort(data, call) + return + } + + mid := len(data) / 2 + merge_rotate(data[:mid], call) + merge_rotate(data[mid:], call) + + merge(data, mid, len(data) - mid, call) + } + + bin_search_left :: proc(data:T, value: E, call: P) -> int{ + from := 0 + len := len(data) + + for len > 0 { + half := len / 2 + mid := from + half + + if less(data[mid], value, call){ + from = mid + 1 + len -= half + 1 + } else { + len = half + } } + return from + } + + bin_search_right :: proc(data: T, value: E, call: P) -> int { + from := 0 + len := len(data) + + for len > 0 { + half := len / 2 + mid := from + half + + if less(value, data[mid], call){ + len = half + } else { + from = mid + 1 + len -= half + 1 + } + } + return from + } + + merge :: proc(data: T, left, right: int, call: P) { + if left == 0 || right == 0 { + return + } + + if left + right == 2 { + if less(data[1],data[0], call) { + data[1], data[0] = data[0], data[1] + } + return + } + + first_cut, second_cut : int + left2, right2 : int + if left > right { + left2 = left / 2 + first_cut = left2 + + second_cut = left + bin_search_left(data[left:], data[first_cut], call) + right2 = second_cut - left + } else { + right2 = right / 2 + second_cut = left + right2 + + first_cut = bin_search_right(data[:left], data[second_cut], call) + left2 = first_cut + } + + slice.rotate_left(data[first_cut:second_cut], left - first_cut) + new_mid := first_cut + right2 + + merge(data[:new_mid], left2, right2, call) + merge(data[new_mid:], left-left2, right-right2, call) } } From df54692a9b05f6a9e0a86d97ebc3f05ab3fd9e37 Mon Sep 17 00:00:00 2001 From: TheRadischen Date: Sun, 16 Aug 2026 12:22:25 +0200 Subject: [PATCH 02/10] addedtest --- core/slice/sort_private.odin | 2 +- tests/core/slice/test_core_slice.odin | 45 +++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/core/slice/sort_private.odin b/core/slice/sort_private.odin index 35886c8c4..f568671a8 100644 --- a/core/slice/sort_private.odin +++ b/core/slice/sort_private.odin @@ -116,7 +116,7 @@ _stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) { left2 = first_cut } - slice.rotate_left(data[first_cut:second_cut], left - first_cut) + rotate_left(data[first_cut:second_cut], left - first_cut) new_mid := first_cut + right2 merge(data[:new_mid], left2, right2, call) diff --git a/tests/core/slice/test_core_slice.odin b/tests/core/slice/test_core_slice.odin index b0fb791a6..4f1148983 100644 --- a/tests/core/slice/test_core_slice.odin +++ b/tests/core/slice/test_core_slice.odin @@ -140,6 +140,51 @@ test_sort_by_indices :: proc(t: ^testing.T) { } } +@test +test_sort_stability :: proc(t: ^testing.T) { + // Test sizes are all prime. + test_sizes :: []int{7, 13, 347, 1031, 10111, 100003} + Data :: struct { + rand: int, + index: int, + } + + for test_size in test_sizes { + rand.reset(t.seed) + + vals := make([]Data, test_size) + defer { + delete(vals) + } + + // Set up test values + for _, i in vals { + vals[i].rand = rand.int_max(10) + vals[i].index = i + } + + // Sort + slice.stable_sort_by(vals, proc(l, r: Data) -> bool {return l.rand < r.rand}) + + // Verify sorted test values + rand.reset(t.seed) + + for i in 1.. vals[i].rand { + testing.expect(t, false, "Expected slice to be sorted") + } + if vals[i - 1].index > vals[i].index { + testing.expect(t, false, "Expected slice to be stable") + } + } + + } +} + + @test test_binary_search :: proc(t: ^testing.T) { index: int From 3f851d2b02d71dd63e087d80459813654020ca08 Mon Sep 17 00:00:00 2001 From: TheRadischen Date: Sun, 16 Aug 2026 12:27:30 +0200 Subject: [PATCH 03/10] smarter_test --- tests/core/slice/test_core_slice.odin | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/core/slice/test_core_slice.odin b/tests/core/slice/test_core_slice.odin index 4f1148983..cda22c1ec 100644 --- a/tests/core/slice/test_core_slice.odin +++ b/tests/core/slice/test_core_slice.odin @@ -169,18 +169,22 @@ test_sort_stability :: proc(t: ^testing.T) { // Verify sorted test values rand.reset(t.seed) + sum := vals[0].index for i in 1.. vals[i].rand { + sum += vals[i].index + if vals[i-1].rand > vals[i].rand { testing.expect(t, false, "Expected slice to be sorted") } - if vals[i - 1].index > vals[i].index { + if vals[i-1].rand < vals[i].rand { + continue + } + if vals[i-1].index > vals[i].index { testing.expect(t, false, "Expected slice to be stable") } } + testing.expect(t, sum == test_size * (test_size - 1) / 2, "Expected slice to have all indecies") + } } From 4c945c995a606db06d87b3577ef8f06ae9cba01d Mon Sep 17 00:00:00 2001 From: TheRadischen Date: Sun, 16 Aug 2026 12:58:23 +0200 Subject: [PATCH 04/10] added_back_where --- core/slice/sort_private.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/slice/sort_private.odin b/core/slice/sort_private.odin index f568671a8..1a2a24b5b 100644 --- a/core/slice/sort_private.odin +++ b/core/slice/sort_private.odin @@ -13,7 +13,7 @@ Sort_Kind :: enum { Cmp, } -_stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) { +_stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (ORD(E) && KIND == .Ordered) || (KIND != .Ordered) #no_bounds_check { less :: #force_inline proc(a, b: E, call: P) -> bool { when KIND == .Ordered { return a < b From d8177f7716c4cf4444a46c7924366be7294984e1 Mon Sep 17 00:00:00 2001 From: TheRadischen Date: Sun, 16 Aug 2026 13:33:32 +0200 Subject: [PATCH 05/10] adjusted_smallsort_threshhold --- core/slice/sort_private.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/slice/sort_private.odin b/core/slice/sort_private.odin index 1a2a24b5b..0818a7e25 100644 --- a/core/slice/sort_private.odin +++ b/core/slice/sort_private.odin @@ -40,7 +40,7 @@ _stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (O } merge_rotate :: proc(data: T, call: P){ - if len(data) <= 64 { + if len(data) <= 200 { insertion_sort(data, call) return } From 9757fed9f987e75564fbcf87e9f6e422b876ee71 Mon Sep 17 00:00:00 2001 From: TheRadischen Date: Sun, 16 Aug 2026 14:28:38 +0200 Subject: [PATCH 06/10] changed_to_tabs --- core/slice/sort_private.odin | 64 +++++++++++++-------------- tests/core/slice/test_core_slice.odin | 36 +++++++-------- 2 files changed, 50 insertions(+), 50 deletions(-) diff --git a/core/slice/sort_private.odin b/core/slice/sort_private.odin index 0818a7e25..a9932fd75 100644 --- a/core/slice/sort_private.odin +++ b/core/slice/sort_private.odin @@ -15,7 +15,7 @@ Sort_Kind :: enum { _stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (ORD(E) && KIND == .Ordered) || (KIND != .Ordered) #no_bounds_check { less :: #force_inline proc(a, b: E, call: P) -> bool { - when KIND == .Ordered { + when KIND == .Ordered { return a < b } else when KIND == .Less { return call(a, b) @@ -29,21 +29,21 @@ _stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (O merge_rotate(data, call) insertion_sort :: proc(data: T, call: P){ - for i in 1.. 0 && less(temp, data[j - 1], call); j -= 1 { - data[j] = data[j - 1] - } - data[j] = temp - } - } + for i in 1.. 0 && less(temp, data[j - 1], call); j -= 1 { + data[j] = data[j - 1] + } + data[j] = temp + } + } merge_rotate :: proc(data: T, call: P){ - if len(data) <= 200 { - insertion_sort(data, call) - return - } + if len(data) <= 200 { + insertion_sort(data, call) + return + } mid := len(data) / 2 merge_rotate(data[:mid], call) @@ -51,7 +51,7 @@ _stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (O merge(data, mid, len(data) - mid, call) } - + bin_search_left :: proc(data:T, value: E, call: P) -> int{ from := 0 len := len(data) @@ -75,10 +75,10 @@ _stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (O len := len(data) for len > 0 { - half := len / 2 - mid := from + half + half := len / 2 + mid := from + half - if less(value, data[mid], call){ + if less(value, data[mid], call){ len = half } else { from = mid + 1 @@ -90,32 +90,32 @@ _stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (O merge :: proc(data: T, left, right: int, call: P) { if left == 0 || right == 0 { - return - } + return + } if left + right == 2 { if less(data[1],data[0], call) { - data[1], data[0] = data[0], data[1] - } + data[1], data[0] = data[0], data[1] + } return } first_cut, second_cut : int left2, right2 : int if left > right { - left2 = left / 2 - first_cut = left2 + left2 = left / 2 + first_cut = left2 - second_cut = left + bin_search_left(data[left:], data[first_cut], call) - right2 = second_cut - left - } else { - right2 = right / 2 - second_cut = left + right2 + second_cut = left + bin_search_left(data[left:], data[first_cut], call) + right2 = second_cut - left + } else { + right2 = right / 2 + second_cut = left + right2 - first_cut = bin_search_right(data[:left], data[second_cut], call) - left2 = first_cut + first_cut = bin_search_right(data[:left], data[second_cut], call) + left2 = first_cut } - + rotate_left(data[first_cut:second_cut], left - first_cut) new_mid := first_cut + right2 diff --git a/tests/core/slice/test_core_slice.odin b/tests/core/slice/test_core_slice.odin index cda22c1ec..7fa122966 100644 --- a/tests/core/slice/test_core_slice.odin +++ b/tests/core/slice/test_core_slice.odin @@ -144,10 +144,10 @@ test_sort_by_indices :: proc(t: ^testing.T) { test_sort_stability :: proc(t: ^testing.T) { // Test sizes are all prime. test_sizes :: []int{7, 13, 347, 1031, 10111, 100003} - Data :: struct { - rand: int, - index: int, - } + Data :: struct { + rand: int, + index: int, + } for test_size in test_sizes { rand.reset(t.seed) @@ -169,21 +169,21 @@ test_sort_stability :: proc(t: ^testing.T) { // Verify sorted test values rand.reset(t.seed) - sum := vals[0].index - for i in 1.. vals[i].rand { - testing.expect(t, false, "Expected slice to be sorted") - } - if vals[i-1].rand < vals[i].rand { - continue - } - if vals[i-1].index > vals[i].index { - testing.expect(t, false, "Expected slice to be stable") - } - } + sum := vals[0].index + for i in 1.. vals[i].rand { + testing.expect(t, false, "Expected slice to be sorted") + } + if vals[i-1].rand < vals[i].rand { + continue + } + if vals[i-1].index > vals[i].index { + testing.expect(t, false, "Expected slice to be stable") + } + } - testing.expect(t, sum == test_size * (test_size - 1) / 2, "Expected slice to have all indecies") + testing.expect(t, sum == test_size * (test_size - 1) / 2, "Expected slice to have all indecies") } } From 1a70315797761ba827f3ad650b13b14b9e1566ed Mon Sep 17 00:00:00 2001 From: TheRadischen Date: Sun, 16 Aug 2026 15:09:12 +0200 Subject: [PATCH 07/10] style_corrections --- core/slice/sort_private.odin | 49 ++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/core/slice/sort_private.odin b/core/slice/sort_private.odin index a9932fd75..7148b1297 100644 --- a/core/slice/sort_private.odin +++ b/core/slice/sort_private.odin @@ -15,7 +15,7 @@ Sort_Kind :: enum { _stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (ORD(E) && KIND == .Ordered) || (KIND != .Ordered) #no_bounds_check { less :: #force_inline proc(a, b: E, call: P) -> bool { - when KIND == .Ordered { + when KIND == .Ordered { return a < b } else when KIND == .Less { return call(a, b) @@ -28,7 +28,7 @@ _stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (O merge_rotate(data, call) - insertion_sort :: proc(data: T, call: P){ + insertion_sort :: proc(data: T, call: P) { for i in 1.. int{ - from := 0 - len := len(data) + bin_search_left :: proc(data: T, value: E, call: P) -> (from: int) { + n := len(data) - for len > 0 { - half := len / 2 + for n > 0 { + half := n / 2 mid := from + half - if less(data[mid], value, call){ + if less(data[mid], value, call) { from = mid + 1 - len -= half + 1 + n -= half + 1 } else { - len = half + n = half } } + return from } - bin_search_right :: proc(data: T, value: E, call: P) -> int { - from := 0 - len := len(data) + bin_search_right :: proc(data: T, value: E, call: P) -> (from: int) { + n := len(data) - for len > 0 { - half := len / 2 + for n > 0 { + half := n / 2 mid := from + half - if less(value, data[mid], call){ - len = half + if less(value, data[mid], call) { + n = half } else { from = mid + 1 - len -= half + 1 + n -= half + 1 } } + return from } @@ -94,14 +94,15 @@ _stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (O } if left + right == 2 { - if less(data[1],data[0], call) { + if less(data[1], data[0], call) { data[1], data[0] = data[0], data[1] } return } - first_cut, second_cut : int - left2, right2 : int + first_cut, second_cut: int + left2, right2: int + if left > right { left2 = left / 2 first_cut = left2 @@ -119,8 +120,8 @@ _stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (O rotate_left(data[first_cut:second_cut], left - first_cut) new_mid := first_cut + right2 - merge(data[:new_mid], left2, right2, call) - merge(data[new_mid:], left-left2, right-right2, call) + merge(data[:new_mid], left2 , right2 , call) + merge(data[new_mid:], left - left2, right - right2, call) } } From f6b8eafdfd4a82e13288313fd8a60974c46b9d52 Mon Sep 17 00:00:00 2001 From: TheRadischen Date: Sun, 16 Aug 2026 15:10:01 +0200 Subject: [PATCH 08/10] imporved_style --- tests/core/slice/test_core_slice.odin | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/tests/core/slice/test_core_slice.odin b/tests/core/slice/test_core_slice.odin index 7fa122966..ee5168e37 100644 --- a/tests/core/slice/test_core_slice.odin +++ b/tests/core/slice/test_core_slice.odin @@ -153,14 +153,11 @@ test_sort_stability :: proc(t: ^testing.T) { rand.reset(t.seed) vals := make([]Data, test_size) - defer { - delete(vals) - } + defer delete(vals) // Set up test values - for _, i in vals { - vals[i].rand = rand.int_max(10) - vals[i].index = i + for &val, i in vals { + val = {rand.int_max(10), i} } // Sort @@ -172,13 +169,13 @@ test_sort_stability :: proc(t: ^testing.T) { sum := vals[0].index for i in 1.. vals[i].rand { + if vals[i - 1].rand > vals[i].rand { testing.expect(t, false, "Expected slice to be sorted") } - if vals[i-1].rand < vals[i].rand { + if vals[i - 1].rand < vals[i].rand { continue } - if vals[i-1].index > vals[i].index { + if vals[i - 1].index > vals[i].index { testing.expect(t, false, "Expected slice to be stable") } } From 0b0778f9813d1327a593e17a5e482b2acb584522 Mon Sep 17 00:00:00 2001 From: TheRadischen Date: Sun, 16 Aug 2026 15:24:19 +0200 Subject: [PATCH 09/10] morestyle improvements mybe this is it? --- core/slice/sort_private.odin | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/slice/sort_private.odin b/core/slice/sort_private.odin index 7148b1297..54a92d986 100644 --- a/core/slice/sort_private.odin +++ b/core/slice/sort_private.odin @@ -74,10 +74,10 @@ _stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (O n := len(data) for n > 0 { - half := n / 2 - mid := from + half + half := n / 2 + mid := from + half - if less(value, data[mid], call) { + if less(value, data[mid], call) { n = half } else { from = mid + 1 From 7d9367e3b443024e7037b69a579627313072b6a5 Mon Sep 17 00:00:00 2001 From: TheRadischen Date: Sun, 16 Aug 2026 15:24:55 +0200 Subject: [PATCH 10/10] morestyle improvements mybe this is it? --- core/slice/sort_private.odin | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/slice/sort_private.odin b/core/slice/sort_private.odin index 54a92d986..d0a2147b5 100644 --- a/core/slice/sort_private.odin +++ b/core/slice/sort_private.odin @@ -104,17 +104,17 @@ _stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (O left2, right2: int if left > right { - left2 = left / 2 - first_cut = left2 + left2 = left / 2 + first_cut = left2 - second_cut = left + bin_search_left(data[left:], data[first_cut], call) - right2 = second_cut - left - } else { - right2 = right / 2 - second_cut = left + right2 + second_cut = left + bin_search_left(data[left:], data[first_cut], call) + right2 = second_cut - left + } else { + right2 = right / 2 + second_cut = left + right2 - first_cut = bin_search_right(data[:left], data[second_cut], call) - left2 = first_cut + first_cut = bin_search_right(data[:left], data[second_cut], call) + left2 = first_cut } rotate_left(data[first_cut:second_cut], left - first_cut)