"I am a person who works hard and plays hard."

Yuan Wei
Second Year Graduate Student Department of Computer Science
University of Virginia Charlottesville, VA 22903
Email: yw3f@cs.virginia.edu


Source Code Analysis

Main Page   Compound List   File List   Compound Members   File Members  

anagram.c

Go to the documentation of this file.
00001 /*
00002  *  Anagram program by Raymond Chen,
00003  *  inspired by a similar program by Brian Scearce
00004  *
00005  *  This program is Copyright 1991 by Raymond Chen.
00006  *              (rjc@math.princeton.edu)
00007  *
00008  *  This program may be freely distributed provided all alterations
00009  *  to the original are clearly indicated as such.
00010  */
00011 
00012 /* There are two tricks.  First is the Basic Idea:
00013  *
00014  * When the user types in a phrase, the phrase is first preprocessed to
00015  * determine how many of each letter appears.  A bit field is then constructed
00016  * dynamically, such that each field is large enough to hold the next power
00017  * of two larger than the number of times the character appears.  For example,
00018  * if the phrase is hello, world, the bit field would be
00019  *
00020  *   00 00 00 000 000 00 00
00021  *    d e   h   l   o  r  w
00022  *
00023  * The phrase hello, world, itself would be encoded as
00024  *
00025  *   01 01 01 011 010 01 01
00026  *    d e   h   l   o  r  w
00027  *
00028  * and the word hollow would be encoded as
00029  *
00030  *   00 00 01 010 010 00 01
00031  *    d  e  h   l   o  r  w
00032  *
00033  * The top bit of each field is set in a special value called the sign.
00034  * Here, the sign would be
00035  *
00036  *   10 10 10 100 100 10 10
00037  *    d  e  h   l   o  r  w
00038  *
00039  * The reason for packing the values into a bit field is that the operation
00040  * of subtracting out the letters of a word from the current phrase can be
00041  * carried out in parallel.  for example, subtracting the word hello from
00042  * the phrase hello, world, is merely
00043  *
00044  *    d e   h   l   o  r  w
00045  *   01 01 01 011 010 01 01 (dehllloorw)
00046  * - 00 00 01 010 010 00 01 (hlloow)
00047  * ========================
00048  *   01 01 00 001 000 01 00 (delr)
00049  *
00050  * Since none of the sign bits is set, the word fits, and we can continue.
00051  * Suppose the next word we tried was hood.
00052  *
00053  *    d e   h   l   o  r  w
00054  *   01 01 00 001 000 01 00 (delr)
00055  * - 01 00 01 000 010 00 00 (hood)
00056  * ========================
00057  *   00 00 11 000 110 01 00
00058  *         ^      ^
00059  * A sign bit is set.  (Two, actually.)  This means that hood does not
00060  * fit in delr, so we skip it and try another word.  (Observe that
00061  * when a sign bit becomes set, it screws up the values for the letters to
00062  * the left of that bit, but that's not important.)
00063  *
00064  * The inner loop of an anagram program is testing to see if a
00065  * word fits in the collection of untried letters.  Traditional methods
00066  * keep an array of 26 integers, which are then compared in turn.  This
00067  * means that there are 26 comparisons per word.
00068  *
00069  * This method reduces the number of comparisons to MAX_QUAD, typically 2.
00070  * Instead of looping through an array, we merely perform the indicated
00071  * subtraction and test if any of the sign bits is set.
00072  */
00073 
00074 /* The nuts and bolts:
00075  *
00076  * The dictionary is loaded and preprocessed.  The preprocessed dictionary
00077  * is a concatenation of copies of the structure:
00078  *
00079  * struct dictword {
00080  *     char bStructureSize;             -- size of this structure
00081  *     char cLetters;                   -- number of letters in the word
00082  *     char achWord[];                  -- the word itself (0-terminated)
00083  * }
00084  *
00085  * Since this is a variable-sized structure, we keep its size in the structure
00086  * itself for rapid stepping through the table.
00087  *
00088  * When a phrase is typed in, it is first preprocessed as described in the
00089  * Basic Idea.  We then go through the dictionary, testing each word.  If
00090  * the word fits in our phrase, we build the bit field for its frequency
00091  * table and add it to the list of candidates.
00092  */
00093 
00094 /*
00095  * The Second Trick:
00096  *
00097  * Before diving into our anagram search, we then tabulate how many times
00098  * each letter appears in our list of candidates, and sort the table, with
00099  * the rarest letter first.
00100  *
00101  * We then do our anagram search.
00102  *
00103  * Like most anagram programs, this program does a depth-first search.
00104  * Although most anagram programs do some sort of heuristics to decide what
00105  * order to place words in the list_of_candidates, the search itself proceeds
00106  * according to a greedy algorithm.  That is, once you find a word that fits,
00107  * subtract it and recurse.
00108  *
00109  * This anagram program exercises some restraint and does not march down
00110  * every branch that shows itself.  Instead, it only goes down branches
00111  * that use the rarest unused letter.  This helps to find dead ends faster.
00112  *
00113  * FindAnagram(unused_letters, list_of_candidates) {
00114  *  l = the rarest letter as yet unused
00115  *  For word in list_of_candidates {
00116  *     if word does not fit in unused_letters, go on to the next word.
00117  *     if word does not contain l, defer.
00118  *      FindAnagram(unused_letters - word, list_of_candidates[word,...])
00119  *  }
00120  * }
00121  *
00122  *
00123  * The heuristic of the Second Trick can probably be improved.  I invite
00124  * anyone willing to improve it to do so.
00125  */
00126 
00127 /* Use the accompanying unproto perl script to remove the ANSI-style
00128  * prototypes, for those of you who have K&R compilers.
00129  */
00130 
00131 #include <stdio.h>
00132 #include <stdlib.h>
00133 #include <ctype.h>
00134 #include <sys/types.h>
00135 #include <sys/stat.h>
00136 #include <setjmp.h>
00137 
00138 /* Before compiling, make sure Quad and MASK_BITS are set properly.  For best
00139  * results, make Quad the largest integer size supported on your machine.
00140  * So if your machine has long longs, make Quad an unsigned long long.
00141  * (I called it Quad because on most machines, the largest integer size
00142  * supported is a four-byte unsigned long.)
00143  *
00144  * If you need to be able to anagram larger phrases, increase MAX_QUADS.
00145  * If you increase it beyond 4, you'll have to add a few more loop unrolling
00146  * steps to FindAnagram.
00147  */
00148 
00149 typedef unsigned long Quad;             /* for building our bit mask */
00150 #define MASK_BITS 32                    /* number of bits in a Quad */
00151 
00152 #define MAX_QUADS 2                     /* controls largest phrase */
00153 
00154 #define MAXWORDS 26000                  /* dictionary length */
00155 #define MAXCAND  5000                   /* candidates */
00156 #define MAXSOL   51                     /* words in the solution */
00157 
00158 #define ALPHABET 26                     /* letters in the alphabet */
00159 #define ch2i(ch) ((ch)-'a')             /* convert letter to index */
00160 #define i2ch(ch) ((ch)+'a')             /* convert index to letter */
00161 
00162 /* IBM PC's don't like globs of memory larger than 64K without
00163  * special gyrations.  That's where the huges get stuck in.  And the
00164  * two types of allocations on an IBM PC need to be handled differently.
00165  *
00166  * HaltProcessing is called during the anagram search.  If it returns nonzero,
00167  * then the search is aborted.
00168  *
00169  * Cdecl is a macro expanded before the name of all functions that must
00170  * use C-style parameter passing.  This lets you change the default parameter
00171  * passing style for the other functions.
00172  */
00173 
00174 /* char *malloc(); */
00175 #   define huge
00176 #   define far
00177 #   define StringFormat    "%15s%c"
00178 #   define bigmalloc       malloc
00179 #   define smallmalloc     malloc
00180 #   define smallmallocfail (char *)0
00181 #   define HaltProcessing() 0           /* no easy way to interrupt on UNIX */
00182 #   define Cdecl
00183 
00184 /* Code to be used only when debugging lives inside Debug().
00185  * Code to be used only when collecting statistics lives inside Stat().
00186  */
00187 #ifdef DEBUG
00188 #define Debug(x) x
00189 #else
00190 #define Debug(x)
00191 #endif
00192 
00193 #ifdef STAT
00194 #define Stat(x) x
00195 #else
00196 #define Stat(x)
00197 #endif
00198 
00199 /* A Word remembers the information about a candidate word. */
00200 typedef struct {
00201     Quad aqMask[MAX_QUADS];  /* the word's mask */
00202     char * pchWord;                 /* the word itself */
00203     unsigned cchLength;                 /* letters in the word */
00204 } Word;
00205 typedef Word * PWord;
00206 typedef Word * * PPWord;
00207 
00208 PWord apwCand[MAXCAND];    /* candidates we've found so far */
00209 unsigned cpwCand;                       /* how many of them? */
00210 
00211 
00212 /* A Letter remembers information about each letter in the phrase to be
00213  * anagrammed.
00214  */
00215 
00216 typedef struct {
00217     unsigned uFrequency;                /* how many times it appears */
00218     unsigned uShift;                    /* how to mask */
00219     unsigned uBits;                     /* the bit mask itself */
00220     unsigned iq;                        /* which Quad to inspect? */
00221 } Letter;
00222 typedef Letter * PLetter;
00223 
00224 Letter alPhrase[ALPHABET]; /* statistics on the current phrase */
00225 #define lPhrase(ch) alPhrase[ch2i(ch)]  /* quick access to a letter */
00226 
00227 int cchPhraseLength;                    /* number of letters in phrase */
00228 
00229 Quad aqMainMask[MAX_QUADS];/* the bit field for the full phrase */
00230 Quad aqMainSign[MAX_QUADS];/* where the sign bits are */
00231 
00232 int cchMinLength = 3;
00233 
00234 /* auGlobalFrequency counts the number of times each letter appears, summed
00235  * over all candidate words.  This is used to decide which letter to attack
00236  * first.
00237  */
00238 unsigned auGlobalFrequency[ALPHABET];
00239 char achByFrequency[ALPHABET];          /* for sorting */
00240 
00241 char * pchDictionary;               /* the dictionary is read here */
00242 
00243 #define Zero(t) memset(t, 0, sizeof(t)) /* quickly zero out an integer array */
00244 
00245 /* Fatal -- print a message before expiring */
00246 void Fatal(char *pchMsg, unsigned u) {
00247     fprintf(stdout, pchMsg, u);
00248     exit(1);
00249 }
00250 
00251 /* ReadDict -- read the dictionary file into memory and preprocess it
00252  *
00253  * A word of length cch in the dictionary is encoded as follows:
00254  *
00255  *    byte 0    = cch + 3
00256  *    byte 1    = number of letters in the word
00257  *    byte 2... = the word itself, null-terminated
00258  *
00259  * Observe that cch+3 is the length of the total encoding.  These
00260  * byte streams are concatenated, and terminated with a 0.
00261  */
00262 
00263 void ReadDict(char *pchFile) {
00264     FILE *fp;
00265     char * pch;
00266     char * pchBase;
00267     unsigned long ulLen;
00268     unsigned cWords = 0;
00269     unsigned cLetters;
00270     int ch;
00271     struct stat statBuf;
00272 
00273     if (stat(pchFile, &statBuf)) Fatal("Cannot stat dictionary\n", 0);
00274 
00275     ulLen = statBuf.st_size + 2 * (unsigned long)MAXWORDS;
00276     pchBase = pchDictionary = (char *)malloc(ulLen);
00277 
00278     if(pchDictionary == NULL)
00279         Fatal("Unable to allocate memory for dictionary\n", 0);
00280 
00281     if ((fp = fopen(pchFile, "r")) == NULL)
00282         Fatal("Cannot open dictionary\n", 0);
00283 
00284     while (!feof(fp)) {
00285         pch = pchBase+2;                /* reserve for length */
00286         cLetters = 0;
00287         while ((ch = fgetc(fp)) != '\n' && ch != EOF) {
00288             if (isalpha(ch)) cLetters++;
00289             *pch++ = ch;
00290         }
00291         *pch++ = '\0';
00292         *pchBase = pch - pchBase;
00293         pchBase[1] = cLetters;
00294         pchBase = pch;
00295         cWords++;
00296     }
00297     fclose(fp);
00298 
00299     *pchBase++ = 0;
00300 
00301     fprintf(stdout, "main dictionary has %u entries\n", cWords);
00302     if (cWords >= MAXWORDS)
00303         Fatal("Dictionary too large; increase MAXWORDS\n", 0);
00304     fprintf(stdout, "%lu bytes wasted\n", ulLen - (pchBase - pchDictionary));
00305 }
00306 
00307 void BuildMask(char * pchPhrase) {
00308     int i;
00309     int ch;
00310     unsigned iq;                        /* which Quad? */
00311     int cbtUsed;                        /* bits used in the current Quad */
00312     int cbtNeed;                        /* bits needed for current letter */
00313     Quad qNeed;                         /* used to build the mask */
00314 
00315     bzero(alPhrase, sizeof(Letter)*ALPHABET);
00316     bzero(aqMainMask, sizeof(Quad)*MAX_QUADS);
00317     bzero(aqMainSign, sizeof(Quad)*MAX_QUADS);
00318 /*
00319     Zero(alPhrase);
00320     Zero(aqMainMask);
00321     Zero(aqMainSign);
00322 */
00323 
00324     /* Tabulate letter frequencies in the phrase */
00325     cchPhraseLength = 0;
00326     while ((ch = *pchPhrase++) != '\0') {
00327         if (isalpha(ch)) {
00328             ch = tolower(ch);
00329             lPhrase(ch).uFrequency++;
00330             cchPhraseLength++;
00331         }
00332     }
00333 
00334     /* Build  masks */
00335     iq = 0;                             /* which quad being used */
00336     cbtUsed = 0;                        /* bits used so far */
00337 
00338     for (i = 0; i < ALPHABET; i++) {
00339         if (alPhrase[i].uFrequency == 0) {
00340             auGlobalFrequency[i] = ~0;  /* to make it sort last */
00341         } else {
00342             auGlobalFrequency[i] = 0;
00343             for (cbtNeed = 1, qNeed = 1;
00344                  alPhrase[i].uFrequency >= qNeed;
00345                  cbtNeed++, qNeed <<= 1);
00346             if (cbtUsed + cbtNeed > MASK_BITS) {
00347                 if (++iq >= MAX_QUADS)
00348                     Fatal("MAX_QUADS not large enough\n", 0);
00349                 cbtUsed = 0;
00350             }
00351             alPhrase[i].uBits = qNeed-1;
00352             if (cbtUsed)
00353                 qNeed <<= cbtUsed;
00354             aqMainSign[iq] |= qNeed;
00355             aqMainMask[iq] |= (Quad)alPhrase[i].uFrequency << cbtUsed;
00356             alPhrase[i].uShift = cbtUsed;
00357             alPhrase[i].iq = iq;
00358             cbtUsed += cbtNeed;
00359         }
00360     }
00361 }
00362 
00363 PWord
00364 NewWord(void) {
00365     PWord pw;
00366 
00367     pw = (Word *)malloc(sizeof(Word));
00368     if (pw == NULL)
00369         Fatal("Out of memory after %d candidates\n", cpwCand);
00370     return pw;
00371 }
00372 
00373 /* wprint -- print a word, followed by a space
00374  *
00375  * We would normally just use printf, but the string being printed is
00376  * is a huge pointer (on an IBM PC), so special care must be taken.
00377  */
00378 void wprint(char * pch) {
00379     printf("%s ", pch);
00380 }
00381 
00382 PWord NextWord(void);
00383 
00384 /* NextWord -- get another candidate entry, creating if necessary */
00385 PWord NextWord(void) {
00386     PWord pw;
00387     if (cpwCand >= MAXCAND)
00388         Fatal("Too many candidates\n", 0);
00389     pw = apwCand[cpwCand++];
00390     if (pw != NULL)
00391         return pw;
00392     apwCand[cpwCand-1] = NewWord();
00393     return apwCand[cpwCand-1];
00394 }
00395 
00396 /* BuildWord -- build a Word structure from an ASCII word
00397  * If the word does not fit, then do nothing.
00398  */
00399 void BuildWord(char * pchWord) {
00400     unsigned char cchFrequency[ALPHABET];
00401     int i;
00402     char * pch = pchWord;
00403     PWord pw;
00404     int cchLength = 0;
00405 
00406     bzero(cchFrequency, sizeof(unsigned char)*ALPHABET);
00407     /* Zero(cchFrequency); */
00408 
00409     /* Build frequency table */
00410     while ((i = *pch++) != '\0') {
00411         if (!isalpha(i)) continue;
00412         i = ch2i(tolower(i));
00413         if (++cchFrequency[i] > alPhrase[i].uFrequency)
00414             return;
00415         ++cchLength;
00416     }
00417 
00418     Debug(wprint(pchWord);)
00419 
00420     /* Update global count */
00421     for (i = 0; i < ALPHABET; i++)
00422         auGlobalFrequency[i] += cchFrequency[i];
00423 
00424     /* Create a Word structure and fill it in, including building the
00425      * bitfield of frequencies.
00426      */
00427     pw = NextWord();
00428     bzero(pw->aqMask, sizeof(Quad)*MAX_QUADS);
00429     /* Zero(pw->aqMask); */
00430     pw->pchWord = pchWord;
00431     pw->cchLength = cchLength;
00432     for (i = 0; i < ALPHABET; i++) {
00433         pw->aqMask[alPhrase[i].iq] |=
00434             (Quad)cchFrequency[i] << alPhrase[i].uShift;
00435     }
00436 }
00437 
00438 /* AddWords -- build the list of candidates */
00439 void
00440 AddWords(void) {
00441     char * pch = pchDictionary;     /* walk through the dictionary */
00442 
00443     cpwCand = 0;
00444 
00445     while (*pch) {
00446         if ((pch[1] >= cchMinLength && pch[1]+cchMinLength <= cchPhraseLength)
00447             || pch[1] == cchPhraseLength)
00448             BuildWord(pch+2);
00449         pch += *pch;
00450     }
00451 
00452     fprintf(stdout, "%d candidates\n", cpwCand);
00453 }
00454 
00455 void DumpCandidates(void) {
00456     unsigned u;
00457 
00458     for (u = 0; u < cpwCand; u++)
00459         printf(StringFormat, apwCand[u]->pchWord, (u % 4 == 3) ? '\n' : ' ');
00460     printf("\n");
00461 }
00462 
00463 PWord apwSol[MAXSOL];                   /* the answers */
00464 int cpwLast;
00465 
00466 Debug(
00467 void DumpWord(Quad * pq) {
00468     int i;
00469     Quad q;
00470     for (i = 0; i < ALPHABET; i++) {
00471         if (alPhrase[i].uFrequency == 0) continue;
00472         q = pq[alPhrase[i].iq];
00473         if (alPhrase[i].uShift) q >>= alPhrase[i].uShift;
00474         q &= alPhrase[i].uBits;
00475         while (q--) putchar('a'+i);
00476     }
00477     putchar(' ');
00478 }
00479 )                                       /* End of debug code */
00480 
00481 void DumpWords(void) {
00482     int i;
00483     for (i = 0; i < cpwLast; i++) wprint(apwSol[i]->pchWord);
00484     printf("\n");
00485 }
00486 
00487 Stat(unsigned long ulHighCount; unsigned long ulLowCount;)
00488 
00489 jmp_buf jbAnagram;
00490 
00491 #define OneStep(i) \
00492     if ((aqNext[i] = pqMask[i] - pw->aqMask[i]) & aqMainSign[i]) { \
00493         ppwStart++; \
00494         continue; \
00495     }
00496 
00497 
00498 void
00499 FindAnagram(Quad * pqMask, PPWord ppwStart, int iLetter)
00500 {
00501     Quad aqNext[MAX_QUADS];
00502     register PWord pw;
00503     Quad qMask;
00504     unsigned iq;
00505     PPWord ppwEnd = &apwCand[0];
00506     ppwEnd += cpwCand;
00507 
00508     ;
00509 
00510     if (HaltProcessing()) longjmp(jbAnagram, 1);
00511 
00512     Debug(printf("Trying :"); DumpWord(pqMask); printf(":\n");)
00513 
00514     for (;;) {
00515         iq = alPhrase[achByFrequency[iLetter]].iq;
00516         qMask = alPhrase[achByFrequency[iLetter]].uBits <<
00517                 alPhrase[achByFrequency[iLetter]].uShift;
00518         if (pqMask[iq] & qMask) break;
00519         iLetter++;
00520     }
00521 
00522     Debug(printf("Pivoting on %c\n", i2ch(achByFrequency[iLetter]));)
00523 
00524     while (ppwStart < ppwEnd) {          /* Half of the program execution */
00525         pw = *ppwStart;                  /* time is spent in these three */
00526 
00527         Stat(if (++ulLowCount == 0) ++ulHighCount;)
00528 
00529 #if MAX_QUADS > 0
00530         OneStep(0);                     /* lines of code. */
00531 #endif
00532 
00533 #if MAX_QUADS > 1
00534         OneStep(1);
00535 #endif
00536 
00537 #if MAX_QUADS > 2
00538         OneStep(2);
00539 #endif
00540 
00541 #if MAX_QUADS > 3
00542         OneStep(3);
00543 #endif
00544 
00545 #if MAX_QUADS > 4
00546             @@"Add more unrolling steps here, please."@@
00547 #endif
00548 
00549         /* If the pivot letter isn't present, defer this word until later */
00550         if ((pw->aqMask[iq] & qMask) == 0) {
00551             *ppwStart = *--ppwEnd;
00552             *ppwEnd = pw;
00553             continue;
00554         }
00555 
00556         /* If we get here, this means the word fits. */
00557         apwSol[cpwLast++] = pw;
00558         if (cchPhraseLength -= pw->cchLength) { /* recurse */
00559             Debug(DumpWords();)
00560             /* The recursive call scrambles the tail, so we have to be
00561              * pessimistic.
00562              */
00563             ppwEnd = &apwCand[0];
00564             ppwEnd += cpwCand;
00565             FindAnagram(&aqNext[0],
00566                         ppwStart, iLetter);
00567         } else DumpWords();             /* found one */
00568         cchPhraseLength += pw->cchLength;
00569         --cpwLast;
00570         ppwStart++;
00571         continue;
00572     }
00573 
00574     ;
00575 }
00576 
00577 int Cdecl CompareFrequency(char *pch1, char *pch2) {
00578     return auGlobalFrequency[*pch1] < auGlobalFrequency[*pch2]
00579         ?  -1 :
00580            auGlobalFrequency[*pch1] == auGlobalFrequency[*pch2]
00581         ?   0 : 1;
00582 }
00583 
00584 void SortCandidates(void) {
00585     int i;
00586 
00587     /* Sort the letters by frequency */
00588     for (i = 0; i < ALPHABET; i++) achByFrequency[i] = i;
00589     qsort(achByFrequency, ALPHABET, sizeof(char),
00590           (int (*)(const void *, const void *))CompareFrequency);
00591 
00592     fprintf(stdout, "Order of search will be ");
00593     for (i = 0; i < ALPHABET; i++)
00594         fputc(i2ch(achByFrequency[i]), stdout);
00595     fputc('\n', stdout);
00596 }
00597 
00598 int fInteractive;
00599 
00600 char * GetPhrase(char * pch) {
00601     if (fInteractive) printf(">");
00602     fflush(stdout);
00603     if (gets(pch) == NULL) {
00604 #ifdef PLUS_STATS
00605         PrintDerefStats(stdout);
00606         PrintHeapSize(stdout);
00607 #endif /* PLUS_STATS */
00608         exit(0);
00609     }
00610     return(pch);
00611 }
00612 
00613 char achPhrase[255];
00614 
00615 int Cdecl main(int cpchArgc, char **ppchArgv) {
00616 
00617     if (cpchArgc != 2 && cpchArgc != 3)
00618         Fatal("Usage: anagram dictionary [length]\n", 0);
00619 
00620     if (cpchArgc == 3)
00621         cchMinLength = atoi(ppchArgv[2]);
00622 
00623     fInteractive = isatty(1);
00624 
00625     ReadDict(ppchArgv[1]);
00626 
00627     while (GetPhrase(&achPhrase[0]) != NULL) {
00628         if (isdigit(achPhrase[0])) {
00629             cchMinLength = atoi(achPhrase);
00630             printf("New length: %d\n", cchMinLength);
00631         } else if (achPhrase[0] == '?') {
00632             DumpCandidates();
00633         } else {
00634             BuildMask(&achPhrase[0]);
00635             AddWords();
00636             if (cpwCand == 0 || cchPhraseLength == 0) continue;
00637 
00638             Stat(ulHighCount = ulLowCount = 0;)
00639             cpwLast = 0;
00640             SortCandidates();
00641             if (setjmp(jbAnagram) == 0)
00642                 FindAnagram(&aqMainMask[0], &apwCand[0], 0);
00643             Stat(printf("%lu:%lu probes\n", ulHighCount, ulLowCount);)
00644         }
00645     }
00646     return 0;
00647 }


UVa CS Department of Computer Science
School of Engineering, University of Virginia
151 Engineer's Way, P.O. Box 400740
Charlottesville, Virginia 22904-4740

(434) 982-2200  Fax: (434) 982-2214