random
procedure and ways to use that procedure
and a variety of associated procedures to generate language. We
consider the implications of having a procedure that does not behave
consistently. We also simulate some games of chance.
(random n)
- Given a positive integer, generate a difficult-to-predict
value between 0 and n
-1, inclusive.
a. Make sure that you have the latest version of the loudhum
package
by opening a terminal window and typing /home/rebelsky/bin/csc151/update
.
(Alternately, select File > Install Package…, enter
“https://github.com/grinnell-cs/loudhum.git
” and follow the instructions.)
b. Copy the code from the reading into
your definitions pane. You should include roll-a-die
, roll-dice
,
random-elt
, and the various procedures for generating sentences.
Make sure that when you copy a procedure, you also copy the
documentation.
c. Add the following procedure to your definitions pane.
;;; Procedure:
;;; average
;;; Parameters:
;;; nums, a nonempty list of numbers
;;; Purpose:
;;; Compute the average value in the list
;;; Produces:
;;; ave, a number
;;; Preconditions:
;;; [No additional]
;;; Postconditions:
;;; * (* ave (length nums)) is approximately the sum of the values
;;; in nums.
;;; * If any value in nums is inexact, val is inexact.
;;; * If all values in nums are exact, val is exact.
(define average
(lambda (nums)
(/ (reduce + nums)
(length nums))))
One way to explore the quality of our roll-a-die
procedure is to
average a bunch of dice rolls. Arguably, if the dice are “fair”,
we should find that the average is about 3.5.
a. Try a few experiments to see if we usually get an average of about 3.5 (or 3 1/2, since we’re working with exact numbers) if we roll five dice.
> (average (list (roll-a-die) (roll-a-die) (roll-a-die) (roll-a-die) (roll-a-die))
???
b. Can our experiment ever give a result of exactly 3 1/2? Why or why not?
c. Try a few experiments in which you roll ten dice. We assume you can figure out how to do so by modifying the experiment presented above.
d. Can this revised experiment ever give a result of exactly 3 1/2? Why or why not?
Some students, when confronted with the prior problem, write the following to find the average of ten dice rolls.
> (average (make-list 10 (roll-a-die)))
Don’t type that expression yet!
a. What do you see as the advantages and disadvantages of that approach?
b. Try that experiment a few times. Do you get results that you expected? If you did not expect the results you got, try to explain them. If you’re still not sure, try typing the following.
> (make-list 10 (roll-a-die))
As you’ve likely discovered, (make-list 10 (roll-a-die))
rolls
the die once and then makes a list of ten copies of the same
value. That’s not very useful. Is there an alternative? If you
remember the reading, there’s a procedure, roll-dice
, that you
may not be able to understand, but that the designers claim produces
a list of dice rolls.
a. Verify that roll-dice
does, indeed, appear to produce a list
of dice rolls.
b. Using roll-dice
and average
, find out the average value of
100 rolls. (Try at least two experiments.)
Unfortunately, roll-dice
relies on techniques that we have not yet
covered. Ideally, we would be able to write our own version of
roll-dice
using the techniques we’ve covered to date. For example,
we know that map
can be used to build a list of different values by
applying a function.
a. Here’s one attempt to make a list of ten dice rolls.
> (map roll-a-die (make-list 10 6))
Do you think that approach will work? Why or why not? (“You usually start with non-working examples” is not a good argument that it should not work.)
b. If the approach did not work, is there a similar approach (i.e.,
using map
and make-list
you can think of that might achieve the
goal? Spend a minute or two with your partner discussing possible
approaches.
c. As you probably discovered at the beginning of the problem, you
can’t use map
with a zero-parameter procedure. What’s the solution?
We need a one-parameter procedure. But what should that parameter
do? Here’s a “hack” that works: We’ll write a one-parameter procedure
(ignore-then-roll x)
, that ignores its parameter, rolls a die,
and returns the result of rolling the die. Then we can use
> (map ignore-then-roll (make-list 10 0))
Write ignore-then-roll
. Then verify that it can be used as suggested.
d. Using ignore-then-roll
, map
, and make-list
, write a procedure,
(roll-some-dice n)
that behaves like roll-dice
.
There are some games of chance in which the player rolls two dice and
if those two dice total seven or eleven, the player wins. It might
be interesting to determine how often we are likely to roll a seven
or eleven in n
rolls of two dice.
Consider the following pair of procedures intended to help us achieve that goal.
;;; Procedure:
;;; pair-a-dice
;;; Parameters:
;;; [None]
;;; Purpose:
;;; Roll two six-sided dice and find their sum.
;;; Produces:
;;; roll, an exact integer
;;; Preconditions:
;;; [No additional]
;;; Postconditions:
;;; * 2 <= roll <= 12
;;; * Across multiple calls, the various rolls have the same probabilities
;;; as we would get from rolling two dice.
(define pair-a-dice
(lambda ()
(+ (roll-a-die) (roll-a-die))))
;;; Procedure:
;;; play-seven-eleven
;;; Pararameters:
;;; [None]
;;; Purpose:
;;; Play one round of the seven-eleven game and return either 1 for a
;;; victory or 0 for a loss.
;;; Produces:
;;; result, an exact integer
;;; Preconditions:
;;; [No additional]
;;; Postconditions:
;;; * If the roll of two dice produces 7 or 11, result is 1.
;;; * Otherwise, result is 0.
(define play-seven-eleven
(lambda ()
(cond
[(= 7 (pair-a-dice))
1]
[(= 11 (pair-a-dice))
1]
[else
0])))
a. Verify that both pair-a-dice
and play-seven-eleven
appear to work
as advertised. (Note that, in our own experiments, it can take as many
as ten rolls before you win; theoretically, it could take many more.)
> (pair-a-dice)
?
> (pair-a-dice)
?
> (play-seven-eleven)
?
> (play-seven-eleven)
?
> (play-seven-eleven)
?
b. If we are interested in the likelihood of winning, it would be useful to tally the number of wins. Here’s a procedure that tries to do just that.
;;; Procedure:
;;; count-wins
;;; Parameters:
;;; n, a positive integer
;;; Purpose:
;;; Play n games of seven-eleven and report on the number of wins.
;;; Produces:
;;; wins, a non-negative integer
(define count-wins
(lambda (n)
(reduce +
(map (lambda (x) (play-seven-eleven)) (make-list n 0)))))
c. Explain how this procedure is intended to work.
d. Determine approximately how many wins we get in 1000 games.
e. While count-wins
is conceptually correct, there is a subtle error
in play-seven-eleven
. Identify that error.
If you had difficulty identifying the error (or even if you didn’t), try the following.
a. Decide how many dice rolls should happen if we call (count-wins 10)
.
b. Rewrite pair-a-dice
as follows. It will now write “Rolling …”
each time the procedure is called.
(define pair-a-dice
(lambda ()
(display "Rolling ...") (newline)
(+ (roll-a-die) (roll-a-die))))
c. Confirm that the new pair-a-dice
has the behavior we expected.
d. Determine how many times we roll the dice in (count-wins 10)
. Is
it the number you chose in a? Is it always the same?
e. As you’ve likely seen, we get many more than ten rolls, even though we’re only supposed to be playing ten games. Figure out why.
f. Rewrite play-seven-eleven
to fix that error.
Suppose we have a list of names, students
, that represents
all of the students in a class.
(define students
(list "Andi" "Brook" "Casey" "Devin" "Drew" "Dylan" "Emerson" "Frances"
"Gray" "Harper" "Jamie" "Kennedy" "Morgan" "Phoenix" "Quinn" "Ryan"))
a. Write a zero-parameter procedure, (random-student)
, that
randomly selects the name of a student from the class.
b. Write a zero-parameter procedure, (random-pair)
, that calls
(random-student)
twice and puts the two students together into a
list.
c. What are potential problems with using (random-pair)
to select
partners from the class?
a. Using the sentence
procedure from the reading (you did copy it when
we told you to in the preliminaries, right?), generate about five different
sentences.
b. Add a few names, verbs, adjectives, and nouns to expand the range of sentences, then generate five new sentences.
Rather than using pre-classified words (that is, words identified as verbs, adjectives, and nouns), we might generate our “sentences” from words randomly selected from a text. Suppose we’ve loaded the contents of Jane Eyre into a list.
> (define eyre-words (file->words "/home/rebelsky/Desktop/pg1260.txt"))
a. Write a zero-parameter procedure, (eyre-sentence-a)
, that
randomly selects six words from eyre-words
and combines them into
a single string (with spaces in between).
b. Write a zero-parameter procedure, (eyre-sentence-b)
, that
randomly selects between four and ten words from eyre
words and
combines them into a single string (with spaces in between).
c. Spend a minute or so considering how one might generate
more “realistic” sentences using a corpus like eyre-words
.
Since we’re exploring how randomness helps us generate text, we may as well take advantage of prior experience generating text. You may recall that we recently wrote a variety of simple “Mad Libs” procedure.
(define mad-labs
(lambda (adjective1 adjective2 verb noun1 noun2 noun3 name)
(string-append "CSC 151 labs made me "
adjective2
". Each day we "
verb
" "
noun3
"s. I had the opportunity to work with many "
adjective1
" "
noun1
"s. I would definitely recommend this class to "
name
"'s "
noun2
".")))
a. Write a zero-parameter procedure, (random-mad-labs)
, that uses
mad-labs
to generate a story with parameters randomly selected
from the appropriate grammatical category. You should not change
the mad-labs
procedure, nor should you copy its body. Rather,
random-mad-labs
should generate an appropriate call to mad-labs
.
b. Write a zero-parameter procedure, (really-random-mad-labs)
,
that uses mad-labs
to generate a story with parameters randomly
selected from the words in eyre-words
. (It’s fine that they don’t
match the expected category.) As in the previous problem,
really-random-mad-labs
should call mad-labs
with appropriate
parameters.
If you find that you finish the lab early, consider trying one or more of the following exercises.
a. Write a zero-parameter procedure, (heads?)
that simulates the
flipping of a coin. The heads?
procedure should return #t
(which represents “the coin came up heads”) half the time and #f
(which represents “the coin came up tails”) about half the time.
> (heads?)
#f
> (heads?)
#f
> (heads?)
#t
> (heads?)
#f
> (heads?)
#t
b. Write a procedure, (count-heads n)
, that simulates the flipping of
n
coins (using heads?
to simulate the flipping of each coin) and
returns the number of times the coin is heads.
c. Use count-heads
to explore the distribution heads?
gives by
counting the number of heads you get in 100 flips, 1,000 flips, and
10,000 flips.
Haiku are three-line poems that consist of a line with five syllables, a line with seven syllables, and a line with five syllables. Write a procedure to generate random Haiku.
Read the Poets.org guidance about the form of limericks. Then, describe how you would write a program that randomly generates limericks.
Channel your inner John Belushi, Tina Turner, or John Fogerty to
use the extended pair-a-dice
procedure to make variants of
“Rawhide” or “Proud Mary”. (Note: This question exists primarily
to amuse one of the faculty. It’s fine if you don’t understand it.)
This laboratory was inspired by an earlier lab on randomness from the spring 2018 version of CSC 151. That lab was, itself, based on a sequence of earlier labs on randomness and simulation.
The 2018 lab assumed that students knew recursion, so this version was rewritten to eliminate the reliance on recursion.
This version also adds some new exercises on language generation using
new procedures in the loudhum
library. Those exercises were inspired
by similar exercises designed for the summer 2018 “language of code”
camp.
For those who care, the punny pair-a-dice
first appeared in an
exercise in spring
2001.
It then resurfaced (with a definition) in a Web-game exercise in
fall
2002.