简体   繁体   中英

How to display top 10 highscores from a comma separated value (.txt) file in Python (Pygame)

I'm creating a highscores table for my game. It appends values to a .txt file in the following format:

5.234,0,0,5234
6.345,1,1,8345
1.649,0,1,2649
2.25,0,1,3250

...etc

I want to read the top 10 scores (score being the 4th value on each line) and output them onto the screen. I've tried using the information here , but I can't get my head around it. What's the best way to output them?

I know you can split the values using

for line in f:
    Array = line.split(',')

pretty sure I could manage the output if I had them in a sorted 2D array or equivalent

This will be easier to do with the csv module. For example:

with open('highscores.txt', 'rb') as f:
    reader = csv.reader(f)
    scores = list(reader)

Now, scores will be a list of 10 lists, each of which has 4 values.

You could do the same thing with split if you wanted, but you have to make sure to get all the details right, like stripping the newline off the end of each line (and the details can get a lot more complicated in non-trivial CSV files):

with open('highscores.txt', 'rb') as f:
    scores = [line.strip().split(',') for line in f]

Now you can transform that list a step at a time using a single comprehension or function call for each step. This kind of "declarative programming", where you just say what you want to do to the values instead of writing out a loop and interleaving all the steps, can make your life a lot easier.

If you just want the last column, a comprehension can do that:

with open('highscores.txt', 'rb') as f:
    reader = csv.reader(f)
    scores = [row[-1] for row in reader]

If you want them converted to integers:

with open('highscores.txt', 'rb') as f:
    reader = csv.reader(f)
    scores = (row[-1] for row in reader)
    intscores = [int(score) for score in scores]

… although in this case it's simple enough to merge the two steps together:

with open('highscores.txt', 'rb') as f:
    reader = csv.reader(f)
    scores = [int(row[-1]) for row in reader]

If you want them sorted in reverse order (from highest to lowest):

with open('highscores.txt', 'rb') as f:
    reader = csv.reader(f)
    scores = (int(row[-1]) for row in reader)
    topscores = sorted(scores, reverse=True)

If you want just the top 10:

with open('highscores.txt', 'rb') as f:
    reader = csv.reader(f)
    scores = (int(row[-1]) for row in reader)
    topscores = sorted(scores, reverse=True)
    top10 = topscores[:10]

You can make that last step more efficient, but a bit more complicated, by using the heapq module:

with open('highscores.txt', 'rb') as f:
    reader = csv.reader(f)
    scores = (int(row[-1]) for row in reader)
    top10 = heapq.nlargest(10, scores)

From a comment on another answer, you actually want to get all four values in each row. So, instead of just reading the last column of each row, you need to read the whole row. Also, you presumably want to convert the first column to floats, the rest to ints, which is a bit more complicated than just mapping a function over all the columns in each row. Meanwhile, nlargest or sorted will compare by the first column, but you want it to compare by the last, which means you need to provide a key function . While it's not at all hard to write one yourself, itemgetter already does exactly what you want.

with open('highscores.txt', 'rb') as f:
    reader = csv.reader(f)
    def convert_row(row):
        return [float(row[0])] + [int(value) for value in row[1:]]
    scores = (convert_row(row) for row in reader)
    top10 = heapq.nlargest(10, scores, key=operator.itemgetter(-1))
for record in top10:
    print('time: {} | moves: {} | penalties: {} | score: {}'.format(*record))

The only problem with this code is that knowledge of the column order is sort of scattered implicitly throughout your code. If you had a list of dicts, instead of a list of lists, this would allow you to access values by name. And DictReader lets you do that:

with open('highscores.txt', 'rb') as f:
    reader = csv.DictReader(f, fieldnames=('time', 'moves', 'penalties', 'score'))
    def convert_row(row):
        return {k: float(v) if k == 'time' else int(v) for k, v in row.items()}
    scores = [convert_row(row) for row in reader]
    top10 = heapq.nlargest(10, scores, key=operator.itemgetter('score'))
for record in top10:
    print('time: {time} | moves: {moves} | penalties: {penalties} | score: {score}'
          .format(**record))

No more 0, 1:, -1, or implicit ordering in the format function; now everything goes by the field name— time , anything but time , score , etc. On the other hand, the code is a lot more verbose, and some people find dict comprehensions harder to read than list comprehensions/generator expressions, so… it's a matter of taste whether this is an improvement.

heapq.nlargest is designed exactly for this task. Having your sample following code outputs top 2 scores. Modify for your needs

import heapq
lst = ['5.234,0,0,5234',
       '6.345,1,1,8345',
       '1.649,0,1,2649',
       '2.25,0,1,3250']
scores = map(int, (l.split(',')[3] for l in lst))
print heapq.nlargest(2, scores)

Although not strictly required, I'd use the csv module:

import csv

with open('scores.txt', 'rb') as csvfile:
    values = [[float(row[0])] + map(int, row[1:]) for row in csv.reader(csvfile)]

top_ten = sorted(values, reverse=True, key=lambda v: v[3])[:10]

for row in top_ten:
    print("time: {0:.3f} | moves: {1:,d} | "
          "penalties: {2:,d} | score: {3:,d}".format(*row))

Output:

time: 6.345 | moves: 1 | penalties: 1 | score: 8,345
time: 5.234 | moves: 0 | penalties: 0 | score: 5,234
time: 2.250 | moves: 0 | penalties: 1 | score: 3,250
time: 1.649 | moves: 0 | penalties: 1 | score: 2,649

Of course there actually aren't enough total scores in your sample data to be able to display the top 10 of them.

Using the csv library for Python, this is pretty simple.

import csv
with open('scores.txt', 'r') as csvfile:
    scorereader = csv.reader(csvfile, delimiter=',', quotechar='"')
    for row in scorereader:
        print ', '.join(row)
with open('high_scores.txt') as f:
    sorted([line.strip().split(',') for line in f], key=lambda x: int(x[3]), \
             reverse=True)[:10]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM