mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-07 08:08:50 +00:00
Merge pull request #2979 from rope-hmg/master
Binary search improvements
This commit is contained in:
+83
-34
@@ -117,46 +117,95 @@ linear_search_proc :: proc(array: $A/[]$T, f: proc(T) -> bool) -> (index: int, f
|
|||||||
return -1, false
|
return -1, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Binary search searches the given slice for the given element.
|
||||||
|
If the slice is not sorted, the returned index is unspecified and meaningless.
|
||||||
|
|
||||||
|
If the value is found then the returned int is the index of the matching element.
|
||||||
|
If there are multiple matches, then any one of the matches could be returned.
|
||||||
|
|
||||||
|
If the value is not found then the returned int is the index where a matching
|
||||||
|
element could be inserted while maintaining sorted order.
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
|
||||||
|
Looks up a series of four elements. The first is found, with a
|
||||||
|
uniquely determined position; the second and third are not
|
||||||
|
found; the fourth could match any position in `[1, 4]`.
|
||||||
|
|
||||||
|
```
|
||||||
|
index: int
|
||||||
|
found: bool
|
||||||
|
|
||||||
|
s := []i32{0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55}
|
||||||
|
|
||||||
|
index, found = slice.binary_search(s, 13)
|
||||||
|
assert(index == 9 && found == true)
|
||||||
|
|
||||||
|
index, found = slice.binary_search(s, 4)
|
||||||
|
assert(index == 7 && found == false)
|
||||||
|
|
||||||
|
index, found = slice.binary_search(s, 100)
|
||||||
|
assert(index == 13 && found == false)
|
||||||
|
|
||||||
|
index, found = slice.binary_search(s, 1)
|
||||||
|
assert(index >= 1 && index <= 4 && found == true)
|
||||||
|
```
|
||||||
|
|
||||||
|
For slices of more complex types see: binary_search_by
|
||||||
|
*/
|
||||||
@(require_results)
|
@(require_results)
|
||||||
binary_search :: proc(array: $A/[]$T, key: T) -> (index: int, found: bool)
|
binary_search :: proc(array: $A/[]$T, key: T) -> (index: int, found: bool)
|
||||||
where intrinsics.type_is_ordered(T) #no_bounds_check {
|
where intrinsics.type_is_ordered(T) #no_bounds_check
|
||||||
|
{
|
||||||
n := len(array)
|
// I would like to use binary_search_by(array, key, cmp) here, but it doesn't like it:
|
||||||
switch n {
|
// Cannot assign value 'cmp' of type 'proc($E, $E) -> Ordering' to 'proc(i32, i32) -> Ordering' in argument
|
||||||
case 0:
|
return binary_search_by(array, key, proc(key: T, element: T) -> Ordering {
|
||||||
return -1, false
|
|
||||||
case 1:
|
|
||||||
if array[0] == key {
|
|
||||||
return 0, true
|
|
||||||
}
|
|
||||||
return -1, false
|
|
||||||
}
|
|
||||||
|
|
||||||
lo, hi := 0, n-1
|
|
||||||
|
|
||||||
for array[hi] != array[lo] && key >= array[lo] && key <= array[hi] {
|
|
||||||
when intrinsics.type_is_ordered_numeric(T) {
|
|
||||||
// NOTE(bill): This is technically interpolation search
|
|
||||||
m := lo + int((key - array[lo]) * T(hi - lo) / (array[hi] - array[lo]))
|
|
||||||
} else {
|
|
||||||
m := lo + (hi - lo)/2
|
|
||||||
}
|
|
||||||
switch {
|
switch {
|
||||||
case array[m] < key:
|
case element < key: return .Less
|
||||||
lo = m + 1
|
case element > key: return .Greater
|
||||||
case key < array[m]:
|
case: return .Equal
|
||||||
hi = m - 1
|
|
||||||
case:
|
|
||||||
return m, true
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
if key == array[lo] {
|
|
||||||
return lo, true
|
|
||||||
}
|
|
||||||
return -1, false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(require_results)
|
||||||
|
binary_search_by :: proc(array: $A/[]$T, key: T, f: proc(T, T) -> Ordering) -> (index: int, found: bool)
|
||||||
|
where intrinsics.type_is_ordered(T) #no_bounds_check
|
||||||
|
{
|
||||||
|
// INVARIANTS:
|
||||||
|
// - 0 <= left <= (left + size = right) <= len(array)
|
||||||
|
// - f returns .Less for everything in array[:left]
|
||||||
|
// - f returns .Greater for everything in array[right:]
|
||||||
|
size := len(array)
|
||||||
|
left := 0
|
||||||
|
right := size
|
||||||
|
|
||||||
|
for left < right {
|
||||||
|
mid := left + size / 2
|
||||||
|
|
||||||
|
// Steps to verify this is in-bounds:
|
||||||
|
// 1. We note that `size` is strictly positive due to the loop condition
|
||||||
|
// 2. Therefore `size/2 < size`
|
||||||
|
// 3. Adding `left` to both sides yields `(left + size/2) < (left + size)`
|
||||||
|
// 4. We know from the invariant that `left + size <= len(array)`
|
||||||
|
// 5. Therefore `left + size/2 < self.len()`
|
||||||
|
cmp := f(key, array[mid])
|
||||||
|
|
||||||
|
left = mid + 1 if cmp == .Less else left
|
||||||
|
right = mid if cmp == .Greater else right
|
||||||
|
|
||||||
|
switch cmp {
|
||||||
|
case .Equal: return mid, true
|
||||||
|
case .Less: left = mid + 1
|
||||||
|
case .Greater: right = mid
|
||||||
|
}
|
||||||
|
|
||||||
|
size = right - left
|
||||||
|
}
|
||||||
|
|
||||||
|
return left, false
|
||||||
|
}
|
||||||
|
|
||||||
@(require_results)
|
@(require_results)
|
||||||
equal :: proc(a, b: $T/[]$E) -> bool where intrinsics.type_is_comparable(E) {
|
equal :: proc(a, b: $T/[]$E) -> bool where intrinsics.type_is_comparable(E) {
|
||||||
|
|||||||
+23
-3
@@ -1,9 +1,26 @@
|
|||||||
ODIN=../../odin
|
ODIN=../../odin
|
||||||
PYTHON=$(shell which python3)
|
PYTHON=$(shell which python3)
|
||||||
|
|
||||||
all: download_test_assets image_test compress_test strings_test hash_test crypto_test noise_test encoding_test \
|
all: c_libc_test \
|
||||||
math_test linalg_glsl_math_test filepath_test reflect_test os_exit_test i18n_test match_test c_libc_test net_test \
|
compress_test \
|
||||||
fmt_test thread_test
|
crypto_test \
|
||||||
|
download_test_assets \
|
||||||
|
encoding_test \
|
||||||
|
filepath_test \
|
||||||
|
fmt_test \
|
||||||
|
hash_test \
|
||||||
|
i18n_test \
|
||||||
|
image_test \
|
||||||
|
linalg_glsl_math_test \
|
||||||
|
match_test \
|
||||||
|
math_test \
|
||||||
|
net_test \
|
||||||
|
noise_test \
|
||||||
|
os_exit_test \
|
||||||
|
reflect_test \
|
||||||
|
slice_test \
|
||||||
|
strings_test \
|
||||||
|
thread_test
|
||||||
|
|
||||||
download_test_assets:
|
download_test_assets:
|
||||||
$(PYTHON) download_assets.py
|
$(PYTHON) download_assets.py
|
||||||
@@ -44,6 +61,9 @@ filepath_test:
|
|||||||
reflect_test:
|
reflect_test:
|
||||||
$(ODIN) run reflect/test_core_reflect.odin -file -collection:tests=.. -out:test_core_reflect
|
$(ODIN) run reflect/test_core_reflect.odin -file -collection:tests=.. -out:test_core_reflect
|
||||||
|
|
||||||
|
slice_test:
|
||||||
|
$(ODIN) run slice/test_core_slice.odin -file -out:test_core_slice
|
||||||
|
|
||||||
os_exit_test:
|
os_exit_test:
|
||||||
$(ODIN) run os/test_core_os_exit.odin -file -out:test_core_os_exit && exit 1 || exit 0
|
$(ODIN) run os/test_core_os_exit.odin -file -out:test_core_os_exit && exit 1 || exit 0
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,11 @@ echo Running core:reflect tests
|
|||||||
echo ---
|
echo ---
|
||||||
%PATH_TO_ODIN% run reflect %COMMON% %COLLECTION% -out:test_core_reflect.exe || exit /b
|
%PATH_TO_ODIN% run reflect %COMMON% %COLLECTION% -out:test_core_reflect.exe || exit /b
|
||||||
|
|
||||||
|
echo ---
|
||||||
|
echo Running core:slice tests
|
||||||
|
echo ---
|
||||||
|
%PATH_TO_ODIN% run slice %COMMON% -out:test_core_slice.exe || exit /b
|
||||||
|
|
||||||
echo ---
|
echo ---
|
||||||
echo Running core:text/i18n tests
|
echo Running core:text/i18n tests
|
||||||
echo ---
|
echo ---
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package test_core_slice
|
package test_core_slice
|
||||||
|
|
||||||
import "core:slice"
|
import "core:slice"
|
||||||
|
import "core:strings"
|
||||||
import "core:testing"
|
import "core:testing"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "core:os"
|
import "core:os"
|
||||||
@@ -30,6 +31,7 @@ when ODIN_TEST {
|
|||||||
main :: proc() {
|
main :: proc() {
|
||||||
t := testing.T{}
|
t := testing.T{}
|
||||||
test_sort_with_indices(&t)
|
test_sort_with_indices(&t)
|
||||||
|
test_binary_search(&t)
|
||||||
|
|
||||||
fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
|
fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
|
||||||
if TEST_fail > 0 {
|
if TEST_fail > 0 {
|
||||||
@@ -180,3 +182,64 @@ test_sort_by_indices :: proc(t: ^testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test
|
||||||
|
test_binary_search :: proc(t: ^testing.T) {
|
||||||
|
builder := strings.Builder{}
|
||||||
|
defer strings.builder_destroy(&builder)
|
||||||
|
|
||||||
|
test_search :: proc(t: ^testing.T, b: ^strings.Builder, s: []i32, v: i32) -> (int, bool) {
|
||||||
|
log(t, fmt.sbprintf(b, "Searching for %v in %v", v, s))
|
||||||
|
strings.builder_reset(b)
|
||||||
|
index, found := slice.binary_search(s, v)
|
||||||
|
log(t, fmt.sbprintf(b, "index: %v, found: %v", index, found))
|
||||||
|
strings.builder_reset(b )
|
||||||
|
|
||||||
|
return index, found
|
||||||
|
}
|
||||||
|
|
||||||
|
index: int
|
||||||
|
found: bool
|
||||||
|
|
||||||
|
s := []i32{0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55}
|
||||||
|
|
||||||
|
index, found = test_search(t, &builder, s, 13)
|
||||||
|
expect(t, index == 9, "Expected index to be 9.")
|
||||||
|
expect(t, found == true, "Expected found to be true.")
|
||||||
|
|
||||||
|
index, found = test_search(t, &builder, s, 4)
|
||||||
|
expect(t, index == 7, "Expected index to be 7.")
|
||||||
|
expect(t, found == false, "Expected found to be false.")
|
||||||
|
|
||||||
|
index, found = test_search(t, &builder, s, 100)
|
||||||
|
expect(t, index == 13, "Expected index to be 13.")
|
||||||
|
expect(t, found == false, "Expected found to be false.")
|
||||||
|
|
||||||
|
index, found = test_search(t, &builder, s, 1)
|
||||||
|
expect(t, index >= 1 && index <= 4, "Expected index to be 1, 2, 3, or 4.")
|
||||||
|
expect(t, found == true, "Expected found to be true.")
|
||||||
|
|
||||||
|
index, found = test_search(t, &builder, s, -1)
|
||||||
|
expect(t, index == 0, "Expected index to be 0.")
|
||||||
|
expect(t, found == false, "Expected found to be false.")
|
||||||
|
|
||||||
|
a := []i32{}
|
||||||
|
|
||||||
|
index, found = test_search(t, &builder, a, 13)
|
||||||
|
expect(t, index == 0, "Expected index to be 0.")
|
||||||
|
expect(t, found == false, "Expected found to be false.")
|
||||||
|
|
||||||
|
b := []i32{1}
|
||||||
|
|
||||||
|
index, found = test_search(t, &builder, b, 13)
|
||||||
|
expect(t, index == 1, "Expected index to be 1.")
|
||||||
|
expect(t, found == false, "Expected found to be false.")
|
||||||
|
|
||||||
|
index, found = test_search(t, &builder, b, 1)
|
||||||
|
expect(t, index == 0, "Expected index to be 0.")
|
||||||
|
expect(t, found == true, "Expected found to be true.")
|
||||||
|
|
||||||
|
index, found = test_search(t, &builder, b, 0)
|
||||||
|
expect(t, index == 0, "Expected index to be 0.")
|
||||||
|
expect(t, found == false, "Expected found to be false.")
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user