Snyder 25 October ’99
Project 1, Part III
Astrological Toys
Day Finder:
The Day Finder, though a small extension to the Zodiac program, is significant because it introduces a powerful algorithm, binary search, and introduces a fundamental component of algorithmic thinking, procedures.Searching for a day. The Day Finder is an extension of the Zodiac application, in which the user is asked questions that enable the program to discover his or her birthday. So, Leo’s are known by the Zodiac application to be born between July 23 and August 22. (Remember this includes both the starting and ending dates.) The task is to formulate questions whose answers will determine the user’s actual birthday. One strategy would be to ask, "Were you born after July 23?" If the answer were "no", then the program could say, "You were born on July 23", but if the answer were "yes", the program would have to try another question, like "Were you born after July 24?" If the person were born on August 22, this strategy would take 30 questions to cover the 9 days in July (23-31) and the 22 days in August (1-22), which is actually 31 days, but we don’t have to ask about the last day, since a "no" to "Were you born after August 21" implies an August 21 birthday, while a "yes" implies an August 22 birthday.
There is a better way. The Day Finder will use an intelligent searching strategy, called the binary search algorithm. Binary search can be used on any ordered objects, like letters or the dates of a Zodiac sign. It uses a series of questions, sometimes called probes, to eliminate half of the remaining possibilities with each question. For example, suppose I’m thinking of a letter. You may ask, "Is the letter between N and Z?", a question that is equivalent to asking if it is in the last half of the alphabet. Regardless of which answer is given half of the alphabet is eliminated. The binary search for the letter "L" goes as follows:
Question Is the letter after M? Is the letter after G? Is the letter after J? Is the letter after L? Is the letter after K? The letter must be L |
Ans N Y Y N Y |
Letters Eliminated A B C D E F G H I J K L M A B C D E F G H I J K L M A B C D E F G H I J K L M A B C D E F G H I J K L A B C D E F G H I J K L |
Notice three points about the example: First, the questions always ask if the letter is after the probe letter. It is possible to ask before and after questions, but always asking the same type simplifies the programming. Second, the intervals sometimes have an odd number of letters and sometimes an even number. When it is an odd number, then the middle letter is the probe. When it is an even number, e.g. H-M, then the probe is the last element of the first half of the interval. This is because of the "after formulation" of the questions. If the questions had been formulated as before questions, then the first element in the second half would be used. These choices have been made to assure that half of the interval will be eliminated when the answer is received. Third the binary search scheme requires only five questions to find any letter. Scientifically, and only for those interested in such things, the binary search takes log2 n probes to find the answer among n items, which is the minimum. The point to remember is that this is the best way to find an item in an ordered sequence.
Finding a birthday works in the just the same way. The only complication is that the sequence of days changes months under the sign, e.g. Leo goes from July to August. This is not really a problem, however, since it is possible to imagine looking for the birthday from July 23rd through July 53rd. Of course, there is no July 53; that’s what August 22 would be if the month didn’t change. The 53 is how far past the end of July the Leo sign goes, which is computed by adding the number of days in July, 31, to the last day of the sign, the 22nd, giving 31 + 22 = 53. This means that the search can look for the birthday between 23 and 53, and whenever a question must be asked about a date larger than 31, the day gets 31 subtracted from it and the month becomes August. Otherwise it is just a normal July date.
For example, to figure out the first question to ask, we find the mid point between July 23 and July 53 by subtracting the smaller endpoint from the larger:
53 – 23 = 30
and dividing by 2
30 \ 2 = 15
If the number had been odd, the 0.5 would have been truncated (or rounded down). Then this number is added to the start of the Leo sign, the 23rd
23 + 15 = 38
which is a day in August since it is larger than the largest day in July, the 31st. Thus, July 38 is really August 7th, since 38-31 = 7. And, it can be checked that this is the middle of the Leo range, just by counting out the days. The result of this computation would be to ask the question:
Were you born after August 7?
which to us is July 38. If the answer is "no," then the new interval is 23 to 38. Remember, August 7 (= July 38) is still a possibility. If the answer is "yes" then the interval is 39 to 53. By repeatedly shrinking the interval we can reduce the interval size to one day, say 32 to 32. And that must be the person’s birthday.
The questions to find the birthday August 1 are as follows, where Lo is the lower end of the interval, Hi is the upper end of the interval, Mid is the computed midpoint and Size is the size of the interval.
Lo 23 23 31 31 31 |
Hi 53 38 38 34 32 |
Size 30 15 7 3 1 |
Mid 38 30 34 32 31 |
Question Were you born after August 7? Were you born after July 30? Were you born after August 3? Were you born after August 1? Were you born after July 31? You were born on August 1. |
Ans No Yes No No Yes |
NewLo 23 31 31 31 32 |
New Hi 38 38 34 32 32 |
Notice that when the endpoints are the same, then they must be for the birthday being sought.
Window asking the first question for the Day Finder application.
Encoding the Binary Search. The overall plan is to extend the Zodiac program by adding a procedure to make guesses, and include the other features needed to implement a Yes/No dialog to produce a program finding the user’s birthday. The guess procedure will generate questions of the form
"Were you born after <month> <day>?"
where the <month> and <day> are chosen to reduce the interval by half, as described above. The interval begins as the whole interval for whatever Zodiac sign was chosen by the user, i.e. the output of Zodiac. After Zodiac displays the interval, the program will make the first guess, and set up the Yes/No buttons for the reply. On each response by the user, the interval will be reduced, a check will be made as to whether the interval has shrunk to a length of 0, i.e. the endpoints are the same, and if the interval is nontrivial another guess will be formulated. Thus, there are three places where a guess must be made: When the interval is first defined and the computation begins, and on both the Yes and No replies.
The first step is to make a copy of the Zodiac program, and save it as the initial version of the Day Finder program. Next, the form must be enhanced by adding the "Y" and "N" buttons, the labels "Were you born after ", "Month" and "Day", all of which should be hidden during the initial operation of the program. In addition to the declared variables,
loMo, hiMo, loDate and hiDate of the Zodiac program, more variables will be needed and must be declared: numDays, loEnd, hiEnd, and midPt, all integers. Declarations for these variables should be added after the declarations made for the Zodiac program. Finally, once the declarations are in, the twelve Zodiac option event handlers must be augmented to assign to the variable numDays, the number of days in their loMo month. This tells the program on what day the month changes. For example, for Leo July is the loMo, which has 31 days, so numDays = 31 must be added to Leo.Next, you will declare a new procedure (
Private Sub) named guess. (We will not use parameters, but will refer to the variables globally.) guess includes the logic to make a guess for the interval ranging from loEnd to hiEnd, inclusively. (This logic is spelled out above.) It will do this by figuring the midpoint (use integer division) of the interval and assigning the value to the variable midPt. It then looks to see which month midPt is in: If midPt is less than or equal to numDays, then the month field is loMo, but if it is greater than numDays, then the month field is hiMo. In the latter case, the day value must also be reduced by numDays. The procedure should modify the captions on the labels for the <month> and <day> fields of the question so as to present the question to the user.The last set of changes involves calling the
guess procedure. It will be called with the program textCall guess
but the critical thing is to locate where it must be called and to set up the variables so it works correctly at each place. There are three locations for a guess call and in each case preparations must be made before the call:
For the latter two calls, similar preparatory code must be written. First, the interval must be reduced. It will either be
loEnd to midPt because the person clicked on No (birthday not after <month> <day>), or it will be midPt + 1 to hiEnd because the person clicked on Yes, (birthday after <month> <day>). Either way one of the end points will be changed. Once the end points have been changed, it is necessary to test to see if the birthday has been found. As mentioned above this case occurs when the new interval has no other days in it, i.e. loEnd = hiEnd. If the birthday is found, it is necessary to confirm to the user that the birthday has been found by typing, "You were born on" followed by the <month> and <day>. The Yes/No buttons should be hidden, just to clean things up.That’s it. Review the following checklist to verify that you’ve incorporated all the changes:
Your program should now run. There will likely be some bugs in the program so try it out on the Leo
Þ August 1 birthday example given above, since all of the correct intermediate steps have been worked out and the relevant values shown in the table. Once that’s working, try it out on your own birthday.Très cool, n’est-ce pas? Try it out on your roommate and friends. Look on as they become mesmerized, repeatedly asking Day Finder to locate some birthday, dumbfounded that it always finds the answer in 5 questions and amazed at how effortlessly it navigates back and forth across the month boundaries. Please do not reveal our secrets to them. If the public finds out how easy this stuff is, everyone will be doing it and programmers’ salaries will go into the toilet. Not good.
And here's a sample executable that you can tinker around with if you'd like.