On Thu, Jul 15, 2010 at 9:24 AM, kirby urner <span dir="ltr"><<a href="mailto:kirby.urner@gmail.com">kirby.urner@gmail.com</a>></span> wrote:<br><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">
Here is some of the code we will be using in class today. After that,<br>
a source code file from yesterday's class:<br>
<br>
<br>
Some Games<br>
"""<br>
<br>
from random import randint, choice<br>
<br>
# a list of (question, answer) tuples<br>
quiz1 = [("What is the capital of Oregon?", "Salem"),<br>
("Is Portland north of Salem?", "Yes"),<br>
("Does I-5 go to Seattle?", "Yes"),<br>
("Does I-5 go to Pendleton?", "No")]<br>
<br>
quiz2 = [("What is the capital of Washington State","Olympia"),<br>
("Are kangaroos native to China?","No"),<br>
("Who is the president of the USA?", "Obama"),<br>
("What computer language is named for Monty Python?", "Python")]<br>
<br>
quizzes = [quiz1, quiz2] # list of possible quizzes<br>
<br>
def askq(quiz = quiz1):<br>
score = 0<br>
possible = len(quiz)<br>
while len(quiz) > 0:<br>
pickone = randint(0, len(quiz)-1)<br>
print(quiz[pickone][0])<br>
answer = raw_input("Your answer? ")<br>
<br>
if answer.upper() == quiz[pickone][1].upper():<br>
# yes and Yes would both count (but not Y)<br>
print("Correct!")<br>
score = score + 1<br>
else:<br>
print("Correct answer was %s" % quiz[pickone][1])<br>
<br>
quiz.pop(pickone)<br>
<br>
print("Your score was %s out of a possible %s" % (score, possible))<br>
<br></blockquote><div><br></div><div>So there's a not-so-subtle bug in the above loop, in that the quiz.pop</div><div>method exhausts the target list -- forever and for good, so that next</div><div>time you run the quiz, either quiz1 or quiz2 (both global variables) will </div>
<div>have been commended to the ether.</div><div><br></div><div>As a class exercise, I was encouraging students to use this scaffolding</div><div>to think of other quizzes and add them as quiz3, quiz4 etc. Note that</div>
<div>the menu loop picks one at random:</div><div><br></div><div><div> elif sel == "1":</div><div> # randomly choose a quiz</div><div> askq(choice(quizzes))</div></div><div><br></div>
<div>So the chances are good that one will get an empty quiz </div><div>(list of (Q,A) tuples). The program doesn't crash, but you get</div><div>a score of 0 out of a possible 0.</div><div><br></div><div>The remedy, one of them, is just to use a copy of the list internally.</div>
<div><br></div><div>At this point, a student discussed his travails wanting to copy </div><div>a dictionary, after getting bit in the butt by the "multiple references </div><div>to same object" feature. </div><div>
<br></div><div>He'd discovered the copy module on his own. </div><div><br></div><div>However in this case, with a list, I went with thelist[:] way of doing it.</div><div>Here's the remedied function:</div><div><br>
</div><div><div>def askq(quiz = quiz1):</div><div> score = 0</div><div> possible = len(quiz)</div><div> thequiz = quiz[:] # from hence forth, use thequiz, leave quiz alone</div><div> </div><div> while len(quiz) > 0:</div>
<div> pickone = randint(0, len(quiz)-1)</div><div> print(thequiz[pickone][0])</div><div> answer = raw_input("Your answer? ")</div><div><br></div><div> if answer.upper() == quiz[pickone][1].upper():</div>
<div> # yes and Yes would both count (but not Y)</div><div> print("Correct!")</div><div> score = score + 1</div><div> else:</div><div> print("Correct answer was %s" % quiz[pickone][1])</div>
<div><br></div><div> thequiz.pop(pickone)</div><div><br></div><div> print("Your score was %s out of a possible %s" % (score, possible))</div></div><div> </div><div>Now of course it's problematic to be popping off a list just because</div>
<div>you wanna not hit the same question twice, and are picking </div><div>questions randomly. Sure, this is one way to prevent duplicates.</div><div>Even the while loop is pegged to the list length. Didn't have to</div>
<div>be this way.</div><div><br></div><div>Just as good if not way better, is to shuffle the indexes ahead </div><div>of time e.g. indexes = range(len(thelist)); indexes.shuffle().</div><div><br></div><div>That'll give you a random sequence on a "primary key" with no</div>
<div>need to mess with popping data off a list copy.</div><div><br></div><div>Remember that in Python 3.x, range no longer returns a list </div><div>but a range object, a kind of iterable, but not an iterator (you</div><div>
can't run next on it):</div><div><br></div><div><div><div>>>> indexes = range(20)</div><div>>>> indexes</div><div>range(0, 20)</div><div><br></div><div>Some older Python...?</div><div><br></div><div>
>>> indexes.next()</div><div>Traceback (most recent call last):</div><div> File "<pyshell#12>", line 1, in <module></div><div> indexes.next()</div><div>AttributeError: 'range' object has no attribute 'next'</div>
<div><br></div><div>However, next(indexes) is still no good, because an </div><div>iterator needn't be nextable:</div><div><br></div><div>>>> next(indexes)</div><div>Traceback (most recent call last):</div><div>
File "<pyshell#13>", line 1, in <module></div><div> next(indexes)</div><div>TypeError: range object is not an iterator</div><div><br></div><div>OK, make us an iterator then:</div><div><br></div>
<div>>>> theiter = iter(indexes)</div><div>>>> next(theiter)</div><div>0</div><div>>>> next(theiter)</div><div>1</div><div><br></div><div>Now we're in business. Start over if we like:</div>
<div><br></div><div>>>> theiter = iter(indexes)</div><div>>>> next(theiter)</div><div>0</div><div><br></div><div>You can have lots of iterators off the same iterable </div><div>and they'll iterate independently...</div>
<div><br></div><div><div>>>> [next((theiterA, theiterB)[randint(0,1)]) for x in range(10)]</div><div>[0, 1, 2, 1, 2, 3, 4, 3, 4, 5]</div><div>>>> next(theiterA)</div><div>5</div><div>>>> next(theiterB)</div>
<div>6</div><div>>>> next(theiterA)</div><div>6</div><div>>>> </div></div><div><br></div><div>You can't shuffle an iterator though:</div><div><br></div><div>>>> theiter = iter(indexes)</div>
<div>>>> shuffle(theiter)</div><div>Traceback (most recent call last):</div><div> File "<pyshell#20>", line 1, in <module></div><div> shuffle(theiter)</div><div> File "C:\Python31\lib\random.py", line 267, in shuffle</div>
<div> for i in reversed(range(1, len(x))):</div><div>TypeError: object of type 'range_iterator' has no len()</div><div>>>> </div></div></div><div><br></div><div>So before using shuffle, you need to turn this range </div>
<div>object into a list. </div><div><br></div><div>>>> from random import shuffle</div><div><div>>>> help(shuffle)</div><div>Help on method shuffle in module random:</div><div><br></div><div>shuffle(self, x, random=None, int=<class 'int'>) method of random.Random instance</div>
<div> x, random=random.random -> shuffle list x in place; return None.</div><div> </div><div> Optional arg random is a 0-argument function returning a random</div><div> float in [0.0, 1.0); by default, the standard random.random.</div>
<div><br></div><div>>>> shuffle(indexes)</div><div>Traceback (most recent call last):</div><div> File "<pyshell#4>", line 1, in <module></div><div> shuffle(indexes)</div><div> File "C:\Python31\lib\random.py", line 270, in shuffle</div>
<div> x[i], x[j] = x[j], x[i]</div><div>TypeError: 'range' object does not support item assignment</div><div><br></div><div>>>> indexes = list(range(20))</div><div>>>> shuffle(indexes)</div><div>
>>> indexes</div><div>[11, 3, 16, 2, 4, 13, 17, 9, 19, 1, 12, 8, 10, 14, 18, 15, 0, 5, 7, 6]</div><div><br></div></div><div><br></div><div><div>Or is there something we wanna to check out in itertools? </div><div>
<br></div><div>Permutation?</div></div><div><br></div><div>Nah, we're done.</div><div><br></div><div>Kirby</div><div><br></div><div>Note: more genteel writers may prefer indices to indexes.</div><div>I also prefer vertexes to vertices.</div>
<div><br></div></div>