Jul 97 Challenge
Volume Number: 13
Issue Number: 7
Column Tag: Programmer's Challenge
Jul 97 - Programmer's Challenge
by Bob Boonstra, Westford, MA
Disambiguator
The Challenge this month is to write a string completion routine loosely patterned
after the keyword lookup facility in the QuickView utility. QuickView will suggest a
completion of the keyword as you begin to type it, and update that suggested completion
as you continue to type. In the Toolbox Assistant, for example, if you are looking for
documentation on InitGraf and type "i", the suggested completion is "iconIDToRgn". As
you continue by typing "n", the suggestion becomes "index2Color". Adding "i" yields
"initAllPacks"; adding "t" leaves the suggestion intact; adding "g" changes it to
"initGDevice". Finally, typing "r" gives the desired "initgraf".
For our disambiguator, you will be given an unsorted list of words and an opportunity
to preprocess them. Then you will be given a string to match and asked to return a list
of words matching findString. To make the problem more interesting, the match string
can contain wild card characters, as described below.
The prototype for the code you should write is:
typedef unsigned long ulong;
void InitDisambiguator(
const char *const wordList[], /* words to match against */
ulong numWords, /* number of words in wordList */
void *privStorage, /* private storage preinitialized
to zero */
ulong storageSize /* number of bytes of privStorage
*/
);
ulong /*numMatch*/ Disambiguator(
const char *const wordList[], /* words to match against */
ulong numWords, /* number of words in wordList */
void *privStorage, /* private storage */
ulong storageSize, /* number of bytes of privStorage
*/
char *findString, /* string to match, includes wild
cards */
char *matchList[] /* return matched words here */
);
Your InitDisambiguator routine will be called with an unsorted list wordList of
numWords null-terminated words to match. The wordList words will include
alphanumeric characters, spaces, and underscores. You will also be provided with a
pointer privStorage to storageSize bytes of preallocated memory initialized to zero.
The amount of storage provided will be at least 20 bytes for each word in wordList,
plus one byte for each character in the wordList (including the null byte, and rounded
up to a multiple of 4). In other words, storageSize will be no smaller than minStorage,
calculated as:
for (minStorage=0,i=0; i
minStorage += 20 + 4*(1+strlen(wordList[i])/4);
InitDisambiguator is not allowed to modify the wordList, but you may store a sorted
version of wordList, or pointers to the words in sorted order, in privStorage. The first
four parameters provided to Disambiguator will be identical as those provided to
InitDisambiguator. In addition, you will be provided with the null-terminated
findString and a preallocated array matchList with numWords entries where you are
to store pointers to the words that match findString. Your string matches should be
case insensitive (i.e., "initgr" matches "InitGraf". The matchList should be returned
with the strings ordered in case-insensitive ASCII order (i.e., space < [0..9] <
[A-Za-z] < underscore).
The findString may also contain zero or more of the wildcard characters '?', '*', and
'+'. The wildcard '?' matches any single character, '*' matches zero or more
characters, and '+' matches one or more characters. So, for example, "*graf" matches
any string ending in the (case-insensitive) string "graf", while "+1Ind+" matches
any string containing "1Ind" between the first and last characters of a word.
For each call to InitDisambiguator, your Disambiguator routine will be called an
average of 100 to 1000 times. The winner will be the solution that finds the correct
matchList in the minimum amount of time, including the time taken by the
initialization routine.
This will be a native PowerPC Challenge, using the latest CodeWarrior environment.
Solutions may be coded in C, C++, or Pascal. The problem is based on a suggestion by
Charles Kefauver, who pointed me to an April, 1995, AppleDirections article
discussing the user interface for a disambiguator. Charles wins 2 Challenge points for
his suggestion.
Three Months Ago Winner
Congratulations to ACC Murphy (Perth, Australia), for submitting the faster (and
smaller) of the two entries I received for the Projection Challenge. This problem
required contestants to calculate the image of a set of input polygons, including the
shadows cast by one polygon on another, given an observation viewpoint and an
illumination point.
Both of the submitted solutions used a ray-tracing technique. The winning solution
calculates, for each point on the projection plane, the nearest polygon to the viewpoint
among those intersecting the ray from the plane to the viewpoint. It then does another
ray-trace to determine if there are any other polygons between the illumination point
and the projected polygon, identifying the point as being in shadow if an intervening
polygon is found.
I ran three test cases, moving the polygons 10 times for a given viewpoint in each
case, using a GWorld bounds rectangle slightly smaller than my 1024x768 monitor.
As you can see from the execution times, considerable refinement would be needed
before this code could be used for animation.
A good discussion of the projection and hidden surface removal algorithms applicable to
this problem can be found in the Black Art of Macintosh Game Programming, by Kevin
Tieskoetter. In addition to discussing the z-buffer ray-tracing algorithm, it describes
another technique for hidden surface removal called the Painter's algorithm. This
approach breaks the polygons to be displayed into pieces such that each piece is
entirely in front of or entirely behind any other piece, as seen from the viewpoint. The
polygons can then be sorted and displayed without looking at each pixel in the image.
For our application, two polygon decompositions would be required, one for the image,
and one for the shadows.
The table below lists, for each entry, the execution time for each case and the code size.
The number in parentheses after the entrant's name is the total number of Challenge
points earned in all Challenges to date prior to this one.
Case 1 Case 2 Case 3 Total Code
Name Time Time Time Time (secs) Size
A.C.C. Murphy (10) 29.02 23.64 81.61 134.27 4196
Ernst Munter (232) 20.87 58.11 89.76 168.74 7192
Top 20 Contestants
Here are the Top Contestants for the Programmer's Challenge. The numbers below
include points awarded over the 24 most recent contests, including points earned by
this month's entrants.
Rank Name Points Rank Name Points
1. Munter, Ernst 194 11. Beith, Gary 24
2. Gregg, Xan 114 12. Cutts, Kevin 21
3. Cooper, Greg 54 13. Nicolle, Ludovic 21
4. Larsson, Gustav 47 14. Picao, Miguel Cruz 21
5. Lengyel, Eric 40 15. Brown, Jorg 20
6. Boring, Randy 37 16. Gundrum, Eric 20
7. Mallett, Jeff 37 17. Higgins, Charles 20
8. Lewis, Peter 32 18. Kasparian, Raffi 20
9. Murphy, ACC 30 19. Slezak, Ken 20
10. Antoniewicz, Andy 24 20. Studer, Thomas 20
There are three ways to earn points: (1) scoring in the top 5 of any Challenge, (2)
being the first person to find a bug in a published winning solution or, (3) being the
first person to suggest a Challenge that I use. The points you can win are:
1st place 20 points 5th place 2 points
2nd place 10 points finding bug 2 points
3rd place 7 points suggesting Challenge 2 points
4th place 4 points
Here is A.C.C. Murphy's winning solution:
Challenge.p
A.C.C. Murphy
unit Challenge;
(*
Assumptions:
Storage space must be big enough for 13 floats per polygon
All points must be significantly smaller in magnitude than
BIG_FLOAT =
1000000.0
Polygons are translucent (their colour based uplon lighting is
independent
of the side of the polygon that is lit)
50% attenuation of colour is used
50% attenuation of black is black
Method:
InitProjection is not used
First we precalculate a small bounding sphere for the polygon
points.
Next we get the information about the GWorld to allow direct pixel
access.
Then for each point on the GWorld, we trace the ray from the point
to the
eye, intersecting it with each polygon and finding the one
closes to
the eye (furthest forward, since the eye is infront of all
polygons).
That determines the colour. We then trace the ray from that
intersection
point to the light source to determine whether the point is in
shadow,
and if so we halve the intensity. We set the colour of the pixel
and
move on.
Optimizations:
Direct pixel access to the GWorld (known to be 32 bit)
Bounding sphere used to optimize the ray/polygon intersection
test.
Time is approximately 2 microseconds per pixel per polygon on an
8500.
*)
interface
uses
Types, Quickdraw, QDOffscreen;
const
kMAXPOINTS = 10;
const
BIG_FLOAT = 1000000.0;
float = real;
My2DPoint = record (* point in z==0 plane*)
x2D: float; (* x coordinate*)
y2D: float; (* y coordinate*)
end;
My3DPoint = record
x3D: float; (* x coordinate*)
y3D: float; (* y coordinate*)
z3D: float; (* z coordinate*)
end;
My3DDirection = record
thetaX:float; (* angle in radians*)
thetaY:float; (* angle in radians*)