简体   繁体   English

Python:创建将高分写入文件的字典

[英]Python: creating a dictionary that writes high scores to a file

First: you don't have to code this for me, unless you're a super awesome nice guy. 首先:除非您是个超级棒的好人,否则不必为我编写代码。 But since you're all great at programming and understand it so much better than me and all, it might just be easier (since it's probably not too many lines of code) than writing paragraph after paragraph trying to make me understand it. 但是,由于你们都擅长编程,并且比我和所有人都了解它要好得多,因此,与试图让我理解它的逐段编写相比,它可能更容易(因为它可能没有太多的代码行)。

So - I need to make a list of high scores that updates itself upon new entries. 所以-我需要列出一个高分列表,以便在新条目输入时自动更新。 So here it goes: 所以就这样:

First step - done 第一步 -完成

I have player-entered input, which has been taken as a data for a few calculations: 我有玩家输入的输入,该输入已作为一些计算的数据:

import time
import datetime

print "Current time:", time1.strftime("%d.%m.%Y, %H:%M")
time1 = datetime.datetime.now()
a = raw_input("Enter weight: ")    
b = raw_input("Enter height: ")
c = a/b

Second step - making high score list 第二步 -制作高分列表

Here, I would need some sort of a dictionary or a thing that would read the previous entries and check if the score ( c ) is (at least) better than the score of the last one in "high scores", and if it is, it would prompt you to enter your name. 在这里,我需要某种字典或能读取先前条目的东西,并检查分数( c )是否(至少)比“高分”中的最后一个分数好,并且是否,它会提示您输入名称。

After you entered your name, it would post your name, your a , b , c , and time in a high score list. 输入姓名后,它将在高分列表中张贴您的姓名, abc和时间。

This is what I came up with, and it definitely doesn't work: 这是我想出的,它绝对不起作用:

list = [("CPU", 200, 100, 2, time1)]
player = "CPU"
a = 200
b = 100
c = 2
time1 = "20.12.2012, 21:38"
list.append((player, a, b, c, time1))
list.sort()

import pickle
scores = open("scores", "w")
pickle.dump(list[-5:], scores)
scores.close()

scores = open("scores", "r")
oldscores = pickle.load(scores)
scores.close()
print oldscores()

I know I did something terribly stupid, but anyways, thanks for reading this and I hope you can help me out with this one. 知道我做了一件非常愚蠢的事情,但是无论如何,感谢您阅读本文,希望您能帮助我解决这个问题。 :-) :-)

First, don't use list as a variable name. 首先,不要使用list作为变量名。 It shadows the built-in list object. 它遮盖了内置list对象。 Second, avoid using just plain date strings, since it is much easier to work with datetime objects, which support proper comparisons and easy conversions. 其次,避免只使用简单的日期字符串,因为使用datetime对象要容易得多,它支持适当的比较和轻松的转换。

Here is a full example of your code, with individual functions to help divide up the steps. 这是您的代码的完整示例,其中的各个功能可帮助您划分步骤。 I am trying not to use any more advanced modules or functionality, since you are obviously just learning: 我正在尝试不使用任何其他高级模块或功能,因为您显然只是在学习:

import os
import datetime
import cPickle

# just a constants we can use to define our score file location
SCORES_FILE = "scores.pickle"

def get_user_data():
    time1 = datetime.datetime.now()
    print "Current time:", time1.strftime("%d.%m.%Y, %H:%M")

    a = None
    while True:
        a = raw_input("Enter weight: ")    
        try:
            a = float(a)
        except:
            continue
        else:
            break

    b = None
    while True:
        b = raw_input("Enter height: ")    
        try:
            b = float(b)
        except:
            continue
        else:
            break

    c = a/b

    return ['', a, b, c, time1]

def read_high_scores():
    # initialize an empty score file if it does
    # not exist already, and return an empty list
    if not os.path.isfile(SCORES_FILE):
        write_high_scores([])
        return []

    with open(SCORES_FILE, 'r') as f:
        scores = cPickle.load(f)
    return scores

def write_high_scores(scores):
    with open(SCORES_FILE, 'w') as f:
        cPickle.dump(scores, f)

def update_scores(newScore, highScores):
    # reuse an anonymous function for looking
    # up the `c` (4th item) score from the object
    key = lambda item: item[3]

    # make a local copy of the scores
    highScores = highScores[:]

    lowest = None
    if highScores:
        lowest = min(highScores, key=key)

    # only add the new score if the high scores
    # are empty, or it beats the lowest one
    if lowest is None or (newScore[3] > lowest[3]):
        newScore[0] = raw_input("Enter name: ")
        highScores.append(newScore)

    # take only the highest 5 scores and return them
    highScores.sort(key=key, reverse=True)
    return highScores[:5]

def print_high_scores(scores):
    # loop over scores using enumerate to also
    # get an int counter for printing
    for i, score in enumerate(scores):
        name, a, b, c, time1 = score
        # #1    50.0    jdi    (20.12.2012, 15:02)
        print "#%d\t%s\t%s\t(%s)" % \
            (i+1, c, name, time1.strftime("%d.%m.%Y, %H:%M"))


def main():
    score = get_user_data()
    highScores = read_high_scores()

    highScores = update_scores(score, highScores)

    write_high_scores(highScores)
    print_high_scores(highScores)

if __name__ == "__main__":
    main()

What it does now is only add new scores if there were no high scores or it beats the lowest. 现在所做的只是在没有高分或低于最低分时才添加新分数。 You could modify it to always add a new score if there are less than 5 previous scores, instead of requiring it to beat the lowest one. 您可以对其进行修改,以便在以前的分数少于5个时,总是添加一个新分数,而不是要求其得分最低。 And then just perform the lowest check after the size of highscores >= 5 然后在高分的大小> = 5之后执行最低的检查

The first thing I noticed is that you did not tell list.sort() that the sorting should be based on the last element of each entry. 我注意到的第一件事是,您没有告诉list.sort()排序应该基于每个条目的最后一个元素。 By default, list.sort() will use Python's default sorting order, which will sort entries based on the first element of each entry (ie the name), then mode on to the second element, the third element and so on. 默认情况下, list.sort()将使用Python的默认排序顺序,该顺序将基于每个条目的第一个元素(即名称)对条目进行排序,然后切换到第二个元素,第三个元素,依此类推。 So, you have to tell list.sort() which item to use for sorting: 因此,您必须告诉list.sort()用于排序的项目:

from operator import itemgetter
[...]
list.sort(key=itemgetter(3))

This will sort entries based on the item with index 3 in each tuple, ie the fourth item. 这将基于每个元组中索引为3的项(即第四项)对条目进行排序。

Also, print oldscores() will definitely not work since oldscores is not a function, hence you cannot call it with the () operator. 另外,由于oldscores不是函数,因此print oldscores()绝对不起作用,因此无法使用()运算符来调用它。 print oldscores is probably better. print oldscores可能更好。

You definitely don't want a dictionary here. 您绝对不想在这里有字典。 The whole point of a dictionary is to be able to map keys to values, without any sorting. 字典的重点是能够将键映射到值,而无需任何排序。 What you want is a sorted list. 您想要的是一个排序列表。 And you've already got that. 而且您已经知道了。

Well, as Tamás points out, you've actually got a list sorted by the player name, not the score. 好吧,正如塔玛斯(Tamás)指出的那样,您实际上得到了一个按玩家姓名而不是得分排序的列表。 On top of that, you want to sort in downward order, not upward. 最重要的是,您想按降序排序,而不是按升序排序。 You could use the decorate-sort-undecorate pattern, or a key function, or whatever, but you need to do something . 您可以使用decorate-sort-undecorate模式或键函数或其他任何方法,但是您需要做一些事情 Also, you've put it in a variable named list , which is a very bad idea, because that's already the name of the list type. 另外,您将其放在名为list的变量list ,这是一个非常糟糕的主意,因为这已经是list类型的名称。

Anyway, you can find out whether to add something into a sorted list , and where to insert it if so, using the bisect module in the standard library. 无论如何,您可以使用标准库中的bisect模块来确定是否将某些内容添加到排序list ,以及在哪里将其插入。 But it's probably simpler to just use something like SortedCollection or blist . 但是,仅使用SortedCollectionblist类的方法可能更简单。

Here's an example: 这是一个例子:

highscores = SortedCollection(scores, key=lambda x: -x[3])

Now, when you finish the game: 现在,当您完成游戏时:

highscores.insert_right((player, a, b, newscore, time1))
del highscores[-1]

That's it. 而已。 If you were actually not in the top 10, you'll be added at #11, then removed. 如果您实际上不在前10名中,则将您添加到#11,然后将其删除。 If you were in the top 10, you'll be added, and the old #10 will now be #11 and be removed. 如果您在前10名中,则会被添加,旧的#10现在将成为#11并被删除。

If you don't want to prepopulate the list with 10 fake scores the way old arcade games used to, just change it to this: 如果您不想像以前的街机游戏那样以10个假分数预填充列表,只需将其更改为:

highscores.insert_right((player, a, b, newscore, time1))
del highscores[10:]

Now, if there were already 10 scores, when you get added, #11 will get deleted, but if there were only 3, nothing gets deleted, and now there are 4. 现在,如果已经有10个乐谱,那么当您添加乐谱时,#11将被删除,但是如果只有3个,则没有任何内容被删除,现在有4个。

Meanwhile, I'm not sure why you're writing the new scores out to a pickle file, and then reading the same thing back in. You probably want to do the reading before adding the highscore to the list, and then do the writing after adding it. 同时,我不确定为什么要将新乐谱写到pickle文件中,然后再读回相同的内容。您可能想先进行阅读,然后再将高分添加到列表中,然后再进行写作添加之后。

You also asked how to "beautify the list". 您还询问了如何“美化列表”。 Well, there are three sides to that. 嗯,这有三个方面。

First of all, in the code, (player, a, b, c, time1) isn't very meaningful. 首先,在代码中(player, a, b, c, time1)不是很有意义。 Giving the variables better names would help, of course, but ultimately you still come down to the fact that when accessing list, you have to do entry[3] to get the score or entry[4] to get the time. 给变量取更好的名称当然会有所帮助,但是最终您仍然会得出这样一个事实,即访问列表时,必须执行entry[3]来获取分数,或者必须进行entry[4]来获取时间。

There are at least three ways to solve this: 至少有三种方法可以解决此问题:

  • Store a list (or SortedCollection ) of dict s instead of tuple s. 存储dict (而不是tuple )的list (或SortedCollection )。 The code gets a bit more verbose, but a lot more readable. 代码变得更加冗长,但可读性更高。 You write {'player': player, 'height': a, 'weight': b, 'score': c, 'time': time1} , and then when accessing the list, you do entry['score'] instead of entry[3] . 您输入{'player': player, 'height': a, 'weight': b, 'score': c, 'time': time1} ,然后在访问列表时, entry['score'] entry[3]
  • Use a collection of namedtuple s. 使用namedtuple的集合。 Now you can actually just insert ScoreEntry(player, a, b, c, time1) , or you can insert ScoreEntry(player=player, height=a, weight=b, score=c, time=time1) , whichever is more readable in a given case, and they both work the same way. 现在,您实际上可以只插入ScoreEntry(player, a, b, c, time1) ,也可以插入ScoreEntry(player=player, height=a, weight=b, score=c, time=time1) ,以较易读的形式为准在给定的情况下,它们都以相同的方式工作。 And you can access entry.score or as entry[3] , again using whichever is more readable. 您也可以使用更易读的方式访问entry.score或作为entry[3]
  • Write an explicit class for score entries. 为分数条目编写一个明确的类。 This is pretty similar to the previous one, but there's more code to write, and you can't do indexed access anymore, but on the plus side you don't have to understand namedtuple . 这与上一个非常相似,但是有更多的代码需要编写,并且您不再可以进行索引访问,但是从正面namedtuple您不必了解namedtuple

Second, if you just print the entries, they look like a mess. 其次,如果仅print条目,它们看起来就像一团糟。 The way to deal with that is string formatting. 处理该问题的方法是字符串格式化。 Instead of print scores , you do something like this: 代替print scores ,您可以执行以下操作:

print '\\n'.join("{}: height {}, weight {}, score {} at {}".format(entry) for entry in highscores) 打印'\\ n'.join(“ {}:身高{},体重{},在{}得分{}”。格式(条目)以高分形式输入)

If you're using a class or namedtuple instead of just a tuple , you can even format by name instead of by position, making the code much more readable. 如果您使用的是classnamedtuple而不是tuple ,则甚至可以按名称而不是按位置进行格式化,从而使代码更具可读性。

Finally, the highscore file itself is an unreadable mess, because pickle is not meant for human consumption. 最后,高分文件本身是一个难以理解的烂摊子,因为pickle并不是供人食用的。 If you want it to be human-readable, you have to pick a format, and write the code to serialize that format. 如果您希望它是人类可读的,则必须选择一种格式,然后编写代码以序列化该格式。 Fortunately, the CSV format is pretty human-readable, and most of the code is already written for you in the csv module. 幸运的是,CSV格式非常易于阅读,并且大多数代码已经在csv模块中为您编写。 (You may want to look at the DictReader and DictWriter classes, especially if you want to write a header line. Again, there's the tradeoff of a bit more code for a lot more readability.) (您可能想要查看DictReaderDictWriter类,尤其是如果您要编写标题行。同样,为了获得更多可读性,还需要折衷一点代码。)

Here are the things I notice. 这是我注意到的事情。

These lines seem to be in the wrong order: 这些行似乎顺序错误:

print "Current time:", time1.strftime("%d.%m.%Y, %H:%M")
time1 = datetime.datetime.now()

When the user enters the height and weight, they are going to be read in as strings, not integers, so you will get a TypeError on this line: 当用户输入身高和体重时,它们将以字符串而不是整数的形式读取,因此您将在此行上收到TypeError:

c = a/b

You could solve this by casting a and b to float like so: 您可以通过将a和b转换为float来解决此问题,如下所示:

a = float(raw_input("Enter weight: "))

But you'll probably need to wrap this in a try/catch block, in case the user puts in garbage, basically anything that can't be cast to a float. 但是您可能需要将其包装在try / catch块中,以防用户放入垃圾,这基本上是所有不能转换为float的东西。 Put the whole thing in a while block until they get it right. 将整个过程放一段时间,直到他们正确为止。

So, something like this: 因此,如下所示:

b = None
while b == None:
    try:
        b = float(raw_input("Enter height: "))
    except:
        print "Weight should be entered using only digits, like '187'"

So, on to the second part, you shouldn't use list as a variable name, since it's a builtin, I'll use high_scores . 因此,在第二部分中,您不应使用list作为变量名,因为它是内置变量,因此我将使用high_scores

# Add one default entry to the list
high_scores = [("CPU", 200, 100, 2, "20.12.2012, 4:20")]

You say you want to check the player score against the high score, to see if it's best, but if that's the case, why a list? 您说您想对照高分检查球员得分,以查看是否最好,但是如果是这样,为什么要列出? Why not just a single entry? 为什么不只是一个条目? Anyhow, that's confusing me, not sure if you really want a high score list, or just one high score. 无论如何,这让我感到困惑,不确定您是否真的想要一个高分列表,或者只是一个高分。

So, let's just add the score, no matter what: 因此,无论如何,我们都可以添加分数:

Assume you've gotten their name into the name variable. 假设您已将其名称添加到name变量中。

high_score.append((name, a, b, c, time1))

Then apply the other answer from @Tamás 然后应用@Tamás的其他答案

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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