Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Second python program: classes, sorting

2 views
Skip to first unread message

WP

unread,
Aug 10, 2008, 2:26:51 PM8/10/08
to
Hello, here are some new things I've problems with. I've made a program
that opens and reads a text file. Each line in the file contains a name
and a score. I'm assuming the file has the correct format. Each
name-score pair is used to instantiate a class Score I've written. This
works fine, but here's my problem: After reading the file I have list of
Score objects. Now I want to sort them in descending order. But no
matter how I write my __cmp__ the order remains unchanged. I've
determined that __cmp__ is called but it's only called twice for three
list elements, isn't that odd?

Complete program:
class Score:
def __init__(self, name_, score_):
self.name = name_
self.score = score_

def __str__(self):
return "Name = %s, score = %d" % (self.name, self.score)

def __cmp__(self, other):
print "in __cmp__"
return self.score >= other.score

name = ""
score = 0
# End class Score

filename = "../foo.txt";

try:
infile = open(filename, "r")
except IOError, (errno, strerror):
print "IOError caught when attempting to open file %s. errno = %d,
strerror = %s" % (filename, errno, strerror)
exit(1)

lines = infile.readlines()

infile.close()

lines = [l.strip() for l in lines] # Strip away trailing newlines.

scores = []

for a_line in lines:
splitstuff = a_line.split()

scores.append(Score(splitstuff[0], int(splitstuff[1])))

scores.sort()

for a_score in scores:
print a_score

Test file contents:
Michael 11
Hanna 1337
Lena 99

Output:
in __cmp__
in __cmp__
Name = Michael, score = 11
Name = Hanna, score = 1337
Name = Lena, score = 99

What am I doing wrong here?

- Eric (WP)

WP

unread,
Aug 10, 2008, 3:11:03 PM8/10/08
to
WP wrote:
Solved the problem, see below...

I solved it, I rewrote __cmp__ to:
def __cmp__(self, other):
if self.score == other.score:
return cmp(self.name, other.name)
else:
return cmp(other.score, self.score)

This sorts so that score goes from highest to lowest. If two person have
the same score, I sort on names in alphabetical order. The following
text file:


Michael 11
Hanna 1337
Lena 99

Anna 99
yields (just as I want):


Name = Hanna, score = 1337

Name = Anna, score = 99


Name = Lena, score = 99

Name = Michael, score = 11

- Eric (WP)

B

unread,
Aug 10, 2008, 3:17:09 PM8/10/08
to

From http://docs.python.org/ref/customization.html:
__cmp__( self, other)
Called by comparison operations if rich comparison (see above) is
not defined. Should return a negative integer if self < other, zero if
self == other, a positive integer if self > other.


You're not handling all the comparison cases, just assumping __cmp__ is
being called for >=, when it's not.

You can fix your __cmp__ function, but it's probably easier to
understand if you overload specific comparison functions: __lt__,
__gt__, etc.

Wojtek Walczak

unread,
Aug 10, 2008, 3:32:48 PM8/10/08
to
Dnia Sun, 10 Aug 2008 20:26:51 +0200, WP napisa³(a):

Hi,

> Hello, here are some new things I've problems with. I've made a program

...

> def __cmp__(self, other):
> print "in __cmp__"
> return self.score >= other.score

Check this out: http://docs.python.org/ref/customization.html

Definition of this method should rather look like this:
==========================================


def __cmp__(self, other):
print "in __cmp__"

if self.score == other.score:
return 0
elif self.score > other.score:
return 1
return -1
==========================================

Should work fine now.

--
Regards,
Wojtek Walczak,
http://www.stud.umk.pl/~wojtekwa/

Peter Otten

unread,
Aug 10, 2008, 3:50:48 PM8/10/08
to
WP wrote:

> I solved it, I rewrote __cmp__ to:
> def __cmp__(self, other):
>     if self.score == other.score:
>        return cmp(self.name, other.name)
>     else:
>        return cmp(other.score, self.score)

You can simplify that to

def __cmp__(self, other):
return cmp(other.score, self.score) or cmp(self.name, other.name)

Alternatively you can define a key function

def descending_score(s):
return -s.score, s.name

and then use it:

scores.sort(key=descending_score)

Because list.sort() is stable sorting twice will also work:

scores.sort(key=lambda s: s.name)
scores.sort(key=lambda s: s.score, reverse=True)

Peter

Eric Brunel

unread,
Aug 11, 2008, 4:51:02 AM8/11/08
to
Others have replied to your original question. As an aside, just a few
stylistic notes:

> class Score:
> def __init__(self, name_, score_):
> self.name = name_
> self.score = score_

These trailing underscores look like a habit from another language. They
are unneeded in Python; you can write:
class Score:
def __init__(self, name, score):
self.name = name
self.score = score
That's an advantage of the explicit self: no ambiguity between local
variables and attributes.

> try:
> infile = open(filename, "r")
> except IOError, (errno, strerror):
> print "IOError caught when attempting to open file %s. errno = %d,
> strerror = %s" % (filename, errno, strerror)
> exit(1)

This try/except block is unneeded too: what you do with the exception is
more or less the same as the regular behaviour (print an error message and
exit), except your error message is far less informative than the default
one, and printed to stdout instead of stderr. I would remove the entire

try/except block and just write:
infile = open(filename, "r")

HTH
--
python -c "print ''.join([chr(154 - ord(c)) for c in
'U(17zX(%,5.zmz5(17l8(%,5.Z*(93-965$l7+-'])"

Bruno Desthuilliers

unread,
Aug 11, 2008, 10:16:34 AM8/11/08
to
WP a écrit :

> Hello, here are some new things I've problems with. I've made a program
> that opens and reads a text file. Each line in the file contains a name
> and a score. I'm assuming the file has the correct format. Each
> name-score pair is used to instantiate a class Score I've written. This
> works fine, but here's my problem: After reading the file I have list of
> Score objects. Now I want to sort them in descending order. But no
> matter how I write my __cmp__ the order remains unchanged.

You fixed this, so I'll just comment on the rest of the code...

(snip)


> Complete program:
> class Score:

Unless you have a compelling reason (mostly: compat with ages old Python
versions), better to use new-style classes:

class Score(object):

> def __init__(self, name_, score_):
> self.name = name_
> self.score = score_

cf Eric Brunel's comments here about the trailing underscores.

> def __str__(self):
> return "Name = %s, score = %d" % (self.name, self.score)
>

(snip)

> name = ""
> score = 0

Do you have any reason to have these as class attributes too ?


> # End class Score
>
> filename = "../foo.txt";
>
> try:
> infile = open(filename, "r")
> except IOError, (errno, strerror):
> print "IOError caught when attempting to open file %s. errno = %d,
> strerror = %s" % (filename, errno, strerror)
> exit(1)

cf Eric Brunel's comment wrt/ why this try/except clause is worse than
useless here.

> lines = infile.readlines()

File objects are their own iterators. You don't need to read the whole
file in memory.

> infile.close()

> lines = [l.strip() for l in lines] # Strip away trailing newlines.
>
> scores = []
>
> for a_line in lines:

> splitstuff = a_line.split()
>
> scores.append(Score(splitstuff[0], int(splitstuff[1])))


scores = []
mk_score = lambda name, score : Score(name, int(score))

infile = open(filename)
for line in infile:
line = line.strip()
if not line:
continue
scores.append(mk_score(*line.split()))

infile.close()


As a side note : I understand that this is a learning exercice, but in
real life, if that's all there is to your Score class, it's not really
useful - I'd personnally use a much simpler representation, like a list
of score / name pairs (which is easy to sort, reverse, update, turn into
a name=>scores or scores=>names dict, etc).

My 2 cents.

0 new messages