home *** CD-ROM | disk | FTP | other *** search
/ Gold Fish 3 / goldfish_volume_3.bin / files / dev / c / cweb / examples / sample.w < prev    next >
Encoding:
Text File  |  1994-11-09  |  8.9 KB  |  247 lines

  1. % SAMPLE example of WEB/CWEB portability.  This documentation was
  2. % originally published in the ``Communications of the ACM'', Volume 29,5
  3. % (May 1986), and later in the book ``Literate Programming'', October 1991,
  4. % where it appeared as an example of WEB programming for Pascal.  It has
  5. % been translated into CWEB by Andreas Scherer to show the ease of
  6. % portability between Pascal/WEB and C/CWEB.  Minor changes to include
  7. % extra C functionality are mentioned in the text that follows.
  8.  
  9. % This program is distributed WITHOUT ANY WARRANTY, express or implied.
  10. % WEB Version --- Don Knuth, 1986
  11. % CWEB Version --- Andreas Scherer, 1993
  12.  
  13. \font\csc=cmcsc10
  14. \def\PASCAL/{{\csc Pascal\spacefactor1000}}
  15.  
  16. \def\title{SAMPLE (C Version 1.0)}
  17. \def\topofcontents{\null\vfill
  18.   \centerline{\titlefont Producing random numbers}
  19.   \vskip15pt
  20.   \centerline{(C Version 1.0)}
  21.   \vfill}
  22.  
  23. @* Introduction.  Jon Bentley@^Bentley, Jon Louis@> recently discussed
  24. the following interesting problem as one of his ``Programming Pearls''
  25. [{\sl Communications of the ACM}~{\bf 27} (December, 1984), 1179--1182]:
  26.  
  27. {\narrower\noindent The input consists of two integers $M$ and $N$, with
  28. $M<N${}.  The output is a sorted list of $M$ random numbers in the range
  29. $1\ldots N$ in which no integer occurs more than once.  For probability
  30. buffs, we desire a sorted selection without replacement in which each
  31. selection occurs equiprobably.\par}
  32.  
  33. The present program illustrates what I (i.e., Donald E. Knuth
  34. @^Knuth, Donald E.@> in his {\sl Literate Programming} (October, 1991),
  35. 144--149) think is the best solution to this problem, when $M$ is
  36. reasonably large yet small compared to $N${}.  It's the method described
  37. tersely in the answer to exercise 3.4.2--15 of my book {\sl Seminumerical
  38. Algorithms}, pp.~141 and~555.  The \.{WEB} program was translated to \.{CWEB}
  39. by Andreas Scherer.  Necessary changes for \CEE/ were included and some
  40. \PASCAL/ restrictions removed.
  41. @^Scherer, Andreas@>
  42.  
  43. @ For simplicity, all input and output in this program is assumed to be
  44. handled at the terminal.  The \.{CWEB} macro |read_terminal| defined here
  45. can easily be changed to accommodate other conventions.  The macro |trunc|
  46. replaces the \PASCAL/ routine of the same name for \CEE/.
  47. @^Differences between \PASCAL/ and \CEE/@>
  48.  
  49. @d read_terminal(a) fscanf(stdin,"%d",&a)
  50.    /* input a value from the terminal */
  51. @d trunc(a) (int)(a)
  52.  
  53. @ Here's an outline of the entire \CEE/ program:
  54.  
  55. @c
  56. @<Global \#|include|s@>@/
  57. @<Global variables@>@/
  58. @<The random number generation procedure@>@/
  59. @<The |main| program@>
  60.  
  61. @ The library interfaces of \PASCAL/ and \CEE/ are different, so here is
  62. the needed information.
  63. @^Differences between \PASCAL/ and \CEE/@>
  64.  
  65. @<Global \#|include|s@>=
  66. #include <stdio.h>
  67. #include <stdlib.h>
  68. #include <limits.h>
  69. #include <time.h>
  70.  
  71. @ The global variables $M$ and $N$ have already been mentioned; we had
  72. better declare them.  Other global variables will be declared later.
  73.  
  74. @<Global variables@>=
  75.    int M; /* size of the sample */
  76.    int N; /* size of the population */
  77.  
  78. @ In \CEE/ it's very easy to generate random integers in the range $i\ldots
  79. j$, so the assumed external routine for the \PASCAL/ version comes down to
  80. this.
  81. @^Differences between \PASCAL/ and \CEE/@>
  82.  
  83. @<The random number generation procedure@>=
  84. int rand_int(int i,int j)
  85.    {
  86.    return(i + rand()%(j-i+1));
  87.    }
  88.  
  89. @ @<Initialize the random number generator@>=
  90.    srand((unsigned)time(NULL) % UINT_MAX);
  91. @^Differences between \PASCAL/ and \CEE/@>
  92.  
  93. @* A plan of attack.  After the user has specified $M$ and $N$, we compute
  94. the sample by following a general procedure recommended by Bentley:
  95. @^Bentley, Jon Louis@>
  96.  
  97. @<The |main| program@>=
  98. void main(void)
  99.    {
  100.    @<Establish the values of |M| and |N|@>@;
  101.    @<Initialize the random number generator@>@;
  102.    size = 0; @+ @<Initialize set |S| to empty@>@;
  103.    while(size < M) {
  104.       T = rand_int(1,N);
  105.       @<If |T| is not in |S|, insert it and increase |size|@>@;
  106.       }
  107.    @<Print the elements of |S| in sorted order@>@;
  108.    @<Free the allocated |hash| table@>@;
  109.    }
  110.  
  111. @ The main program just sketched has introduced several more global
  112. variables.  There's a set |S| of integers, whose representation will be
  113. deferred until later; but we can declare two auxiliary integer variables
  114. now.
  115.  
  116. @<Global variables@>=
  117.    int size; /* the number of elements in set |S| */
  118.    int T; /* new candidate for membership in |S| */
  119.  
  120. @ The first order of business is to have a short dialog with the user.
  121.  
  122. @<Establish the values of |M| and |N|@>=
  123.    do @+ {
  124.       printf("population size: N = "); @+ read_terminal(N);
  125.       if(N<=0) printf("N should be positive!\n");
  126.       } @+ while(N<=0);
  127.    do @+ {
  128.       printf("sample size: M = "); @+ read_terminal(M);
  129.       if(M<0) printf("M shouldn't be negative!\n");
  130.       else if(M>N) printf("M shouldn't exceed N!\n");
  131.       } @+ while((M<0) || (M>N));
  132.  
  133. @ @<Free the allocated |hash| table@>=
  134.    if(hash) free(hash);
  135. @^Differences between \PASCAL/ and \CEE/@>
  136.  
  137. @* An ordered hash table.  The key idea to an efficient solution of this
  138. sampling problem is to maintain a set whose entries are easily sorted.  The
  139. method of ``ordered hash tables'' [Amble and Knuth, {\sl The Computer
  140. Journal}~{\bf 17} (May 1974), 135--142] is ideally suited to this task, as
  141. we shall see.
  142. @^Amble, Ole@>
  143. @^Knuth, Donald E.@>
  144.  
  145. Ordered hashing is similar to ordinary linear probing, except that the
  146. relative order of keys is taken into account.  The cited paper derives
  147. theoretical results that will not be rederived here, but we shall use the
  148. following fundamental property: {\sl The entries of an ordered hash table
  149. are independent of the order in which its keys were inserted.}  Thus, an
  150. ordered hash table is a ``canonical'' representation of its set of entries.
  151.  
  152. We shall represent |S| by an array of $2M$ integers.
  153.  
  154. @<Global variables@>=
  155.    int *hash; /* (a pointer to) the ordered hash table */
  156.    int H; /* an index into |hash| */
  157.    int H_max; /* the current hash size */
  158.    float alpha; /* the ratio of table size to |N| */
  159. @^Differences between \PASCAL/ and \CEE/@>
  160.  
  161. @ @<Initialize set |S| to empty@>=
  162.    if(hash = (int *)calloc(2*M,sizeof(int))) {
  163.       H_max = 2*M-1; @+ alpha = 2*M/N;
  164.       for(H=0; H<=H_max; H++)
  165.          hash[H] = 0;
  166.       }
  167.    else exit(1);
  168. @^Differences between \PASCAL/ and \CEE/@>
  169.  
  170. @ Now we come to the interesting part, where the algorithm tries to insert
  171. |T| into an ordered hash table.  We use the hash address $H=\lfloor
  172. 2M(T-1)/N\rfloor$ as a starting point, since this quantity is monotonic in
  173. |T| and almost uniformly distributed in the range $0\leq H<2M$.
  174.  
  175. @<If |T| is not in |S|, insert it and increase |size|@>=
  176.    H=trunc(alpha*(T-1));
  177.    while(hash[H]>T)
  178.       if(H==0)
  179.          H=H_max;
  180.       else
  181.          H--;
  182.    if(hash[H]<T) { /* |T| is not present */
  183.       size++; @+ @<Insert |T| into the ordered hash table@>@;
  184.    }
  185.  
  186. @ The heart of ordered hashing is the insertion process.  In general, the
  187. new key |T| will be inserted in place of a previous key $T_1<T$, which is
  188. then reinserted in place of $T_2<T_1$, etc., until an empty slot is
  189. discovered.
  190.  
  191. @<Insert |T| into the ordered hash table@>=
  192.    while(hash[H]>0) {
  193.       TT=hash[H]; /* we have $0<TT<T$ */
  194.       hash[H]=T; @+ T=TT;
  195.       do @+ {
  196.          if(H==0)
  197.             H=H_max;
  198.          else
  199.             H--;
  200.          } @+ while(hash[H]>=T);
  201.       }
  202.    hash[H]=T;
  203.  
  204. @ @<Global variables@>=
  205.    int TT; /* a key that's being moved */
  206.  
  207. @* Sorting in linear time.  The climax of this program is the fact that the
  208. entries in our ordered hash table can easily be read out in increasing
  209. order.
  210.  
  211. Why is this true?  Well, we know that the final state of the table is
  212. independent of the order in which the elements entered.  Furthermore it's
  213. easy to understand what the table looks like when the entries are inserted
  214. in decreasing order, because we have used a monontonic hash function.
  215. Therefore we know that the table must have an especially simple form.
  216.  
  217. Suppose the nonzero entries are $T_1<\dots<T_M$. If $k$ of these have
  218. ``wrapped around'' in the insertion process (i.e., if |H| passed from $0$
  219. to |H_max|, $k$ times), table position |hash[0]| will either be zero (in
  220. which case $k$ must also be zero) or it will contain $T_{k+1}$.  In the
  221. latter case, the entries $T_{k+1}<\dots<T_M$ and $T_1<\dots<T_k$ will
  222. appear in order from left to right.  Thus the output can be sorted with at
  223. most two passes over the table.
  224.  
  225. @d print_it printf("%10d\n",hash[H])
  226.  
  227. @<Print the elements of |S| in sorted order@>=
  228.    if(hash[0]==0) { /* there was no wrap-around */
  229.       for(H=1; H<=H_max; H++)
  230.          if(hash[H]>0)
  231.             print_it;
  232.       }
  233.    else { /* print the wrap-around entries */
  234.       for(H=1; H<=H_max; H++)
  235.          if(hash[H]>0)
  236.             if(hash[H]<hash[0])
  237.                print_it;
  238.       for(H=0; H<=H_max; H++)
  239.           if(hash[H]>=hash[0])
  240.              print_it;
  241.       }
  242.  
  243. @* Index.  The uses of single-letter variables aren't indexed by \.{CWEB},
  244. so this list is quite short.  (And an index is quite pointless anyway, for
  245. a program of this size.)  Changes for \.{CWEB} can be found under the
  246. entry ``Differences between \PASCAL/ and \CEE/.''
  247.