|
@@ -117,46 +117,95 @@ linear_search_proc :: proc(array: $A/[]$T, f: proc(T) -> bool) -> (index: int, f
|
|
|
return -1, false
|
|
|
}
|
|
|
|
|
|
-@(require_results)
|
|
|
-binary_search :: proc(array: $A/[]$T, key: T) -> (index: int, found: bool)
|
|
|
- where intrinsics.type_is_ordered(T) #no_bounds_check {
|
|
|
+/*
|
|
|
+ Binary search searches the given slice for the given element.
|
|
|
+ If the slice is not sorted, the returned index is unspecified and meaningless.
|
|
|
|
|
|
- n := len(array)
|
|
|
- switch n {
|
|
|
- case 0:
|
|
|
- return -1, false
|
|
|
- case 1:
|
|
|
- if array[0] == key {
|
|
|
- return 0, true
|
|
|
- }
|
|
|
- return -1, false
|
|
|
- }
|
|
|
+ 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.
|
|
|
|
|
|
- lo, hi := 0, n-1
|
|
|
+ If the value is not found then the returned int is the index where a matching
|
|
|
+ element could be inserted while maintaining sorted order.
|
|
|
|
|
|
- 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
|
|
|
- }
|
|
|
+ # 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)
|
|
|
+binary_search :: proc(array: $A/[]$T, key: T) -> (index: int, found: bool)
|
|
|
+ where intrinsics.type_is_ordered(T) #no_bounds_check
|
|
|
+{
|
|
|
+ // I would like to use binary_search_by(array, key, cmp) here, but it doesn't like it:
|
|
|
+ // Cannot assign value 'cmp' of type 'proc($E, $E) -> Ordering' to 'proc(i32, i32) -> Ordering' in argument
|
|
|
+ return binary_search_by(array, key, proc(key: T, element: T) -> Ordering {
|
|
|
switch {
|
|
|
- case array[m] < key:
|
|
|
- lo = m + 1
|
|
|
- case key < array[m]:
|
|
|
- hi = m - 1
|
|
|
- case:
|
|
|
- return m, true
|
|
|
+ case element < key: return .Less
|
|
|
+ case element > key: return .Greater
|
|
|
+ case: return .Equal
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- 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)
|
|
|
equal :: proc(a, b: $T/[]$E) -> bool where intrinsics.type_is_comparable(E) {
|