9.1.4. Pick 'em
To show how to use rand(),
we'll look at a script that implements a "quick-pick" for a lottery game.
This script, named lotto, picks x
numbers from a series of numbers 1 to y.
Two arguments can be supplied on the command line: how many numbers to pick
(the default is 6) and the highest number in the series (the default
is 30).
Using the default values for x and y, the script generates
six unique random numbers between 1 and 30.
The numbers are sorted for readability from lowest to highest
and output.
Before looking at the script itself, let's run the program:
$ lotto
Pick 6 of 30
9 13 25 28 29 30
$ lotto 7 35
Pick 7 of 35
1 6 9 16 20 22 27
The first example uses the default values to print six random numbers
from 1 to 30. The second example prints seven random numbers out
of 35.
The full lotto script is fairly complicated, so before looking
at the entire script, let's look at a smaller script that
generates a single random number in a series:
awk -v TOPNUM=$1 '
# pick1 - pick one random number out of y
# main routine
BEGIN {
# seed random number using time of day
srand()
# get a random number
select = 1 + int(rand() * TOPNUM)
# print pick
print select
}'
The shell script expects a single argument from the command line
and this is passed into the program as "TOPNUM=$1,"
using the -v option.
All the action happens in the BEGIN procedure. Since there are
no other statements in the program, awk exits when the BEGIN
procedure is done.
The main routine first calls the srand() function
to seed the random number generator. Then we get a
random number by calling the rand() function:
select = 1 + int(rand() * TOPNUM)
It might be helpful to see this expression broken up
so each part of it is obvious.
Statement |
Result |
print r = rand() |
0.467315 |
print r * TOPNUM |
14.0195 |
print int(r * TOPNUM) |
14 |
print 1 + int(r * TOPNUM) |
15 |
Because the rand() function returns a number between 0 and 1,
we multiply it by TOPNUM to get a number between 0 and
TOPNUM. We then truncate the number to remove the fractional
values and then add 1 to the number. The latter is necessary
because rand() could return 0. In this example, the
random number that is generated is 15.
You could use this program to print any single number, such
as picking a number between 1 and 100.
$ pick1 100
83
The lotto script must "pick one" multiple times. Basically,
we need to set up a for loop to execute the rand()
function as many times as needed. One
of the reasons this is difficult is that we have to
worry about duplicates. In other words, it is possible
for a number to be picked again; therefore we have to
keep track of the numbers already picked.
Here's the lotto script:
awk -v NUM=$1 -v TOPNUM=$2 '
# lotto - pick x random numbers out of y
# main routine
BEGIN {
# test command line args; NUM = $1, how many numbers to pick
# TOPNUM = $2, last number in series
if (NUM <= 0)
NUM = 6
if (TOPNUM <= 0)
TOPNUM = 30
# print "Pick x of y"
printf("Pick %d of %d\n", NUM, TOPNUM)
# seed random number using time and date; do this once
srand()
# loop until we have NUM selections
for (j = 1; j <= NUM; ++j) {
# loop to find a not-yet-seen selection
do {
select = 1 + int(rand() * TOPNUM)
} while (select in pick)
pick[select] = select
}
# loop through array and print picks.
for (j in pick)
printf("%s ", pick[j])
printf("\n")
}'
Unlike the previous program, this one looks for two command-line
arguments, indicating x numbers out of
y. The main routine looks to see if these
numbers were supplied and if not, assigns default values.
There is only one array, pick, for holding the random numbers that
are selected. Each number is guaranteed to be in the desired range,
because the result of rand() (a value between 0 and 1) is
multiplied by TOPNUM and then truncated.
The heart of the script is a loop that occurs NUM times
to assign NUM elements to the pick array.
To get a new non-duplicate random number, we
use an inner loop that generates selections
and tests to see if they are in the pick array.
(Using the in operator is much faster than looping through the
array comparing subscripts.)
While (select in pick), the corresponding element
has been found already, so the
selection is a duplicate and we reject the selection.
If it is not true that select in pick, then
we assign select to an element of the pick array.
This will make
future in tests
return true, causing the do loop to continue.
Finally, the program loops through the pick array and
prints the elements.
This version of the lotto script leaves one thing out.
See if you can tell what it is if we run it again:
$ lotto 7 35
Pick 7 of 35
5 21 9 30 29 20 2
That's right, the numbers are not sorted. We'll
defer showing the code for the sort routine
until we discuss user-defined functions. While it's not
necessary to have written the sorting code as a function,
it makes a lot of sense. One reason is that you
can tackle a more generalized problem and retain
the solution for use in other programs. Later
on, we will write a
function that sorts the elements of an array.
Note that the pick array isn't ready for sorting, since its
indices are the same as its values, not numbers in order.
We would have to set up a separate array for sorting by our sort
function:
# create a numerically indexed array for sorting
i = 1
for (j in pick)
sortedpick[i++] = pick[j]
The lotto program is set up to do everything in the BEGIN block.
No input is processed.
You could, however,
revise this script to read a list of names from a file and
for each name generate a "quick-pick."