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

通讯编程

开发平台:

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{LADDERS}
  5. prerequisites{GB_WORDS}{GB_,DIJK}
  6. @* Introduction. This demonstration program uses graphs
  7. constructed by the {sc GB_WORDS} module to produce
  8. an interactive program called .{ladders}, which finds shortest paths
  9. between two given five-letter words of English.
  10. The program assumes that UNIX/ conventions are being used. Some code in
  11. sections listed under `UNIX/ dependencies' in the index might need to change
  12. if this program is ported to other operating systems.
  13. def<#1>{$langle${rm#1}$rangle$}
  14. To run the program under UNIX/, say `.{ladders} <options>', where <options>
  15. consists of zero or more of the following specifications in any order:
  16. {narrower
  17. def\#1 {smallskipnoindent
  18.   hbox to 6em{tt#1hfill}hangindent 8emhangafter1 }
  19. \-v Verbosely print all words encountered during the shortest-path
  20.      computation, showing also their distances from the goal word.
  21. \-a Use alphabetic distance instead of considering adjacent words to be one
  22.      unit apart; for example, the alphabetic distance from `.{words}' to
  23.      `.{woods}' is~3, because `.r' is three places from `.o' in the
  24.      alphabet.
  25. \-f Use distance based on frequency (see below), instead of considering
  26.      adjacent words to be one unit apart. This option is ignored if either
  27.      .{-a} or .{-r} has been specified.
  28. \-h Use a lower-bound heuristic to shorten the search (see below). This option
  29.      is ignored if option .{-f} has been selected.
  30. \-e Echo the input to the output (useful if input comes from a file instead
  31.      of from the terminal).
  32. \-n<number> Limit the graph to the |n| most common English words, where |n|
  33.      is the given <number>.
  34. \-r<number> Limit the graph to <number> randomly selected words. This option
  35.      is incompatible with~.{-n}.
  36. \-s<number> Use <number> instead of 0 as the seed for random numbers, to get
  37.      different random samples or to explore words of equal frequency in
  38.      a different order.
  39. smallskip}
  40. noindent Option .{-f} assigns a cost of 0 to the most common words and a
  41. cost of 16 to the least common words; a cost between 0 and~16 is assigned to
  42. words of intermediate frequency. The word ladders that are found will then have
  43. minimum total cost by this criterion. Experience shows that the .{-f} option
  44. tends to give the ``friendliest,'' most intuitively appealing ladders.
  45. smallskip
  46. Option .{-h} attempts to focus the search by giving priority to words that
  47. are near the goal. (More precisely, it modifies distances between adjacent
  48. words by using a heuristic function $\{hh}(v)$, which would be the shortest
  49. possible distance between $v$ and the goal if every five-letter combination
  50. happened to be an English word.) The {sc GB_,DIJK} module explains more
  51. about such heuristics; this option is most interesting to watch when used in
  52. conjunction with .{-v}.
  53. @ The program will prompt you for a starting word. If you simply type
  54. <return>, it exits; otherwise you should enter a five-letter word
  55. (with no uppercase letters) before typing <return>.
  56. Then the program will prompt you for a goal word. If you simply type
  57. <return> at this point, it will go back and ask for a new starting word;
  58. otherwise you should specify another five-letter word.
  59. Then the program will find and display an optimal word ladder from the start
  60. to the goal, if there is a path from one to the other
  61. that changes only one letter at a time.
  62. And then you have a chance to start all over again, with another starting word.
  63. The start and goal words need not be present in the program's graph of
  64. ``known'' words. They are temporarily added to that graph, but removed
  65. again whenever new start and goal words are given. If the .{-f} option is
  66. being used, the cost of the goal word will be 20 when it is not in the
  67. program's dictionary.
  68. @ Here is the general layout of this program, as seen by the CEE/ compiler:
  69. @^UNIX dependencies@>
  70. @p
  71. #include <ctype.h> /* system file for character types */
  72. #include "gb_graph.h" /* the standard GraphBase data structures */
  73. #include "gb_words.h" /* routines for five-letter word graphs */
  74. #include "gb_dijk.h" /* routines for shortest paths */
  75. @h@#
  76. @<Global variables@>@;
  77. @<Subroutines@>@;
  78. main(argc,argv)
  79.   int argc; /* the number of command-line arguments */
  80.   char *argv[]; /* an array of strings containing those arguments */
  81. {
  82.   @<Scan the command-line options@>;
  83.   @<Set up the graph of words@>;
  84.   while(1) {
  85.     @<Prompt for starting word and goal word; |break| if none given@>;
  86.     @<Find a minimal ladder from |start| to |goal|, if one exists,
  87.         and print it@>;
  88.   }
  89.   return 0; /* normal exit */
  90. }
  91. @* Parsing the options. Let's get the UNIX/ command-line junk out of the
  92. way first, so that we can concentrate on meatier stuff. Our job in this part
  93. of the program is to see if the default value zero of external variable
  94. |verbose| should change, and/or if the default values of any of the following
  95. internal variables should change:
  96. @<Global variables@>=
  97. char alph=0; /* nonzero if the alphabetic distance option is selected */
  98. char freq=0; /* nonzero if the frequency-based distance option is selected */
  99. char heur=0; /* nonzero if the heuristic search option is selected */
  100. char echo=0; /* nonzero if the input-echo option is selected */
  101. unsigned long n=0; /* maximum number of words in the graph (0 means infinity) */
  102. char randm=0; /* nonzero if we will ignore the weight of words */
  103. long seed=0; /* seed for random number generator */
  104. @ @<Scan the command-line options@>=
  105. while (--argc) {
  106. @^UNIX dependencies@>
  107.   if (strcmp(argv[argc],"-v")==0) verbose=1;
  108.   else if (strcmp(argv[argc],"-a")==0) alph=1;
  109.   else if (strcmp(argv[argc],"-f")==0) freq=1;
  110.   else if (strcmp(argv[argc],"-h")==0) heur=1;
  111.   else if (strcmp(argv[argc],"-e")==0) echo=1;
  112.   else if (sscanf(argv[argc],"-n%lu",&n)==1) randm=0;
  113.   else if (sscanf(argv[argc],"-r%lu",&n)==1) randm=1;
  114.   else if (sscanf(argv[argc],"-s%ld",&seed)==1) ;
  115.   else {
  116.     fprintf(stderr,"Usage: %s [-v][-a][-f][-h][-e][-nN][-rN][-sN]n",argv[0]);
  117.     return -2;
  118.   }
  119. }
  120. if (alph || randm) freq=0;
  121. if (freq) heur=0;
  122. @*Creating the graph. The GraphBase |words| procedure will produce the
  123. five-letter words we want, organized in a graph structure.
  124. @d quit_if(x,c)
  125.   if (x) {
  126.     fprintf(stderr,
  127.       "Sorry, I couldn't build a dictionary (trouble code %ld)!n",c);
  128.     return c;
  129.   }
  130. @<Set up the graph of words@>=
  131. g=words(n,(randm? zero_vector: NULL), 0L,seed);
  132. quit_if(g==NULL,panic_code);
  133. @<Confirm the options selected@>;
  134. @<Modify the edge lengths, if the |alph| or |freq| option was selected@>;
  135. @<Modify the priority queue algorithm, if unequal edge lengths are possible@>;
  136. @ @<Glob...@>=
  137. Graph *g; /* graph created by |words| */
  138. long zero_vector[9];
  139.  /* weights to use when ignoring all frequency information */
  140. @ The actual number of words might be decreased to the size of the GraphBase
  141. dictionary, so we wait until the graph is generated before confirming
  142. the user-selected options.
  143. @<Confirm the options selected@>=
  144. if (verbose) {
  145.   if (alph) printf("(alphabetic distance selected)n");
  146.   if (freq) printf("(frequency-based distances selected)n");
  147.   if (heur)
  148.     printf("(lowerbound heuristic will be used to focus the search)n");
  149.   if (randm) printf("(random selection of %ld words with seed %ld)n",
  150.     g->n,seed);
  151.   else printf("(the graph has %ld words)n",g->n);
  152. }
  153. @ The edges in a |words| graph normally have length 1, so we must change them
  154. if the user has selected |alph| or |freq|. The character position in which
  155. adjacent words differ is recorded in the |loc| field of each arc. The
  156. frequency of a word is stored in the |weight| field of its vertex.
  157. @d a_dist(k) (*(p+k)<*(q+k)? *(q+k)-*(p+k): *(p+k)-*(q+k))
  158. @<Modify the edge lengths, if the |alph| or |freq| option was selected@>=
  159. if (alph) {@+register Vertex *u;
  160.   for (u=g->vertices+g->n-1; u>=g->vertices; u--) {@+register Arc *a;
  161.     register char *p=u->name;
  162.     for (a=u->arcs; a; a=a->next) {@+register char *q=a->tip->name;
  163.       a->len = a_dist(a->loc);
  164.     }
  165.   }
  166. }@+else if (freq) {@+register Vertex *u;
  167.   for (u=g->vertices+g->n-1; u>=g->vertices; u--) {@+register Arc *a;
  168.     for (a=u->arcs; a; a=a->next)
  169.       a->len = freq_cost(a->tip);
  170.   }
  171. }
  172. @ The default priority queue algorithm of |dijkstra| is quite efficient
  173. when all edge lengths are~1. Otherwise we change it to the
  174. alternative method that works best for edge lengths less than~128.
  175. @<Modify the priority queue algorithm...@>=
  176. if (alph || freq || heur) {
  177.   init_queue=init_128;
  178.   del_min=del_128;
  179.   enqueue=enq_128;
  180.   requeue=req_128;
  181. }
  182. @ The frequency has been computed with the default weights explained in the
  183. documentation of |words|; it is usually less than $2^{16}$.
  184. A word whose frequency is 0 costs~16; a word whose frequency is 1 costs~15;
  185. a word whose frequency is 2 or 3 costs~14; and the costs keep decreasing
  186. by~1 as the frequency doubles, until we reach a cost of~0.
  187. @<Sub...@>=
  188. long freq_cost(v)
  189.   Vertex *v;
  190. {@+register long acc=v->weight; /* the frequency, to be shifted right */
  191.   register long k=16;
  192.   while (acc) k--, acc>>=1;
  193.   return (k<0? 0: k);
  194. }
  195. @* Minimal ladders. The guts of this program is a routine to compute shortest
  196. paths between two given words, |start| and |goal|.
  197. The |dijkstra| procedure does this, in any graph with nonnegative arc lengths.
  198. The only complication we need to deal with here is that |start| and |goal|
  199. might not themselves be present in the graph. In that case we want to insert
  200. them, albeit temporarily.
  201. The conventions of {sc GB_,GRAPH} allow us to do the desired augmentation
  202. by creating a new graph |gg| whose vertices are borrowed from~|g|. The
  203. graph~|g| has space for two more vertices (actually for four), and any
  204. new memory blocks allocated for the additional arcs present in~|gg| will
  205. be freed later by the operation |gb_recycle(gg)| without confusion.
  206. @<Glob...@>=
  207. Graph *gg; /* clone of |g| with possible additional words */
  208. char start[6], goal[6];
  209.    /* .{words} dear to the user's .{heart}, plus |''| */
  210. Vertex *uu, *vv; /* start and goal vertices in |gg| */
  211. @ @<Find a minimal ladder from |start| to |goal|...@>=
  212. @<Build the amplified graph |gg|@>;
  213. @<Let |dijkstra| do the hard work@>;
  214. @<Print the answer@>;
  215. @<Remove all traces of |gg|@>;
  216. @ @<Build the amplified graph |gg|@>=
  217. gg=gb_new_graph(0L);
  218. quit_if(gg==NULL,no_room+5);  /* out of memory */
  219. gg->vertices = g->vertices;
  220. gg->n = g->n;
  221. @<Put the |start| word into |gg|, and let |uu| point to it@>;
  222. @<Put the |goal| word into |gg|, and let |vv| point to it@>;
  223. if (gg->n==g->n+2) @<Check if |start| is adjacent to |goal|@>;
  224. quit_if(gb_trouble_code,no_room+6); /* out of memory */
  225. @ The |find_word| procedure returns |NULL| if it can't find the given word
  226. in the graph just constructed by |words|. In that case it has applied its
  227. second argument to every adjacent word. Hence the program logic here
  228. does everything needed to add a new vertex to~|gg| when necessary.
  229. @<Put the |start| word into |gg|, and let |uu| point to it@>=
  230. (gg->vertices+gg->n)->name = start; /* a tentative new vertex */
  231. uu=find_word(start,plant_new_edge);
  232. if (!uu)
  233.   uu = gg->vertices + gg->n++; /* recognize the new vertex and refer to it */
  234. @ @<Put the |goal|...@>=
  235. if (strncmp(start,goal,5)==0) vv=uu; /* avoid inserting a word twice */
  236. else {
  237.   (gg->vertices+gg->n)->name = goal; /* a tentative new vertex */
  238.   vv=find_word(goal,plant_new_edge);
  239.   if (!vv)
  240.     vv = gg->vertices + gg->n++; /* recognize the new vertex and refer to it */
  241. }
  242. @ The |alph_dist| subroutine calculates the alphabetic distance between
  243. arbitrary five-letter words, whether they are adjacent or not.
  244. @<Sub...@>=
  245. long alph_dist(p,q)
  246.   register char *p, *q;
  247. {
  248.   return a_dist(0)+a_dist(1)+a_dist(2)+a_dist(3)+a_dist(4);
  249. }
  250. @ @<Sub...@>=
  251. void plant_new_edge(v)
  252.   Vertex *v;
  253. {@+Vertex *u=gg->vertices+gg->n; /* the new edge runs from |u| to |v| */
  254.   gb_new_edge(u,v,1L);
  255.   if (alph)
  256.     u->arcs->len=(u->arcs-1)->len=alph_dist(u->name,v->name);
  257.   else if (freq) {
  258.     u->arcs->len=20; /* adjust the arc length from |v| to |u| */
  259.     (u->arcs-1)->len=freq_cost(v); /* adjust the arc length from |u| to |v| */
  260.   }
  261. }
  262. @ There's a bug in the above logic that could be embarrassing,
  263. although it will come up only when a user is trying to be clever: The
  264. |find_word| routine knows only the words of~|g|, so it will fail to
  265. make any direct connection between |start| and |goal| if they happen
  266. to be adjacent to each other yet not in the original graph. We had
  267. better fix this, otherwise the computer will look stupid.
  268. @<Check if |start|...@>=
  269. if (hamm_dist(start,goal)==1) {
  270.   gg->n--; /* temporarily pretend |vv| hasn't been added yet */
  271.   plant_new_edge(uu); /* make |vv| adjacent to |uu| */
  272.   gg->n++; /* and recognize it again */
  273. }
  274. @ The Hamming distance between words is the number of character positions
  275. @^Hamming, Richard Wesley, distance@>
  276. in which they differ.
  277. @d h_dist(k) (*(p+k)==*(q+k)? 0: 1)
  278. @<Sub...@>=
  279. long hamm_dist(p,q)
  280.   register char *p, *q;
  281. {
  282.   return h_dist(0)+h_dist(1)+h_dist(2)+h_dist(3)+h_dist(4);
  283. }
  284. @ OK, now we've got a graph in which |dijkstra| can operate.
  285. @<Let |dijkstra| do the hard work@>=
  286. if (!heur) min_dist=dijkstra(uu,vv,gg,NULL);
  287. else if (alph) min_dist=dijkstra(uu,vv,gg,alph_heur);
  288. else min_dist=dijkstra(uu,vv,gg,hamm_heur);
  289. @ @<Sub...@>=
  290. long alph_heur(v)
  291.   Vertex *v;
  292. {@+return alph_dist(v->name,goal);@+}
  293. @#
  294. long hamm_heur(v)
  295.   Vertex *v;
  296. {@+return hamm_dist(v->name,goal);@+}
  297. @ @<Glob...@>=
  298. long min_dist; /* length of the shortest ladder */
  299. @ @<Print the answer@>=
  300. if (min_dist<0) printf("Sorry, there's no ladder from %s to %s.n",start,goal);
  301. else print_dijkstra_result(vv);
  302. @ Finally, we have to clean up our tracks. It's easy to remove all arcs
  303. from the new vertices of~|gg| to the old vertices of~|g|; it's a bit
  304. trickier to remove the arcs from old to new. The loop here will also
  305. remove arcs properly between start and goal vertices, if they both
  306. belong to |gg| not~|g|.
  307. @<Remove all traces of |gg|@>=
  308. for (uu=g->vertices+gg->n-1; uu>=g->vertices+g->n; uu--) {@+register Arc *a;
  309.   for (a=uu->arcs; a; a=a->next) {
  310.     vv=a->tip; /* now |vv->arcs==a-1|, since arcs for edges come in pairs */
  311.     vv->arcs=vv->arcs->next;
  312.   }
  313.   uu->arcs=NULL; /* we needn't clear |uu->name| */
  314. }
  315. gb_recycle(gg); /* the |gg->data| blocks disappear, but |g->data| remains */
  316. @* Terminal interaction. We've finished doing all the interesting things.
  317. Only one minor part of the program still remains to be written.
  318. @<Prompt for...@>=
  319. putchar('n'); /* make a blank line for visual punctuation */
  320. restart: /* if we try to avoid this label,
  321.               the |break| command will be broken */
  322. if (prompt_for_five("Starting",start)!=0) break;
  323. if (prompt_for_five("    Goal",goal)!=0) goto restart;
  324. @ @<Sub...@>=
  325. long prompt_for_five(s,p)
  326.   char *s; /* string used in prompt message */
  327.   register char *p; /* where to put a string typed by the user */
  328. {@+register char *q; /* current position to store characters */
  329.   register long c; /* current character of input */
  330.   while (1) {
  331.     printf("%s word: ",s);
  332.     fflush(stdout); /* make sure the user sees the prompt */
  333.     q=p;
  334.     while (1) {
  335.       c=getchar();
  336.       if (c==EOF) return -1; /* end-of-file */
  337.       if (echo) putchar(c);
  338.       if (c=='n') break;
  339.       if (!islower(c)) q=p+5;
  340.       else if (q<p+5) *q=c;
  341.       q++;
  342.     }
  343.     if (q==p+5) return 0; /* got a good five-letter word */
  344.     if (q==p) return 1; /* got just <return> */
  345.     printf("(Please type five lowercase letters and RETURN.)n");
  346.   }
  347. }
  348. @* Index. Finally, here's a list that shows where the identifiers of this
  349. program are defined and used.