├── .gitignore ├── 2017-10-10-BinarySearch.markdown ├── README.md └── src └── binarySearch ├── BinarySearchTest.java └── BinarySearcher.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | out 4 | 5 | -------------------------------------------------------------------------------- /2017-10-10-BinarySearch.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Two old programmers write a Binary Search 4 | tags: ["Software"] 5 | --- 6 | Andrew Koenig (ARK) wrote to me and said: 7 | 8 | >_"I have an idea for an essay, and wondered if you'd like to collaborate on it. A simple case study, really: How would you use TDD to write a binary-search function?"_ 9 | 10 | "That sounds like fun." I replied; and began to write this article. 11 | 12 | Andy continued: 13 | 14 | >_Sounds easy, but..._ 15 | 16 | >_Once upon a time, I had dinner with a fellow named Luther Woodrum, who had been introduced to me as IBM's leading export on sorting. He told me that he was in the habit of asking people in lectures, etc., to write a binary search, and almost no one could do it correctly, not even experienced professional programmers._ 17 | 18 | >_And yet it's obviously a simple concept. So the question is how to translate that simple concept into analogously simple code in a way that gives assurance that it actually works._ 19 | 20 | My first thought was to write the binary search in Java. This simplifies a lot of the memory management and pointer arithmetic issues. 21 | 22 | You can follow along with [this](https://github.com/unclebob/BinarySearch) github project. 23 | 24 | Since we know the algorithm already, we don't need to use TDD, or the [TPP](http://blog.cleancoder.com/uncle-bob/2013/05/27/TheTransformationPriorityPremise.html) to derive it. So we'll just write our tests to verify that the algorithm is correct. 25 | 26 | We begin, as usual, with an empty test, just to get an execution environment working. 27 | 28 | package binarySearch; 29 | import org.junit.Test; 30 | 31 | public class BinarySearchTest { 32 | @Test 33 | public void nothing() throws Exception { 34 | } 35 | } 36 | 37 | Next we'll write the test that forces us to create the class for the binary search. 38 | 39 | public class BinarySearchTest { 40 | @Test 41 | public void createSearcher() throws Exception { 42 | long array[] = new long[2]; 43 | BinarySearcher searcher = new BinarySearcher(array); 44 | 45 | } 46 | } 47 | 48 | ---- 49 | 50 | package binarySearch; 51 | 52 | public class BinarySearcher { 53 | public BinarySearcher(long[] array) { 54 | } 55 | } 56 | 57 | As you can see, we've made the decision to include the array to be searched in the constructor of the `BinarySearcher` class. 58 | 59 | The next few tests make sure we properly handle an invalid input array. 60 | 61 | @Test(expected = BinarySearcher.InvalidArray.class) 62 | public void nullInputThrowsException() throws Exception { 63 | BinarySearcher searcher = new BinarySearcher(null); 64 | } 65 | 66 | @Test(expected = BinarySearcher.InvalidArray.class) 67 | public void zeroSizedArrayThrowsException() throws Exception { 68 | BinarySearcher searcher = new BinarySearcher(new long[0]); 69 | } 70 | 71 | ---- 72 | 73 | public class BinarySearcher { 74 | public BinarySearcher(long[] array) { 75 | if (array == null || array.length == 0) 76 | throw new InvalidArray(); 77 | } 78 | 79 | public class InvalidArray extends RuntimeException { 80 | } 81 | } 82 | 83 | As we continue, you will note that we are following the principle of "Avoiding the gold". We will write tests around the _outside_ of the problem first; making sure that all the validation and organization is working before we head into the actual problem of doing a binary search. 84 | 85 | The next tests check that the input array can be verified as ordered. We used this approach because the check is expensive. Programmers who use this class may already know that their input is properly ordered. Other programmers may need the check. 86 | 87 | >_That's what Dijkstra described as wearing flotation vests during training and then discarding them for the actual voyage..._ 88 | 89 | @Test 90 | public void checkInOrder() throws Exception { 91 | long[][] inOrderArrays = { {0}, {0, 1}, {0, 1, 2, 3} }; 92 | for (long[] inOrder : inOrderArrays) { 93 | searcher = new BinarySearcher(inOrder); 94 | searcher.validate(); 95 | } 96 | } 97 | 98 | @Test 99 | public void outOfOrderThrowsException() throws Exception { 100 | long[][] outOfOrderArrays = { {1, 0}, {1, 0, 2}, {0, 2, 1}, {0, 1, 2, 4, 3} }; 101 | int exceptions = 0; 102 | for (long[] outOfOrder : outOfOrderArrays) { 103 | searcher = new BinarySearcher(outOfOrder); 104 | try { 105 | searcher.validate(); 106 | } catch (OutOfOrderArray e) { 107 | exceptions++; 108 | } 109 | } 110 | assertEquals(outOfOrderArrays.length, exceptions); 111 | } 112 | 113 | ---- 114 | 115 | public class BinarySearcher { 116 | private long[] array; 117 | 118 | //... 119 | 120 | public void validate() { 121 | for (int i=0; i array[i+1]) 123 | throw new OutOfOrderArray(); 124 | } 125 | 126 | //... 127 | 128 | public class OutOfOrderArray extends RuntimeException{ 129 | } 130 | } 131 | 132 | That concludes the perhiphery of the system. Now we can start to go for the gold. 133 | 134 | One of the internal calculations that must be made for a binary search, is the calculation of the midpoint. Given two positions in the array (`l` and `r`), this function must find the position that is the appropriate midpoint between them. This is typically done with `floor((l+r)/2)`. 135 | 136 | @Test 137 | public void findsProperMidpoint() throws Exception { 138 | assertEquals(0, findMidpoint(0, 0)); 139 | assertEquals(0, findMidpoint(0, 1)); 140 | assertEquals(1, findMidpoint(0, 2)); 141 | assertEquals(1, findMidpoint(0, 3)); 142 | assertEquals(Integer.MAX_VALUE/2, findMidpoint(0,Integer.MAX_VALUE)); 143 | } 144 | 145 | ---- 146 | 147 | public static int findMidpoint(int l, int r) { 148 | return (l+r)>>1; 149 | } 150 | 151 | This works so long as the sum `l+r` is `<` `Integer.MAX_VALUE`. But for very large arrays that may not be the case. So we should try those cases. 152 | 153 | `Integer.MAX_VALUE` is the largest positive integer. If you increment it you get a negative number. So you have to be careful how you do the math. The cases below explore that math. Make sure you understand them. 154 | 155 | @Test 156 | public void findsProperMidpoint() throws Exception { 157 | assertEquals(0, findMidpoint(0,0)); 158 | assertEquals(0, findMidpoint(0, 1)); 159 | assertEquals(1, findMidpoint(0, 2)); 160 | assertEquals(1, findMidpoint(0, 3)); 161 | assertEquals(Integer.MAX_VALUE/2, findMidpoint(0,Integer.MAX_VALUE)); 162 | assertEquals(Integer.MAX_VALUE/2+1, findMidpoint(1, Integer.MAX_VALUE)); 163 | assertEquals(Integer.MAX_VALUE/2+1, findMidpoint(2, Integer.MAX_VALUE)); 164 | assertEquals(Integer.MAX_VALUE/2+2, findMidpoint(3, Integer.MAX_VALUE)); 165 | assertEquals(Integer.MAX_VALUE/2, findMidpoint(1, Integer.MAX_VALUE-2)); 166 | } 167 | 168 | The solution is to divide the two indexes by two _first_ and then add them together. However, you also have to remember any possible carry from the low order bit, and add that in too. Thus: 169 | 170 | public static int findMidpoint(int l, int r) { 171 | int carry = ((l&1)+(r&1))>>1; 172 | return (l>>1)+(r>>1)+carry; 173 | } 174 | 175 | Now we can write some simply tests to check the search results. 176 | 177 | private void assertFound(int target, long[] domain) { 178 | BinarySearcher searcher = new BinarySearcher(domain); 179 | assertTrue(searcher.find(target)); 180 | } 181 | 182 | private void assertNotFound(int target, long[] domain) { 183 | BinarySearcher searcher = new BinarySearcher(domain); 184 | assertFalse(searcher.find(target)); 185 | } 186 | 187 | @Test 188 | public void simpleFinds() throws Exception { 189 | assertFound(0, new long[]{0}); 190 | assertFound(5, new long[]{0,1,5,7}); 191 | assertFound(7, new long[]{0,1,5,7}); 192 | 193 | assertNotFound(1, new long[]{0}); 194 | assertNotFound(6, new long[]{1,2,5,7,9}); 195 | } 196 | 197 | We can test these tests by writing a simple linear search. 198 | 199 | public boolean find(int element) { 200 | for (int i=0; i 0); 221 | assertTrue(spy.compares <= compares); 222 | } 223 | 224 | @Test 225 | public void logNCheck() throws Exception { 226 | assertCompares(1,1); 227 | assertCompares(5,32); 228 | assertCompares(16,65536); 229 | } 230 | } 231 | 232 | class BinarySearcherSpy extends BinarySearcher { 233 | public int compares = 0; 234 | 235 | public BinarySearcherSpy(long[] array) { 236 | super(array); 237 | } 238 | 239 | protected boolean find(int l, int r, int element) { 240 | compares++; 241 | return super.find(l, r, element); 242 | } 243 | } 244 | 245 | And, of course, the algorithm that passes these tests is: 246 | 247 | public boolean find(int element) { 248 | return find(0,array.length,element); 249 | } 250 | 251 | protected boolean find(int l, int r, int element) { 252 | if (l>=r) 253 | return false; 254 | int midpoint = findMidpoint(l,r); 255 | if (array[midpoint] == element) 256 | return true; 257 | else if (array[midpoint] < element) 258 | return find(midpoint+1, r, element); 259 | else 260 | return find(l, midpoint-1, element); 261 | } 262 | 263 | I showed this to Andy, and he said: 264 | 265 | >_Why do you consider it an error to be asked to search a zero-length array?_ 266 | 267 | That was an arbitrary decision on my part. Perhaps I was a bit hasty. A zero length array is simply the most degenerate form. By the rules of TDD, that should have been the first thing I tested. 268 | 269 | @Test 270 | public void zeroSizedArrayFindsNothing() throws Exception { 271 | searcher = new BinarySearcher(new long[0]); 272 | assertFalse(searcher.find(1)); 273 | } 274 | 275 | >_I don't see any test that would detect a validate function that incorrectly fails on two adjacent equal elements._ 276 | 277 | Hmmm. Good catch. (See, this pair programming stuff really works. ;-) 278 | 279 | @Test 280 | public void checkInOrder() throws Exception { 281 | long[][] inOrderArrays = { {0}, {0, 1}, {0, 1, 2, 3}, {0,1,1,2,3 } }; 282 | for (long[] inOrder : inOrderArrays) { 283 | searcher = new BinarySearcher(inOrder); 284 | searcher.validate(); 285 | } 286 | } 287 | 288 | >_`findsProperMidpoint` is too clever by half--in this case, literally. I would expect `Integer.MAX_VALUE` to be an odd number, which means that `Integer.MAX_VALUE/2` truncates away a half. It seems to me that `findsProperMidpoint` should require the midpoint to be exact if the bounds are an even number apart, but should permit rounding in either direction because either rounding will yield correct results. Otherwise, I think there's some overspecification going on._ 289 | 290 | Instead of rounding, I choose to use the `floor` of the midpoint simply because that's what the [wikipeadia](https://en.wikipedia.org/wiki/Binary_search_algorithm) article suggested. 291 | 292 | >_Is it true that if you increment `Integer.MAX_VALUE` (in Java) you get a negative number? Or do you get an overflow exception?_ 293 | 294 | Yes, at least empirically. I'm not sure what the spec says; but my implementation went negative when I incremented `Integer.MAX_VALUE`. 295 | 296 | >_By the way, here's a much, much simpler implementation of findMidpoint:_ 297 | 298 | public static int findMidpoint(int l, int r) { 299 | return l + (r - l)/2; 300 | } 301 | 302 | `` Um... Yeah, that works fine. `` 303 | 304 | >_When I proposed the original problem of testing a binary search, I did not actually specify the interface to the binary search. I think that the most useful interface is probably something like the C++ lower_bound library function, augmented to return a boolean. That is, the result of a binary search might reasonably be a boolean that indicates whether the value sought is in the array, together with the index of the first element that is >= the element sought (or one off the end if there is no such element)._ 305 | 306 | Hmmm. OK, that's an easy change (I think). 307 | 308 | private void assertFound(int target, int index, long[] domain) { 309 | BinarySearcher searcher = new BinarySearcher(domain); 310 | assertEquals(index, searcher.findLowerBound(target)); 311 | } 312 | 313 | private void assertNotFound(int target, long[] domain) { 314 | BinarySearcher searcher = new BinarySearcher(domain); 315 | assertEquals(domain.length, searcher.findLowerBound(target)); 316 | } 317 | 318 | @Test 319 | public void simpleFinds() throws Exception { 320 | assertFound(0, 0, new long[]{0}); 321 | assertFound(5, 2, new long[]{0,1,5,7}); 322 | assertFound(7, 3, new long[]{0,1,5,7}); 323 | 324 | assertNotFound(1, new long[]{0}); 325 | assertNotFound(6, new long[]{1,2,5,7,9}); 326 | } 327 | 328 | ---- 329 | 330 | public int findLowerBound(int element) { 331 | return findLowerBound(0,array.length,element); 332 | } 333 | 334 | protected int findLowerBound(int l, int r, int element) { 335 | if (l>=r) 336 | return array.length; 337 | int midpoint = findMidpoint(l,r); 338 | if (array[midpoint] == element) 339 | return midpoint; 340 | else if (array[midpoint] < element) 341 | return findLowerBound(midpoint+1, r, element); 342 | else 343 | return findLowerBound(l, midpoint-1, element); 344 | } 345 | 346 | Now I wonder if we don't need some more test cases -- especially with duplicates in the input array. 347 | 348 | private void assertFound(int target, int index, long[] domain) { 349 | BinarySearcher searcher = new BinarySearcher(domain); 350 | assertTrue(searcher.find(target)); 351 | assertEquals(index, searcher.findLowerBound(target)); 352 | } 353 | 354 | private void assertNotFound(int target, int index, long[] domain) { 355 | BinarySearcher searcher = new BinarySearcher(domain); 356 | assertFalse(searcher.find(target)); 357 | assertEquals(index, searcher.findLowerBound(target)); 358 | } 359 | 360 | @Test 361 | public void simpleFinds() throws Exception { 362 | assertFound(0, 0, new long[]{0}); 363 | assertFound(5, 2, new long[]{0,1,5,7}); 364 | assertFound(7, 3, new long[]{0,1,5,7}); 365 | assertFound(7, 5, new long[]{0,1,2,2,3,7,8}); 366 | assertFound(2, 2, new long[]{0,1,2,2,3,7,8}); 367 | assertFound(2, 2, new long[]{0,1,2,2,2,3,7,8}); 368 | 369 | assertNotFound(1, 1, new long[]{0}); 370 | assertNotFound(6, 3, new long[]{1,2,5,7,9}); 371 | assertNotFound(0, 0, new long[]{1,2,2,5,7,9}); 372 | assertNotFound(10, 6, new long[]{1,2,2,5,7,9}); 373 | } 374 | 375 | Yeah, that fails. OK, let's fix the algorithm a bit... 376 | 377 | public boolean find(int element) { 378 | int lowerBound = findLowerBound(element); 379 | return lowerBound < array.length && array[lowerBound] == element; 380 | } 381 | 382 | public int findLowerBound(int element) { 383 | return findLowerBound(0,array.length,element); 384 | } 385 | 386 | protected int findLowerBound(int l, int r, int element) { 387 | if (l==r) 388 | return l; 389 | int midpoint = findMidpoint(l,r); 390 | if (element > array[midpoint]) 391 | return findLowerBound(midpoint+1, r, element); 392 | else 393 | return findLowerBound(l, midpoint, element); 394 | } 395 | 396 | The searches all work, but the O(log n) test is failing. That's because there are a couple of extra compares now. I should adjust the test. 397 | 398 | private void assertCompares(int compares, int n) { 399 | long[] array = makeArray(n); 400 | BinarySearcherSpy spy = new BinarySearcherSpy(array); 401 | spy.findLowerBound(0); 402 | assertTrue(spy.compares > 0); 403 | assertTrue(""+spy.compares ,spy.compares <= compares+2); 404 | } 405 | 406 | Whew! OK, everything works again. Algorithm's a bit pretter too. The tests could use a bit of condensing; I don't think that's the minimal set. But let's leave that for awhile. I think there's more coming. 407 | 408 | >_I think it's interesting that you chose a recursive implementation of binary search, because that turns what could have been an algorithm that runs in O(log N) time and O(1) space into one that runs in O(log N) time and O(log N) space._ 409 | 410 | I chose recursion because it made it simple to write the spy to test O(log N). However, I was a bit careless since the algorithm can easily be turned into tail recursion. 411 | 412 | protected int findLowerBound(int l, int r, int element) { 413 | if (l==r) 414 | return l; 415 | int midpoint = findMidpoint(l,r); 416 | if (element > array[midpoint]) 417 | l = midpoint+1; 418 | else 419 | r = midpoint; 420 | 421 | return findLowerBound(l, r, element); 422 | } 423 | 424 | Sadly, Java does not (yet) support tail call optimization. 425 | 426 | >_Which brings up (again) the question of how one goes about testing code that has performance requirements._ 427 | 428 | Storage is a tough one for simple unit tests like this. We could spy on the heap calls. We could also zero out the stack, and inspect it after the calls to check stack depth. (Maybe the guys at Toyota wish they'd done that a bit more carefully.) 429 | 430 | As for execution time, our spy has managed to measure that for us by counting the number of loops (recursions). However, there are costs. The `findLowerBound` function is polymorphically deployed -- and must be in order for the spy to work. Polymorphic deployment requires a nanosecond or two. Tests are observers, and so there is some "observer effect" going on here. 431 | 432 | In order for our code to be testable we need to tolerate a certain amount of space and time overhead. We can work to minimize it; but eliminating it entirely would likely require extra hardware (like an emulator). 433 | 434 | >_So...let me suggest another strategy. I'm just going to talk about the innermost part of the search, and I want to implement it as a loop, not as a recursion. Also, as a long-time C programmer, I shall express the range asymmetrically, so I'll use `begin` and `end` as indices rather than `l` and `r`._ 435 | 436 | >_The essence of a binary search is that if the element we're seeking is in the array at all, it is in the range `[begin, end)`, so we can use that as a loop invariant. And then we can write:_ 437 | 438 | while (begin != end) { 439 | mid = findMidpoint(begin, end); 440 | if (array[mid] < element) 441 | begin = mid + 1; 442 | else 443 | end = mid; 444 | } 445 | 446 | >_Here, the asymmetry of the assignments reflect the asymmetry of the loop bounds. When we're done, `begin` and `end` are equal, and now, there are three cases:_ 447 | 448 | >_1) `end` is unchanged from its initial value, in which case every array element < the value sought._ 449 | 450 | >_2) The value sought < `array[end]`, in which case the value is not in the array and `array[end]` is the leftmost element greater than the value sought, or_ 451 | 452 | >_3) the value sought == `array[end]`. This is the only case in which a second comparison is necessary._ 453 | 454 | This is the beginnings of a Dijkstra style proof. Such a case analysis proof is feasible with a small function like this. But as the state space grows... 455 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BinarySearch 2 | -------------------------------------------------------------------------------- /src/binarySearch/BinarySearchTest.java: -------------------------------------------------------------------------------- 1 | package binarySearch; 2 | 3 | import binarySearch.BinarySearcher.*; 4 | import org.junit.Test; 5 | 6 | import static binarySearch.BinarySearcher.findMidpoint; 7 | import static org.junit.Assert.assertEquals; 8 | import static org.junit.Assert.assertFalse; 9 | import static org.junit.Assert.assertTrue; 10 | 11 | public class BinarySearchTest { 12 | private BinarySearcher searcher; 13 | 14 | @Test 15 | public void createSearcher() throws Exception { 16 | searcher = new BinarySearcher(new long[1]); 17 | } 18 | 19 | @Test(expected = InvalidArray.class) 20 | public void nullInputThrowsException() throws Exception { 21 | searcher = new BinarySearcher(null); 22 | } 23 | 24 | @Test 25 | public void zeroSizedArrayFindsNothing() throws Exception { 26 | searcher = new BinarySearcher(new long[0]); 27 | assertEquals(0, searcher.findLowerBound(1)); 28 | } 29 | 30 | @Test 31 | public void checkInOrder() throws Exception { 32 | long[][] inOrderArrays = {{0}, {0, 1}, {0, 1, 2, 3}, {0,1,1,2,3 }}; 33 | for (long[] inOrder : inOrderArrays) { 34 | searcher = new BinarySearcher(inOrder); 35 | searcher.validate(); 36 | } 37 | } 38 | 39 | @Test 40 | public void outOfOrderThrowsException() throws Exception { 41 | long[][] outOfOrderArrays = {{1, 0}, {1, 0, 2}, {0, 2, 1}, {0, 1, 2, 4, 3}}; 42 | int exceptions = 0; 43 | for (long[] outOfOrder : outOfOrderArrays) { 44 | searcher = new BinarySearcher(outOfOrder); 45 | try { 46 | searcher.validate(); 47 | } catch (OutOfOrderArray e) { 48 | exceptions++; 49 | } 50 | } 51 | assertEquals(outOfOrderArrays.length, exceptions); 52 | } 53 | 54 | @Test 55 | public void findsProperMidpoint() throws Exception { 56 | assertEquals(0, findMidpoint(0,0)); 57 | assertEquals(0, findMidpoint(0, 1)); 58 | assertEquals(1, findMidpoint(0, 2)); 59 | assertEquals(1, findMidpoint(0, 3)); 60 | assertEquals(Integer.MAX_VALUE/2, findMidpoint(0,Integer.MAX_VALUE)); 61 | assertEquals(Integer.MAX_VALUE/2+1, findMidpoint(1, Integer.MAX_VALUE)); 62 | assertEquals(Integer.MAX_VALUE/2+1, findMidpoint(2, Integer.MAX_VALUE)); 63 | assertEquals(Integer.MAX_VALUE/2+2, findMidpoint(3, Integer.MAX_VALUE)); 64 | assertEquals(Integer.MAX_VALUE/2, findMidpoint(1, Integer.MAX_VALUE-2)); 65 | 66 | } 67 | 68 | private void assertFound(int target, int index, long[] domain) { 69 | BinarySearcher searcher = new BinarySearcher(domain); 70 | assertTrue(searcher.find(target)); 71 | assertEquals(index, searcher.findLowerBound(target)); 72 | } 73 | 74 | private void assertNotFound(int target, int index, long[] domain) { 75 | BinarySearcher searcher = new BinarySearcher(domain); 76 | assertFalse(searcher.find(target)); 77 | assertEquals(index, searcher.findLowerBound(target)); 78 | } 79 | 80 | @Test 81 | public void simpleFinds() throws Exception { 82 | assertFound(0, 0, new long[]{0}); 83 | assertFound(5, 2, new long[]{0,1,5,7}); 84 | assertFound(7, 3, new long[]{0,1,5,7}); 85 | assertFound(7, 5, new long[]{0,1,2,2,3,7,8}); 86 | assertFound(2, 2, new long[]{0,1,2,2,3,7,8}); 87 | assertFound(2, 2, new long[]{0,1,2,2,2,3,7,8}); 88 | 89 | assertNotFound(1, 1, new long[]{0}); 90 | assertNotFound(6, 3, new long[]{1,2,5,7,9}); 91 | assertNotFound(0, 0, new long[]{1,2,2,5,7,9}); 92 | assertNotFound(10, 6, new long[]{1,2,2,5,7,9}); 93 | } 94 | 95 | long[] makeArray(int n) { 96 | long[] array = new long[n]; 97 | for (int i=0; i 0); 107 | assertTrue(""+spy.compares ,spy.compares <= compares+2); 108 | } 109 | 110 | @Test 111 | public void logNCheck() throws Exception { 112 | assertCompares(1,1); 113 | assertCompares(5,32); 114 | assertCompares(16,65536); 115 | } 116 | } 117 | 118 | class BinarySearcherSpy extends BinarySearcher { 119 | public int compares = 0; 120 | 121 | public BinarySearcherSpy(long[] array) { 122 | super(array); 123 | } 124 | 125 | protected int findLowerBound(int l, int r, int element) { 126 | compares++; 127 | return super.findLowerBound(l, r, element); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/binarySearch/BinarySearcher.java: -------------------------------------------------------------------------------- 1 | package binarySearch; 2 | 3 | public class BinarySearcher { 4 | private long[] array; 5 | 6 | public BinarySearcher(long[] array) { 7 | this.array = array; 8 | if (array == null) 9 | throw new InvalidArray(); 10 | } 11 | 12 | public void validate() { 13 | for (int i=0; i array[i+1]) 15 | throw new OutOfOrderArray(); 16 | } 17 | 18 | public static int findMidpoint(int l, int r) { 19 | return l + (r - l)/2; 20 | } 21 | 22 | public boolean find(int element) { 23 | int lowerBound = findLowerBound(element); 24 | return lowerBound < array.length && array[lowerBound] == element; 25 | } 26 | 27 | public int findLowerBound(int element) { 28 | return findLowerBound(0,array.length,element); 29 | } 30 | 31 | protected int findLowerBound(int l, int r, int element) { 32 | if (l==r) 33 | return l; 34 | int midpoint = findMidpoint(l,r); 35 | if (element > array[midpoint]) 36 | l = midpoint+1; 37 | else 38 | r = midpoint; 39 | 40 | return findLowerBound(l, r, element); 41 | } 42 | 43 | public static class InvalidArray extends RuntimeException { 44 | } 45 | 46 | public static class OutOfOrderArray extends RuntimeException{ 47 | } 48 | } 49 | --------------------------------------------------------------------------------