diff --git a/core/slice/sort_private.odin b/core/slice/sort_private.odin index efa9c596b..d0a2147b5 100644 --- a/core/slice/sort_private.odin +++ b/core/slice/sort_private.odin @@ -25,15 +25,104 @@ _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) <= 200 { + 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) -> (from: int) { + n := len(data) + + for n > 0 { + half := n / 2 + mid := from + half + + if less(data[mid], value, call) { + from = mid + 1 + n -= half + 1 + } else { + n = half + } + } + + return from + } + + bin_search_right :: proc(data: T, value: E, call: P) -> (from: int) { + n := len(data) + + for n > 0 { + half := n / 2 + mid := from + half + + if less(value, data[mid], call) { + n = half + } else { + from = mid + 1 + n -= 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 + } + + 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) + } } @(private) diff --git a/tests/core/slice/test_core_slice.odin b/tests/core/slice/test_core_slice.odin index b0fb791a6..ee5168e37 100644 --- a/tests/core/slice/test_core_slice.odin +++ b/tests/core/slice/test_core_slice.odin @@ -140,6 +140,52 @@ 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 &val, i in vals { + val = {rand.int_max(10), 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) + + 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") + + } +} + + @test test_binary_search :: proc(t: ^testing.T) { index: int