Programming Assignment 4
Monty's Gambit
Monday Oct 18, 1998
Thursday Oct 28, 1998

Problem Description

In the television game show Let's Make a Deal, there are three closed doors on the set with a prize hidden behind just one of them. The contestant in the game selects a door to be opened, hoping that it will be the one with the prize behind it! On occasion, after the contestant has chosen a door, Monty Hall (the host of the show), adds an interesting twist: instead of opening the chosen door, Monty opens one of the unchosen doors without a prize behind it. Then Monty offers the contestant the opportunity to switch their selection to the remaining door. Should the contestant stay with their original choice or switch to the other unopened door? Does it make any difference?

One approach to answering such questions is to construct a computer simulation of the problem. To simulate a single trial of Monty's gambit, the computer must

  1. select a random door behind which the prize is located
  2. select a random door initially chosen by the contestant
  3. select a random door other than (1) or (2) that will be opened by Monty
  4. select a random door other than (2) or (3) that will be the new door chosen by a contestant if they choose to switch.
Given these selections, the computer can determine the outcome of the trial for both strategies (stick and switch). But these outcomes represent just one trial. The idea is that, by simulating many trials and tabulating the number of winning selections for each strategies, we will approximate the chances of winning with each strategy. This is the objective of the programming assignment, except that we will generalize it to simulate the problem with an arbitrary number of doors (3 or more).

Programming Assignment

Construct a Haskell function monty :: Int -> Int -> Int -> IO () such that monty n d r prints the percentage of wins for each strategy in n simulated trials with d doors on stage. Here n and d are positive integers with d at least 3. The last input r is a random integer to be used in making the random selections (more on this later).

The output of your function should look something like:

    Main>  monty 100 3 1234
    Performing 100 trials with 3 doors...
    Percentage of wins with 'stick' strategy:  ***%
    Percentage of wins with 'switch' strategy: ***%
Of course your program should print real percentages rather than the stars shown here!

The correctness of your solution is, as usual, the key determinant of the program grade, but we will also be grading the clarity and simplicity of your solution. Do not consider your program finished simply because it appears to solve the problem! Review your solution carefully to see how it might be simplified and improved, just as you review and revise an essay in an English or History class.

Random Numbers in Haskell

The creation and use of random numbers requires special attention in Haskell. First of all, a Haskell function random :: Int must give the same result every time it is used. So a function of this form can not be very random! The best we can do is to create a function randomNext :: Int -> Int that, given a random number as input, generates a new random number by applying some arithmetic operations to the input number. Here is one possible definition that depends on the representation of Int values inside the computer. Given an input between 1 and 2147483646, the result is a (different) integer in the same range.

    randomNext:: Int -> Int
    randomNext rand
      = if newRand > 0 then newRand else newRand + 2147483647
	 newRand = 16807 * lo - 2836 * hi
         hi      = rand `div` 127773
         lo      = rand `rem` 127773
Through repeated application, this function can be used to generate a sequence of pseudo-random numbers that have many of the properties of sequences of random numbers, if the arithmetic operations are chosen carefully (as they are here).

The function randomNext can be difficult to use, because we must always "remember" the last random number generated, in order to produce the next one. In this assignment you may wish to define a function runtrials which calls a function trial for each trial, supplying the random numbers needed to perform the trial as arguments. This way only runtrials need be concerned with the generation of random numbers.

What can we do with a pseudo-random numbers drawn from a large domain such as the integers between 1 and 2147483646? Here is one example. Suppose we have a list ds of available doors, and we wish to select one at random. We could do so using the following function:
    -- randElem ds r: given list of items ds and a random Int r, 
    -- return a random member of the list ds.
    randElem :: [a] -> Int -> a
    randElem ds r
      = ds !! (r `mod` (length ds))
The expression r `mod` (length ds) yields a random integer in the range 0..(length ds)-1, and the !! function selects the corresponding element from ds.

Submission Procedure

The times available for submission of program 4 are (tentatively)

$ Revised: Oct 16 1999 by