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