1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
use std::ops::{Shr, Add, BitAnd};

pub trait Betweenable where Self: Copy {
  fn between(x: Self, y: Self) -> Option<Self>;
}

impl<X> Betweenable for X
    where
      X: Copy,
      X: Shr<i8, Output=X>,
      X: Add<X, Output=X>,
      X: BitAnd<X, Output=X>,
      X: From<u8>,
      X: PartialOrd {
  fn between(low: Self, high: Self) -> Option<Self> {
    let one = X::from(1);
    if high <= low + one {
      None
    } else {
      let mid = (low >> 1) + (high >> 1) + (low & high & one);
      Some(mid)
    }
  }
}

pub enum Direction<A, B> {
  Low(A),
  High(B),
}

pub fn binary_search<X, A, B, F>(
    low: (X, A),
    high: (X, B),
    f: F,
  ) -> ((X, A), (X, B))
  where
    X: Betweenable,
    F: Fn(X) -> Direction<A, B> {
  match X::between(low.0, high.0) {
    None => {
      (low, high)
    },
    Some(x) => {
      match (f)(x) {
        Direction::Low(low) => {
          binary_search((x, low), high, f)
        },
        Direction::High(high) => {
          binary_search(low, (x, high), f)
        },
      }
    }
  }
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn split_usize() {
    assert_eq!(usize::between(1, 0), None);
    assert_eq!(usize::between(1, 1), None);
    assert_eq!(usize::between(1, 2), None);
    assert_eq!(usize::between(1, 3), Some(2));
    assert_eq!(
      usize::between(usize::max_value()-3, usize::max_value()-1),
      Some(usize::max_value()-2),
    );
    assert_eq!(
      usize::between(usize::max_value()-2, usize::max_value()),
      Some(usize::max_value()-1),
    );
  }

  #[test]
  fn binary_search_test() {
    let result =
      binary_search((1 as usize, ()), (100, ()), |x| {
        if x < 23 {
          Direction::Low(())
        } else {
          Direction::High(())
        }
      });
    assert_eq!(result, ((22, ()), (23, ())))
  }
}