gb_words.w
上传用户:rrhhcc
上传日期:2015-12-11
资源大小:54129k
文件大小:21k
源码类别:

通讯编程

开发平台:

Visual C++

  1. % This file is part of the Stanford GraphBase (c) Stanford University 1993
  2. @i boilerplate.w %<< legal stuff: PLEASE READ IT BEFORE MAKING ANY CHANGES!
  3. @i gb_types.w
  4. deftitle{GB_WORDS}
  5. fontlogosl=logosl10
  6. prerequisites{GB_,GRAPH}{GB_,IO}
  7. @* Introduction. This GraphBase module provides two external subroutines:
  8. $$vcenter{halign{#hfilcr
  9.  |words|, a routine that creates a graph based on five-letter words;cr
  10.  |find_word|, a routine that looks for a given vertex in such a graph.cr}}$$
  11. Examples of the use of these routines can be found in two demo programs,
  12. {sc WORD_,COMPONENTS} and {sc LADDERS}.
  13. @(gb_words.h@>=
  14. extern Graph *words();
  15. extern Vertex *find_word();
  16. @ The subroutine call |words(n,wt_vector,wt_threshold,seed)|
  17. constructs a graph based on the five-letter words in .{words.dat}.
  18. Each vertex of the graph corresponds to a single five-letter word. Two
  19. words are adjacent in the graph if they are the same except in one
  20. letter position. For example, `.{words}' is adjacent to other words such as
  21. `.{cords}', `.{wards}', `.{woods}', `.{worms}', and `.{wordy}'.
  22. The constructed graph has at most |n| vertices; indeed, it has exactly
  23. |n| vertices if there are enough qualifying words. A word qualifies
  24. if its ``weight'' is |wt_threshold| or more, when weights are
  25. computed from a table pointed to by~|wt_vector| according to rules
  26. described below. (If parameter~|wt_vector|
  27. is |NULL|, i.e., .{NULL}, default weights are used.) The fourth parameter,
  28. |seed|, is the seed of a random number generator.
  29. All words of .{words.dat} will be sorted by weight. The first vertex of
  30. the graph will be the word of largest
  31. weight, the second vertex will have second-largest weight, and so on.
  32. Words of equal weight will appear in pseudo-random order, as determined
  33. by the value of |seed| in a system-independent fashion.
  34. The first |n| words in order of decreasing weight are chosen to be
  35. vertices of the graph. However, if fewer than |n| words have weight |>=
  36. wt_threshold|, the graph will contain only the words that qualify. In
  37. such cases the graph will have fewer than |n| vertices---possibly none at all.
  38. Exception: The special case |n=0| is equivalent to the case when |n|
  39. has been set to the highest possible value. It causes all qualifying
  40. words to appear.
  41. @ Every word in .{words.dat} has been classified as `common' (.*), `advanced'
  42. (.+), or `unusual' (. ). Each word has also been assigned seven
  43. frequency counts $c_1$, dots,~$c_7$, separated by commas; these counts show
  44. how often the word has occurred in different publication contexts:
  45. $$vcenter{halign{$c_#$ times in &#hfilcr
  46. 1&the American Heritage Intermediate Corpus of elementary school material;cr
  47. 2&the Brown Corpus of reading material from America;cr
  48. 3&the Lancaster-Oslo/Bergen Corpus of reading material from Britain;cr
  49. 4&the Melbourne-Surrey Corpus of newspaper material from Australia;cr
  50. 5&the Revised Standard Version of the Bible;cr
  51. 6&{sl The TEX/book/} and {sl The {logosl METAFONTkern1pt}book/}
  52.  by D. E. Knuth;cr
  53. 7&{sl Concrete Mathematics/} by Graham, Knuth, and Patashnik.cr}}$$
  54. @^Graham, Ronald Lewis@>
  55. @^Knuth, Donald Ervin@>
  56. @^Patashnik, Oren@>
  57. For example, one of the entries in .{words.dat} is
  58. $$.{happy*774,92,121,2,26,8,1}$$
  59. indicating a common word with $c_1=774$, dots, $c_7=1$.
  60. Parameter |wt_vector| points to an array of nine integers
  61. $(a,b,w_1,ldots,w_7)$.
  62. The weight of each word is computed from these nine numbers by using the
  63. formula
  64. $$c_1w_1+cdots+c_7w_7+
  65.  cases{a,&if the word is `common';cr
  66.         b,&if the word is `advanced';cr
  67.         0,&if the word is `unusual'.cr}$$
  68. The components of |wt_vector| must be chosen so that
  69. $$maxbigl(vert avert, vert bvertbigr)
  70.  + C_1vert w_1vert + cdots +C_7vert w_7vert < 2^{30},$$
  71. where $C_j$ is the maximum value of $c_j$ in the file; this restriction
  72. ensures that the |words| procedure will produce the same results on all
  73. computer systems.
  74. @ The maximum frequency counts actually present are $C_1=15194$, $C_2=3560$,
  75. $C_3=4467$, $C_4=460$, $C_5=6976$, $C_6=756$, and $C_7=362$; these can be
  76. found in the entries for the common words `.{shall}', `.{there}',
  77. `.{which}', and `.{would}'.
  78. The default weights are $a=100$, $b=10$, $c_1=4$, $c_2=c_3=2$, $c_4=c_5=
  79. c_6=c_7=1$.
  80. File .{words.dat} contains 5757 words, of which 3300 are `common', 1194 are
  81. `advanced', and 1263 are `unusual'. Included among the unusual words are
  82. 891 having $c_1=cdots=c_7=0$; such words
  83. will always have weight zero, regardless of the weight vector parameter.
  84. @<Private variables@>=
  85. static long max_c[]={15194,3560,4467,460,6976,756,362};
  86.  /* maximum counts $C_j$ */
  87. static long default_wt_vector[]={100,10,4,2,2,1,1,1,1}; 
  88.  /* use this if |wt_vector=NULL| */
  89. @ Examples: If you call |words(2000,NULL,0,0)|, you get a graph with
  90. 2000 of the most common five-letter words of English, using the
  91. default weights.  The GraphBase programs are designed to be
  92. system-independent, so that identical graphs will be obtained by
  93. everybody who asks for |words(2000,NULL,0,0)|.  Equivalent experiments
  94. on algorithms for graph manipulation can therefore be performed by
  95. researchers in different parts of the world.
  96. The subroutine call |words(2000,NULL,0,s)| will produce slightly
  97. different graphs when the random seed |s| varies, because some words
  98. have equal weight. However, the graph for any particular value of~|s|
  99. will be the same on all computers. The seed value can be any integer
  100. in the range $0le s<2^{31}$.
  101. Suppose you call |words(0,w,1,0)|, with |w| defined by the CEE/ declaration
  102. $$hbox{|long w[9] = {1};|}$$
  103. this means that $a=1$ and $b=w_1=cdots=w_7=0$. Therefore you'll get a graph
  104. containing only the 3300 `common' words. Similarly, it's possible to obtain
  105. only the $3300+1194=4494$ non-`unusual' words, by specifying the weight vector
  106. $$hbox{|long w[9] = {1,1};|}$$
  107. this makes $a=b=1$ and $w_1=cdots=w_7=0$. In both of these examples, the
  108. qualifying words all have weight~1, so the vertices of the graph will appear
  109. in pseudo-random order.
  110. If |w| points to an array of nine 0's, the call |words(n,w,0,s)| gives a
  111. random sample of |n| words, depending on |s| in a system-independent fashion.
  112. If the entries of the weight vector are all nonnegative, and if the
  113. weight threshold is zero, every word of .{words.dat} will qualify. Thus
  114. you will obtain a graph with $min(n,5757)$ vertices.
  115. If |w| points to an array with {sl negative/} weights, the call
  116. |words(n,w,-0x7fffffff,0)| selects |n| of the {sl least/} common
  117. words in .{words.dat}.
  118. @ If the |words| routine encounters a problem, it returns |NULL|, after putting
  119. a code number into the external variable |panic_code|. This code number
  120. identifies the type of failure. Otherwise |words| returns a pointer to the
  121. newly created graph, which will be represented with the data structures
  122. explained in {sc GB_,GRAPH}. (The external variable |panic_code| is itself
  123. defined in {sc GB_,GRAPH}.)
  124. @d panic(c) @+{@+gb_free(node_blocks);
  125.   panic_code=c;@+gb_trouble_code=0;@+return NULL;@+}
  126. @ Now let's get going on the program. The CEE/ file .{gb_words.c} begins
  127. as follows:
  128. @p
  129. #include "gb_io.h" /* we will use the {sc GB_,IO} routines for input */
  130. #include "gb_flip.h"
  131.  /* we will use the {sc GB_,FLIP} routines for random numbers */
  132. #include "gb_graph.h" /* we will use the {sc GB_,GRAPH} data structures */
  133. #include "gb_sort.h" /* and |gb_linksort| for sorting */
  134. @h@#
  135. @<Type declarations@>@;
  136. @<Private variables@>@;
  137. @<Private functions@>@;
  138. @#
  139. Graph *words(n,wt_vector,wt_threshold,seed)
  140.   unsigned long n; /* maximum number of vertices desired */
  141.   long wt_vector[]; /* pointer to array of weights */
  142.   long wt_threshold; /* minimum qualifying weight */
  143.   long seed; /* random number seed */
  144. {@+@<Local variables@>@;@#
  145.   gb_init_rand(seed);
  146.   @<Check that |wt_vector| is valid@>;
  147.   @<Input the qualifying words to a linked list, computing their weights@>;
  148.   @<Sort and output the words, determining adjacencies@>;
  149.   if (gb_trouble_code) {
  150.     gb_recycle(new_graph);
  151.     panic(alloc_fault); /* oops, we ran out of memory somewhere back there */
  152.   }
  153.   return new_graph;
  154. }
  155. @ @<Local var...@>=
  156. Graph *new_graph; /* the graph constructed by |words| */
  157. @* Validating the weights. The first job that |words| needs to tackle is
  158. comparatively trivial:
  159. We want to verify the condition
  160. $$maxbigl(vert avert, vert bvertbigr)
  161.  + C_1vert w_1vert + cdots +C_7vert w_7vert < 2^{30}.eqno(*)$$
  162. This proves to be an interesting exercise in ``portable
  163. CEE/ programming,'' because we don't want to risk integer overflow.
  164. Our approach is to do the
  165. calculation first in floating point arithmetic, thereby ruling out cases
  166. that are clearly unacceptable. Once that test is passed, we can safely
  167. test the condition with ordinary integer arithmetic. Floating
  168. point arithmetic is system dependent, but we use it carefully so that
  169. system-independent results are obtained.
  170. @<Check that |wt_vector| is valid@>=
  171. if (!wt_vector) wt_vector=default_wt_vector;
  172. else {@+register double flacc;
  173.   register long *p,*q;
  174.   register long acc;
  175.   @<Use floating point arithmetic to check that |wt_vector| isn't
  176.     totally off base@>;
  177.   @<Use integer arithmetic to check that |wt_vector| is truly OK@>;
  178. }
  179. @ The floating-point calculations are facilitated by a routine that
  180. converts an integer to its absolute value, expressed as a |double|:
  181. @<Private functions@>=
  182. static double flabs(x)
  183.   long x;
  184. {@+if (x>=0) return (double)x;
  185.   return -((double)x);
  186. }
  187. @ Although floating point arithmetic is system dependent, we can certainly
  188. assume that at least 16 bits of precision are used. This implies that
  189. the difference between |flabs(x)| and $vert xvert$ must be less
  190. than $2^{14}$. Also, if $x$ and $y$ are nonnegative values less than $2^{31}$,
  191. the difference between their floating-point sum and their true sum must be
  192. less than $2^{14}$.
  193. The floating point calculations in the following test will never reject a
  194. valid weight vector. For if condition $(*)$ holds, the floating-point value of
  195. $max(hbox{|flabs(a)|},hbox{|flabs(b)|})+C_1*|flabs|(w_1)+cdots
  196. +C_7*|flabs|(w_7)$ will be less than $2^{30}+(8+C_1+cdots+C_7)2^{14}$,
  197. which is less than $2^{30}+2^{29}$.
  198. @<Use float...@>=
  199. p=wt_vector;
  200. flacc=flabs(*p++);
  201. if (flacc<flabs(*p)) flacc=flabs(*p);
  202.   /* now $|flacc|=max(vert avert,vert bvert)$ */
  203. for (q=&max_c[0]; q<&max_c[7]; q++)
  204.   flacc += *q * flabs(*++p);
  205. if (flacc>=(double)0x60000000) /* this constant is
  206.     $6times2^{28}=2^{30}+2^{29}$ */
  207.   panic(very_bad_specs); /* whoa; the weight vector is way too big */
  208. @ Conversely, if the floating point test just made is passed, the true
  209. value of the sum will be less than $2^{30}+2^{29}+2^{29}=2^{31}$; hence
  210. integer overflow will never occur when we make the following more
  211. refined test:
  212. @<Use int...@>=
  213. p=wt_vector;
  214. acc=iabs(*p++);
  215. if (acc<iabs(*p)) acc=iabs(*p);
  216.   /* now $|acc|=max(vert avert,vert bvert)$ */
  217. for (q=&max_c[0]; q<&max_c[7]; q++)
  218.   acc += *q * iabs(*++p);
  219. if (acc>=0x40000000)
  220.   panic(bad_specs); /* the weight vector is a bit too big */
  221. @ @<Private f...@>=
  222. static long iabs(x)
  223.   long x;
  224. {@+if (x>=0) return (long)x;
  225.   return -((long)x);
  226. }
  227. @* The input phase. Now we're ready to read .{words.dat}.
  228. @<Local...@>=
  229. register long wt; /* the weight of the current word */
  230. char word[5]; /* the current five-letter word */
  231. long nn=0; /* the number of qualifying words found so far */
  232. @ As we read the words, we will form a linked list of nodes containing
  233. each qualifying word and its weight, using the memory management
  234. routines of {sc GB_,GRAPH} to allocate space for 111 nodes at a
  235. time. These nodes should be returned to available memory later, so we
  236. will keep them in a separate area under local control.
  237. The nodes start out with |key| and |link| fields, as required by the
  238. |gb_linksort| routine, which we'll use to sort by weight. The sort key must be
  239. nonnegative; we obtain it by adding $2^{30}$ to the weight.
  240. @d nodes_per_block 111
  241. @<Type...@>=
  242. typedef struct node_struct {
  243.   long key; /* the sort key (weight plus $2^{30}$) */
  244.   struct node_struct *link; /* links the nodes together */
  245.   char wd[5]; /* five-letter word
  246.                    (which typically consumes eight bytes, too bad) */
  247. } node;
  248. @ @<Local...@>=
  249. node *next_node; /* the next node available for allocation */
  250. node *bad_node; /* if |next_node=bad_node|, the node isn't really there */
  251. node *stack_ptr; /* the most recently created node */
  252. node *cur_node; /* current node being created or examined */
  253. @ @<Private v...@>=
  254. static Area node_blocks; /* the memory area for blocks of nodes */
  255. @ @<Input the qualifying words...@>=
  256. next_node=bad_node=stack_ptr=NULL;
  257. if (gb_open("words.dat")!=0)
  258.   panic(early_data_fault);
  259.    /* couldn't open |"words.dat"| using GraphBase conventions;
  260.                 |io_errors| tells why */
  261. do @<Read one word, and put it on the stack if it qualifies@>@;
  262.   while (!gb_eof());
  263. if (gb_close()!=0)
  264.   panic(late_data_fault);
  265.     /* something's wrong with |"words.dat"|; see |io_errors| */
  266. @ @<Read one...@>=
  267. {@+register long j; /* position in |word| */
  268.   for (j=0; j<5; j++) word[j]=gb_char();
  269.   @<Compute the weight |wt|@>;
  270.   if (wt>=wt_threshold) { /* it qualifies */
  271.     @<Install |word| and |wt| in a new node@>;
  272.     nn++;
  273.   }
  274.   gb_newline();
  275. }
  276. @ @d copy5(y,x) {@+
  277.     *(y)=*(x);@+
  278.     *((y)+1)=*((x)+1);@+
  279.     *((y)+2)=*((x)+2);
  280.     *((y)+3)=*((x)+3);@+
  281.     *((y)+4)=*((x)+4);@+
  282.   }
  283. @<Install...@>=
  284. if (next_node==bad_node) {
  285.   cur_node=gb_typed_alloc(nodes_per_block,node,node_blocks);
  286.   if (cur_node==NULL)
  287.     panic(no_room+1); /* out of memory already */
  288.   next_node=cur_node+1;
  289.   bad_node=cur_node+nodes_per_block;
  290. }@+else cur_node=next_node++;
  291. cur_node->key=wt+0x40000000;
  292. cur_node->link=stack_ptr;
  293. copy5(cur_node->wd,word);
  294. stack_ptr=cur_node;
  295. @ Recall that |gb_number()| returns 0, without giving an error, if no
  296. digit is present in the current position of the file being read. This
  297. implies that the .{words.dat} file need not include zero counts
  298. explicitly. Furthermore, we can arrange things so that trailing zero
  299. counts are unnecessary; commas can be omitted if all counts
  300. following them on the current line are zero.
  301. @<Compute the weight...@>=
  302. {@+register long *p,*q; /* pointers to $C_j$ and $w_j$ */
  303.   register long c; /* current count */
  304.   switch (gb_char()) {
  305.    case '*': wt=wt_vector[0];@+break; /* `common' word */
  306.    case '+': wt=wt_vector[1];@+break; /* `advanced' word */
  307.    case ' ': case'n': wt=0;@+break; /* `unusual' word */
  308.    default: panic(syntax_error); /* unknown type of word */
  309.   }
  310.   p=&max_c[0]; q=&wt_vector[2];
  311.   do@+{
  312.     if (p==&max_c[7])
  313.       panic(syntax_error+1); /* too many counts */
  314.     c=gb_number(10);
  315.     if (c>*p++)
  316.       panic(syntax_error+2); /* count too large */
  317.     wt += c * *q++;
  318.   }@+while (gb_char()==',');
  319. }
  320. @* The output phase. Once the input phase has examined all of
  321. .{words.dat}, we are left with a stack of |nn| nodes containing the
  322. qualifying words, starting at |stack_ptr|.
  323. The next step is to call |gb_linksort|, which takes the qualifying words
  324. and distributes them into the 128 lists |gb_sorted[j]|, for |0<=j<128|.
  325. We can then access the words in order of decreasing weight by reading through
  326. these lists, starting with |gb_sorted[127]| and ending with |gb_sorted[0]|.
  327. (See the documentation of |gb_linksort| in the {sc GB_,SORT} module.)
  328. The output phase therefore has the following general outline:
  329. @<Sort and output...@>=
  330. gb_linksort(stack_ptr);
  331. @<Allocate storage for the new graph; adjust |n| if it is zero or too large@>;
  332. if (gb_trouble_code==0 && n) {
  333.   register long j; /* runs through sorted lists */
  334.   register node *p; /* the current node being output */
  335.   nn=n;
  336.   for (j=127; j>=0; j--)
  337.     for (p=(node*)gb_sorted[j]; p; p=p->link) {
  338.       @<Add the word |p->wd| to the graph@>;
  339.       if (--nn==0) goto done;
  340.     }
  341. }
  342. done:gb_free(node_blocks);
  343. @ The only slightly unusual data structure needed is a set of five hash tables,
  344. one for each of the strings of four letters obtained by suppressing
  345. a single letter of a five-letter word. For example, a word like `.{words}'
  346. will lead to entries for `.{ ords}', `.{w rds}, `.{wo ds}', `.{wor s}',
  347. and `.{word }', one in each of the hash tables.
  348. @d hash_prime 6997 /* a prime number larger than the total number of words */
  349. @<Type...@>=
  350. typedef Vertex *hash_table[hash_prime];
  351. @ @<Local...@>=
  352. Vertex *cur_vertex; /* the current vertex being created or examined */
  353. char *next_string; /* where we'll store the next five-letter word */
  354. @ @<Private v...@>=
  355. static hash_table *htab; /* five dynamically allocated hash tables */
  356. @ The weight of each word will be stored in the utility field |u.I| of its
  357. |Vertex| record. The position in which adjacent words differ will be
  358. stored in utility field |a.I| of the |Arc| records between them.
  359. @d weight u.I /* weighted frequencies */
  360. @d loc a.I /* index of difference (0, 1, 2, 3, or 4) */
  361. @(gb_words.h@>=
  362. #define weight @[u.I@] /* repeat the definitions in the header file */
  363. #define loc @[a.I@]
  364. @ @<Allocate storage for the new graph...@>=
  365. if (n==0 || nn<n)
  366.   n=nn;
  367. new_graph=gb_new_graph(n);
  368. if (new_graph==NULL)
  369.   panic(no_room); /* out of memory before we're even started */
  370. if (wt_vector==default_wt_vector)
  371.   sprintf(new_graph->id,"words(%lu,0,%ld,%ld)",n,wt_threshold,seed);
  372. else sprintf(new_graph->id,
  373.      "words(%lu,{%ld,%ld,%ld,%ld,%ld,%ld,%ld,%ld,%ld},%ld,%ld)",
  374.      n,wt_vector[0],wt_vector[1],wt_vector[2],wt_vector[3],wt_vector[4],
  375.      wt_vector[5],wt_vector[6],wt_vector[7],wt_vector[8],wt_threshold,seed);
  376. strcpy(new_graph->util_types,"IZZZZZIZZZZZZZ");
  377. cur_vertex=new_graph->vertices;
  378. next_string=gb_typed_alloc(6*n,char,new_graph->data);
  379. htab=gb_typed_alloc(5,hash_table,new_graph->aux_data);
  380. @ @<Add the word...@>=
  381. {@+register char *q; /* the new word */
  382.   q=cur_vertex->name=next_string;
  383.   next_string+=6;
  384.   copy5(q,p->wd);
  385.   cur_vertex->weight=p->key-0x40000000;
  386.   @<Add edges for all previous words |r| that nearly match |q|@>;
  387.   cur_vertex++;
  388. }
  389. @ The length of each edge in a |words| graph is set to~1; the
  390. calling routine can change it later if desired.
  391. @d mtch(i) (*(q+i)==*(r+i))
  392. @d match(a,b,c,d) (mtch(a)&&mtch(b)&&mtch(c)&&mtch(d))
  393. @d store_loc_of_diff(k) cur_vertex->arcs->loc=(cur_vertex->arcs-1)->loc=k
  394. @d ch(q) ((long)*(q))
  395. @d hdown(k) h==htab[k]? h=htab[k+1]-1: h--
  396. @<Add edges for all previous words |r| that nearly match |q|@>=
  397. {@+register char *r; /* previous word possibly adjacent to |q| */
  398.   register Vertex **h; /* hash address for linear probing */
  399.   register long raw_hash; /* five-letter hash code before remaindering */
  400.   raw_hash=(((((((ch(q)<<5)+ch(q+1))<<5)+ch(q+2))<<5)+ch(q+3))<<5)+ch(q+4);
  401.   for (h=htab[0]+(raw_hash-(ch(q)<<20)) % hash_prime; *h; hdown(0)) {
  402.     r=(*h)->name;
  403.     if (match(1,2,3,4))
  404.       gb_new_edge(cur_vertex,*h,1L), store_loc_of_diff(0);
  405.   }
  406.   *h=cur_vertex;
  407.   for (h=htab[1]+(raw_hash-(ch(q+1)<<15)) % hash_prime; *h; hdown(1)) {
  408.     r=(*h)->name;
  409.     if (match(0,2,3,4))
  410.       gb_new_edge(cur_vertex,*h,1L), store_loc_of_diff(1);
  411.   }
  412.   *h=cur_vertex;
  413.   for (h=htab[2]+(raw_hash-(ch(q+2)<<10)) % hash_prime; *h; hdown(2)) {
  414.     r=(*h)->name;
  415.     if (match(0,1,3,4))
  416.       gb_new_edge(cur_vertex,*h,1L), store_loc_of_diff(2);
  417.   }
  418.   *h=cur_vertex;
  419.   for (h=htab[3]+(raw_hash-(ch(q+3)<<5)) % hash_prime; *h; hdown(3)) {
  420.     r=(*h)->name;
  421.     if (match(0,1,2,4))
  422.       gb_new_edge(cur_vertex,*h,1L), store_loc_of_diff(3);
  423.   }
  424.   *h=cur_vertex;
  425.   for (h=htab[4]+(raw_hash-ch(q+4)) % hash_prime; *h; hdown(4)) {
  426.     r=(*h)->name;
  427.     if (match(0,1,2,3))
  428.       gb_new_edge(cur_vertex,*h,1L), store_loc_of_diff(4);
  429.   }
  430.   *h=cur_vertex;
  431. }
  432. @* Finding a word. After |words| has created a graph |g|, the user can
  433. remove the hash tables by calling |gb_free(g->aux_data)|. But if the
  434. hash tables have not been removed, another procedure can be used to
  435. find vertices that match or nearly match a given word.
  436. The subroutine call |find_word(q,f)| will return a pointer to a vertex
  437. that matches a given five-letter word~|q|, if that word is in the graph;
  438. otherwise, it returns |NULL| (i.e., .{NULL}), after calling |f(v)| for
  439. each vertex~|v| whose word matches |q| in all but one letter position.
  440. @p Vertex *find_word(q,f)
  441.   char *q;
  442.   void @[@] (*f)(); /* |*f| should take one argument, of type |Vertex *|,
  443.                         or |f| should be |NULL| */
  444. {@+register char *r; /* previous word possibly adjacent to |q| */
  445.   register Vertex **h; /* hash address for linear probing */
  446.   register long raw_hash; /* five-letter hash code before remaindering */
  447.   raw_hash=(((((((ch(q)<<5)+ch(q+1))<<5)+ch(q+2))<<5)+ch(q+3))<<5)+ch(q+4);
  448.   for (h=htab[0]+(raw_hash-(ch(q)<<20)) % hash_prime; *h; hdown(0)) {
  449.     r=(*h)->name;
  450.     if (mtch(0) && match(1,2,3,4))
  451.       return *h;
  452.   }
  453.   @<Invoke |f| on every vertex that is adjacent to word~|q|@>;
  454.   return NULL;
  455. @ @<Invoke |f| on every vertex that is adjacent to word~|q|@>=
  456. if (f) {
  457.   for (h=htab[0]+(raw_hash-(ch(q)<<20)) % hash_prime; *h; hdown(0)) {
  458.     r=(*h)->name;
  459.     if (match(1,2,3,4))
  460.       (*f)(*h);
  461.   }
  462.   for (h=htab[1]+(raw_hash-(ch(q+1)<<15)) % hash_prime; *h; hdown(1)) {
  463.     r=(*h)->name;
  464.     if (match(0,2,3,4))
  465.       (*f)(*h);
  466.   }
  467.   for (h=htab[2]+(raw_hash-(ch(q+2)<<10)) % hash_prime; *h; hdown(2)) {
  468.     r=(*h)->name;
  469.     if (match(0,1,3,4))
  470.       (*f)(*h);
  471.   }
  472.   for (h=htab[3]+(raw_hash-(ch(q+3)<<5)) % hash_prime; *h; hdown(3)) {
  473.     r=(*h)->name;
  474.     if (match(0,1,2,4))
  475.       (*f)(*h);
  476.   }
  477.   for (h=htab[4]+(raw_hash-ch(q+4)) % hash_prime; *h; hdown(4)) {
  478.     r=(*h)->name;
  479.     if (match(0,1,2,3))
  480.       (*f)(*h);
  481.   }
  482. }
  483. @* Index. Here is a list that shows where the identifiers of this program are
  484. defined and used.