Struggling with basics

Discussion in 'Python' started by Jason, Sep 25, 2005.

  1. Jason

    Jason Guest

    A week ago I posted a simple little hi-score routine that I was using to
    learn Python.

    I've only just managed to examine the code, and the responses that
    people gave, and I'm now seriously struggling to understand why things
    aren't working correctly.

    At present my code is as follows...

    import random
    import bisect

    class HiScores:
    def __init__(self,hiScores):
    self.hiScores=[entry for entry in hiScores]

    def showScores(self):
    for score,name in self.hiScores:
    score=str(score).zfill(5)
    print "%s - %s" % name,score


    def addScore(self,score,name):
    score.zfill(5)
    bisect.insort(self.hiScores,(score,name))
    if len(self.hiScores)==6:
    self.hiScores.pop()

    def lastScore(self):
    return self.hiScores[-1][0]

    def main():

    hiScores=[('10000','Alpha'),('07500','Beta'),('05000','Gamma'),('02500','Delta'),('00000','Epsilon')]

    a=HiScores(hiScores)
    print "Original Scores\n---------------"
    a.showScores()

    while 1:
    newScore=str(random.randint(0,10000))
    if newScore > a.lastScore():
    print "Congratulations, you scored %s " % newScore
    name=raw_input("Please enter your name :")
    a.addScore(newScore,name)
    a.showScores()

    if __name__=="__main__":
    main()


    My first problem (lack of understanding of course) is that if I run the
    above, I get an error saying:

    print "%s - %s" % name,score
    TypeError: not enough arguments for format string

    Now I understand what it's saying, but I don't understand why.

    If I change the code to read:

    print "%s - %n" % name, score (thinking of course that ah-ha, score is
    numeric) then I get the same error.

    The only way for the program to run is to simply have

    print name,score (or print score,name)


    The final part that's simply not working correctly is that the entire
    program isn't sorting the data.

    If I run the program and get a score of, say, 6789, then when I add my
    name, nothing is entered. I have changed the clause that deletes (pops)
    the last array if the array count is 6 and seen what figures are being
    entered into the array.

    Sure enough they are going in the array, and they are being sorted, but
    they are only being sorted AFTER the 00000 of the initial array creation.

    I'm pretty sure it's to do with comparing a string against an integer
    but can't for the life of me see where to force the comparrison to check
    against two integers.

    Apologies for going over old ground and if I'm not understanding, I'm
    getting there honest ;)
     
    Jason, Sep 25, 2005
    #1
    1. Advertising

  2. Jason wrote:
    > A week ago I posted a simple little hi-score routine that I was using to
    > learn Python.
    >
    > I've only just managed to examine the code, and the responses that
    > people gave, and I'm now seriously struggling to understand why things
    > aren't working correctly.
    >
    > At present my code is as follows...
    >
    > import random
    > import bisect
    >
    > class HiScores:
    > def __init__(self,hiScores):
    > self.hiScores=[entry for entry in hiScores]
    >
    > def showScores(self):
    > for score,name in self.hiScores:
    > score=str(score).zfill(5)
    > print "%s - %s" % name,score
    >
    >
    > def addScore(self,score,name):
    > score.zfill(5)
    > bisect.insort(self.hiScores,(score,name))
    > if len(self.hiScores)==6:
    > self.hiScores.pop()
    >
    > def lastScore(self):
    > return self.hiScores[-1][0]
    >
    > def main():
    >
    > hiScores=[('10000','Alpha'),('07500','Beta'),('05000','Gamma'),('02500','Delta'),('00000','Epsilon')]
    >
    > a=HiScores(hiScores)
    > print "Original Scores\n---------------"
    > a.showScores()
    >
    > while 1:
    > newScore=str(random.randint(0,10000))
    > if newScore > a.lastScore():
    > print "Congratulations, you scored %s " % newScore
    > name=raw_input("Please enter your name :")
    > a.addScore(newScore,name)
    > a.showScores()
    >
    > if __name__=="__main__":
    > main()
    >
    >
    > My first problem (lack of understanding of course) is that if I run the
    > above, I get an error saying:
    >
    > print "%s - %s" % name,score
    > TypeError: not enough arguments for format string
    >
    > Now I understand what it's saying, but I don't understand why.


    The '%' operator expects a tuple or a single value on its right. So you
    have to set parentheses around "name, score".

    That needs getting used to, but otherwise it can't be discerned from
    print ("%s - %s" % name), (score).

    > If I change the code to read:
    >
    > print "%s - %n" % name, score (thinking of course that ah-ha, score is
    > numeric) then I get the same error.


    For integers you can use %s or %i (or %d), see http://docs.python.org/lib/typesseq-strings.html.

    > Apologies for going over old ground and if I'm not understanding, I'm
    > getting there honest ;)


    No problem. c.l.py is newbie-friendly.

    Reinhold
     
    Reinhold Birkenfeld, Sep 25, 2005
    #2
    1. Advertising

  3. Jason

    Duncan Booth Guest

    Jason wrote:

    > My first problem (lack of understanding of course) is that if I run the
    > above, I get an error saying:
    >
    > print "%s - %s" % name,score
    > TypeError: not enough arguments for format string
    >
    > Now I understand what it's saying, but I don't understand why.
    >


    The problem is precedence.

    print "%s - %s" % name,score

    is equivalent to:

    print ("%s - %s" % name),score

    not:

    print "%s - %s" % (name,score)

    The % operator binds more tightly than the comma, so you need to put
    parentheses around the argument to % (as in the last line above).
     
    Duncan Booth, Sep 25, 2005
    #3
  4. On Sun, 25 Sep 2005 19:37:32 +0100, Jason <>
    declaimed the following in comp.lang.python:


    > My first problem (lack of understanding of course) is that if I run the
    > above, I get an error saying:
    >
    > print "%s - %s" % name,score
    > TypeError: not enough arguments for format string
    >

    print "%s - %s" % (name, score)

    You need to pass a two-element tuple to the % operator; without the
    parens you have the equivalent of two print statements:

    print "%s - %s" % name, # the comma says "don't go to new line"
    print score


    > I'm pretty sure it's to do with comparing a string against an integer
    > but can't for the life of me see where to force the comparrison to check
    > against two integers.
    >

    Look closely:
    1) You initialize the high-score table with zero-filled STRINGS
    2) show() is also taking the entry from the table, converting to STRING
    (which is redundant for a string data item), and then zero-filling it
    (also redundant at this point)
    3) add() is taking a value, presumed to be a string, zero-filling it,
    and then... THROWS AWAY the zero-filled version and saves the original
    non-zero-filled.
    4) bisect requires a sorted list -- but your list doesn't really count
    as sorted; you have a REVERSE sorted list. bisect puts larger values
    AFTER smaller ones.

    Here's a DIFF report between your code, and what I did to it... If
    you aren't familiar with the notation...

    m1,m2cn1,n2 old file lines m1 to m2 CHANGE new file lines n1 to n2
    < text old file contents
    > text new file contents


    m1an1,n2 old file lines m1 APPEND after new file n1 to n2

    m1,m2dn1? old file lines m1 to m2 DELETE (? showing new file line just
    above?)[not used in this diff]

    9,12c9,11
    < def showScores(self):
    < for score,name in self.hiScores:
    < score=str(score).zfill(5)
    < print "%s - %s" % name,score
    ---
    > def showScores(self):
    > for i in range(len(self.hiScores)-1, -1, -1):
    > print "%s - %s" % self.hiScores

    16c15
    < score.zfill(5)
    ---
    > score = score.zfill(5)

    19c18
    < self.hiScores.pop()
    ---
    > self.hiScores.pop(0)


    I think the next one is a problem of wrap-around caused by having no
    spaces in the line... they need to be indented properly...
    26c25
    <
    hiScores=[('10000','Alpha'),('07500','Beta'),('05000','Gamma'),('02500','Delta'),('00000','Epsilon')]
    ---
    > hiScores=[('10000','Alpha'),('07500','Beta'),('05000','Gamma'),('02500','Delta'),('00000','Epsilon')]

    27a27,28
    > hiScores.reverse()
    >

    --
    > ============================================================== <
    > | Wulfraed Dennis Lee Bieber KD6MOG <
    > | Bestiaria Support Staff <
    > ============================================================== <
    > Home Page: <http://www.dm.net/~wulfraed/> <
    > Overflow Page: <http://wlfraed.home.netcom.com/> <
     
    Dennis Lee Bieber, Sep 25, 2005
    #4
  5. Jason

    Peter Guest

    Duncan Booth wrote:

    >Jason wrote:
    >
    >
    >
    >>My first problem (lack of understanding of course) is that if I run the
    >>above, I get an error saying:
    >>
    >> print "%s - %s" % name,score
    >>TypeError: not enough arguments for format string
    >>
    >>Now I understand what it's saying, but I don't understand why.
    >>
    >>
    >>

    >
    >The problem is precedence.
    >
    > print "%s - %s" % name,score
    >
    >is equivalent to:
    >
    > print ("%s - %s" % name),score
    >
    >not:
    >
    > print "%s - %s" % (name,score)
    >
    >The % operator binds more tightly than the comma, so you need to put
    >parentheses around the argument to % (as in the last line above).
    >
    >

    Well said. :)

    - Peter
     
    Peter, Sep 25, 2005
    #5
  6. Jason

    Tom Anderson Guest

    On Sun, 25 Sep 2005, Jason wrote:

    > A week ago I posted a simple little hi-score routine that I was using to
    > learn Python.
    >
    > I've only just managed to examine the code, and the responses that people
    > gave, and I'm now seriously struggling to understand why things aren't
    > working correctly.


    Others have dealt with the string printing problem, so i'll leave that.

    The problem with the sorting is that you're not consistent about how
    scores are represented - are they strings or integers? At present, you
    sometimes use one and sometimes the other, with the result that the sort
    basically pukes all over you. To fix this, pick one type (hint: integers),
    and use that consistently. I'll show you how to do that below (although
    it's not exactly hard).

    Oh, and i'm a picky git, so i'm going to point out some other flaws in the
    code!

    > At present my code is as follows...
    >
    > import random
    > import bisect
    >
    > class HiScores:
    > def __init__(self,hiScores):
    > self.hiScores=[entry for entry in hiScores]


    One bug and one wart here.

    The wart is the way you initialise self.hiScores - you use a list
    comprehension when you can just call the list builtin:

    self.hiScores = list(hiScores)

    The bug is that you don't sort the list. If you're certain that the
    initial set of high scores will always come sorted, that's okay, but i'd
    say it was good practice to sort them, just in case.

    In fact, i'd punt the addition to addScore:

    def __init__(self, hiScores):
    self.hiScores = []
    for score, name in hiScores:
    self.addScore(score, name)

    This is the 'Once And Only Once' principle in action; the knowledge about
    how to keep the list sorted is expressed once and only once, in addScore;
    if any other parts of the code need to add items, they call that. This
    means there's only one piece of code you have to check to make sure it's
    going to get this right.

    > def showScores(self):
    > for score,name in self.hiScores:
    > score=str(score).zfill(5)
    > print "%s - %s" % name,score


    As has been pointed out, you need to wrap parens round "name, score" to
    make it into a tuple.

    Apart from that, i'd skip the string interpolation and just write:

    for score, name in self.hiScores:
    print name, "-", str(score).zfill(5)

    If you insist on the string interpolation, i'd still elide the
    intermediate variable and write:

    for score, name in self.hiScores:
    print "%s - %05i" % (name, score)

    The %05i in the format string means 'an integer, zero-filled to five
    digits'. Good, eh?

    > def addScore(self,score,name):
    > score.zfill(5)
    > bisect.insort(self.hiScores,(score,name))
    > if len(self.hiScores)==6:
    > self.hiScores.pop()


    Two problems there. Well, two and a half.

    Firstly, the type confusion - are scores strings or integers? the zfill
    indicates that you're thinking in terms of strings here. You should be
    using integers, so you can just drop that line.

    And if you were working with strings, the zfill would still be wrong (this
    is the half problem!) - zfill doesn't affect the string it's called on
    (strings are immutable), it makes a new zero-filled string and returns it.
    You're not doing anything with the return value of that call, so the
    zero-filled string would just evaporate into thin air.

    Secondly, bisect.insort sorts the list so that the highest scores are at
    the tail end of the list; list.pop takes things off that same end, so
    you're popping the highest scores, not the lowest! You need to say pop(0)
    to specify that the item should be popped off the head (ie the low end) of
    the list.

    Also, i'd be tempted to program defensively and change the test guarding
    the pop to "while (len(self.hiScores > 6):".

    All in all, that makes my version:

    def addScore(self, score, name):
    bisect.insort(self.hiScores, (int(score), name))
    while(len(self.hiScores) > 6):
    self.hiScores.pop(0)

    > def lastScore(self):
    > return self.hiScores[-1][0]


    This will return the top score; you want self.hiScores[0][0].

    > def main():
    >
    > hiScores=[('10000','Alpha'),('07500','Beta'),('05000','Gamma'),('02500','Delta'),('00000','Epsilon')]


    Here you've got scores as strings, and this is the root of the problem.
    Change this to:

    hiScores=[(10000,'Alpha'),(7500,'Beta'),(5000,'Gamma'),(2500,'Delta'),(0,'Epsilon')]

    Note that i've taken the leading zeroes off - leading zeroes on integers
    in python are a magic signal that the number is octal (yes, base eight!),
    which is not what you want at all.

    > a=HiScores(hiScores)
    > print "Original Scores\n---------------"
    > a.showScores()
    >
    > while 1:


    "while True:" is preferred here.

    > newScore=str(random.randint(0,10000))


    Take out the str().

    > if newScore > a.lastScore():
    > print "Congratulations, you scored %s " % newScore


    Make that a %i (or a %05i).

    > name=raw_input("Please enter your name :")
    > a.addScore(newScore,name)
    > a.showScores()
    >
    > if __name__=="__main__":
    > main()


    Works like a charm!

    > The final part that's simply not working correctly is that the entire program
    > isn't sorting the data.
    >
    > If I run the program and get a score of, say, 6789, then when I add my name,
    > nothing is entered. I have changed the clause that deletes (pops) the last
    > array if the array count is 6 and seen what figures are being entered into
    > the array.
    >
    > Sure enough they are going in the array, and they are being sorted, but they
    > are only being sorted AFTER the 00000 of the initial array creation.
    >
    > I'm pretty sure it's to do with comparing a string against an integer
    > but can't for the life of me see where to force the comparrison to check
    > against two integers.


    Simple - you just make sure the scores are all integers in the first
    place!

    tom

    --
    double mashed, future mashed, millennium mashed; man it was mashed
     
    Tom Anderson, Sep 25, 2005
    #6
  7. Jason

    Jason Guest

    Tom, best explanation yet! Entertaining as well as educational.

    The "%05i" trick is very neat, must remember that one!

    Everything working a charm apart from the viewScores is still returning
    the results from the lowest score (at the top) to the highest score.

    What I'd like to know is do you think it would be better to sort the
    list in memory, or print it out sorted? If the latter, then naturally
    I'd need to change the showScores section to show the list in a reverse
    order. But, would sorting the list in memory be more effective?
     
    Jason, Sep 25, 2005
    #7
  8. "Jason" <> wrote:

    > What I'd like to know is do you think it would be better to sort the
    > list in memory, or print it out sorted? If the latter, then naturally
    > I'd need to change the showScores section to show the list in a reverse
    > order. But, would sorting the list in memory be more effective?


    The list *is* sorted; the thing is that it is in ascending order (from lowest to highest) but you
    would rather have it in descending. There are (at least) two alternatives:

    1. Keep the list as it is now in ascending order and print it in reverse. In python 2.4, this is as
    elegant and efficient as it can, using the reversed() builtin function. Just replace in showScores
    "for score,name in self.hiScores" with "for score,name in reversed(self.hiScores)". reversed()
    returns an iterator over the sequence, not a new list, so the memory overhead is minimal.

    2. Instead of storing (score,name) pairs, store (-score,name). When a list of the latter is in
    ascending order, the former is in descending. In this case of course, you have to make sure that
    showScores() and lastScore() return the actual (positive) score, not the stored (negative) one.

    I would go for the first alternative but YMMV.

    George
     
    George Sakkis, Sep 26, 2005
    #8
  9. Jason

    Jason Guest

    George Sakkis wrote:
    > "Jason" <> wrote:
    >
    >> What I'd like to know is do you think it would be better to sort the
    >> list in memory, or print it out sorted? If the latter, then naturally
    >> I'd need to change the showScores section to show the list in a reverse
    >> order. But, would sorting the list in memory be more effective?

    >
    > The list *is* sorted; the thing is that it is in ascending order (from lowest to highest) but you
    > would rather have it in descending. There are (at least) two alternatives:
    >
    > 1. Keep the list as it is now in ascending order and print it in reverse. In python 2.4, this is as
    > elegant and efficient as it can, using the reversed() builtin function. Just replace in showScores
    > "for score,name in self.hiScores" with "for score,name in reversed(self.hiScores)". reversed()
    > returns an iterator over the sequence, not a new list, so the memory overhead is minimal.
    >
    > 2. Instead of storing (score,name) pairs, store (-score,name). When a list of the latter is in
    > ascending order, the former is in descending. In this case of course, you have to make sure that
    > showScores() and lastScore() return the actual (positive) score, not the stored (negative) one.
    >
    > I would go for the first alternative but YMMV.
    >
    > George
    >
    >

    Thanks George, I've learned a lot tonight.
     
    Jason, Sep 26, 2005
    #9
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Martin
    Replies:
    6
    Views:
    674
    Martin
    Dec 29, 2003
  2. One Handed Man \( OHM#\)

    Struggling With Concept

    One Handed Man \( OHM#\), Jun 12, 2004, in forum: ASP .Net
    Replies:
    1
    Views:
    373
    Jared
    Jun 12, 2004
  3. Guest
    Replies:
    5
    Views:
    399
    Guest
    Dec 26, 2004
  4. John
    Replies:
    1
    Views:
    405
    =?Utf-8?B?RWx0b24gVw==?=
    Oct 20, 2005
  5. Jason

    Re: Struggling with basics

    Jason, Sep 25, 2005, in forum: Python
    Replies:
    3
    Views:
    298
    Dennis Lee Bieber
    Sep 26, 2005
Loading...

Share This Page