Second python program: classes, sorting

W

WP

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)
 
W

WP

WP wrote:
Solved the problem, see below...
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)

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

B

WP said:
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)

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.
 
P

Peter Otten

WP said:
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
 
E

Eric Brunel

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
 
B

Bruno Desthuilliers

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.
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,581
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top