Are you one of the 10% of programmers who can write a binary search?

There are some programming books that I’ve read from cover to cover repeatedly; there are others that I have dipped into many times, reading a chapter or so at a time. Jon Bentley’s 1986 classic Programming Pearls is a rare case where both of these are true, as the scuffs at the bottom of my copy’s cover attest:

(I have the First Edition [amazon.com, amazon.co.uk], so that’s what I scanned for the cover image above, but it would probably make more sense to get the newer and cheaper Second Edition [amazon.com, amazon.co.uk] which apparently has three additional chapters.)

Only 10% of programmers can write a binary search

Every single time I read Programming Pearls, this passage brings me up short:

Binary search solves the problem [of searching within a pre-sorted array] by keeping track of a range within the array in which T [i.e. the sought value] must be if it is anywhere in the array. Initially, the range is the entire array. The range is shrunk by comparing its middle element to T and discarding half the range. The process continues until T is discovered in the array, or until the range in which it must lie is known to be empty. In an N-element table, the search uses roughly log(2) N comparisons.

Most programmers think that with the above description in hand, writing the code is easy; they’re wrong. The only way you’ll believe this is by putting down this column right now and writing the code yourself. Try it.

I’ve assigned this problem in courses at Bell Labs and IBM. Professional programmers had a couple of hours to convert the above description into a program in the language of their choice; a high-level pseudocode was fine. At the end of the specified time, almost all the programmers reported that they had correct code for the task. We would then take thirty minutes to examine their code, which the programmers did with test cases. In several classes and with over a hundred programmers, the results varied little: ninety percent of the programmers found bugs in their programs (and I wasn’t always convinced of the correctness of the code in which no bugs were found).

I was amazed: given ample time, only about ten percent of professional programmers were able to get this small program right. But they aren’t the only ones to find this task difficult: in the history in Section 6.2.1 of his Sorting and Searching, Knuth points out that while the first binary search was published in 1946, the first published binary search without bugs did not appear until 1962.

— Jon Bentley, Programming Pearls (1st edition), pp. 35-36.

Several hours! Ninety percent! Dude, SRSLY! Isn’t that terrifying?

One of the reasons I’d like to see a copy of the Second Edition is to see whether this passage has changed — whether the numbers improved between 1986 and the Second-Edition date of 1999. My gut tells me that the numbers must have improved, that things can’t be that bad; yet logic tells me that in an age when programmers spend more time plugging libraries together than writing actual code, core algorithmic skills are likely if anything to have declined. And remember, these were not doofus programmers that Bentley was working with: they were professionals at Bell Labs and IBM. You’d expect them to be well ahead of the curve.

And so, the Great Binary Search Experiment

I would like you, if you would, to go away and do the exercise right now. (Well, not right now. Finish reading this article first!) I am confident that nearly everyone who reads this blog is already familiar with the binary search algorithm, but for those of you who are not, Bentley’s description above should suffice. Please fire up an editor buffer, and write a binary search routine. When you’ve decided it’s correct, commit to that version. Then test it, and tell me in the comments below whether you got it right first time. Surely — surely — we can beat Bentley’s 10% hit-rate?

Here are the rules:

Use whatever programming language you like.

No cutting, pasting or otherwise copying code. Don’t even look at other binary search code until you’re done.

I need hardly say, no calling bsearch(), or otherwise cheating :-)

Take as long as you like — you might finish, and feel confident in your code, after five minutes; or you’re welcome to take eight hours if you want (if you have the time to spare).

You’re allowed to use your compiler to shake out mechanical bugs such as syntax errors or failure to initialise variables, but …

NO TESTING until after you’ve decided your program is correct.

Finally, the most important one: if you decide to begin this exercise, then you must report — either to say that you succeeded, failed or abandoned the attempt. Otherwise the figures will be skewed towards success.

(For the purposes of this exercise, the possibility of numeric overflow in index calculations can be ignored. That condition is described here but DO NOT FOLLOW THAT LINK until after writing your program, if you’re participating, because the article contains a correct binary search implementation that you don’t want to see before working on your clean-room implementation.)

If your code does turn out to be correct, and if you wish, you’re welcome to paste that code into your comment … But if you do, and if a subsequent commenter points out a bug in it, you need to be prepared to deal with the public shame :-)

For extra credit: those of you who are really confident in your programming chops may write the program, publish it in a comment here and then test it. If you do that, you’ll probably want to mention the fact in your comment, so we cut you extra slack when we find your bugs.

I will of course summarise the results of this exercise — let’s say, in one week’s time.

Let’s go!

Update (an hour and a half later)

Thanks for the many posted entries already! I should have warned you that the WordPress comment system interprets HTML, and so eats code fragments like

if a[mid] < value

The best way to avoid this is to wrap your source code in {source}…{/source} tags, but using square brackets rather than curly. (The first time I tried to tell you all this, I used literal square brackets, and my markup-circumvention instructions were themselves marked up — D’oh!). Do not manually escape < and > as &lt; and &gt; — the {source} wrapper deals with these. Doing it this way also has the benefit of preserving indentation, which no other method seems to do.

And an apology for WordPress: I really, really wish that this platform allowed commenters to preview their comments and/or edit them after posting, so that all the screwed-up source code could have been avoided. I’ve tried to go and fix some of them myself, but — arrgh! — it turns out that WordPress not only displays code with < symbols wrongly, it actually throws away what follows, so there’s nothing for me to restore.

Update 2 (four hours after the initial post)

Wow, you guys are amazing. Four hours, and this post already has more comments than the previous record holder (Whatever Happened to Programming, 206 comments at the time of writing.)

995 responses to “Are you one of the 10% of programmers who can write a binary search?”

I have the second edition and can tell you that the section you quoted above is essentially unchanged. The reference to IBM and Bell Labs was replaced by the more general “I’ve assigned this problem in courses for professional programmers” but the numbers are still there.

Gah, once I hit “put down this column and write the code yourself”, I did. Failed to read the rules that said don’t test it. So essentially, I failed by not reading specifications, which is probably just as bad.

@Josh: You sometimes return a boolean (False) and sometimes an integer (mid). Assuming you meant to return True instead of mid, you risk an infinite loop because you don’t guarantee that your interval gets smaller each step.

Don’t forget to account for numbers outside the range of your sorted array. I forgot it in my first attempt, so searching for something less than the first element or greater than the last would result in an infinite loop!

I was not confident enough to post it without testing, and rightly so because I had a bug because I wrote the code in a way that seemed very elegant to me but that turned out to loop forever.

The original comparison was:

if (array[h] = x) hi = h + 1;

Which would terminate the loop immediately if it so happened that array[h] == x, but which would lead to an infinite loop if the interval was 2 elements large with the first element smaller than the search value and the second element larger (as with my first test case).

Wrote a recursive solution in python with more or less no error checking. Worked until I changed from printing results to returning them and forgot to make the recursive calls return statements. Otherwise working fairly well. What am I missing?

@Josh: If you want to return False, then the case of “low == high” is wrong because it returns “array[low] == x”. I think you can just remove that case altogether as long as you fix the recursion parameters.

Don’t forget to account for numbers outside the range of your sorted array. I forgot it in my first attempt, so searching for something less than the first element or greater than the last would result in an infinite loop!

I wasn’t going to bother posting, but since the only Python versions so far are recursive, here’s a really awful “whipped up in five minutes” iterative version. Hopefully, WP won’t mangle this too badly.

Untested (but compiled). Scrolling down to the comment box, I couldn’t help but glance at the other submissions. Fortunately, due to their similarity, that only increased my faith in my own attempt. Perhaps it would be best if they were somehow hidden for the next week, though…

I wish there were a comment preview – no idea whether this will be formatted correctly.

That was fun – I took about 10 minutes and couldn’t wait to try it so I failed the test. I’m in the 90%. My submission has two problems, it compares the value to the index instead of the value and it doesn’t terminate when the value isn’t there. Both problems were easily fixed once I identified them.

In terms of "Worked on first try"… well, sortakinda. :) I needed to add arrays.sort. Once the array was, in fact, sorted, the algorithm SEEMS to work. If I put in an element that's not there, it returns -1, if I put in an element that is there, it finds it. I'm sure I'm missing something, I always do….

Wrote it first, tested it afterwards, posting finally. As far as I can test it, no bugs found, and recognizes unavailable elements correctly. *phew*. Wrote and checked the code in 20 minutes or so. I Used Java List instead of arrays, since it’s easier to sort in my tests. But there’s no magical properties…

Did it recursively. Returns the index in which the element is, or throws a suitable exception if not found.

Here’s an updated post now that I can read how to mark up the code s.t. wordpress is happy.

Time taken: 8 minutes

Result: failed. My original attempt had “end = mid – 1″ instead of “end = mid”. That’s the only change I made from the original version.

Total testing/fixing time: 5 minutes

So 13 minutes total. After the off-by-one I mentioned above was fixed, it passed my tests and the other regression tests other people have posted in C… but still, my first version was a failure, so I fail the test. :)

This was my original version, and I've found 1 bug, my +1 is on the wrong branch.

The no testing clause was a killer on this, because I'd normally do something like this with unit tests via TDD. So, I did not get a working version in my first cut, but would have if I had written it as I normally write code.

This is not the way I wrote it when assigned this 10 years ago in college (I’m pretty sure I had the non-recursive loop with 3 comparisons), but it is the way we were shown to do it after turning it in.
I’ve never had to do anything like this in my career to date, for whatever reason (probably because I’m a java developer working on web based apps).

Well, I’m clearly not ready to join the ranks of the great and good just yet… I got my “greater than” and “less than” the wrong way around. That amended, here it is, tested, as a Smalltalk method definition:

I did manual testing by mentally stepping through my pseudocode with a few test inputs, which identified a couple of bugs that I fixed. Hope that is not cheating! After I decided that pseudocode was correct, here it is translated into Python:

You didn’t seem to provide an email to contact you with, so I’ll comment here.

I wrote my response in Python, and there did turn out to be two bugs. One, I mixed up the label for the length of the incoming list and the label for the point to be searched, and two I forgot divide the length in half when I recursed. Ah well. Got it right in about ten minutes though, including the testing.

in C, it took me 1 hour with all the cosmetics (randomly initializing an array or user-provided size and sorting it…). I think the core function took me about 20 minutes. Damn, so much longer than I thought.

Test for uneven number of elements:
Not in list, below: None
Not in list, above: None
Finding all the elements in the list:
Element 0 (with value 5) was found at: None
Element 1 (with value 6) was found at: None
Element 2 (with value 7) was found at: 2
Element 3 (with value 8) was found at: None
Element 4 (with value 9) was found at: None
Test for even number of elements:
Not in list, below: None
Not in list, above: None
Finding all the elements in the list:
Element 0 (with value 5) was found at: None
Element 1 (with value 7) was found at: None
Element 2 (with value 12) was found at: 2
Element 3 (with value 15) was found at: None

Wept a little, and corrected the 2 (two!) bugs, which were extremely obvious after the fact, namely that the greater-than should be less-than and that the index returned is wrong because I forgot to add the pivot when splitting. The end result is this:

Wrote this and thought through some test cases on paper. Took about 40 min, over which time I also pared it down from about 2x as long. Tested using arrays of 0,1,2,3 elements with search items hitting each as well as missing below and between each.
[pre]
def binarySearch(A, t):
a, b = 0, len(A)-1
while b >= a:
mp = a + (b-a)/2
if A[mp] == t:
return t
elif A[mp] < t:
a = mp+1
else:
b = mp-1
return None
[/pre]

# vim:tabstop=8:shiftwidth=4:smarttab:expandtab:softtabstop=4:autoindent:
# Python 2.5.4
# Non-obvious ends of blocks have been indicated with comments, in
# case the indents get lost when posting as a comment on the blog.
# This code has only been syntax checked.

Next, describe an algorithm to implement an insertion sort where you insert data into the array, in the correct position so that the array doesn’t need to be sorted before use. Finally, alter that algorithm for dealing with inserting already sorted data into the array. As someone once said, been there, done that… :-)

In any case, these algorithms appear simple on the face of it, but implementing them correctly can take a considerable of time and doh!

BTW, you only mentioned not cheating by use of bsearch() – what about qsort()? :-)

After writing this I looked at some of the comments. One that I thought was particularly interesting was @Juanjo’s, which I think will infinite-loop if you look for, say, 3 in the one-element array [2]. Haven’t tested that, though.

Steve Witham: yes, the <pre> tag does sort of work in WordPress comments, but it won’t do all the things you’d want it to do; in particular, it throws away indentation, which is pretty critical in code samples (especially if you write Python). Instead, use {source}…{/source}, but use square brackets instead of curlies.

Doh! Of course I missed the point about returning the index of item in the array (which the description from the book doesn’t really point out). After a bit of thought here was my updated version to do that.

Iterative python, seems like a lot of people did the same. Once I remembered how the range of an array is specified, think it is OK. Caught the index out of bounds condition that others seemed to miss. Wouldn’t take a bet that something else is missing though!

#tail recursive binary search in python. Returns -1 or index of item in array
def bsearch(arr, value, lidx=None, hidx=None):
if arr is None:
return -1
if lidx==None:
lidx=0
hidx=len(arr)
#get the middle index and middle value in the section of the array
midx = (hidx+lidx)/2
midval = arr[midx]
# see if we found it
if midval == value:
return midx
#if we didn't find it, and there is nothing else to search, return not found
elif hidx == lidx:
return -1
# if we found it and it is greater, look in the top half
elif value > midval:
return bsearch(arr,value,midx+1,hidx)
# value < midval, look in the bottom half
else :
return bsearch(arr,value,lidx,midx)

Cool Post!
Almost correct in python on first try…. It would return the correct index, and would handle out-of-bounds conditions, but with a search value in the range of but not in the list I hit a loop. I fixed it on the 2nd try. You can decide whether to count it as a success or fail.

Whipped this up in Obj-C. Haven’t even compiled it, much less tested it. But I believe it should work (shouldn’t even have the integer overflow bug documented in that link, and yes I wrote the code before following the link).

Updated (and simpler) test section for my implementation, taking into account the need (mentioned in other comments) to test for numbers outside the range of values in the list and to test for numbers that are in the range, but not in the list:

I was confident in my code until I was about to write the first testcase and realized that I’d forgotten to test for an array size of zero, but otherwise bugfree. Unlike most of the code posted here, I tested the target value against the first and last entry before doing a divide and conquer loop, since the definition states that the range being tested is the range in which the value must lie.

Here’s my go. Took me about 20 minutes. I guess I technically failed as it didn’t work on my first test, but it was really just a typo (I had first/last reverse on line 13). After fixing that, it seems to work fine:

It would be helpful if you had a test set of searches that could be used to verify correctness.

Brad and others have expressed a wish that I’d provided a set of testcases. Unfortunately, to be useful such a set would need to be made available in a language usable by the code being tested, and as at least a dozen different languages have been used that’s not really feasible. Maybe I should have picked a lowest-common-denominator language like Java and mandated that … but I am glad I didn’t. It’s done my heart good to see the range of languages used here.

Tested, but no corrections seemed necessary. Of course, I iterated through the three examples at the bottom of my code by hand. Not my prettiest code, but it handles the major cases. Took me about 35 minutes, including testing. The code was written in about 20 minutes with two bug fixes discovered during hand iteration (needed to add bot to mid if lowering the upper bound and mid to mid if raising the lower bound). I’ve not tried it with a larger data set.

“Mike – you could just provide test cases in pseudocode and leave it up to us to convert it to our language.”

I guess. But in part, too, I deliberately held off because I know that some of the test cases will immediately show people aspects of the problem that I wanted to see whether they’d spot for themselves. One obvious example is the zero-length-array test case: as Kernighan Pike say, “Make sure your code “does nothing” gracefully”. And, sure enough, a few commenters have been brave and honest enough to admit that they overlooked that case.

There have been so many interesting comments here (and on Reddit and Hacker News) that I have lots of material for a followup article, probably to be posted tomorrow. That might include some abstract test-cases.

Ok so I did this one in ColdFusion and I was pretty lazy but used recursion. Since there is no “array split” type function in CF I also wrote one of those to make my life easier. I have not tested it; beyond making sure CF doesn’t find a syntax error

Seems to work…though it’ll report incorrectly if end < start. I thought about that condition after I ran the test (but without checking for the case or failing the test) so I'm not sure if that counts as a failure or not…given the way the code works, it should be an impossible input anyways but I figured it should be complete in isolation, without relying on the usage in main to be right (can't do much about whether the array is ordered or the len is specified correctly though…gotta love pointers, eh?).

Success, I think. SBCL’s compiler found a typo bug for me, but it worked the first time I tested it. I haven’t heavily tested it though. For large arrays (log(n) bigger than stack), an optimize declaration is required on bsearch to get most lisps to perform a TCO. I was aware of this before testing, and have only omitted it to make the code look cleaner.

The 90% error rate doesn’t surprise me at all. Where I work, we have a rule of thumb: “If it hasn’t been tested, it doesn’t work” It is right a lot more than 90% of the time.

If I got this right, it’s only because I’ve done it so many times before.

It did work the first time I ran it, much to my delight. However, I'm pretty sure that without the problem setup, designed to induce extreme paranoia, I would have failed. It's easy to see why 90% of the people in Bentley's test would have failed.

wow, wordpress is a terrible medium for this exercise. this should really be on a forum where it’s possible to have threaded comments, because the comment section here has become a complete clusterf*ck.

I wont post the code because you have 100s of examples, but for the sake of your “poll” – I did it in Python, coded it, reviewed it and than ran it without a single bug (Well I did slack off on writing meaningful exception messages :)

Took me about 10 minutes.

I hope to see the results soon, although I fear that they will be badly skewed.

def find(v,T):
"v is sorted; return i so that v[i]=T, or None if no such i exists"
a=0
b=len(v)
# i in [a,b) if v[i]=T
while a<b:
m=a+(b-a)/2
p=v[m]
if p==T: return m
if T<p: b=m
else: a=m+1
return None

I’m 99.5% sure there’s no bug in the above. I was aware already of the integer overflow complaint, which is the only reason I didn’t use (a+b)/2.

A more interesting task would be to return the largest range [a,b) such that v[i]=T for a<=i<b

What constitutes testing? Just running the code? What about using pencil and paper to help you test it by eye? I wrote the whole thing, but found one obvious bug really quickly just by looking at it. Then I did some testing with an example array and used paper to help track values and found another bug.

I wrote up unit tests in the code then ran it. Output: “Done. Press any key.” Total time = 1 hour. Language = C#.

It may not be as elegant as other posted algorithms. Rather than just checking the midpoint, it also checks the start and end at the same time. It might make more comparisons than is necessary in some cases, but it also makes far fewer comparisons in other cases (where the value you want is in a very early or late position of a large array.) It’s probably a wash. It at least never compares the same position more than once.

I tested it against one array quickly…and it failed. Had two bugs, both 1 character long. First I initially passed the array by reference (so an extra &) in order not to have to copy the array, but I guess you can’t do that in PHP. Second, I accidentally substracted the end and starts to get the average instead of dividing.

Note that your results will skew for success anyway, for several reasons. One is that people who read programming blogs are skewing for the better end, but the more important thing is that some unsuccessful people will not post, no matter what you told them.

Unfortunately, as noted in my gist, I don’t know what I don’t know! I might be iterating too many times, and I don’t know which other cases I should be testing against. Someone needs to put together a test suite for the numerous Python submissions!

I had one bug that prevented me from identifying the value stored at the end of the array i.e. the largest value. Fixing it was pretty simple.

I have the 2nd edition of the book and every year and a half or so I come across this very section and end up re-coding it. Usually, I make a careless error. Never takes more than a few minutes to fix.

I think my attempt works. I first wrote it as a “contains” check before I realized the point of the task was to find the right index. Because I didn’t think enough before patching it accordingly I actually created an off-by-one error.

I find the requirements a bit harsh, though, because I’ve made it a habit to code with a Python shell open next to my text editor and writing in both, testing my logic and assumptions as I write the code.

I guess I’m not one of the 10 percent then, if only because I test my code rather than mentally parsing it line for line to check for errors prior to testing.

The really humbling thing isn’t how hard it is to write correct code without testing, but how long it takes, even for a simple textbook algorithm. Binary search is the sort of thing that sounds like it should take five minutes, but I took 45, and I don’t think I was distracted for more than 5 minutes of that. At least a third of the time was due to the added complexity of usefully supporting arbitrary types and orderings, not just numbers in ascending order. Here it is (in Common Lisp):

It has passed all my tests so far. However, I’m not sure this counts entirely as a success, because I reinterpreted the requirements to make it easier: I originally intended to allow equivalent elements in the ordering, but when I realized that was awkward, I just gave up and declared that the ordering had to be total.

Okay, about to test. I should say I’ve written binary search a number of times in the past and got it wrong a number of ways and fixed it. So I have specific personal rules (which I won’t reveal at this time) about writing binary search!

But in this case I first wrote a very simple linear_search() function, then a tester, then a broken_search() function to make sure the tester actually catches the bugs it’s looking for. In that step I came across a bug I hadn’t tested for: not returning the ( i, nsteps ) tuple that the tester expects. So I must admit I had practice making and fixing that error. My code, alternate searchers, and tester are here. Note that I fleshed out Bentley’s spec to my own liking.

while (length(A)>0)
j = ceil(length(A)/2)
if (A(j) > T)
if (j ==1)
A = [];
else
A = A(1:(j-1));
end
elseif (A(j)<T))
if (j==length(A))
A = [];
else
A = A((j+1):length(A))
end
else
Found = 1;
A = [];
end
end

if (Found ==1)
display('found the target number')
else
display('target number not found')
end

Hey, sounds fun. I love a good challenge. I’m writing this in a text editor to avoid reading any previous comments that have come in since I read the article. Below is my implementation in Python 3 syntax (it “compiles” without errors). I have to say, it’s very hard not to test this before posting! I’m a bit of a novice, so I don’t expect this to be bug free. Go easy on me.

Here is a VB.NET version that is tested. I failed a few cases on the first try. Biggest mistake was an off by one error. This is a rewrite to remove all the unnecessary code and fix all the edge cases.

Testing is part of software development. This challenge is a bit like asking artists to draw a perfect circle while blindfolded. It’s a neat parlor trick, but you can’t use it as a litmus test for a “real artist” or “real programmer.”

Anyhow; I saw that someone already posted an overly-generic C# IEnumerable solution, so I wasn’t going to post mine. But since the earlier solution uses the ElementAt extension method, mine is still notable in the “overkill” category.

I wrote this from memory – but as I wrote an assembler version only a few weeks ago, the technique is still fresh in my mind – and specifically the use of array.size as the upper bound, rather than array.size-1.

Scanning through the other comments, seems like this one is very similar.

However, I don’t really see a point in this exercise – are you(or the author of the book) implying that if a programmer can’t implement this on first try, without testing, he’s not as good as one who can?

IMO, it’s not a good metric for the ability of the programmer.

Now, if a programmer doesn’t _understand_ the binary search algorithm after reading it – then we might have a problem…

When I read “The only way you’ll believe this is by putting down this column right now and writing the code yourself. Try it.” I stopped reading and tried it.

My binary searches worked without error. Although the first test case I wrote made me think it didn’t because it passed in an unsorted array – and the item wasn’t found.

I think the lack of testing is bunk – who writes code without testing it? We need more people that write correct code because they’ve tested it fully. Not more people who submit code without testing because they think they’re one of the 10% who can do it.

Mine (posted above) seems to work. Since we were ignoring numeric overflow I went ahead and assumed we could ignore stack overflow as well. For a real library or to run on an embedded device (I like AVRs) I’d write an explicitly iterative version.

Given the blog owner’s choice of Lisp language, I’m surprised that of the first ~300 entries, mine was the only Scheme one. Wow! :-) (I just grepped for “(define”, so someone correct me if I missed something.)

So perhaps it doesn’t count having written once already… but I wrote a new one, avoided the syntax errors that caught me up the first time, realized that recursion is going to eat a lot of unnecessary resources for large arrays, and ran correctly out of the box this time. :)

Untested, un-peeked (except for the non-overflowing mid calculation which was in one of the first solutions). I already know it doesn’t do the empty array test so, I fail it but, let hope the rest works…

This is interesting, a somewhat related post can be found here:http://googleresearch.blogspot.com/2006/06/extra-extra-read-…
The gist of this is that version of BSearch implemented in the JDK contained a bug due to an overflow error. I recall reading about it some time ago, funny to see it come up again.

[Mike says: this is, I think, the third comment point out the Josh Bloch integer-overflow article as though I hadn’t linked to it from the original post itself. I can only assume these are being posted by people who’ve not actually read my post. I’m letting them all through moderation because they’re not spam and not abusive, but they really don’t add much to the discussion.]

I think this is a bit of a sham. My guess is the author of the book is just overly picky when reviewing. My first run seg faulted :P but this was not a result of the function, but rather an argument reading error where I tried to read argv at argc instead of argc-1, so I don’t count that as my logic error as it was outside the bsearch function. All subsequent testing succeeded.

@Joe User (comment 1805): Applied my tests to your code and it failed in the first one, ie. looking for a value outside the range. Say you have a list [1, 3, 5, 7] and I look for 0… your code gets into an infinite loop

I created a binary tree (computing next node pointer during the search, not ahead of time). It uses recursion. It’s probably not the fastest, and with 3 classes it’s also the most lines that i’ve seen so far… but it works.

Test cases are key. My first implementation worked for all values in the list, and values that fell inside the range of the list (> than least, greatest and < least test cases popped into my head. Had to add another check for the latter. So, does it count as a fail if you yell "Done!" then realize you missed something?

Though the do-without-testing concept is interesting, I’m not really sure it accurately represents programming prowess. If an application is being correctly designed, automated testing while working should be encouraged.

I *could* have spent half an hour poring over the code, running test cases myself, but instead I just ran the test suite, found the problem, and identified and fixed it within a minute. Writing an algorithm in one’s head, pass/fail, seems like an interesting test, but it not the mark of a good, productive programmer.

find(array,val) new pos,delta,found
set delta=array,pos=array\2
for set delta=delta\2 do quit:found quit:delta<1
. if pos>array set pos=pos-delta quit
. if pos<0 set pos=pos+delta quit
. if array(pos)>val set pos=pos-delta quit
. if array(pos)<val set pos=pos+delta quit
. if array(pos)=val set found=1 quit
quit $select(found:pos,1:-1)

(It may be worth noting that the right way to do this in M would be to use the values as the subscripts to the array, since arrays in M are actually more of a map structure.)

Success under the specified conditions, but a slight failure in the enhancement I tried to make at the same time.

I tried to get the .NET semantics where the index is returned when the element is found, and the two’s complement of the index the element should have been otherwise. I rushed the two’s complement part and got the value slightly wrong, but the basic ‘is-it-there-or-not’ search was correct, including overflow safety provided my ‘a + ((b – a)/2)’ is the correct solution for a safe midpoint calculation… I didn’t have enough memory to determine whether there is a gotcha there ;)

Tested briefly, seems to work correctly. Assuming I can use the rule “You’re allowed to use your compiler to shake out mechanical bugs such as syntax errors or failure to initialise variables” to cover learning to write classes in Python (because it took me three tries to reference class variables correctly), it worked first try.

To say only 10% can write a binary search is inaccurate. The rules being applied here are not realistic and rather contradictory to some development processes. For example:

NO testing until done writing? What about Test Driven Development?

Do you actually expect programmers to be able to write completely bug free code on their first run through?

I understand the basic premise: that programmers seemingly aren’t as good as we’d expect. But this feels like another apocalyptic assessment of how “kids” these days can’t code worth a damn. Not to say that all the high level coding with libraries degrading overall competency isn’t a worry.

I should mention that I fall in the kid age group, as I’m still in college. But I’m nearing the teenage years regarding overall experience and intuition.

You can find zipped and gzipped versions of the file, a more detailed explanation, and Python code to test your Python function against the file, here.

I’m amazed how many people thought Mike’s point or Bentley’s point was about not testing code. Of course you should test! The point is whether you can get it right without using testing as a crutch to get there before you test.

I would agree with several other posters that the implement without testing is a strange way to go about development. I could have found my problem if I sat and looked long enough, but instead I ran it, found the problem and fixed it in minutes. *shrug*

So I wrote my binary search in Perl and it worked at first go, just like it will for so many other readers of your blog. I doubt anyone’s day will be enhanced by me posting the code, so please take my word for it.

I think the reason why only circa 10% of programmers can write a functioning binary search routine is that only circa 10% of programmers have brains wired for implementing low-level algorithms.

Twenty years ago, “not being able to write low-level algorithms” was the same as saying “not able to program computers” but the past few decades of progress in software development have been aimed specifically at allowing the other 90% – the ones who can’t actually program – to produce useful software despite their handicap. This has been great for the 90%, and for the companies who employ them, but it’s come at a cost to programming culture.

I actually wrote two implementations. The first was a completely naive recursion version, which worked perfectly.

The second was an iterative version of the first, but I made a stupid, horrible mistake. When I’d finished, I decided that my extra slice variable was redundant and I could just mutate the original array.

This wouldn’t have been a problem except that I’d chosen the sentinel value for “not in array” to be the length of the array as opposed to -1. Since I was now mutating the original array, my iterative version would always return index 0 for any element not in the array. WHOOPSIE.

That’ll teach me to try and be clever.

On the upside, I was pleasantly surprised when all the assertion failures for the recursive method turned out to be bugs in the tests.

Also on the up side is that neither function should be susceptible to the overflow bug by virtue of using D’s slicing syntax.

This sounds like a lot of fun, but it’s sort of a party novelty. Are you allowed to use backspace to delete, or is it a one shot: write it and release it? I don’t know if I could avoid hitting backspace. It’s rather horribly ingrained by now.

I don’t really see the point of writing code without testing. The whole point of programming is to write broken code and fix it. Usually I just create an empty source file and start debugging. That’s one of the reasons I like Realbasic. You create an empty program and run it, and up comes a trivial window and a menu with the quit command. It’s a very satisfying starting point, full of possibilities. Then I pop open a text editor and start the spec.

After all, the last few times I’ve written a binary search were to find an insertion point, not a particular element, and to search a very large, but relatively uniform database by estimating a “mid” point based on the extreme and search values. A old working binary search I had lying around made an excellent starting point. Just replace the axe blade, slip in a new handle, try a different blade, and voila, it’s all debugged.

I’m going for extra credit here, posting my Python code before I test it. This isn’t the first time I’ve written a binary search, but it’s been a long time – at least 15 years as best as I can remember. I long ago learned the secret – make sure each pass is narrowing the search range by at least 1, otherwise you can get into an infinite loop.

To make this more interesting, I made it work with only a less-than operator, similar to the way the standard C++ library works.

I’m quite surprised that you didn’t include some test conditions, but I see a few comments above mine that at least one person has volunteered. Thanks!

I had two bugs (so far). The first was that I got the middle comparison around the wrong way, and the second was that I forgot to add 1 to the final result. I might have a go at an iterative version later on…

// Returns the index of Item in Arr or -1 if not found
function BinarySearch(const Arr: array of Integer; Item: Integer): Integer;
var
Left, Right: Integer;
begin
Left := 0;
Right := High(Arr);
while Left <= Right do begin
Result := (Left + Right) div 2;
if Item < Arr[Result] then
Right := Result - 1
else if Item > Arr[Result] then
Left := Result + 1
else
Exit;
end;
Result := -1;
end;

I had a number of bugs in this one, which took a while to work out. The first was that I (again!) had my comparison around the wrong way, and the second is that I’m not sure that there’s a clean way to prevent my code going into an infinite loop for non-existent values. I have an off-by-one error in there, perhaps?

Yep, so I’m a terrible programmer – I’ll go hang my head in shame now… but read up on binary sort :)

Many commenters have said that this challenge is meaningless, since there is no real-world application of coding without testing. I disagree.

First, I have found that practicing coding on paper forced me to learn better habits about correctness, which results in higher coding speed. So [if you’re like me], doing drills of this non-interactive nature will improve your productivity.

Second, frankly, if you can’t manage this elementary a case (without tests), how can you trust yourself to come up with all the relevant tests? Consider those who posted their allegedly-tested programs and were incorrect. There but for the grace of formal proof methods go I.

Oh, and I wrote it in 10 or 15 minutes, recursively in Python, and believe it to be error-free. Let’s see if I can post it successfully:

I could have written this, debugged it, and written a battery of unit tests in five minutes. Instead it took me about 25 minutes of being a human compiler/computer before I was confident enough to compile and run it. I used pen and paper to test it by hand and clean out the bugs. This is what I have a problem with; I could have just used the computer to do this for me. I found this problem incredibly frustrating because I was stripped of everything that I consider makes me a good programmer.

Here’s the test I ran against it in case anyone was wondering. Not terribly thorough, but I really don’t care.

All values in the output matched up with their position in the array. I also had no compiler errors or warnings in the implementation of bs() on first compile (as C99); I got an error first because gcc compiles in C89 by default (so it complained about the for loop declaration), and I got a warning because I forgot to include stdio.h (technically part of the test, since the algorithm does not need that header.)

Oh also, I should mention I tested mine on paper for (almost) all branches for array sizes zero through five (yes, this is why I am frustrated.) So I am confident that it is correct.

I still don’t think these rules are fair. For instance my code obviously fails integer overflow, as others have mentioned in their own code. Does this even matter? I wouldn’t expect an algorithm of bs() to guard against this in any way besides an assert(). I don’t consider this an error.

I find it silly that putting – instead of +, for example (as I did in (min + max) / 2, luckily I noticed it before compiling) is considered failure, which is something that would immediately be caught if we were allowed to test; and yet, people here are claiming success with recursive solutions in languages without tail-call optimization, so they have O(log n) space requirements (the algorithm should be O(1) memory.)

This can’t possibly be graded pass/fail. A question like this on a paper test makes sense if you can still get part marks for a decent attempt.

* If you use a lim-index instead of a max-index, you’re likely to access outside the buffer when searching for an element higher than the array max. Fencepost error.

* It’s easy to forget the case of searching for a non-existent element. In the case above, I’ve even provided a return value that tells you (unambiguously) where the insertion point would be for a non-existent element.

Seems to work, although I have only made like three tests. :) I find the idea of a challenge where you aren’t allowed to actually run your code moronic. This isn’t testing for how good programmers are, it’s more about luck, IMO. I’d say the passage in the book is outdated, because now we have something called test-driven development. :)

Followup: seems to work. Tested:
1. target is in list
2. target is at start of list
3. target is at end of list
4. target is not in list
5. target is less than smallest value in list
6. target is greater than largest value in list
7. target is empty
8. list is empty
9. target and list are empty

@dewb:
> Testing is part of software development. This challenge is a bit like asking artists to draw a perfect circle while blindfolded. It’s a neat parlor trick, but you can’t use it as a litmus test for a “real artist” or “real programmer.”

My daughter went to art school, and one of the exercises they used was to draw something without ever looking at the paper. So perhaps your analogy is more confirming than refuting.

Alright, it actually looks correct. Tried out of range values as well as valid ones, empty array as well as a huge one, and I confirmed the results with a debugger, just in case.

Now, here are some bugs that 20 minutes of thinking solved (without testing!):

* When computing the middle index, avoid additions. They might overflow for large arrays. I’ve learned that in the past, the HARD way.
* Testing for (!found) usually leads to infinite loops, so I ditched that approach quickly.
* I started writing a binary sort…then I decided to read the specifications! :-)
* Although this is a typical example of a recursive algorithm, I’m not comfortable enough with recursion (I know, shame on me…), so I decided to go iterative..
* Do not mix semantics. If you decide your binary_search() should return an index into the array, this will impact your implementation – using uints instead of plain ints for example, which may or may not lead to nasty bugs if you’re not careful.

You shouldn’t probably include me in the statistics, because I did test my code a good amount of times. But I only tried it because I was doing the binary search in Haskell, but I’ve only been playing with Haskell for a few days, and with limited time, so I am far from truly getting it.

Like I said before, I’ve only been playing with Haskell for a few days, but it seems to work. Probably not the best way to do it, and maybe I didn’t even exactly follow the binarySearch algorithm (I think I did…did I?), but thats what I came up with.

after reading the description further and encountering the part about “index overflow”, I was banging my head on the table as I fell in exactly that trap :/ for “integers” it does not matter, but for “chars”, it would

I just saw you have more than 400 replies to this entry, and you only posted last night!

Why do you think your blog has exploded in popularity? The catchy title, the uncooked fish (sorry I had to) or the witty writing?

I think the results of this experiment will come out differently than the 10% success rate mentioned. Telling people to only post anonymously so they can’t receive shame nor credit for their code would have been better.

With the amount of readers you have on your blog, you could conduct some pretty interesting experiments.

I tested with random values wich resulted in arrays with repeated values. That is something I didn’t have in mind when wrining the code. It turns out my function returns the first of the repeated values, which is nice. Lucky me :)

I thought I’d give it a crack in C#.
Disclaimer 1: I’m pretty good in C and C++ but I have no serious experience with C#.
Disclaimer 2: I’ve walked myself through this code in my head but I haven’t tested it.

The BinarySearch() function returns the index of the item if found, otherwise -1.

I failed. My version ends up in an infinite loop if the value I’m searching for is larger than all values in the array, or if there are only two values in the array and the searched-for value is between those two.

Having tested my code, it seems to be correct as far as I can tell. The only thing I missed is the possibility that the array reference passed can be null. I’d treat that as a precondition, so I’d insert the following line at the start of the function:

BTW, Google had me do this in my phone interview (reading code over the phone = not fun). I made the same off-by-one error as several commenters (< instead of <=) but caught it a few minutes later while we were still on the phone.

I can’t resist posing the evil question: if your array can be in size up to Integer.MAX_VALUE (for Java, but applies elsewhere as well), are you sure your code will not create integer overflows in your arithmetic? The code is only correct if it works for any arbitrary array being passed in…

This methodology comes from (or rather, where I learned it from is) the C++ standard algorithms library, which is well worth examining because it has stellar design notwithstanding the ugliness of the language itself. The basic principles are applicable in most languages.

I failed, with a simple and easily found error, but I did it when I read “try it” in the quoted section, not your later challenge. So I assumed that testing was allowed, and stopped to do so early. Trying to write perfect code straight out of the blue seems pretty pointless. Some people even say you should write thorough tests first and then code until they pass. The scope of this example is about as big as you can get and hope to have any success just doing it all in your brain, so what’s the point?

First pass, buggy. Tried to be smart and got the sort condition backwards. Added one comprehensive test, fixed the bug. search(array, e)==i for all e=array[i] in a random, sorted array.

That said, coding without tests is just trouble waiting to happen. Doing so is part of my standard work routine, and explicitly specifying “no tests” pushes this firmly into the realm of theoretical uselessness, even for a random exercise.

This was done in matlab
array=[1:.5:100];
T=0;
[m,lenA]=size(array);
pos=floor(lenA/2);
ubound=lenA;
lbound=array(1);
S=input('What to search for?: ');
while (T==0)
if (S>array(pos))
lbound=pos;
pos=lbound+abs(floor((ubound-pos)/2));
elseif (S<array(pos))
ubound=pos;
pos=abs(ceil((pos-lbound)/2));
end
if (S==array(pos))
T=array(pos);
break;
end
lbound;
ubound;
pos;
if (abs(lbound-pos)<=1)||(abs(ubound-pos)<=1)
T=-1;
break
end
end
if T==-1
fprintf('S was not found in the array');
else
fprintf('S was found at location %i in the array', pos);
end

I didn’t get it quite right on the first go because of a stupid mistake – in the elif/else brackets, I’d originally just written “binsearch(…)” instead of “return binsearch(…)”. Other than that it appears to work.

Wow, from a quick audit it seems that mis-handling empty arrays is by far the most common mistake. Eg from a sampling of the solutions stated to be correct, I found it in 8 solutions (@finsprings, @Aaron, @Mike J, @Langtree, @Luke, @Marcus, @cycojesus, @Erik Swanson), with only 1 having a different mistake (@donaq, which was caught later).

I did this in ruby, and my first code was not correct. I had an infinite loop because I was not shrinking the size of my window.

That said, the entire thing was coded, tested, corrected in less than 10 minutes, so I guess this is also a repeat of the “why is it important to be right first, as long as you are right at the end”. Testing is important for code.

After I wrote the code in an editor, I did do about 4 example runs-through (algorithm in my head; pen and paper for variable store) which was how i remembered to add 1 to mid for the slice and return in the RHS block.
I’m not sure if that counts as cheating, but I’m pretty sure it doesn’t.
10mins. and maybe 20-30 more for the test code.

I’m way late to the game, but if you are indeed tallying the results, add me to the list. I assumed arguments would not be null. Up to you whether you consider that a bug or not for the purposes of this exercise.

It seems obvious to me from the problem description that you can assume the array reference is not null (i.e. there is actually an array!) but that zero is a perfectly good number of items for that array to contain. So programs that fail when passed a null Array are fine; programs that go crazy when asked to search and empty array fail the test, and diminish, and go into the west.

@Jeff: Handling a haystack of length 0 certainly IS within the scope of this problem. An array with no elements is sorted (see: vacuous truth), and so is valid input. Also, it doesn’t appear that your code handles arrays of length 1 either (because len(haystack)/2 == 0 in that case).

@David Mihola: Yes, it probably is too complicated. But since “For the purposes of this exercise, the possibility of numeric overflow in index calculations can be ignored”, I didn’t worry about cleaning it up too much – I just copied what I originally wrote.

@Avi: I see. And just in case: I didn’t mean to sound offensive – my intention was more to check if I was right than to tell you that your solution could be better – at least yours was correct whereas mine wasn’t…

Decided to have another look….FAIL. Mine dies if the search is larger than the largest item in the array.

We should go back to the essence of this blog post. I think Bentley’s point was that after explaining the algorithm in english it sounds easy to write. Easy enough that any real programmer could probably spit out a working implementation without actually trying it. His point is that this is deceptive; the code is trickier than the explanation.

Perhaps because we’re plugging other peoples’ libraries together so often, we’re used to black boxes. I think that’s why so many people are appalled by the idea of not testing. Testing is quicker and easier than reading the source of the libraries you’re using.

@Tristain: You are using max to be exclusive, correct? Then in the recurse when value < range[mid], you should not use mid-1 as the new max or you skip the index before mid as well. E.g. try adding a test case to search for 5 in your array.

As I said, I’m confident that my solution works, i hope that I’m correct.

Still it’s fair to note that i almost forgot a couple of details, for example, i almost left line 18 as int pivot = (upperIndex – lowerIndex) / 2; forgetting to add up lowerIndex again to the pivot, and line 22 was a nearly miss to, i almost forgot to check for the pivot becoming greater than the upperIndex when i add 1 to it on line 24.

So far, my implementation has passed all the tests (surprisingly!). I did this in about 8 minutes and to be honest I didn’t even consider some cases (e.g. empty arrays) but so far every test I did has passed. Also my implementation does not add +1 or -1 to the middle index, does anyone care to tell me if my implementation is “good”?

This is one of the reasons that I favour use of ready-made libraries: Trying to write an own sorting/searching/whatnot algorithm is a beginner’s error (in and by it self). There are well-tested and highly performant libraries for such basic functions available in any established high-level language—only rarely is the small gains possible by a custom made solution enough to justify writing an algorith from scratch.

Even if such justification can be found (possibly writing the first such library for a newer language) the correct way is obviously to grab a good book on the topic, pick a suitable implementation, and (if needed) translate/modify it.

More generally, in today’s software development, the main task at hand is limiting and controling complexity of various kinds. One way this is done is re-use: A less optimized solution with re-use is usually preferable to an optimized special-purpose solution. (Notwithstanding that some libraries and frameworks are too bloated to always be acceptable.) We can hope for the return of small Unix tools that each individually are of sufficiently low complexity that such measures are not needed; however, in reality, we are stuck with ever growing applications with ever increasing complexity—and, in today’s world, the good developer is not the one who understands complex code and algorithms, but the one who avoids or manages them. A sad, but near inevitable development.

A is empty.
Can’t find T in certain places or certain sizes of A (even? odd? powers of 2?)
Goes into an infinite loop sometimes. (How do you test for this?)
Becomes a linear search sometimes (Did you test for this?)
Tries to access off the end.
Doesn’t return “not found” sometimes when T isn’t in A.
T is less than the first or greater than the last element.
Does (a+b)/2, which overflows with ints. (Did anyone here find this one by testing?)*

*The last is a problem if you have 2^31 one-byte elements to search (it could happen!). Or would be if you were searching a function or disk file rather than an array. Or if you used 32-bit ints (not size_t) to index arrays in a 64-bit computer, which I think would be the more basic problem. Or, if you’re in Python 2.x not 3.x, overflow bumps you into long ints, and then your function returns a long int which could have cascading inefficiency effects :-(

@vince: It doesn’t look like you’re handling 0-length arrays correctly? That is, if ecx == [ebx] when edi==0, 0 will be returned instead of -1. Assuming [ebx] doesn’t cause an exception in that case, that is.

@eric: You’re right of course. the code doesn’t check for zero length arrays. [ebx] itself can’t cause an exception because ebx is a valid address on the heap, but nonetheless a bug’s a bug, and so take my seat among the 90%…

Well, I thought I had it, I really did. It seemed right until I hit an edge case, an array of 2 items with the first of the 2 equal to the search value.

So I guess that puts me in the fail category as well. But here it is after I debugged (mentally walked through the code line by line to see where it was missing the value). Really, I should have thought of the edge case before I tested. :-/

You wrote: “JEIhrig ,please check once more the instructions at the bottom of the post on how to post code in a WordPress comment. If you don’t do this, then . Repost your comment using the correct form, and I will delete the old broken version.”

I already read that but must have missed the instruction to replace the curly brackets with square ones. It wasn’t just the ‘greater than’ symbol that was missing but commented code somehow became uncommented, and another section was altogether missing, which lead me to believe it wasn’t mis-interpreted by wordpress but actually changed by someone. If it posts correctly this time, hopefully all my previous posts will be deleted.

After re-pasting my code for the third time, (Still not compiling) I noticed a problem. It returns ‘key’ whether or not it’s in the list. So I changed that. (Does it still count if I made a modification after initial submission though I never compiled?)

JEIhrig, I have deleted your previous posts now that this one is up, and properly formatted. Yes, I think it’s fine to change your submission so long as you make your change by inspection rather than only realising after tests have shown you the problem. Let us know how the tests go!

ps: actually, I made mistakes when testing, like forgeting I had a null return and printing out the array with the result as index, which obviously got a null pointer exception. Nothing on the bsearch itself, would like to see if I had any obvious mistake I still can’t see.

Seems like my last comment didn’t go through. Anyway I have to say this is a little hard to believe. Where do they find those 90% of “professional” programmers? BSearch is seriously not that difficult to write. Anyway here’s my untested version in python:

Worked first time, took 7 minutes to code in Java including unit testing.

I would not expect any developer to create a correctly working implementation first time, as there are too many ways to make easy mistakes – however, I would expect a good developer to be able to write, test, and complete a solid implementation in less than 30 minutes.

In sporadic testing of my previous entry over the last 24 hours, I haven’t found a single case that fails, including the tests kindly provided by Steve Witham. While this isn’t completely conclusive, it’s a good sign that I’m in the 10%.

A few notes:

Writing the code took about 6 minutes. I spent another 5 scrutinizing the code to make sure I didn’t miss anything. I did try to import the code into the shell, which uncovered a syntax error that I fixed before my initial post (this was allowed by the rules).

My solution didn’t include the workaround for overflow problems, but I’m not sure Python is susceptible to that particular issue. I know I’ve used the workaround in previous incarnations in the distant past; I don’t think I’ve worried about it since 16-bit days. It’s quite unlikely that I’ll feed it an array of greater than 2^30 in size, and the rules explicitly state that overflow can be ignored.

I’m also quite surprised by the submissions that used slicing to divide the input array, because it defeats the whole purpose of a binary search. Binary search is supposed to be a O(log n) algorithm, but slicing is an O(n) operation.

@Eric Burnett: As I mention in bsearch()’s docstring, I’m interpreting the problem as “return the index where insertion preserves sorting”. IIRC, the original post didn’t state how edge cases should be reported, and I find that returning a valid index in all cases is much more useful than returning -1 or None. (I picked that up from the C++ STL convention of returning v.end() on a failure to match.)

Given an empty list, my code returns 0, indicating that [].insert(0, value) preserves sorting: [] is sorted, as is [value]. Given bsearch(3, [1, 2, 4]), by inspection I believe I properly return 2, which is the position where 3 should be inserted to maintain sorted order. (I don’t have a development environment set up on my iPad yet, so I can’t verify that. That’s also the reason I didn’t test my solution.)

My solution found the correct value as far as I could determine with my test cases, but only returned a boolean. When I switched to use Steve Whithams test cases they expected a return value of position and steps I failed on the position (after converting my code to return those, of course). Not sure if that should count as a fail or sucess. Thanks to Steve for the test cases.

Luke Stebbing is right — I didn’t specify what the search should return when the element is not in the array. That was careless of me (although I suppose I could pass the buck to Jon Bentley :-)). Luke, I did mean for an out-of-band value such as -1 to be returned, as most (I think all) of the other solutions have done; but your approach is not prohibited by my statement of the problem, so you get a pass. (Provided there are no other bugs, of course!)

By the way, I love that Eric is continuing to fight the good fight. Keep up the destructive work!

@Mike Taylor: What I like about returning a valid index in all cases is it allows you to avoid a conditional expression when you don’t need one, i.e. if you’re using bsearch() to build a sorted list. The possibility of a wild and crazy index like -1 forces you to guard every single call in all situations.

I’ve firmly believed the “Make sure special cases are truly special” mantra ever since I encountered overly specific definitions in ninth-grade geometry, and I think I’m following that rule here by treating a failure to find the element normally.

Here’s an O(1) way to determine whether my bsearch() found the value. I really should’ve returned (index, found) in the first place, since that would’ve been a more useful signature. (I also should’ve used Python’s a // b shorthand instead of int(a / b), but I simply forgot.)

@Mike: Thanks :). I should point out that I’m not checking 100% of them though… mis-formatted solutions I think code is missing from are skipped, as are languages I can’t wrap my head around yet (*cough* Haskell *cough*).

@Daniel: Just a nitpick – list[mid+1, len-1] is asking for len-1 elements starting at mid+1. Because there aren’t len-1 elements left you’ll get the ones to the end of the array (as desired), but it would be clearer to just say list[mid+1..len-1].

Also, as noted by other people, using array slices makes the algorithm O(n) rather than O(logn).

@Peter Toneby: What exactly you return is not specified, so not exactly matching someone else’s test cases is fine. I think if it passed in original form, that is enough. Your updated code looks good, although I will note that using array slices will make your search be O(n) not O(logn) as well.

@mondodello: It appears your code will mis-handle single element arrays (or ones that end up single element after recursion) since in that case, lowIndex == highIndex. If the first “if” is changed from greater-than-or-equal to just greater-than, I think your code would work.

@Piotr Gabryanczyk: “int m = s+e/2”: I think operator precedence will get you on this one.

I spent 17 minutes for thinking. After that I spent 21 minutes for writing test fixtures (that I will run only after all code finished).
After that I spent 9 minutes on binary search function.
At all it takes for me 47 minutes.
After that I run all tests and all works fine with first run!
After all I readed comments and links wih typical problems – all of them was solved by me during thinking.

Dmitry, you should win some kind of prize: you belt-and-braces solution of both thinking and testing is clearly the way to go. I an encouraged that, after you’d invested the time into design, it worked first as intended the first time.

I wrote this in five minutes. I didn’t read the rules and ran a single test that found a bug in my stop condition, (= lower (- upper 1)). I fixed that condition, and it passed all my tests.

My algorithm would fail in a language that doesn’t handle bignums transparently, because I did not take into account the overflow issue. I had forgotten that problem altogether! Yet another good reason to re-read Bentley occasionally.

@Will: Lets see…your code does not handle empty arrays (sizeOfData == 0), assumes the length is a power-of-two (otherwise successively dividing by 2 will not cover the array properly), and even when it is, cannot find items in element 0 (eg 4-2-1=1, so successively subtracting the new chopsize will not get to index 0 before chopsize reaches 0 itself). Sorry :(.

@Dmitry/Mike: Looks good to me :).

@Ramesh: In the second recurse, should it be “@ar[0 .. int($#ar/2)-1]” instead? I think the middle element is ending up copied down unnecessarily (caveat: I don’t know perl). Also, the standard warning that performance goes to O(n) when using list slices applies. That said, it looks like the code should work :).

I wouldn’t consider myself a programmer (get out clause?), but I did have a go and I failed (with my first attempt) – basically because I was never going to get anywhere without testing. If interested, I’ve documented my attempts on my website. I think I pretty much got there in the end.

@Eugene Wallingford: “(search-in-range key v middle upper)” should probably be “(search-in-range key v (+ middle 1) upper)” since there is no need to keep the middle element around, but the code looks good otherwise.

I started a version in Common Lisp and FAILED! Not that I’m ignorant in Lisp though I’m far from an expert, but that I’ve dealt with this recently in Smalltalk, and misremembered stuff. Also I’ve been under fair stress with my heart dog being sick with colitis and no clear diagnosis or cure in view. I’ve not had this sort of anguish since my dad died of cancer. Stress plays havoc with concentration.

One thing I note is that few is any of these answers deal with the question: “what comes before ‘elephant?'” Sometimes it’s useful to note the insertion index if you fail to find something. In languages like Scheme and Lisp, you’d
use (values …) to return an index and whether it was a find or insertion point.

I had 10 minutes to kill at work, so… here’s a C# version that uses recursion. It returns -1 if the item was not found. Couldn’t be bothered to make it generic, but the algorithm is the same of course.

I had 10 minutes to kill at work, so… here’s a C# version that uses recursion. It returns -1 if the item was not found. Couldn’t be bothered to make it generic, but the algorithm is the same of course.

Developed without testing and then tested against a few hand-picked edge cases and 2 million arrays of randomly generated size <= 50 with randomly generated values. This code does no special handling for arrays that contain ranges with the same value repeated; if the value is equivalent to the target it will pick whichever one it happens to land on first. (This behavior is different from that of std::find, but is correct w/r/t the Bentley description of the algorithm.)

It's unclear to me why anyone would have trouble writing this algorithm; I guess some would forget to range-check the inputs?

I had almost exactly the same experience as Todd Moon. I started writing code, got something I felt tentatively OK about, then I went through a couple obvious corner cases on paper and found a bug. I fixed that bug, went through the rest of my corner cases, simplified a little, then declared myself done; *then* I wrote the embedded tests, ran them, and they all passed on the first go.

All told , it took me about an hour. Feels like a long time to do such a ubiquitous algorithm in such a simple language!

BTW, I slightly wish the challenge was a bit more clearly specified. Looking through the comments now, I see some people simply treated it as a boolean exercise (Is T in the array or not?), while others (like me) return the index where T is found, or some other value to signal failure. That’s fine, but it would’ve been really cool to go further and have each submission be eg. a command-line program that could be tested by the same test script.

BTW, I slightly wish the challenge was a bit more clearly specified. Looking through the comments now, I see some people simply treated it as a boolean exercise (Is T in the array or not?), while others (like me) return the index where T is found, or some other value to signal failure. That’s fine, but it would’ve been really cool to go further and have each submission be eg. a command-line program that could be tested by the same test script.

I agree that it would have been nice to get all responses in such a format; but by raising the bar that far I’m sure I’d have reduced levels of participation dramatically. I am awed that this article has provoked 635 responses (and counting), of which the majority have been programs. I think that’s only been possible the problem was stated in such a way that people could bash out a quick attempt in the environment of their choice.

As Clay Shirky once said (of HTML), “You cannot simultaneously have mass adoption and rigor” :-)

Jason B, in general I can’t fix your posted code, because in the absence of a correct source-block surrounding it, it’s possible that some of what you pasted in got mangled. If you re-post, I’ll delete the old one (and this comment).

This took about 15 minutes to write and another 45 for testing. Coming up with good test cases is the key, and I don’t know if my tests cover all cases, but with 1 million random arrays, the program passed.

@Mike Beverley: I believe in an earlier attempt you asked for my comments? The code looks like it should work, which puts you in the minority here :). That said, I must question your use of doubles as index counters. It requires a lot more casting, as you have done, and makes it harder to reason about the safety of the code. Also, “pivot = Convert.ToInt32 ((Math.Ceiling ((((upperBound – lowerBound) / 2) + lowerBound)) – 1))” seems to be biased low. For example, in a 3-element array (lowerBound=0, upperBound=2) the pivot is chosen to be 0?

@Martin Elwin: In the second recurse, you should probably use pivot+1 as a lower bound since you’re done with the pivot now, but other than that looks good!

@UnoriginalGuy: Your code does not handle empty arrays. Also, for two element arrays the pivot must be the first or the last, so “if (middle == haystack.Length-1 || middle == 0) return -1;” can cause it to fail unnecessarily searching for elements adjacent to the ends.

@Dave R.: Your code doesn’t handle haystacks of length 0, so yep, it’s harder than it looks :). Come to think of it, you also don’t have an escape clause for not finding the element, so your code can infinite loop if the item isn’t present. Also, with “idx = start + ((end – start) / 2)” idx can be start for a two-element haystacks, so when you recurse to the right (setting start=idx) you may find yourself in another infinite loop (this time even when the item IS present).

For the recurses you probably want end=idx-1, start=idx+1, and instead of “while True”, use “while start <= end".

@Martin: Code looks good, although the standard comment about list slices making it O(n) instead of O(logn) applies.

@Eris: The overflow happens if you write it as (left + right)/2, so you are safe :).

@Darius Bacon: Thanks for all that effort! I was a bit worried that your test suite reported a bug in my function that I didn’t see. But I quickly realized that your wrapper around my function was broken – and likewise for several other functions you tested.

As promised, I’ve forked your tests of 20 python functions here.

With the wrappers fixed, my own binsearch (“paul”) passes . As does Ashish Yadav’s. So the score goes up from 6 passing to 8 passing.

Your mistake was that in several cases your wrapper function returned something like “result or -1” , which turns a successful match at index 0 into a false negative. I fixed all broken wrappers that I could see. It didn’t help everybody though :)

More interesting, after doing those fixes, I discovered a bug in one function that was only found by the random tests, not the exhaustive approach. Which in turn prompted a little fiddling with the failure reporting, since in that case your use of a global `test_case` caused a more recent success to be reported instead of the real failure :)

The buggy case looked like ([0, 0, 2], 1). Visual inspection of Scott’s function should reveal how it fails in that case.

@Eric Burnett
Thanks for looking at the code…! But not sure I can exclude the pivot from the upper half recurse as I don’t compare the pivot for match before recursing. In that case I’d have to also check the pivot for the match before recursing:

Wrote it in Python. Was sure sure sure that I had checked for all errors and all possibilities:
– item not in array
– step out of checking if length of half of array is only 0, special case checking if only one element is left
– check if divide result is even or not even
Result: still bombed out with “list index out of range”…

a bit ashamed about the long code…
def check_even(number):
if number%2==0:
result = True
else:
result = False
return result

I didn’t even try to get it right the first time, because I got interested in
the specfication. Why code to a spec if it’s the wrong spec? Most solutions
here solved for “is the item in the array?” An alternative is “Where would the
item be if it were in the array?” In this case, the caller would have to
check for actual membership, but the function, by returning more information,
fits a broader range of scenarios. The return value works well in python
because it can always be passed to list.insert.

I have to admit that after a long day of work I ended up getting frustrated before I finished proofing on paper what my code was doing. in all cases (but then now days with an interpreted language that lack a substantial cost to compile we don’t program like this anymore, do we?)

Here is my perl solution. Recursive and tail-call optimized. There are tests in there, but I never ran them until I thought it was correct, they were my placeholders of cases that I ran through by hand.

DO NOT RUN THIS!! It is a very non-optimal solution (all of the array slicing is wasteful, and the passing around of arrays instead of refs probably kills too). It consumed 15 GB on my machine and took a long time to complete, but they all passed. If you take out the last few that check arrays of 50 million elements it doesn’t take long at all.

“NO TESTING until after you’ve decided your program is correct.”
This means you’re testing ability to reason about code more than ability to eventually come up with correct code. It’s an important ability, but testing it puts people who are used to relying on their tests at a fairly strong disadvantage. (If you gave us a black box, told us it was a buggy binary search, and asked us to build tests to identify the bugs, I’d’ve definitely failed that because I would be working against a symmetric disadvantage.)
When the first edition of Programming Pearls was written, this disadvantage wasn’t nearly as artificial as it is these days, with lots of cheap computrons on every programmer’s desk (and even in most of their pockets) to run the tests.
(Note that I’m not claiming that reasoning about code isn’t still an important skill; it’s just been demoted from “essential” to merely “important”.)

My attempt: Spent a bit over half an hour, caught no less than three bugs desk-checking, but passed all my tests (an exhaustive list of arrays with not more than five elements bounded between/including 0 and 15, searching for every value in or just outside the range the array covers) without further modifications.

/*Requires arr to be sorted in increasing order.
Returns the index of an array element equal to want, or -1 if no such
element exists.
*/
int bin_search(int *arr,size_t num,int want)
{
size_t lo=0;
size_t hi=num-1;
/*Check the invariant we want for the loop and early-exit if
it won't hold (since checking that it holds will give us
the information we need to return anyways)
*/
if(num==0)
return -1;
if(arr[lo] > want)
return -1;
else if(arr[lo]==want)
return (int)lo;
else if(arr[hi] < want)
return -1;
else if(arr[hi] == want)
return (int)hi;
/*Invariant: arr[lo] < want < arr[hi]*/
/*We get mid=lo from the truncating integer division if hi=lo+1.
This will put us in an infinite loop, since arr[mid]<want,
and in that case we want to move lo up to mid.
(We get a symmetrical problem if we do ceil((hi-lo)/2).)
So we have to fall out of the loop if we've squeezed the
range strictly between lo and hi down to empty, and this
is what motivates the strict inequality in the invariant.
*/
while(hi-lo > 1)
{
size_t mid=lo+(hi-lo)/2;
assert(arr[lo] < want);
assert(arr[hi] > want);
assert(lo<mid);
assert(mid<hi);
if(arr[mid]==want) /*found it*/
return (int)mid;
else if(arr[mid] < want)
lo=mid;
else /*arr[mid] > want*/
hi=mid;
}
assert(arr[lo]<want);
assert(arr[hi]>want);
assert(lo+1==hi);
return -1;
}

“NO TESTING until after you’ve decided your program is correct.”
This means you’re testing ability to reason about code more than ability to eventually come up with correct code.

Yes, exactly right. I knew this all along, but stupidly I never stated it clearly, which I guess is why so many people objected to the rules laid down for this challenge. Thanks for nailing it down for me: if I ever do this again, I’ll try to make this point, explicitly, right up front.

Read on to the subsequent articles for more attempts to articulate this.

Anyway, congratulations on passing your tests first time out. You are a ten-percenter! (It’s nice to see the asserts in your code, too: I’ve been surprised how few of the submitted functions have included these.)

Really great post Mike! I loved your one about K & R, but this one tops it. I realise I am obviously a little late reading the article, but I couldn’t resist the challenge, so I’ve posted my results below. Keep writing these great articles!

Here’s my attempt in python. I got the algorithm right in the first try – except that I initially forgot to import the `math` module!
Worth mentioning that I made silly mistakes like (max – min) / 2 in finding the average, and only spotted those after mentally tracing the execution of the code – that doesn’t count as testing, right?

I read your posts out of order (so I knew to look out for the base cases), used Corman Common Lisp, didn’t follow the rules, and didn’t find any bugs on testing, and found some slack from a missing 1+ which would make it marginally more efficient.

For all those Common Lisp programmers out there, please ‘know’ your functions – floor takes a second argument.

Apparently I’m not one of the 10%, mine didn’t work without testing. I’ve written a binary search before. It probably didn’t work the first time either (though I certainly wasn’t constraining myself to not testing it that time.)