On Thu, Jul 15, 2010 at 9:24 AM, kirby urner <span dir="ltr">&lt;<a href="mailto:kirby.urner@gmail.com">kirby.urner@gmail.com</a>&gt;</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&#39;s class:<br>
<br>
<br>
Some Games<br>
&quot;&quot;&quot;<br>
<br>
from random import randint, choice<br>
<br>
# a list of (question, answer) tuples<br>
quiz1 = [(&quot;What is the capital of Oregon?&quot;, &quot;Salem&quot;),<br>
       (&quot;Is Portland north of Salem?&quot;, &quot;Yes&quot;),<br>
       (&quot;Does I-5 go to Seattle?&quot;, &quot;Yes&quot;),<br>
       (&quot;Does I-5 go to Pendleton?&quot;,  &quot;No&quot;)]<br>
<br>
quiz2 = [(&quot;What is the capital of Washington State&quot;,&quot;Olympia&quot;),<br>
         (&quot;Are kangaroos native to China?&quot;,&quot;No&quot;),<br>
         (&quot;Who is the president of the USA?&quot;, &quot;Obama&quot;),<br>
         (&quot;What computer language is named for Monty Python?&quot;, &quot;Python&quot;)]<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) &gt; 0:<br>
        pickone = randint(0, len(quiz)-1)<br>
        print(quiz[pickone][0])<br>
        answer = raw_input(&quot;Your answer? &quot;)<br>
<br>
        if answer.upper() == quiz[pickone][1].upper():<br>
            # yes and Yes would both count (but not Y)<br>
            print(&quot;Correct!&quot;)<br>
            score = score + 1<br>
        else:<br>
            print(&quot;Correct answer was %s&quot; % quiz[pickone][1])<br>
<br>
        quiz.pop(pickone)<br>
<br>
    print(&quot;Your score was %s out of a possible %s&quot; % (score, possible))<br>
<br></blockquote><div><br></div><div>So there&#39;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 == &quot;1&quot;:</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&#39;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 &quot;multiple references </div><div>to same object&quot; feature.  </div><div>
<br></div><div>He&#39;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&#39;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) &gt; 0:</div>
<div>        pickone = randint(0, len(quiz)-1)</div><div>        print(thequiz[pickone][0])</div><div>        answer = raw_input(&quot;Your answer? &quot;)</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(&quot;Correct!&quot;)</div><div>            score = score + 1</div><div>        else:</div><div>            print(&quot;Correct answer was %s&quot; % quiz[pickone][1])</div>
<div><br></div><div>        thequiz.pop(pickone)</div><div><br></div><div>    print(&quot;Your score was %s out of a possible %s&quot; % (score, possible))</div></div><div> </div><div>Now of course it&#39;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&#39;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&#39;ll give you a random sequence on a &quot;primary key&quot; 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&#39;t run next on it):</div><div><br></div><div><div><div>&gt;&gt;&gt; indexes = range(20)</div><div>&gt;&gt;&gt; indexes</div><div>range(0, 20)</div><div><br></div><div>Some older Python...?</div><div><br></div><div>
&gt;&gt;&gt; indexes.next()</div><div>Traceback (most recent call last):</div><div>  File &quot;&lt;pyshell#12&gt;&quot;, line 1, in &lt;module&gt;</div><div>    indexes.next()</div><div>AttributeError: &#39;range&#39; object has no attribute &#39;next&#39;</div>
<div><br></div><div>However, next(indexes) is still no good, because an </div><div>iterator needn&#39;t be nextable:</div><div><br></div><div>&gt;&gt;&gt; next(indexes)</div><div>Traceback (most recent call last):</div><div>
  File &quot;&lt;pyshell#13&gt;&quot;, line 1, in &lt;module&gt;</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>&gt;&gt;&gt; theiter = iter(indexes)</div><div>&gt;&gt;&gt; next(theiter)</div><div>0</div><div>&gt;&gt;&gt; next(theiter)</div><div>1</div><div><br></div><div>Now we&#39;re in business.  Start over if we like:</div>
<div><br></div><div>&gt;&gt;&gt; theiter = iter(indexes)</div><div>&gt;&gt;&gt; next(theiter)</div><div>0</div><div><br></div><div>You can have lots of iterators off the same iterable </div><div>and they&#39;ll iterate independently...</div>
<div><br></div><div><div>&gt;&gt;&gt; [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>&gt;&gt;&gt; next(theiterA)</div><div>5</div><div>&gt;&gt;&gt; next(theiterB)</div>
<div>6</div><div>&gt;&gt;&gt; next(theiterA)</div><div>6</div><div>&gt;&gt;&gt; </div></div><div><br></div><div>You can&#39;t shuffle an iterator though:</div><div><br></div><div>&gt;&gt;&gt; theiter = iter(indexes)</div>
<div>&gt;&gt;&gt; shuffle(theiter)</div><div>Traceback (most recent call last):</div><div>  File &quot;&lt;pyshell#20&gt;&quot;, line 1, in &lt;module&gt;</div><div>    shuffle(theiter)</div><div>  File &quot;C:\Python31\lib\random.py&quot;, line 267, in shuffle</div>
<div>    for i in reversed(range(1, len(x))):</div><div>TypeError: object of type &#39;range_iterator&#39; has no len()</div><div>&gt;&gt;&gt; </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>&gt;&gt;&gt; from random import shuffle</div><div><div>&gt;&gt;&gt; help(shuffle)</div><div>Help on method shuffle in module random:</div><div><br></div><div>shuffle(self, x, random=None, int=&lt;class &#39;int&#39;&gt;) method of random.Random instance</div>
<div>    x, random=random.random -&gt; 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>&gt;&gt;&gt; shuffle(indexes)</div><div>Traceback (most recent call last):</div><div>  File &quot;&lt;pyshell#4&gt;&quot;, line 1, in &lt;module&gt;</div><div>    shuffle(indexes)</div><div>  File &quot;C:\Python31\lib\random.py&quot;, line 270, in shuffle</div>
<div>    x[i], x[j] = x[j], x[i]</div><div>TypeError: &#39;range&#39; object does not support item assignment</div><div><br></div><div>&gt;&gt;&gt; indexes = list(range(20))</div><div>&gt;&gt;&gt; shuffle(indexes)</div><div>
&gt;&gt;&gt; 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&#39;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>