|
| 1 | +module binarysearch |
| 2 | + |
| 3 | +// Effectful binary search |
| 4 | +// ... inspired by Jules Jacobs https://julesjacobs.com/notes/binarysearch/binarysearch.pdf |
| 5 | +// ... ... and Brent Yorgey https://byorgey.wordpress.com/2023/01/01/competitive-programming-in-haskell-better-binary-search/ |
| 6 | + |
| 7 | +effect breakWith[A](value: A): Nothing |
| 8 | +effect mid[A](l: A, r: A): A / breakWith[(A, A)] |
| 9 | + |
| 10 | +def break2[A, B](x: A, y: B) = |
| 11 | + do breakWith((x, y)) |
| 12 | +def boundary[A] { prog: => A / breakWith[A] }: A = |
| 13 | + try prog() with breakWith[A] { a => a } |
| 14 | + |
| 15 | +def search[A](l: A, r: A) { predicate: A => Bool }: (A, A) / mid[A] = boundary[(A, A)] { |
| 16 | + def go(l: A, r: A): (A, A) = { |
| 17 | + val m = do mid(l, r) |
| 18 | + if (predicate(m)) { |
| 19 | + go(l, m) |
| 20 | + } else { |
| 21 | + go(m, r) |
| 22 | + } |
| 23 | + } |
| 24 | + go(l, r) |
| 25 | +} |
| 26 | + |
| 27 | +def binary[R] { prog: => R / mid[Int] }: R = |
| 28 | + try prog() with mid[Int] { (l, r) => |
| 29 | + resume { |
| 30 | + if ((r - l) > 1) { |
| 31 | + (l + r) / 2 |
| 32 | + } else { |
| 33 | + break2(l, r) |
| 34 | + } |
| 35 | + } |
| 36 | + } |
| 37 | + |
| 38 | +def main() = binary { |
| 39 | + def findSqrtUpTo(pow2: Int, max: Int) = { |
| 40 | + val (l, r) = search(0, max) { x => x * x >= pow2 } |
| 41 | + println("sqrt of " ++ pow2.show ++ " is between:") |
| 42 | + println(l.show ++ " (^2 = " ++ (l * l).show ++ ")") |
| 43 | + println(r.show ++ " (^2 = " ++ (r * r).show ++ ")") |
| 44 | + } |
| 45 | + |
| 46 | + // Comment out the first call below to get much better JS/Core codegen: |
| 47 | + findSqrtUpTo(150, 100) |
| 48 | + findSqrtUpTo(9876543210123, 9000000) |
| 49 | +} |
0 commit comments