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 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
|
(* Binary search
A classical example. Searches a sorted array for a given value v. *)
module BinarySearch
use int.Int
use int.ComputerDivision
use ref.Ref
use array.Array
(* the code and its specification *)
exception Not_found (* raised to signal a search failure *)
let binary_search (a: array int) (v: int) : int
requires { forall i1 i2. 0 <= i1 <= i2 < length a -> a[i1] <= a[i2] }
ensures { 0 <= result < length a /\ a[result] = v }
raises { Not_found -> forall i. 0 <= i < length a -> a[i] <> v }
=
let ref l = 0 in
let ref u = length a - 1 in
while l <= u do
invariant { 0 <= l /\ u < length a }
invariant {
forall i. 0 <= i < length a -> a[i] = v -> l <= i <= u }
variant { u - l }
let m = l + div (u - l) 2 in
assert { l <= m <= u };
if a[m] < v then
l := m + 1
else if a[m] > v then
u := m - 1
else
return m
done;
raise Not_found
end
(* A generalization: the midpoint is computed by some abstract function.
The only requirement is that it lies between l and u. *)
module BinarySearchAnyMidPoint
use int.Int
use ref.Ref
use array.Array
exception Not_found (* raised to signal a search failure *)
val midpoint (l: int) (u: int) : int
requires { l <= u } ensures { l <= result <= u }
let binary_search (a: array int) (v: int) : int
requires { forall i1 i2. 0 <= i1 <= i2 < length a -> a[i1] <= a[i2] }
ensures { 0 <= result < length a /\ a[result] = v }
raises { Not_found -> forall i. 0 <= i < length a -> a[i] <> v }
=
let ref l = 0 in
let ref u = length a - 1 in
while l <= u do
invariant { 0 <= l /\ u < length a }
invariant { forall i. 0 <= i < length a -> a[i] = v -> l <= i <= u }
variant { u - l }
let m = midpoint l u in
if a[m] < v then
l := m + 1
else if a[m] > v then
u := m - 1
else
return m
done;
raise Not_found
end
(* The following version of binary search is faster in practice, by being
friendlier with the branch predictor of most processors. It also happens
to be stable, since it always return the highest index. *)
module BinarySearchBranchless
use int.Int
use int.ComputerDivision
use ref.Ref
use array.Array
exception Not_found (* raised to signal a search failure *)
let binary_search (a: array int) (v: int) : int
requires { forall i1 i2. 0 <= i1 <= i2 < length a -> a[i1] <= a[i2] }
ensures { 0 <= result < length a /\ a[result] = v }
ensures { forall i. result < i < length a -> a[i] <> v }
raises { Not_found -> forall i. 0 <= i < length a -> a[i] <> v }
=
let ref l = 0 in
let ref s = length a in
if s = 0 then raise Not_found;
while s > 1 do
invariant { 0 <= l /\ l + s <= length a /\ s >= 1 }
invariant {
forall i. 0 <= i < length a -> a[i] = v -> a[l] <= v /\ i < l + s }
variant { s }
let h = div s 2 in
let m = l + h in
l := if a[m] > v then l else m;
s := s - h;
done;
if a[l] = v then l
else raise Not_found
end
(* binary search using 32-bit integers *)
module BinarySearchInt32
use int.Int
use mach.int.Int32
use ref.Ref
use mach.array.Array32
exception Not_found (* raised to signal a search failure *)
let binary_search (a: array int32) (v: int32) : int32
requires { forall i1 i2. 0 <= i1 <= i2 < a.length -> a[i1] <= a[i2] }
ensures { 0 <= result < a.length /\ a[result] = v }
raises { Not_found -> forall i. 0 <= i < a.length -> a[i] <> v }
=
let ref l = 0 in
let ref u = length a - 1 in
while l <= u do
invariant { 0 <= l /\ u < a.length }
invariant { forall i. 0 <= i < a.length -> a[i] = v -> l <= i <= u }
variant { u - l }
let m = l + (u - l) / 2 in
assert { l <= m <= u };
if a[m] < v then
l := m + 1
else if a[m] > v then
u := m - 1
else
return m
done;
raise Not_found
end
(* A particular case with Boolean values (0 or 1) and a sentinel 1 at the end.
We look for the first position containing a 1. *)
module BinarySearchBoolean
use int.Int
use int.ComputerDivision
use ref.Ref
use array.Array
let binary_search (a: array int) : int
requires { 0 < a.length }
requires { forall i j. 0 <= i <= j < a.length -> 0 <= a[i] <= a[j] <= 1 }
requires { a[a.length - 1] = 1 }
ensures { 0 <= result < a.length }
ensures { a[result] = 1 }
ensures { forall i. 0 <= i < result -> a[i] = 0 }
=
let ref lo = 0 in
let ref hi = length a - 1 in
while lo < hi do
invariant { 0 <= lo <= hi < a.length }
invariant { a[hi] = 1 }
invariant { forall i. 0 <= i < lo -> a[i] = 0 }
variant { hi - lo }
let mid = lo + div (hi - lo) 2 in
if a[mid] = 1 then
hi := mid
else
lo := mid + 1
done;
lo
end
module Complexity
use int.Int
use int.ComputerDivision
use ref.Ref
use array.Array
let rec function log2 (n: int) : int
variant { n }
= if n <= 1 then 0 else 1 + log2 (div n 2)
let rec lemma log2_monotone (x y: int)
requires { x <= y }
ensures { log2 x <= log2 y }
variant { y }
= if y > 1 then log2_monotone (div x 2) (div y 2)
let function f (n: int) : int
= if n = 0 then 0 else 1 + log2 n
lemma upper_bound:
forall n. n >= 2 -> f n <= 2 * log2 n
val ref time: int
let binary_search (a: array int) (v: int) : int
requires { forall i1 i2. 0 <= i1 <= i2 < length a -> a[i1] <= a[i2] }
requires { time = 0 }
ensures { 0 <= result < length a && a[result] = v
|| result = -1 && forall i. 0 <= i < length a -> a[i] <> v }
ensures { time - old time <= f (length a) }
=
let ref lo = 0 in
let ref hi = length a in
while lo < hi do
invariant { 0 <= lo <= hi <= length a }
invariant { forall i. 0 <= i < lo || hi <= i < length a -> a[i] <> v }
invariant { (time - old time) + f (hi - lo) <= f (length a) }
variant { hi - lo }
let mid = lo + div (hi - lo) 2 in
if a[mid] < v then
lo <- mid + 1
else if a[mid] > v then
hi <- mid
else
return mid;
time <- time + 1
done;
-1
end
(* Search in a two-dimensional grid where all rows and columns are
sorted. Here is an example:
j
+---+---+---+---+---+-->
| 1 | 3 | 4 | 4 | 7*|
+---+---+---+---+---+
| 1 | 4 | 6 | 6 | 8*|
+---+---+---+---+---+
| 2 | 5 | 7 | 9*|11*|
+---+---+---+---+---+
| 4 | 8 | 8*|12*|13 |
+---+---+---+---+---+
| *
i v
Algorithm: start from the upper right corner and then move left
(resp. down) when the value is greater (resp. smaller) than the
value we search for.
In the example above, the stars depict the elements that are
examined when we search for the value 10. Here we end up outside of
the grid and thus the search is unsuccessful.
*)
module TwoDimensional
use int.Int
use matrix.Matrix
let search (m: matrix int) (v: int) : bool
requires { [@expl: rows are sorted] forall i. 0 <= i < rows m ->
forall j1 j2. 0 <= j1 <= j2 < columns m ->
get m i j1 <= get m i j2 }
requires { [@expl: columns are sorted] forall j. 0 <= j < columns m ->
forall i1 i2. 0 <= i1 <= i2 < rows m ->
get m i1 j <= get m i2 j }
ensures { result <->
exists i j. 0 <= i < rows m && 0 <= j < columns m && get m i j = v }
= let ref i = 0 in
let ref j = columns m - 1 in
while i < rows m && 0 <= j do
invariant { 0 <= i <= rows m }
invariant { -1 <= j < columns m }
invariant { forall i' j'. 0 <= i' < rows m -> 0 <= j' < columns m ->
i' < i || j' > j -> get m i' j' <> v }
variant { rows m - i + j }
let x = get m i j in
if x = v then return true;
if x < v then i <- i + 1 else j <- j - 1
done;
return false
end
|