home *** CD-ROM | disk | FTP | other *** search
/ Fresh Fish 2 / FFMCD02.bin / new / dev / misc / cweb / examples / sample.w < prev    next >
Text File  |  1993-12-21  |  10KB  |  271 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. % Copyright (c) 1993 Andreas Scherer
  14.  
  15. % Permission is granted to make and distribute verbatim copies of this
  16. % document provided that the copyright notice and this permission notice
  17. % are preserved on all copies.
  18.  
  19. % Permission is granted to copy and distribute modified versions of this
  20. % document under the conditions for verbatim copying, provided that the
  21. % entire resulting derived work is distributed under the terms of a
  22. % permission notice identical to this one.
  23.  
  24. \font\csc=cmcsc10
  25. \def\CWEB{{\tt CWEB}}
  26. \def\Pascal{{\csc Pascal}}
  27. \def\WEB{{\tt WEB}}
  28.  
  29. \def\title{SAMPLE (C Version 1.0)}
  30. \def\topofcontents{\null\vfill
  31.   \centerline{\titlefont Producing random numbers}
  32.   \vskip15pt
  33.   \centerline{(C Version 1.0)}
  34.   \vfill}
  35. \def\botofcontents{\vfill
  36.   \noindent Copyright \copyright\ 1993 Andreas Scherer
  37.   \bigskip
  38.   \noindent Permission is granted to make and distribute verbatim copies of
  39.   this document provided that the copyright notice and this permission
  40.   notice are preserved on all copies.
  41.   \smallskip
  42.   \noindent Permission is granted to copy and distribute modified versions
  43.   of this document under the conditions for verbatim copying, provided that
  44.   the entire resulting derived work is distributed under the terms of a
  45.   permission notice identical to this one.}
  46.  
  47. @* Introduction.  Jon Bentley@^Bentley, Jon Louis@> recently discussed
  48. the following interesting problem as one of his ``Programming Pearls''
  49. [{\sl Communications of the ACM}~{\bf 27} (December, 1984), 1179--1182]:
  50.  
  51. {\narrower\noindent The input consists of two integers $M$ and $N$, with
  52. $M<N${}.  The output is a sorted list of $M$ random numbers in the range
  53. $1\ldots N$ in which no integer occurs more than once.  For probability
  54. buffs, we desire a sorted selection without replacement in which each
  55. selection occurs equiprobably.\par}
  56.  
  57. The present program illustrates what I (i.e., Donald E. Knuth
  58. @^Knuth, Donald E.@> in his {\sl Literate Programming} (October, 1991),
  59. 144--149) think is the best solution to this problem, when $M$ is
  60. reasonably large yet small compared to $N${}.  It's the method described
  61. tersely in the answer to exercise 3.4.2--15 of my book {\sl Seminumerical
  62. Algorithms}, pp.~141 and~555.  The \WEB\ program was translated to \CWEB\
  63. by Andreas Scherer.  Necessary changes for \Cee\ were included and some
  64. \Pascal\ restrictions removed.
  65. @^Scherer, Andreas@>
  66.  
  67. @ For simplicity, all input and output in this program is assumed to be
  68. handled at the terminal.  The \CWEB\ macro |read_terminal| defined here
  69. can easily be changed to accommodate other conventions.  The macro |trunc|
  70. replaces the \Pascal\ routine of the same name for \Cee{}.
  71. @^Differences between \Pascal\ and \Cee@>
  72.  
  73. @d read_terminal(a) fscanf(stdin,"%d",&a)
  74.    /* input a value from the terminal */
  75. @d trunc(a) (int)(a)
  76.  
  77. @ Here's an outline of the entire \Cee\ program:
  78.  
  79. @c
  80. @<Global |#include|s@>@/
  81. @<Global variables@>@/
  82. @<The random number generation procedure@>@/
  83. @<The |main| program@>
  84.  
  85. @ The library interfaces of \Pascal\ and \Cee\ are different, so here is
  86. the needed information.
  87. @^Differences between \Pascal\ and \Cee@>
  88.  
  89. @<Global |#include|s@>=
  90. #include <stdio.h>
  91. #include <stdlib.h>
  92. #include <limits.h>
  93. #include <time.h>
  94.  
  95. @ The global variables $M$ and $N$ have already been mentioned; we had
  96. better declare them.  Other global variables will be declared later.
  97.  
  98. @<Global variables@>=
  99.    int M; /* size of the sample */
  100.    int N; /* size of the population */
  101.  
  102. @ In \Cee\ it's very easy to generate random integers in the range $i\ldots
  103. j$, so the assumed external routine for the \Pascal\ version comes down to
  104. this.
  105. @^Differences between \Pascal\ and \Cee@>
  106.  
  107. @<The random number generation procedure@>=
  108. int rand_int(int i,int j)
  109.    {
  110.    return(i + rand()%(j-i+1));
  111.    }
  112.  
  113. @ @<Initialize the random number generator@>=
  114.    srand((unsigned)time(NULL) % UINT_MAX);
  115. @^Differences between \Pascal\ and \Cee@>
  116.  
  117. @* A plan of attack.  After the user has specified $M$ and $N$, we compute
  118. the sample by following a general procedure recommended by Bentley:
  119. @^Bentley, Jon Louis@>
  120.  
  121. @<The |main| program@>=
  122. void main(void)
  123.    {
  124.    @<Establish the values of |M| and |N|@>@;
  125.    @<Initialize the random number generator@>@;
  126.    size = 0; @+ @<Initialize set |S| to empty@>@;
  127.    while(size < M) {
  128.       T = rand_int(1,N);
  129.       @<If |T| is not in |S|, insert it and increase |size|@>@;
  130.       }
  131.    @<Print the elements of |S| in sorted order@>@;
  132.    @<Free the allocated |hash| table@>@;
  133.    }
  134.  
  135. @ The main program just sketched has introduced several more global
  136. variables.  There's a set |S| of integers, whose representation will be
  137. deferred until later; but we can declare two auxiliary integer variables
  138. now.
  139.  
  140. @<Global variables@>=
  141.    int size; /* the number of elements in set |S| */
  142.    int T; /* new candidate for membership in |S| */
  143.  
  144. @ The first order of business is to have a short dialog with the user.
  145.  
  146. @<Establish the values of |M| and |N|@>=
  147.    do @+ {
  148.       printf("population size: N = "); @+ read_terminal(N);
  149.       if(N<=0) printf("N should be positive!\n");
  150.       } @+ while(N<=0);
  151.    do @+ {
  152.       printf("sample size: M = "); @+ read_terminal(M);
  153.       if(M<0) printf("M shouldn't be negative!\n");
  154.       else if(M>N) printf("M shouldn't exceed N!\n");
  155.       } @+ while((M<0) || (M>N));
  156.  
  157. @ @<Free the allocated |hash| table@>=
  158.    if(hash) free(hash);
  159. @^Differences between \Pascal\ and \Cee@>
  160.  
  161. @* An ordered hash table.  The key idea to an efficient solution of this
  162. sampling problem is to maintain a set whose entries are easily sorted.  The
  163. method of ``ordered hash tables'' [Amble and Knuth, {\sl The Computer
  164. Journal}~{\bf 17} (May 1974), 135--142] is ideally suited to this task, as
  165. we shall see.
  166. @^Amble, Ole@>
  167. @^Knuth, Donald E.@>
  168.  
  169. Ordered hashing is similar to ordinary linear probing, except that the
  170. relative order of keys is taken into account.  The cited paper derives
  171. theoretical results that will not be rederived here, but we shall use the
  172. following fundamental property: {\sl The entries of an ordered hash table
  173. are independent of the order in which its keys were inserted.}  Thus, an
  174. ordered hash table is a ``canonical'' representation of its set of entries.
  175.  
  176. We shall represent |S| by an array of $2M$ integers.
  177.  
  178. @<Global variables@>=
  179.    int *hash; /* (a pointer to) the ordered hash table */
  180.    int H; /* an index into |hash| */
  181.    int H_max; /* the current hash size */
  182.    float alpha; /* the ratio of table size to |N| */
  183. @^Differences between \Pascal\ and \Cee@>
  184.  
  185. @ @<Initialize set |S| to empty@>=
  186.    if(hash = (int *)calloc(2*M,sizeof(int))) {
  187.       H_max = 2*M-1; @+ alpha = 2*M/N;
  188.       for(H=0; H<=H_max; H++)
  189.          hash[H] = 0;
  190.       }
  191.    else exit(1);
  192. @^Differences between \Pascal\ and \Cee@>
  193.  
  194. @ Now we come to the interesting part, where the algorithm tries to insert
  195. |T| into an ordered hash table.  We use the hash address $H=\lfloor
  196. 2M(T-1)/N\rfloor$ as a starting point, since this quantity is monotonic in
  197. |T| and almost uniformly distributed in the range $0\leq H<2M$.
  198.  
  199. @<If |T| is not in |S|, insert it and increase |size|@>=
  200.    H=trunc(alpha*(T-1));
  201.    while(hash[H]>T)
  202.       if(H==0)
  203.          H=H_max;
  204.       else
  205.          H--;
  206.    if(hash[H]<T) { /* |T| is not present */
  207.       size++; @+ @<Insert |T| into the ordered hash table@>@;
  208.    }
  209.  
  210. @ The heart of ordered hashing is the insertion process.  In general, the
  211. new key |T| will be inserted in place of a previous key $T_1<T$, which is
  212. then reinserted in place of $T_2<T_1$, etc., until an empty slot is
  213. discovered.
  214.  
  215. @<Insert |T| into the ordered hash table@>=
  216.    wh