[Tutor] a ScoreList class (was: Multiple identical keys in a dictionary)

wheelege wheelege@tsn.cc
Mon, 28 May 2001 17:27:52 +1000


> <...>
> > <snip>
> Whether or not a class makes sense for a score list depends on what
> you want to do with the scores, and should have nothing to do with how
> many other classes you may have already devised for the program.
>

  Heh - I should explain in more detail. I already have a detailed class to
handle all operations with the scores - so basically, yes I have a 'scores'
class.  I was saying that I didn't want to make another class on top of that
just to store the values of the scores, since in my mind a 'HighScores'
(actual name of class in this example) class should contain the scores as an
attribute.
  However...*reads on*


> If all you are doing is keeping a list of scores which you want to
> display every now and then, a simple list of tuples is probably best.
> A dictionary looks attractive in general, but depends on having a
> unique key for each entry to really shine... if you don't have a
> unique key available you may end up losing any advantage dicts give
> you through needing to handle collisions yourself, and manually
> looking through the values of the dict to find whatever data you need.
> i.e., It may be easier to generalize extracting data from a flat list
> of tuples, where all datum are equal, than the case where one piece of
> data is artifically elevated in importance and everything else must be
> done in terms of a relationship to it.
>

  What I ended up deciding on was a dictionary where the keys were scores
and the values are a list of lists (which have name, level, difficulty in
them).  The time spent displaying or grabbing values is actually quite fast,
since I'm using a dictionary, and because I can have more than one entry for
each score, it is easy to handle multiple similar scores.

> An example is aways best...
> (works with 1.5.2, lots of tweaks can be done to improve it,
> especially if you are using a 2.x interpreter)
>
> ---8<--- scores.py ---
> class Score:
>     """Defines the basic properties of a 'score'."""
>     def __init__(self, player=None, value=None, level=None):
>         self.player = player
>         self.value = value
>         self.level = level
>         # there may be other things to keep track of, like...
>         #... is "level" the game play level or the players skill level
>         #... when they finished.  Putting stuff into a class lets you
>         #... add properties later on without needing to rewrite old
>         #... code (new code uses the new stuff, old code doesn't even
>         #... know it exists).
>

  :)  The way I wrote my class it is in fact very easy to add further
attributes...all you do is pass an additional argument to it's 'update'
method and everything will work fine...although I do agree having an object
to hold each value would be much 'cleaner' and although no more
possibilities (what can a class have as an attribute that a list can't have
as an item?) it is nicer to think about.

>     def __cmp__(self, other):
>         """Allows for comparison with other Score-s and numerics."""
>         if isinstance(other, Score):
>             cmpval = other.value
>         else:
>             cmpval = other
>         if self.value < cmpval:
>             return -1
>         elif self.value == cmpval:
>             return 0
>         else:
>             return 1
>

  Now that's something I didn't think about...although I didn't have to
because I ended up using a dictionary :)
  Very cool.

>     # these are setup to have an ok display in this specific
>     #... situation, you would probably want something a little
>     #... fancier/better for an actual application.
>     def __str__(self):
>         return "%s %s (%s)" % \
>                (self.player, self.value, self.level)
>
>     def __repr__(self):
>         return "Score(%s, %s, %s)" % \
>                (self.player, self.value, self.level)
>

  It's a tk game so this is actually considerably more complicated :)

>
> class ScoreList:
>     """A high-level interface to a list of scores.
>
>     You get:
>     - "cutoff", so only the most deserving make it into the list
>     - "+" operator, for an implementation independent interface
>     - "top" method to easily extract the top scores
>     - "name" and "level" methods, for extracting specific info
>
>     """
>     def __init__(self, scores=None, cutoff=None):
>         if scores == None:
>             self.values = []
>         else:
>             self.values = scores
>         self.cutoff = cutoff
>
>     def __add__(self, score):
>         # I think this bit is most significant; it defines the
>         #... semantics of adding to the list and is where you would
>         #... do any 'magic' needed to ensure the list has the
>         #... properties you want.  So far the only 'magic' is the
>         #... disappearing score if it is below the cutoff.
>         if self.cutoff == None or score > self.cutoff:
>             self.values.append(score)
>         return ScoreList(self.values, self.cutoff)

  Right now this is implemented by the size of the actual high scores file -
top limit 1k, lowest scores being cut off when the limit is reached.  The
user can ask for how many they wish to see inside the 'Hall of Fame' dialog
(generated by the HighScores.display() method, coincidentally) so a cutoff
as such isn't such an important thing.
  Unless storing a (potentially) 1k long dictionary is a problem for a
computer...

>
>     def __str__(self):
>         import string
>         self.values.sort()
>         return string.join(map(str, self.values), '\n')
>
>
>     def top(self, number):
>         self.values.sort()
>         return ScoreList(self.values[-number:], self.cutoff)
>
>     # These two methods are identical in form, and that form should
>     #... be generalized so one can use any attribute as a key and get
>     #... all the associated information back, IMO.

  Exactly what my HighScores.parse() method does :)

>     def name(self, name):
>         result = []
>         for item in self.values:
>             if item.player == name:
>                 result.append((item.value, item.level))
>         return result
>
>     def level(self, level):
>         result = []
>         for item in self.values:
>             if item.level == level:
>                 result.append((item.player, item.value))
>         return result
>
>
> if __name__ == '__main__':
>     # set things up
>     from random import randint
>     ri = randint
>
>     # you need a score of 2500 to get into the hiscore list
>     hiscores = ScoreList(cutoff=2500)
>
>     # generate random test data...
>     for i in range(3):
>         for j in range(1,7):
>             ascore = Score('player'+str(j), ri(0,5000), ri(1,10))
>             hiscores = hiscores + ascore
>     # ...with some specific properties...
>     for i in range(3):
>         for j in range(1,4):
>             part = ri(0,5000), ri(1,10)
>             hiscores = hiscores \
>                        + Score('player'+str(j), part[0], part[1]) \
>                        + Score('player'+str(j+3), part[0], part[1])
>     # ...although you probably want something a little more thorough.
>
>     # after going through all the above, use had better be simple...
>     print "\nAll scores:\n", hiscores
>     print "\nTop 3:\n", hiscores.top(3)
>     print "\nTop 10:\n", hiscores.top(10)
>     print "\nBy player..."
>     for i in range(1,6):
>         name = 'player' + str(i)
>         print name + ": ", hiscores.name(name)
>     print "\nBy level..."
>     for i in range(1,11):
>         print "level" + str(i) + ": ", hiscores.level(i)
>
> --->8---
>
> Overkill???, sure... unless, for example:  you have written the next
> wildly popular networked game and have a thousand dedicated users
> asking you for a way to analyse hiscore lists, or pestering you
> because they don't like the hardcoded default you think looks great;
> perhaps you just don't want to re-write or integrate(-yet-again) a
> score list subsystem everytime you code up a game, having all the core
> bits in a class or two make for easy code re-use; or maybe you want to
> allow for others to expand on what you have done, so they can have
> their own fancy hiscore display, without having to re-write your code
> and risk introducing incompatibilities.
>

  ...agreed, and although I had considered that (not the 'wildly-popular'
part :) I have a deadline of this friday and alot of things have to be fixed
in it before that time, perhaps if I continue work on it after the deadline
I'll implement the scores as a seperate class.

> Generally, I think you will find that as programs get larger, and the
> user base increases, it becomes more and more attractive to use
> classes.

  Preach the good word :)

>  Simply because it is easier to modify the behaviour of a
> class than to track down and change a convention you scattered
> throughout your code, or keep track of a bunch of stand-alone
> functions and data structure declarations when you need to change or
> replicate some behaviour.
>
> Food for thought, I hope.
>

  You bet.  For what it's worth I do consider your class idea an
intrinsically 'better' approach to the problem, as it does offer advantages
over the existing system but...hmmm.
  Perhaps if something mind-boggling evil comes of the ye olde 'dictionary'
approach and a rewrite is neccessary I will go for the class implementation.
In which case I will be happy for this discussion :)

  Thanks,
  Glen.

>
> - Bruce
>