15-110 Spring 2011 Homework 5
Due Tuesday, 1-Mar, at 10pm
Same basic directions as hw4, so read those carefully
(replacing "hw4" with "hw5", where appropriate, of course).
Additional notes:
- Start with hw5.zip.
- Reminder: portions
marked SOLO must be solved
entirely on your own (you may discuss with course staff, but not with each
other or any outside sources). See syllabus for details.
- This week you may use "if" statements (conditionals),
and "for" or "while" statements (loops), or lists, but not recursion or other collections (dictionaries, sets, etc).
- Note that you are responsible for malformed input in the SOLO problems this week. See below for details.
- SOLO Problem-Solving (with Monte Carlo Methods) [50 pts]
Background: in this problem, we will consider the game of Yahtzee.
Actually, we will consider a limited form of Yahtzee, where a
turn progresses as such: first, a player rolls 5 (six-sided)
dice. Then, the player keeps the dice with the most-frequent
value (with ties going to the larger value), and re-rolls the others.
For example, if the player first rolls 3,6,4,3,4, then the player would keep the two 4's, and re-roll the other dice. The player once again
keeps the dice with the most-frequent value (which may have changed!), and re-rolls the others
(so there are 3 total rolls of the dice -- the first roll of all the
dice, followed by two re-rolls of some of the dice). At this
point, if all 5 dice have the same value, the player wins (we say that
the dice are a Yahtzee, or 5-of-a-kind).
Here is another example:
Start of turn.
Player rolls 5 dice, and the player's hand is 1,4,5,4,1
Player keeps the 4's, and re-rolls the other 3 dice.
Player rolls 2,2,2.
Now the player's hand is 2,4,2,4,2 (see?).
Player keeps the 2's (not the 4's), and re-rolls the other 2 dice.
Player rolls 1,2.
Now the player's hand is 2,1,2,2,2
The turn is over, and since not all 5 dice match, the player did not win the round with a Yahtzee.
Using the appropriate
math, or a quick Google search, we can see that the odds of scoring a
Yahtzee in this game are about 347897/7558272, or about 4.6%. In
this problem, we will use Monte Carlo methods to confirm this result.
Actually,
we will generalize the game to allow for different numbers of rolls per
turn. So if we set rollsPerTurn to 3, then we are playing
traditional Yahtzee, and we expect the odds of a Yahtzee to be about
4.6%. We can use our simulation to not only confirm this result,
but also to investigate the odds of a Yahtzee if we allow other numbers
of rolls per turn.
To aid in this problem, we will first write some useful helper functions, along with their test functions.
- SOLO rollDie and testRollDie [5 pts]
Write the function rollDie (in the file hw5-solo.py). This
function takes no parameters and returns a random integer between 1 and
6, inclusive. You should use the function random.randint
appropriately.
Then,
write
the function testRollDie (in the file hw5-solo.py). This function
takes no
parameters and tests whether or not the rollDie function works
properly. If rollDie does work properly, this function should do
nothing. But if rollDie does not work properly, this function
should raise an AssertionError (this is how all our test functions
work). Now, how can you tell if rollDie works properly?
Well, what would you expect to be true of, say, ten thousand
calls to rollDie? We cannot say that all 6 possible values would
occur exactly 1/6th of the time, but we can expect this to be close to
true (and even closer as we increase the rolls). So what is a
reasonable range to allow before flagging an error? This requires
some statistics to answer properly, so instead here we'll simply use a
range of 10% (which is large enough to handle the expected variance
among 10,000 rolls). So if we expect a value of E, and we observe
a value of O, we will consider this correct if (0.90E <= O <=
1.10E). Of course, you have to check each possible expected value from
1 to 6 (though you only need to the roll the 10,000 dice once, and
check all 6 counts after that). Also: since we are testing
correctness, you cannot assume that rollDie will return a value between
1 and 6, or even an integer, so be sure to test for those cases, too!
We will test your testRollDie function not only against your own
rollDie function, but against other rollDie functions that we supply
(and those will contain numerous errors for your test function to
detect).
- SOLO isYahtzee and testIsYahtzee [10 pts]
Write the function isYahtzee (in the file hw5-solo.py). This
function
takes one parameter, the hand, which is a list of dice values, and
returns True if the hand is a Yahtzee (that is, all the dice values are
the same), and False otherwise. This function must work for
hands of arbitrary positive length, and not assume there are 5 dice.
This function should also return False, rather than crash, for
any malformed input, including if the hand is not a list, or is an
empty list (of length 0), or contains a non-integer, or contains a
value outside the range of 1 to 6.
Then,
write the function testIsYahtzee (in the file hw5-solo.py). This
function
takes no parameters and tests whether or not the isYahtzee function
works properly. Again, be thorough, as we will test your
testIsYahtzee function against numerous error-ful isYahtzee functions
we supply. - SOLO dieToKeep and testDieToKeep [10 pts]
Write the function dieToKeep (in the file hw5-solo.py). This
function
takes one parameter, the hand, which is a list of dice values, and
returns the value of the most-frequent die, with ties going to the
larger value. So dieToKeep([3,6,4,3,4]) should return 4. As
with isYahtzee, this function must
work for hands of arbitrary positive length, and not assume there are 5
dice. This function should return -1, signifying an error, rather than crash, for
any malformed input.
Then, write the function testDieToKeep (in the file hw5-solo.py). This
function
takes no parameters and tests whether or not the dieToKeep function
works properly.
- SOLO gotAYahtzee and testGotAYahtzee [10 pts]
Write the function gotAYahtzee (in the file hw5-solo.py). This
function
takes one parameter, the rolls per turn, and simulates one round of
Yahtzee, as described above (so it rolls 5 dice, keeps some and
re-rolls, and repeats for the appropriate number of total rolls per
turn). The function returns True if at the end the resulting hand
is a Yahtzee and False otherwise. The function should also return
False for malformed input: in particular, if rollsPerTurn is not
an integer or is not positive. Note: this function must
also use the rollDie(), dieToKeep(), and isYahtzee() helper functions
that you already wrote (after all, that's why you wrote them!).
Then,
write the function testGotAYahtzee (in the file hw5-solo.py).
This
function
takes no parameters and tests whether or not the gotAYahtzee function
works properly. First, it should handle the easy case of checking
that gotAYahtzee deals properly with malformed input. After that,
it becomes unclear how to test gotAYahtzee (do you see why?). It
seems that the best test would run this many times, and then compare
the results to our expected value (allowing for some variance).
But that is precisely what our next function does, so we will not
do that here. Thus, this test function is very weak, but that's ok,
since the next test function will subsume this task. Be sure you
understand this! Also, be sure to include a thoughtful comment in
your testGotAYahtzee function explaining that most of its testing is
deferred to testYahtzeeOdds (below).
- SOLO yahtzeeOdds and testYahtzeeOdds [10 pts]
Write the function yahtzeeOdds (in the file hw5-solo.py). This
function
takes two parameters -- the number of rolls per turn, and the number of
total trials to run -- and performs a Monte Carlo simulation of
Yahtzee, calling the gotAYahtzee function once for each trial.
The function returns the observed odds, which is the ratio of
Yahtzees to the total number of trials (beware integer division!).
If either rollsPerTurn or trials is malformed (either not an
integer, or not positive), the function should return -1 to signify an
error.
Then, write the function testYahtzeeOdds (in the file hw5-solo.py). This
function
takes no parameters and tests whether or not the yahtzeeOdds function works properly. First,
it should handle the easy case of checking that yahtzeeOdds deals
properly with malformed input. Then things get more challenging,
given the variability in expected results of Monte Carlo functions.
Even so, there is plenty we can test. Proceed as such: - Confirm
that calling yahtzeeOdds with any number of rollsPerTurn (say, between
1 and 10) and any number of trials (say, 1, 10, 100, or 1000) always
returns a number between 0.0 and 1.0.
- Confirm
the expected result that the odds of a traditional Yahtzee (with 3
rolls per turn) is about 4.6%. Use 10,000 trials and require the
observed odds to be within 0.01 of the expected odds (0.046).
Note that we could lock down our accuracy closer than 0.01
if we use more trials, which is a good idea, but we'll stick with
10,000 trials to allow your testing (and ours!) to run faster.
Also
note: since this is probabilistic, there is some very small
chance that working code will still fail this test by having the very
bad luck to be beyond 0.01 away from the expected odds. In this
case, just re-run your test function a few times, and if it "mostly"
works, you are fine. Our robotic autograder will do the same.
This note applies to the next two test cases, too.
- Confirm
that as we increase the number of trials, our accuracy increases.
Specifically, confirm that the (absolute value of the) difference
between the observed odds and expected odds (4.6%) decreases as the
number of trials increases from 1 to 100 to 50,000 (we use large
gaps in trials to be confident this pattern emerges).
- Finally,
confirm that as we increase the number of rollsPerTurn, the odds of a
Yahtzee increases. For that, lock down the trials at 10,000 and
check this pattern for each even number of rollsPerTurn from 2 to 12
(2, 4, 6, 8, 10, 12) -- again using gaps (here, skipping the odds) to
be confident the pattern emerges.
- SOLO Yahtzee Odds for various rollsPerTurn [5 pts]
In
your hw5.doc file, answer the following: how many rollsPerTurn
are required so that the odds of a Yahtzee are greater than 50%?
75%? 90%? Briefly explain how you used the
yahtzeeOdds function to obtain these answers.
- SOLO Reasoning About Code (Mystery Functions) [15 pts; 3 pts each]
In just a few words of plain English (in your hw5.doc file), state what each of the following functions does in general. You will receive no credit for describing the line-by-line low-level behavior of the code. For example:
def mystery(list):
count = 0
for value in list:
if (value == 0):
count += 1
return count
Correct
answer: "This function returns the number of 0's in the given
list." Or, if you prefer to be succinct, just: "number of
0's".
Incorrect answer (no points): "This function sets count
to 0, then sets value to each element in list in turn, and for each
value, if it is 0, it adds one to count, and then returns count".
This is all true, but completely misses the high-level behavior
of the function, and so would receive zero points. - def mystery1(x):
# assume x is an integer
c = str(x)[0]
return ((c < "0") or (c > "9"))
- def mystery2(x,y):
# assume x and y are non-negative integers
count = 0
for target in range(min(x,y), max(x,y)+1):
product = 1
for check in range(2,target):
product *= (target % check)
if (product > 0):
count += 1
return count
- def mystery3(list):
# assume list contains integers
list = [] + list
while (len(list) > 2):
list.remove(min(list))
list.remove(max(list))
return (list[0] + list[-1])/2
- def mystery4(matrix):
rows = len(matrix)
cols = len(matrix[0])
for row in range(rows):
for col in range(cols):
if (matrix[row][col] != matrix[row][cols-1-col]):
return False
return True
- def mystery5(s):
x = 0
y = 0
positions = [[x,y]]
for c in s:
if (c == "u"):
y += 1
elif (c == "d"):
y -= 1
elif (c == "r"):
x += 1
elif (c == "l"):
x -= 1
else:
return False
if ([x,y] in positions):
return True
positions += [[x,y]]
return False
- Collaborative
Problem-Solving (with File IO)
[15 pts]
- K-Letter Word List
Background:
sometimes it's handy (say, for playing Scrabble or other such
games) to have a word list that only contains words of a specific
length (eg, 3-letter-words.txt). Here, we'll write a tool that
extracts such word lists from a larger list of words.
Your task:
Write the function makeKLetterWordListFile (in the file
hw5-collab.py). This function takes two parameters -- the name of
a wordList file that is on your desktop (say, "words.txt") and a
positive integer k. The function creates a new file, also on the
desktop, with the original file's name prefaced with "k-letter" (where
k is the actual value supplied). So if the word list is
"words.txt" and k=3, then the function creates the file
"3-letter-words.txt" on the desktop. In this file, you include
each k-letter word, one-per-line, that occurs in the original word
list, in the same order as they occur in the original word list.
Your function should return True if this operation succeeds, and False
otherwise. For example, if the original file does not exist, or
if k is not an integer or is not positive, then your function should
not crash or raise an AssertionError but instead should return False.
Then, write the function testMakeKLetterWordListFile. This
function
takes no parameters and tests whether or not the makeKLetterWordListFile function works properly. Note that you may test the makeKLetterWordListFile
function using a smaller list of words than "words.txt", but you should
create that file in your test function and not just assume it is
on the desktop. Also note that you may assume that "words.txt" is on the desktop.
- SOLO Readings
in Computational Thinking [20 pts]
- SOLO Chazelle's Pithy Omnibus
Read “Could Your iPod Be Holding the Greatest Mystery in Modern Science?” by Bernard Chazelle. In hw5.doc, answer the following questions: - Briefly state Moore's Law.
- If
we assume (not entirely correctly, but closely enough, at least for the
past 40 years) that "twice as many transistors" roughly equates to
"twice as fast (at the same price)", then we expect a new $500 computer
to be roughly 2x faster every 1.5 years, or 4x faster every 3 years, or
8x faster every 4.5 years, and so on. Say you have a $500
computer and it is running an incredibly awesome 3d virtual reality
program, only it's way too slow to be enjoyable. No worries.
In how many years would you expect a new $500 computer to run
approximately one thousand times faster, and to handle that same 3d virtual reality program with no problem?
- In just a few words, what does it mean to be a universal computer?
- What is the duality Chazelle refers to?
- What does Chazelle mean by this: "Life's but a self-printing iPod!"?
- What is tractability? State one intractable problem, and one tractable problem.
- What does Chazelle mean by a zero-knowledge paradox?
- Chazelle
says: "Algorithmic thinking is likely to cause the most disruptive
paradigm shift in the sciences since quantum mechanics." Do you
agree. Explain (briefly, but thoughtfully).
- Collaborative
Bonus/Optional:
Farkle [10 pts]
In the file hw5-bonus-farkle.py (which is not supplied to you), write the game of Farkle.
Allow for multiple players, including a computer player.
Make your text-based user interface really intuitive, so the game
is "walk-up-and-use" (that is, it does not require training, or even
reading a help screen, though you may print out some rules and
instructions at the start of the game.