/* Program: ANAGRAMS Author : Kim Moser Date : 3 December, 1990 System : IBM PC / Borland Turbo C 2.0 Descrip: Prints anagrams for given string Usage : ANAGRAMS [-option ...] Copyright (C) 1990 by Kim Moser All rights reserved */ // #define DEMO #if sizeof(int) != 2 #ifdef AMIGA #error Please compile with the '-w' switch to ensure that ints take 2 bytes. #else #error ints must be 2 bytes long. #endif #endif #ifdef __TURBOC__ #ifndef __COMPACT__ #error For optimum performance, I suggest you use the Compact memory model. #error Anything smaller might not work if more than 64k of words are read in, #error and anything larger is probably unnecessary. #endif #endif #include #include #include #include #include #ifdef AMIGA #define kbhit() 0 #else #include /* kbhit() */ #endif /*************************************************************************/ /* All this stuff is for decompressing compressed word files: */ static FILE *WORDFILE; /* The file of (compressed) words. */ /* Global only so we don't have to pass it on the stack thousands of times. */ #define ASTERISK ('Z'-'A'+1) /* Bitwise & with LEFTMOST[n] to clear all but the leftmost n bits: */ static unsigned char LEFTMOST[] = { (unsigned char) (0), (unsigned char) (128), (unsigned char) (128+64), (unsigned char) (128+64+32), (unsigned char) (128+64+32+16), (unsigned char) (128+64+32+16+8), (unsigned char) (128+64+32+16+8+4), (unsigned char) (128+64+32+16+8+4+2), (unsigned char) (128+64+32+16+8+4+2+1) }; #define leftmost(_ch,_bits) ((unsigned char)(((_ch) & (LEFTMOST[(_bits)]))>>(8-(_bits)))) /* Returns the leftmost 'bits' bits (as a char) in character 'ch' */ #if 0 static void out(int i); static void out(int i) { static unsigned char bits = '\0'; static int index = 0; /* How many trailing bits in 'bits' are valid (when it reaches 8, we write 'bits' to stdout) */ unsigned char ch; /* 'i' as an unsigned char */ int shift; if (i >= 32) { fprintf(stderr, "out(): i (%d) is too large.\n", i); exit(-1); } if (i == -1) { putchar((unsigned int) (bits << (8-index))); bits = '\0'; index = 0; return; } ch = (unsigned char) i; ch <<= 3; /* Left-justify bits within 'ch' */ shift = min(5, (8-index)); /* Shift 'bits' left to make room for 'shift' bits: */ bits <<= shift; /* Add leftmost 'shift' bits from 'ch' into 'bits': */ bits |= (leftmost(ch, shift)); if ((index += shift) == 8) { putchar((unsigned int) bits); bits = '\0'; index = 0; } /* If they didn't all fit, stuff remaining (leftmost) bits into 'bits': */ if (shift != 5) { ch <<= shift; bits = (leftmost(ch, (5-shift))); index += (5 - shift); } } static char str[512], prev[sizeof(str)]; static void compress(void); static void compress(void) { int i; prev[0] = '\0'; while (gets(str) != NULL) { if (str[strlen(str)-1] == '*') { str[strlen(str)-1] = '\0'; out(ASTERISK); } /* Count how many leading chars from str[] match leading chars in prev[]: */ for (i=0; (i<31) && str[i] && prev[i] && (str[i]==prev[i]); i++); out(i); /* Output rest of str[]: */ while (str[i]) { out(str[i]-'A'); i++; } /* 'str[]' becomes new 'prev[]': */ strcpy(prev, str); } /* Flush 'out' buffer: */ out(31); out(-1); } #endif static unsigned char in_index = 0; /* How many leading bits in 'ch' are used */ static int in(void); static int in(void) { static int i; /* Char read from stdin */ static unsigned char ch; /* 'i' as an unsigned char */ register int shift; /* Tmp */ register unsigned char r = '\0'; register int got = 0; /* How many rightmost bits in 'r' are valid */ while (got != 5) { if (!in_index) { if ((i = fgetc(WORDFILE)) == EOF) { return (i); } in_index = 8; ch = (unsigned char) i; } shift = min(in_index, (5-got)); r <<= shift; r |= leftmost(ch, shift); ch <<= shift; in_index -= shift; got += shift; } return ((int) r); } static char *readword(register char *s, int len, int *marked); static char *readword(register char *s, int len, int *marked) { register int i; *marked = 0; /* Assume */ switch (i = in()) { case 31: case EOF: return NULL; case ASTERISK: *marked = 1; if ((i = in()) == EOF) return NULL; break; } while (i < len) { s[i++] = in() + 'A'; } return (s); } /*************************************************************************/ /* All the rest is for anagrams: */ static int ECHO_STDERR=0; /* Whether anagrams should be echoed to stderr, too. */ static int SHOW_CANDIDATE_WORDS = 0; /* Whether candidate words should be displayed */ #ifdef DEMO #define LONGEST_WORD_FILE 6 #else #define LONGEST_WORD_FILE 27 #endif static char *WORDS[LONGEST_WORD_FILE+1]; static long COUNTS[LONGEST_WORD_FILE+1]; /* How many words of each length were read in */ static unsigned char *INTERESTINGS[LONGEST_WORD_FILE+1]; /* Bit INTERESTINGS[len][index/8] determines whether index'th word of length len is interesting. */ static char **WORDARRAY; static int WORDARRAYINDEX=0; /* Next free spot in WORDARRAY */ /* (Also tells us how many words are in current anagram) */ static int *INTERESTINGARRAY; /* Array of ints */ /* INTERESTINGARRAY[i] is a boolean which notes whether WORDARRAY[i] is interesting. Later, when the histogram is empty and we're about to print the words in WORDARRAY[], we check if INTERESTING!=0, and if so, we print the words in WORDARRAY[] only if at least one value in INTERESTINGARRAY[] is !=0 (i.e. there's at least one interesting word in this anagram). */ static int HISTOGRAM[256], OTHER_HISTOGRAM[sizeof(HISTOGRAM)/sizeof(HISTOGRAM[0])]; /* Although we really use only HISTOGRAM['A']..HISTOGRAM['Z'], the 256-sized array lets us reference any character's count directly (e.g. HISTOGRAM['K']), instead of having to scale it (e.g. HISTOGRAM['K'-'A']). */ static int LETTERSLEFT=0; /* Running total of how many letters are left in HISTOGRAM. (Shrinks as we recurse more deeply, grows as we un-recurse.) Used by anagrams() to tell which word length to use next. */ static char *STRING = NULL; /* Will point to string to be "anagramized" */ static int STRINGLEN; /* Length of STRING */ static unsigned long OUT_SO_FAR = 0; /* How many anagrams have been output so far */ static int INTERESTING = 0; /* 0: Print all anagrams 1..32767: Print only anagrams which contain at least that many "interesting" words */ static int NO_DUPES = 0; /* Whether a word may NOT appear more than once in an anagram. */ static int MIN_WORD_LEN = 1; static int MAX_WORD_LEN = LONGEST_WORD_FILE; static int MIN_WORDS = -1; /* Minimum number of words allowed in an anagram (i.e. if the anagram contains fewer than MIN_WORDS words, don't display it). We start it at -1 because that way, after parse() is called, if it's still -1 then the user didn't change it, and it should default to 1. */ static int MAX_WORDS = -1; /* Maximum number of words allowed in an anagram (i.e. if the anagram contains more than MAX_WORDS words, don't display it). We start it at -1 because that way, after parse() is called, if it's still -1 then the user didn't change it, and it should default to 32767. */ static int FLAG_MIN_WORDS = 0; /* Minimum number of words an anagram must have for it to be flagged. */ static int FLAG_MAX_WORDS = 0; /* Maximum number of words an anagram may have for it to be flagged. */ static int FLAG_INTERESTING = 0; /* Whether to flag 'interesting' anagrams. */ static int STATUS = 0; /* Whether user should see status as anagram is being computed. STATUS = 0: show nothing (status off) 1: show leading word 2: show leading word and % done 3: show leading word, % done, and how many out */ static int COL_WIDTH = 0; /* Width of columns to output to. We start it at 0 because that way, after parse() is called, if it's still 0 then the user didn't change it, and, if they specified NUM_COLS > 1, then COL_WIDTH should default to (strlen(STRING) + strlen(STRING)/2) + 1. When COL_WIDTH == 0, that means only 1 column of unlimited width. */ static int NUM_COLS = 0; /* Number of columns to output to. We start it at 0 because that way, after parse() is called, if it's still 0 then the user didn't change it, so it should default to 1. */ static int PAGE_WIDTH = 0; #if 0 static int SHOWALLSTATUS = 0; /* When '?' key is hit, this global is set to 1 to indicate to processword() that it should show the current word stack when it hits a dead end. */ #endif static int OUTERMOST_LEN; static long int OUTERMOST_INDEX; /* The current value of the first recursion (used for status()). */ static struct { struct { int min, max; } *lens; } *RANGETABLE; /* If you have 'left' characters left in your histogram, and you're checking words of length 'len', then RANGETABLE[left].len[len].min and RANGETABLE[left].len[len].max tell you the minimum and maximum word length you >really< should be checking. This table is set up after the candidate words are read in. It's used to optimize the anagrams() function, so that it doesn't unnecessarily check word combinations of different lengths if it can be predetermined that such combinations won't produce any anagrams. Example: If your histogram has 15 characters left, and you've read in words of length 4, 5, 6 ... 15, then you can safely start with words of length 15 and go all the way down to words of length 5. If you blindly continue down to words of length 4, you'll be merely wasting your time. */ #define rangetablemin(_left,_len) (RANGETABLE[(_left)].lens[(_len)].min) #define rangetablemax(_left,_len) (RANGETABLE[(_left)].lens[(_len)].max) #if 0 static int rangetablemin(int left, int len); static int rangetablemin(int left, int len) { if (left < MIN_WORD_LEN || left > STRINGLEN) { fprintf(stderr, "\nrangetablemin(): left (%ld) out of range.\n", len); exit(-1); } if (len < MIN_WORD_LEN || len > left) { fprintf(stderr, "\nrangetablemin(): len (%d) out of range.\n", len); exit(-1); } return RANGETABLE[left].lens[len].min; } static int rangetablemax(int left, int len); static int rangetablemax(int left, int len) { if (left < MIN_WORD_LEN || left > STRINGLEN) { fprintf(stderr, "\nrangetablemax(): left (%ld) out of range.\n", len); exit(-1); } if (len < MIN_WORD_LEN || len > left) { fprintf(stderr, "\mrangetablemax(): len (%d) out of range.\n", len); exit(-1); } return RANGETABLE[left].lens[len].max; } #endif /***********************************************************************/ #if 0 static void removeword(register char *s); static void removeword(register char *s) /* Remove word from histogram and add it to WORDARRAY */ { int i; WORDARRAY[WORDARRAYINDEX++] = s; for (i=0; s[i]; i++) { HISTOGRAM[s[i]]--; } } #endif #if 0 static void addword(char *s); static void addword(char *s) /* Add word to histogram and remove it from WORDARRAY */ { int i; WORDARRAYINDEX--; for (i=0; s[i]; i++) { HISTOGRAM[s[i]]++; } LETTERSLEFT += i; } #endif #define addword(_s) { \ int i; \ \ WORDARRAYINDEX--; \ for (i=0; (_s)[i]; i++) { \ HISTOGRAM[(_s)[i]]++; \ } \ LETTERSLEFT += i; \ } static int testremoveword(register char *s, register int interesting); static int testremoveword(register char *s, register int interesting) /* If word can be removed from histogram, do so, add it to WORDARRAY, and return 1, otherwise return 0. */ { register int i; for (i=0; s[i]; i++) { /* if (!isalpha(s[i])) { fprintf(stderr, "\ntestremoveword(): string='%s' contains non-alpha char.\n", s); exit(-1); } */ if (--HISTOGRAM[s[i]] < 0) break; } if (s[i]) { /* Word won't fit, so fix part of histogram that we screwed up: */ while (i >= 0) HISTOGRAM[s[i--]]++; return (0); } else { /* Word fit */ LETTERSLEFT -= i; INTERESTINGARRAY[WORDARRAYINDEX] = interesting; WORDARRAY[WORDARRAYINDEX++] = s; return (1); } } #define processword(_s,_interesting) { \ memcpy((void*)(OTHER_HISTOGRAM+'A'), (void*) (HISTOGRAM+'A'), sizeof(HISTOGRAM[0])*('Z'-'A'+1)); \ for (tmp=0; (_s)[tmp]; tmp++) { \ if (--HISTOGRAM[(_s)[tmp]] < 0) break; \ } \ if ((_s)[tmp]) { \ /* Word won't fit, so fix part of histogram that we screwed up: */ \ /* Swap histograms: */ \ \ memcpy((void*) (HISTOGRAM+'A'), (void*) (OTHER_HISTOGRAM+'A'), sizeof(HISTOGRAM[0])*('Z'-'A'+1)); \ } else { \ /* Word fit */ \ LETTERSLEFT -= tmp; \ INTERESTINGARRAY[WORDARRAYINDEX] = (_interesting); \ WORDARRAY[WORDARRAYINDEX++] = (_s); \ \ if (LETTERSLEFT >= MIN_WORD_LEN) { \ /* Determine what maximum length word we can now test: */ \ tmp = min(LETTERSLEFT, i); \ if ((tmp2 = rangetablemin(LETTERSLEFT, tmp)) != 0) { \ if ((STARTLEN = rangetablemax(LETTERSLEFT, tmp)) >= 0) { \ anagrams(/*tmp,*/ (long) ((STARTLEN==i)?(j+NO_DUPES):0), tmp2); \ } \ } \ } else if (!LETTERSLEFT) { \ printlist(); \ } \ addword(w); \ } \ } static int testword(char *s, int interesting); static int testword(char *s, int interesting) { int r = testremoveword(s, interesting); if (r) { #if 0 if (VOWELCHECK) { /* Word can be removed, but are there any vowels left? */ r = (HISTOGRAM['A'] || HISTOGRAM['E'] || HISTOGRAM['I'] || HISTOGRAM['O'] || HISTOGRAM['U'] || HISTOGRAM['Y']); } #endif addword(s); } return (r); } /***********************************************************************/ #if 0 static char *getword(int len, long index); static char *getword(int len, long index) /* Given 'len' and 'word', returns word'th word of length 'len' */ { if (len > MAX_WORD_LEN) { fprintf(stderr, "\ngetword(): len (%d) is out of range.\n", len); exit(-1); } if ((index*(len+1) + len) >= COUNTS[len]*(len+1)) { fprintf(stderr, "\ngetword(): out of range (len=%d, index=%ld)\n", len, index); exit(-1); } return (WORDS[len] + (index*(len+1))); } #endif #define getword(_len,_index) (WORDS[(_len)] + ((_index)*((_len)+1))) static unsigned char _MASKS[] = { (unsigned char)128, (unsigned char)64, (unsigned char)32, (unsigned char)16, (unsigned char)8, (unsigned char)4, (unsigned char)2, (unsigned char)1 }; #define getinteresting(_len,_index) \ ( (*(INTERESTINGS[((_len)/* -1 */)] + ((_index)/8))) & (_MASKS[((_index)%8)]) ) #define setinteresting(_len,_index,_interesting) ( \ (_interesting) ? \ ( *(INTERESTINGS[(_len)] + ((_index)/8)) |= (_MASKS[(_index)%8]) ) : \ ( *(INTERESTINGS[(_len)] + ((_index)/8)) &= (~_MASKS[(_index)%8]) ) \ ) static int readwordfrom(register char *dest, int len, int *interesting); static int readwordfrom(register char *dest, int len, int *interesting) /* Attempts to read word of length 'len' from file 'fp' into 'dest'. Returns whether successful. */ { return (readword(dest, len, interesting) != NULL); #if 0 int r; int i; r = (fread((void*)dest, (size_t)len, (size_t)1, fp) == 1); if (r) { dest[len] = '\0'; /* Seal off word properly */ /* Get past '\n' (or '*', if first): */ switch (i = getc(fp)) { case '*': if ((i = getc(fp)) != '\n') { fprintf(stderr, "\nreadwordfrom(): expected newline after '*', got chr(%d) (word = '%s').\n", i, dest); exit(-1); } *interesting = 1; break; case '\n': *interesting = 0; break; default: fprintf(stderr, "\nreadwordfrom(): expected newline or '*', got chr(%d); (word = '%s').\n", i, dest); exit(-1); } #if 0 /* Make sure word contains only alpha chars: */ for (i=0; dest[i]; i++) { if (!isalpha(dest[i])) { fprintf(stderr, "\nreadwordfrom(): word (%s) contains non-alpha char (%d).\n", dest, dest[i]); exit(-1); } } #endif } return (r); #endif } static int readheader(void); static int readheader(void) { int i1, i2; i1 = in(); i2 = in(); return ((i1 == 0) && (i2 == 0)); } static long countwordfile(char *s, int len); static long countwordfile(char *s, int len) /* Count and return number of words in file 's' that fit in histogram */ { long count = 0; char word[LONGEST_WORD_FILE+1]; /* Tmp word */ int interesting; if ((WORDFILE = fopen(s, "rb")) == NULL) { fprintf(stderr, "\ncountwordfile(): fopen() failed for word file '%s'.\n", s); exit(-1); } in_index = 0; word[len] = '\0'; if (!readheader()) { fprintf(stderr, "\nDictionary file '%s' has bad header.\n", s); exit(-1); } while (readwordfrom(word, len, &interesting)) { if (testword(word, interesting)) { count++; } } if (fclose(WORDFILE)) { fprintf(stderr, "\ncountwordfile(): fclose() failed for word file '%s'.\n", s); exit(-1); } return (count); } #if 0 static int countwords(void); static int countwords(void) { long count = 0; char word[LONGEST_WORD_FILE+1]; /* Tmp word */ int interesting; if ((WORDFILE = fopen(s, "rb")) == NULL) { fprintf(stderr, "\ncountwords(): fopen() failed for word file '%s'.\n", s); exit(-1); } in_index = 0; readheader(); /* Ignore return result; assume it worked */ for (len=1; len<=LONGEST_WORD_FILE; len++) { word[len] = '\0'; while (readwordfrom(word, len, &interesting)) { if (testword(word, interesting)) { count++; } } } if (fclose(WORDFILE)) { fprintf(stderr, "\ncountwords(): fclose() failed for word file '%s'.\n", s); exit(-1); } return (count); } #endif static long readwordfile(char *s, int len, long count); static long readwordfile(char *s, int len, long count) /* Reads 'count' words, each assumed to be 'len' chars long, into proper element of WORDS[] (assumed allocated). Returns how many words read (presumably 'count'). */ { long index=0; char word[LONGEST_WORD_FILE+1]; int interesting; if ((WORDFILE = fopen(s, "rb")) == NULL) { fprintf(stderr, "\nreadwordfile(): fopen() failed for word file '%s'.\n", s); exit(-1); } in_index = 0; word[len] = '\0'; readheader(); /* Assume it worked */ while (1) { if (!readwordfrom(word, len, &interesting)) break; if (testword(word, interesting)) { memcpy((void*)getword(len, index), (void*)word, len+1); /* (Used only because it might be faster than strcpy()) */ if (INTERESTING || FLAG_INTERESTING) { /* Set whether this word is interesting: */ setinteresting(len, index, interesting); } #if 0 printf("%*s", len+3, word); col += len+3; if (col+len+3 >= 80) { printf("\n"); col = 0; } #endif if (++index >= count) break; } } if (fclose(WORDFILE)) { fprintf(stderr, "\nreadwordfile(): fclose() failed for word file '%s'.\n", s); } return (index); } #if 0 void dump(int len, long count); void dump(int len, long count) { FILE *fp; fp = fopen("e:\\dump.txt", "wb"); fwrite((void*)WORDS[len/*-1*/], (size_t)count, len+1, fp); fclose(fp); } #endif static void readwords(void); static void readwords(void) { int len; long words; /* How many words */ long wordtotal=0; long bytes; /* How many bytes */ long bytetotal=0; char filename[15]; int longestread=0; /* Longest words read */ int shortestread=0; /* Shortest words read */ int tmp; if (!STATUS) { fprintf(stderr, " "); } for (len=1; len <= LONGEST_WORD_FILE; len++) { if ((len <= STRINGLEN) && (len >= MIN_WORD_LEN) && (len <= MAX_WORD_LEN)) { sprintf(filename, "%d.cmp", len); if (STATUS) { fprintf(stderr, "File '%s': ", filename); } else { tmp = (100 * (2*len-1)) / (2*min(STRINGLEN, MAX_WORD_LEN)); fprintf(stderr, "\b\b\b\b\b\b\b\b\b\b%2d/%2d %3d%%", len, min(STRINGLEN, MAX_WORD_LEN), tmp); /* fputc('.', stderr); */ } COUNTS[len] = words = countwordfile(filename, len); bytes = words * ((long) (len+1)); if (STATUS) { fprintf(stderr, "%ld words (%ld bytes); ", words, bytes); } else { tmp = (((100 * (2*len)) / (2*min(STRINGLEN, MAX_WORD_LEN))) + tmp) / 2; fprintf(stderr, "\b\b\b\b%3d%%", tmp); } if (words) { wordtotal += words; bytetotal += bytes; longestread = len; if (!shortestread) shortestread = len; if ((WORDS[len] = (char*) calloc((size_t)words+1, (size_t)(len+1))) == NULL) { fprintf(stderr, "\nreadwords(): calloc() failed for WORDS[%d].\n", len); exit(-1); /* The '+1' is not necessary (in 'words+1') because we know exactly how many words are to be read in, but since readwordfile() reads until fread() fails, I'm not sure if fread() will put an EOF after the last real word read, so this '+1' is our buffer zone. */ } if (INTERESTING || FLAG_INTERESTING) { if ((INTERESTINGS[len] = (unsigned char*) calloc((size_t)((words/8) + 1), (size_t)1)) == NULL) { fprintf(stderr, "\nreadwords(): calloc() failed for INTERESTINGS.\n"); exit(-1); } } if (STATUS) fprintf(stderr, "reading..."); if (words != readwordfile(filename, len, words)) { fprintf(stderr, "\nreadwords(): wrong number of words read from file '%s'.\n", filename); exit(-1); } } else { WORDS[len] = NULL; } if (STATUS) fprintf(stderr, "%ld words read.\n", words); } else { COUNTS[len] = 0; } } if (STATUS) { fprintf(stderr, "%ld words total (%ld bytes total); ", wordtotal, bytetotal); } else { fprintf(stderr, "\b\b\b\b\b\b\b\b\b\b"); } #if 0 if (!wordtotal) { fprintf(stderr, "\nSorry, no candidate words found.\n"); exit(-1); } #endif /* If the string is "KIMMOSER" but no 8-letter words are read, we must readjust MAX_WORD_LEN to be the longest words read. Likewise, if no 1-letter words are read, we must readjust MIN_WORD_LEN to be the shortest words read. */ MAX_WORD_LEN = min(MAX_WORD_LEN, longestread); MIN_WORD_LEN = max(MIN_WORD_LEN, shortestread); #if 0 if (index != count) { fprintf(stderr, "\nreadwords(): index (%ld) != count (%ld)\n", index, count); exit(-1); } #endif fprintf(stderr, "shortest=%d, longest=%d\n", MIN_WORD_LEN, MAX_WORD_LEN); } static char TITLESTRING[] = "ANAGRAMS v1.5 (2/27/94) Copyright (C) 1991-4 Kim Moser All Rights Reserved\n"; static char *USAGESTRINGS[] = { "Usage: ANAGRAMS [-option ...] \n", "\n", " OPTION MEANING\n", " b Begin with word (useful for restarting)\n", #ifndef DEMO " c Find only the candidate words (no anagrams)\n", #endif " d Ignore anagrams which contain duplicate words\n", " e Echo anagrams to stderr (i.e. screen), too\n", #ifndef DEMO " i[nnn] Find only anagrams which contain at least [nnn] \"interesting\" words\n", #endif " s Set status level to (0=off, 1=outermost word, 2=% done, 3=# out)\n", " m Minimum word length = \n", " a Maximum word length = \n", " w Find anagrams which contain at least words\n", " x Find anagrams which contain no more than words\n", #ifndef DEMO " fi Flag \"interesting\" anagrams\n", #endif " fw Flag anagrams which contain at least words\n", " fx Flag anagrams which contain no more than words\n", " pn Format output into columns\n", " pw Columns will be characters wide\n", " pp Page is characters wide\n", NULL }; static char *GOODBYESTRINGS[] = { "\n", NULL }; static void usage(void); static void usage(void) { int i; for (i=0; USAGESTRINGS[i] != NULL; i++) fputs(USAGESTRINGS[i], stderr); for (i=0; GOODBYESTRINGS[i] != NULL; i++) fputs(GOODBYESTRINGS[i], stderr); exit(-1); } #if 0 static void dump(void); static void dump(void) { int i; long j; for (i=0; i MAX_WORDS) return; if (INTERESTING || FLAG_INTERESTING) { for (i=okay=0; i= FLAG_MIN_WORDS); } if (FLAG_MAX_WORDS) { tmp &= (WORDARRAYINDEX <= FLAG_MAX_WORDS); } if (tmp) { for (i=0; i= 0; i--) { putchar(' '); } } out_column += (out_index / COL_WIDTH) + ((out_index % COL_WIDTH) ? 1 : 0); out_index = 0; } if (!COL_WIDTH || (out_column >= NUM_COLS)) { putchar('\n'); out_column = out_index = 0; } if (ECHO_STDERR) { out_index = old_out_index; out_column = old_out_column; for (i=0; i= 0; i--) { putc(' ', stderr); } } out_column += (out_index / COL_WIDTH) + ((out_index % COL_WIDTH) ? 1 : 0); out_index = 0; } if (!COL_WIDTH || (out_column >= NUM_COLS)) { putc('\n', stderr); out_column = out_index = 0; } } old_out_index = out_index; old_out_column = out_column; } static void currentword(char *s); static void currentword(char *s) /* Displays (to stderr) what words are being worked on, including 's'. */ { int i; fputs("Testing '", stderr); for (i=0; i 0) { fprintf(stderr, "Current word '%s'", s); } if (STATUS > 1) { percentdone(); } if (STATUS > 2) { howmanyout(); } if (STATUS) putc('\n', stderr); } static void allstatus(char *s); static void allstatus(char *s) { currentword(s); percentdone(); howmanyout(); fputs("\n", stderr); } static void showactivekeys(void); static void showactivekeys(void) { fputs("\ \n\ Active keys:\n\ Q Quits\n\ N Skip to next leading word\n\ E Toggle 'E' option (echoing to screen)\n\ P Pause\n\ S Change 'S' (status) option\n\ ? Show current word being worked on\n\ \n\ ", stderr); } static int STARTLEN=0; /* Global so it doesn't have to be a parameter on the stack */ static int getkey(void); static int getkey(void) { int r = getch(); return (r ? r : getch()+256); } static void anagrams(register long index, register int endlen); static void anagrams(register long index, register int endlen) { /* In order of most frequently accessed: */ register int tmp, tmp2; register long j; register int i; register char *w; static int level=0; static int quit=0; static int skipword=0; static int interesting; /* Static only so it's not on stack */ level++; for (i=STARTLEN; i>=endlen; i--) { if (rangetablemax(LETTERSLEFT, i) && (WORDARRAYINDEX < MAX_WORDS)) { for (j=index; j1)) { level--; return; } skipword = 0; interesting = getinteresting(i, j); if (level == 1) { OUTERMOST_LEN = i; OUTERMOST_INDEX = j; status(w); } if (kbhit()) { switch (toupper(getkey())) { case 'P': fprintf(stderr, "Paused; hit any key to continue..."); while (!kbhit()); getkey(); fprintf(stderr, "Continuing...\n"); break; case 'Q': quit = 1; fprintf(stderr, "Quitting...\n"); return; case 'E': ECHO_STDERR = !ECHO_STDERR; fprintf(stderr, "Echo to stderr: %s\n", ECHO_STDERR ? "ON" : "OFF"); break; case 'N': fprintf(stderr, "Skipping to next word...\n"); skipword = 1; if (level > 1) { level--; return; } continue; case 'S': if (++STATUS > 3) STATUS = 0; fputs("Status: ", stderr); if (STATUS) { fprintf(stderr, "%d\n", STATUS); } else { fputs("OFF\n", stderr); } break; case '?': allstatus(w); break; default: showactivekeys(); break; } } #if 0 printf("len=%d (COUNTS[%d]==%d), index=%ld ", i, i, COUNTS[i], j); #endif #if 0 if (level == 0) { fprintf(stderr, "Testing word '%s'\n", getword(i, j)); } else { for (tmp=0; tmp 3 || STATUS < 0) usage(); break; #if 0 case 'V': VOWELCHECK = 0; break; #endif case 'W': if ((MIN_WORDS = atoi(s+2)) <= 0) { usage(); } break; case 'X': if ((MAX_WORDS = atoi(s+2)) <= 0) { usage(); } break; default: usage(); break; } } /*************************************************************************/ static int GLOBAL; static int DONE; static int findmax(int len); static int findmax(int len) { int i, j; int hold; int tmp; #if 0 static int level=0; #endif #if 0 level++; #endif for (i=len; (i>=MIN_WORD_LEN) && !DONE; i--) { if (COUNTS[i]) { hold = GLOBAL; for (j = hold/i /*min((hold/i), COUNTS[i])*/; !DONE && j>=0; j--) { GLOBAL = hold - j*i; if (GLOBAL < 0) { break; } else if (!GLOBAL) { #if 0 level--; #endif DONE = 1; return (j ? i : 0); } else { tmp = min(GLOBAL, (i-1)); while (tmp && !COUNTS[tmp]) tmp--; if (tmp > 0) { #if 0 printf("Recursing with len=%d\n", tmp); #endif tmp = findmax(tmp); if (tmp) { #if 0 level--; #endif return (j?i:tmp); } } } } GLOBAL = hold; } } #if 0 level--; #endif return (0); } static int findmin(int len, int maxl); static int findmin(int len, int maxl) { int i, j, k; int hold; int tmp; #if 0 static int level=0; #endif #if 0 level++; #endif for (i=len; i<=maxl && !DONE; i++) { if (COUNTS[i]) { hold = GLOBAL; for (k=1; (k<=hold/i + 1 /*min((hold/i), COUNTS[i])+1*/) && !DONE; k++) { j = (k == hold/i + 1 /*min((hold/i), COUNTS[i])+1*/) ? 0 : k; GLOBAL = hold - j*i; if (GLOBAL < 0) { break; } else if (!GLOBAL) { #if 0 level--; #endif DONE = 1; return (j ? i : 0); } else { tmp = i + 1; while (tmp<=maxl && !COUNTS[tmp]) tmp++; if (tmp <= maxl) { #if 0 printf("Recursing with len=%d\n", tmp); #endif tmp = findmin(tmp, maxl); if (tmp) { #if 0 level--; #endif return (j?i:tmp); } } } } GLOBAL = hold; } } #if 0 level--; #endif return (0); } static void computerangetable(void); static void computerangetable(void) { int left, len; if ((RANGETABLE = malloc((size_t) ((1+sizeof(*RANGETABLE)) * STRINGLEN))) == NULL) { fprintf(stderr, "\ncomputerangetable(): malloc() failed.\n"); exit(-1); } #if 0 for (left=0; left<=STRINGLEN; left++) { printf("Length %2d, count %2d\n", left, COUNTS[left]); } printf("\n"); #endif fputs(": ", stderr); for (left=0; left<=STRINGLEN; left++) { if ((RANGETABLE[left].lens = malloc((left+1) * (sizeof(*(RANGETABLE[left].lens))))) == NULL) { fprintf(stderr, "\ncomputerangetable(): malloc() failed for RANGETABLE[%d].lens.\n", left); exit(-1); } fprintf(stderr, "\b\b\b\b%3d%", ((left)*100)/STRINGLEN); /* putc('.', stderr); */ for (len = left; len>=MIN_WORD_LEN; len--) { DONE = 0; GLOBAL = left; rangetablemin(left, len) = findmin(MIN_WORD_LEN, len); DONE = 0; GLOBAL = left; rangetablemax(left, len) = findmax(min(left,len)); #if 0 printf("Left %2d, len %2d: min=%2d, max=%2d\n", left, len, rangetablemin(left, len), rangetablemax(left, len) ); #endif } #if 0 printf("\n"); #endif } fprintf(stderr, "\b\b\b\bDone.\n"); } /*************************************************************************/ static void computebegin(char *s, int *startlen, long int *index, int *endlen); static void computebegin(char *s, int *startlen, long int *index, int *endlen) /* Begin at word 's' */ { char *p; int length; /* Compute where to begin: */ if ((s != NULL) && s[0]) { *endlen = MIN_WORD_LEN; /* Assume */ /* Look for word 's': */ for (*startlen = MIN_WORD_LEN; *startlen <= MAX_WORD_LEN; (*startlen)++) { for ((*index)=0; (*index) < COUNTS[*startlen]; (*index)++) { p = getword((*startlen), (*index)); if (!strcmp(s, p)) { fprintf(stderr, "Beginning with word '%s'.\n", s); return; } } } fprintf(stderr, "(Word '%s' not found; beginning with optimum word.)\n", s); } /* Word 's' not found, so find optimum word to begin with (i.e. check RANGETABLE), given 'LETTERSLEFT' letters left: */ *index = 0L; for (length=MAX_WORD_LEN; length >= MIN_WORD_LEN; length--) { *startlen = rangetablemax(LETTERSLEFT, length); *endlen = rangetablemin(LETTERSLEFT, length); #if 0 fprintf(stderr, "\nrangetablemax(LETTERSLEFT=%d,length=%d) = %d\n", LETTERSLEFT, length, rangetablemax(LETTERSLEFT, length)); fprintf(stderr, "\nrangetablemin(LETTERSLEFT=%d,length=%d) = %d\n", LETTERSLEFT, length, rangetablemin(LETTERSLEFT, length)); #endif if (*startlen && *endlen) { #if 0 fprintf(stderr, "\ncomputebegin(): startlen=%d, endlen=%d\n", *startlen, *endlen); exit(-1); #endif return; } } fprintf(stderr, "Sorry, too few candidate words found to generate any anagrams.\n"); exit(-1); } /*************************************************************************/ static void goodbye(void); static void goodbye(void) { int i; for (i=0; GOODBYESTRINGS[i] != NULL; i++) fputs(GOODBYESTRINGS[i], stderr); } static void showcandidatewords(void); static void showcandidatewords(void) { int i; long j; int k; char *w; static int old_out_column=0, old_out_index=0; static int out_column=0, out_index=0; /* Current column and current position in that column */ for (i=LONGEST_WORD_FILE; i>=1; i--) { for (j=0; j= 0; k--) { putchar(' '); } } out_column += (out_index / COL_WIDTH) + ((out_index % COL_WIDTH) ? 1 : 0); out_index = 0; } if (!COL_WIDTH || (out_column >= NUM_COLS)) { putchar('\n'); out_column = out_index = 0; } if (ECHO_STDERR) { out_index = old_out_index; out_column = old_out_column; fputs(w, stderr); out_index += strlen(w); /* If we've gone past the end of this column, note it: */ if (COL_WIDTH) { /* If we're not about to print a '\n', pad with spaces to beginning of next column: */ if (out_column+1 < NUM_COLS) { for (k=(COL_WIDTH - (out_index % COL_WIDTH))-1; k >= 0; k--) { putc(' ', stderr); } } out_column += (out_index / COL_WIDTH) + ((out_index % COL_WIDTH) ? 1 : 0); out_index = 0; } if (!COL_WIDTH || (out_column >= NUM_COLS)) { putc('\n', stderr); out_column = out_index = 0; } } old_out_column = out_column; old_out_index = out_index; } } } void main(int argc, char **argv) { int i, j; long int BEGIN_INDEX; int END_LEN=0; fputs(TITLESTRING, stderr); /* Parse arguments: */ for (i=1; (i MAX_WORD_LEN, or more than 1 arg left, show usage: */ if ((i != argc-1) || (MIN_WORD_LEN > MAX_WORD_LEN)) usage(); /* Last argument must be name to process: if it contains any non-alpha chars, show usage: */ for (j=0; argv[i][j]; j++) if (!isalpha(argv[i][j])) usage(); STRINGLEN = strlen(strupr(STRING = argv[i])); #if 0 if ((STRING = strdup(argv[i])) == NULL) { fprintf(stderr, "\nANAGRAMS.main(): strdup() failed.\n"); exit(-1); } STRINGLEN = strlen(strupr(STRING)); #endif /* If MIN_WORD_LEN > STRINGLEN/2 && MIN_WORD_LEN != STRINGLEN, don't bother continuing: */ if ((MIN_WORD_LEN > STRINGLEN/2) && (MIN_WORD_LEN != STRINGLEN)) { fprintf(stderr, "Sorry, the minimum word length is too restrictive to find any anagrams.\n"); exit(-1); } /* If MIN_WORDS > MAX_WORDS, show usage: */ if ((MAX_WORDS != -1) && (MIN_WORDS > MAX_WORDS)) { usage(); } /* If FLAG_MIN_WORDS != 0 and FLAG_MAX_WORDS != 0 and FLAG_MIN_WORDS > FLAG_MAX_WORDS, show usage: */ if ((FLAG_MIN_WORDS && FLAG_MAX_WORDS) && (FLAG_MIN_WORDS > FLAG_MAX_WORDS)) usage(); /* If PAGE_WIDTH was specified, and if it's < COL_WIDTH, then show usage: */ if (PAGE_WIDTH && (PAGE_WIDTH < COL_WIDTH)) usage(); #if 0 if (STRINGLEN > LONGEST_WORD_FILE) { usage(); } #endif /* Allocate room for word array: */ if ((WORDARRAY = (char **) malloc((size_t) STRINGLEN * sizeof(WORDARRAY[0]))) == NULL) { fprintf(stderr, "\nmalloc() failed for WORDARRAY.\n"); exit(-1); } /* Allocate room for interesting array: */ /* (It should never contain more than STRINGLEN elements) */ if ((INTERESTINGARRAY = (int *) malloc((size_t) STRINGLEN * sizeof(INTERESTINGARRAY[0]))) == NULL) { fprintf(stderr, "\nmalloc() failed for INTERESTINGARRAY.\n"); exit(-1); } /* Initialize histogram: */ for (i='A'; i<='Z'; i++) { HISTOGRAM[i] = 0; } addword(STRING); WORDARRAYINDEX = 0; /* Show status: */ fputc('\n', stderr); fprintf(stderr, " Phrase: %s\n", STRING); fprintf(stderr, " Output: "); if (SHOW_CANDIDATE_WORDS) { fputs("candidate words only", stderr); } else { if (INTERESTING) { fprintf(stderr, "anagrams with >= %d \"interesting\" word%s", INTERESTING, (INTERESTING == 1 ? "" : "s")); if (MIN_WORDS != -1 || MAX_WORDS != -1) { fputs(",", stderr); } } else { fputs("all anagrams", stderr); } if (MIN_WORDS == MAX_WORDS && MIN_WORDS != -1) { fprintf(stderr, " with exactly %d words", MIN_WORDS); } else { if (MIN_WORDS != -1) { fprintf(stderr, " with at least %d words", MIN_WORDS); } if (MAX_WORDS != -1) { fprintf(stderr, " %s at most %d words", MIN_WORDS!=-1 ? "and" : "with", MAX_WORDS); } } } fputc('\n', stderr); if (MIN_WORDS == -1) MIN_WORDS = 1; if (MAX_WORDS == -1) MAX_WORDS = 32767; if (FLAG_INTERESTING || FLAG_MIN_WORDS || FLAG_MAX_WORDS) { fputs(" Flag: ", stderr); if (FLAG_INTERESTING) { fprintf(stderr, "interesting anagrams"); if (FLAG_MIN_WORDS || FLAG_MAX_WORDS) { fputs("; ", stderr); } } if (FLAG_MIN_WORDS || FLAG_MAX_WORDS) { fputs("anagrams", stderr); } if (FLAG_MIN_WORDS) { if (FLAG_MIN_WORDS == FLAG_MAX_WORDS) { fprintf(stderr, " with exactly %d words", FLAG_MIN_WORDS); } else { fprintf(stderr, " with >= %d words", FLAG_MIN_WORDS); } } if (FLAG_MAX_WORDS) { if (FLAG_MIN_WORDS) { if (FLAG_MIN_WORDS != FLAG_MAX_WORDS) { fprintf(stderr, " and <= %d words", FLAG_MAX_WORDS); } } else { fprintf(stderr, " with <= %d words", FLAG_MAX_WORDS); } } fputc('\n', stderr); } if (ECHO_STDERR) { fputs(" Echo: to stderr also\n", stderr); } fprintf(stderr, " Dupe words: %sallowed\n", NO_DUPES ? "NOT " : ""); fprintf(stderr, " Status: "); if (STATUS) { fprintf(stderr, "%d\n", STATUS); } else { fputs("OFF\n", stderr); } if (PAGE_WIDTH) { fprintf(stderr, " Page width: %d\n", PAGE_WIDTH); } if (PAGE_WIDTH || COL_WIDTH) { fputs("Col. width : ", stderr); if (PAGE_WIDTH) { if (!COL_WIDTH) { if (NUM_COLS) { COL_WIDTH = PAGE_WIDTH / NUM_COLS; } else { if (!SHOW_CANDIDATE_WORDS) { COL_WIDTH = STRINGLEN + (STRINGLEN/2) + 3; } } } } if (!COL_WIDTH) { if (SHOW_CANDIDATE_WORDS) { fputs("depends on longest candidate word read.\n", stderr); } else { fputs("unlimited\n", stderr); } } else { fprintf(stderr, "%d\n", COL_WIDTH); } } if (PAGE_WIDTH || NUM_COLS) { fputs("Num columns: ", stderr); if (PAGE_WIDTH) { if (!NUM_COLS) { /* NUM_COLS depends on PAGE_WIDTH (and COL_WIDTH, if specified) */ if (COL_WIDTH) { NUM_COLS = PAGE_WIDTH / COL_WIDTH; } else { if (!SHOW_CANDIDATE_WORDS) { NUM_COLS = PAGE_WIDTH / (STRINGLEN + (STRINGLEN/2) + 3); } } } } else { if (!SHOW_CANDIDATE_WORDS && !COL_WIDTH) { COL_WIDTH = STRINGLEN + (STRINGLEN/2) + 3; } } if (SHOW_CANDIDATE_WORDS && !NUM_COLS) { fputs("depends on longest candidate word read.\n", stderr); } else { fprintf(stderr, "%d\n", NUM_COLS); } } fputc('\n', stderr); #if 0 fputs("Num columns: ", stderr); if (SHOW_CANDIDATE_WORDS) { fputs("Depends on longest word read\n", stderr); } else if (PAGE_WIDTH) { fprintf(stderr, "%d\n", COL_WIDTH ? PAGE_WIDTH/COL_WIDTH : NUM_COLS); } fputs("Col. width : ", stderr); if (SHOW_CANDIDATE_WORDS) { fputs("depends on longest word read\n", stderr); } else if (PAGE_WIDTH) { if (COL_WIDTH) { fprintf(stderr, "%d\n", PAGE_WIDTH / (COL_WIDTH ? COL_WIDTH : 1)); } else { fputs("unlimited\n", stderr); } } else if (COL_WIDTH) { fprintf(stderr, "%d\n", COL_WIDTH); } else { fputs("unlimited\n", stderr); } fputc('\n', stderr); #endif /* If no duplicate letters, set NO_DUPES to 1 to speed things up: */ for (i='A'; i<='Z'; i++) if (HISTOGRAM[i] > 1) break; if (i > 'Z') NO_DUPES = 1; fprintf(stderr, "Reading candidate words%s", STATUS ? "...\n" : ": "); readwords(); if (SHOW_CANDIDATE_WORDS && !COL_WIDTH) { COL_WIDTH = MAX_WORD_LEN + 1; } if (PAGE_WIDTH) { NUM_COLS = PAGE_WIDTH / COL_WIDTH; } if (SHOW_CANDIDATE_WORDS) { showcandidatewords(); } else { fputs("Computing range table", stderr); computerangetable(); computebegin(BEGIN, &STARTLEN, &BEGIN_INDEX, &END_LEN); fputs("Starting (press '?' for status, 'h' for help)...\n", stderr); anagrams(BEGIN_INDEX, END_LEN); } fputs("\nDone.\n", stderr); goodbye(); exit(0); }