Skip to main content

Lab: Random language generation

Held
Wednesday, 20 February 2019
Writeup due
Friday, 22 February 2019
Summary
We explore Racket’s 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.

Important procedures

(random n) - Given a positive integer, generate a difficult-to-predict value between 0 and n-1, inclusive.

Preparation

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))))

Exercises

Exercise 1: Averaging dice rolls

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?

Exercise 2: Rolling multiple dice

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))

Exercise 3: Rolling multiple dice, revisited

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.)

Exercise 4: Rolling multiple dice, re-revisited

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.

Exercise 5: Tallying sevens and elevens

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.

Exercise 6: Subtle errors

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.

Exercise 7: Choosing names

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?

Exercise 8: Generating sentences

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.

Exercise 9: Generating less sensible 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.

Exercise 10: Madder Libs

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.

For those with extra time

If you find that you finish the lab early, consider trying one or more of the following exercises.

Extra 1: Flipping a coin.

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.

Extra 2: Haiku

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.

Extra 3: Limericks

Read the Poets.org guidance about the form of limericks. Then, describe how you would write a program that randomly generates limericks.

Extra 4: Rolling …

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.)

Acknowledgements

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.