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
|
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 text
Call 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.