Python:如何向包含名称和分数的文本文件添加其他信息?

时间:2015-01-19 09:21:49

标签: python file text

我正在为一所学校数学课程开设一个简单的数学课程,孩子们在那里写下自己的名字,然后程序会询问10个简单的数学问题。之后,孩子们'名称和分数将根据其类别存储在不同的文本文件中。

以下是我的问题。

目前,他们的名字以这种格式存储:William W - 10

所以第一部分是他们的名字,最后一部分是他们的分数。我不确定是否有更好的方法。

我如何做到这一点,如果同一个孩子再次进行测试,它会与之前的分数平均?

另外,如果孩子获得更高的分数,我怎么做才能让孩子的分数过高? (这将是不同的文本文件到平均分数的一个文件)

我试图制作一个排名板,其中最高点的孩子出现在顶部,最低点出现在底部。

如果在排行榜上显示,我希望它看起来像这样。

  1. William W - 10
  2. Nikko E - 9
  3. 等。我到处搜索,我似乎找不到答案。如果有人可以分享他们的知识,那将是很好的。谢谢

    这是我未完成的代码。我是python XD的新手 我需要在排行榜上添加额外的东西:平均,最高和字母顺序,每个学生的测试得分最高。

    def menu():
        print "Welcome to Simple-Maths Questions program"
        print "Your options are the following:"
        print "1 - Do a test"
        print "2 - Leaderboard"
        print "3 - Exit"
        question()
    
    
    def question():
        ans = input("Please enter the number: ")
        if ans ==1:
            start()
        elif ans ==2:
            leaderboard()
        elif ans ==3:
            exitprogramme()
        else:
            print "Error - invalid choice"
            print " "
        menu()
    
    def leaderboard():
        menu()
    
    
    def exitprogramme():
        quit()
    
    def start():
        f = open('names.txt', 'a')
        print "Here are 10 easy maths question for you to solve"
        name = raw_input("Please enter in your name and surname enitial (e.g      Willam G)")
        import random
        import re
        import operator
    
        n1 = random.randint(1,9)
        n2 = random.randint(1,9)
        ops = {'+': operator.add, '-': operator.sub, '*': operator.mul}
        op = random.choice(ops.keys())
        a = 1
        count = 0
    
        while a <=10:
            question = input("What is " + str(n1) + str(op) + str(n2) + "?")
            a = a+1
            ans = ops[op](n1, n2)
            n1 = random.randint(1,9)
            n2 = random.randint(1,9)
            op = random.choice(ops.keys())
    
            if question == ans:
            count = count + 1
                print "Well done"
            else:
                count = count + 0
                print "WRONG"
    
        print " "
        print "You got score of "+ str(count)+"."
        f.write(name+' - '+str(count)+'\n')
        f.close()
        f1 = open("names.txt", "r")
        sortToFile = open("sortedFile.txt", "w")
        for line in sorted(f1, key = str.lower):
            sortToFile.write(line)
        f1.close()
        sort_nicely()
    
    
        def sort_nicely():
            import re
        ''' this is my attempt to do sort the names with score with highest score at the top '''
    
        convert = lambda text: int(text) if text.isdigit() else text
        alphanum_key = lambda key: [ convert(c) for c in re.split('([0-9]+)', key) ]
        Leaderboard = open("Leaderboard.txt", "w")
        f2 = open("names.txt", "r")
        for line in sorted(f2, key=alphanum_key, reverse=True):
            Leaderboard.write(line)
    
    
        print ""
    
    
        menu()
    

2 个答案:

答案 0 :(得分:5)

好吧,我打算只编写程序的相关部分,但最后我编写了一个完整的工作程序。因为我不知道你知道多少Python我只是要解释整个程序;对不起,如果我有一点迂腐和/或浮夸的气氛。随意跳到底部并复制代码。

我编写的代码演示了4个Python习语:面向对象设计,字节数组/字符串切片和格式化,xrange()函数和错误捕获。

我认为csv不是解决问题的好角色。我选择将数据存储为文本表。每一行采用以下形式: 30个字符的名字| 10个字符的总分| 9个字符尝试| 1个换行符 这样做的好处是每一行都是50个字符宽。因此,与csv方法不同,我们不必扫描逗号来查找相关数据;我们只需要以50个字符为增量来查看数据。这使得代码更具可读性,并且现在不像文本存储空间那样昂贵。

示例:Chell R在5次尝试中获得42分变为:

"                       Chell R        42        5\n"

由于我们稍后将对这些数据进行排序,因此将学生的姓名和他/她的分数存储在对象中将是有利的。这个对象使排序更简单;因为该名称永久地附在分数上,所以我们不必担心在数据排序时确保名称数据跟随分数数据。它只是隐含地发生。

这是代码的第一部分:

import time, random, operator
directory_name = r"C:\Users\YOU\yourDirectoryHere\\"
#When you change the directory name, be SURE to include
#the double backslash at the end.
datafile = open(directory_name + "scores.txt")
data = bytearray(datafile.read())
datafile.close()

class entry():
    def __init__(self, i):
        #i is the location of the student's entry in the data.
        global data
        self.name = data[i:i + 30]
        self.total = int(data[i + 30:i + 40])
        self.attempts = int(data[i + 40:i + 49])
        if self.attempts:
            self.average = float(self.total) / self.attempts
        else: self.average = 0.0

    def update_score(self, score, i):
        global data, directory_name
        self.total += score
        self.attempts += 1
        self.average = float(self.total) / self.attempts
        data[i + 30: i + 40] = "%10i" % self.total
        data[i + 40: i + 49] = "%9i" % self.attempts
        datafile = open(directory_name + "scores.txt",'w')
        datafile.write(data)
        datafile.close()

init 下定义的方法称为魔术方法。这意味着与常规方法update_score不同,您不必自己调用该方法。它是由Python自动调用的;在这种情况下,在创建条目对象时调用它。

要指出五点: 1:为了快速解释语法烦恼,每个方法必须将self作为第一个参数,并且存储在对象中的每个变量都需要一个self。前缀。

2:请注意,从文件读取的数据将转换为bytearray类型。字节是有用的,因为不像字符串它们是可变的,所以我们可以更新学生&#39;必要时得分而不会低效地重新创建整个字符串。我选择将数据作为全局变量而不是常规参数传递,以避免必须通过多层函数传递它。

3:表达式foo = bar [x:y]是切片操作。这意味着&#34;使用bar&#34;的字符x到y-1填充变量foo。我在 init 函数中使用它来初始化条目的名称和分数值。反过来也有效,如update_score方法中所示。

4:字符串或字节数组格式化的另一个有用技巧是从C继承。由于每个总分数条目必须占用10个字符才能保持每个条目50个字符宽,请使用字符串&#34;%10i&#34; %self.total,以强制整数为10个字符宽。

5:最后,我写了一个简短的说明我写了float(self.total)/ self.attempts。将整数除以整数会产生舍入的,不准确的整数。

程序的下一部分是主play()函数,它调用find_entry():

def find_entry(name):
    global data
    for i in xrange(0,len(data),50):
        if data[i:i + 30] == "%30s" % name:
            return i
    print "Name not found. Please check the spelling of your name."
    print "If you spelled your name correctly please ask your teacher for help."
    return None

xrange函数首先出现在find_entry函数中。这提供了比while循环更可读,更pythonic的迭代方式。 &#34; for x in xrange(0,len(data),50):&#34;相当于。 &#34; i = 0;虽然我&lt; len(data):... i + = 50.无论如何,它所做的只是一次检索50个字符的数据(因为每个条目的宽度为50个字符)并检查前30个字符是否与输入的名称匹配。 (请注意,我使用数据[i:i + 30] ==&#34;%30s&#34;%name。输入的名称必须最多抽取30个字符,否则相等运算符将始终失败)。如果成功则返回找到的条目的索引i,并返回None表示失败。

接下来我们将检查主要的play()函数:

def play():
    global data
    entry_num = None
    while entry_num == None:
        entry_num = find_entry(raw_input("Please enter your name and surname initial (e.g. William G): "))

    student_entry = entry(entry_num)
    score = 0
    for i in xrange(10): #same as xrange(0,10,1)
        score += question()

    student_entry.update_score(score, entry_num)
    print "Your score this time was %i / 10" % score

我们在这里看到,在失败时find_entry返回None的事实得到了很好的利用。 while循环意味着程序继续尝试,直到输入有效名称。另请注意raw_input函数的使用。这迫使Python将输入解释为字符串。这将在下一个函数中更有意义,即问题函数:

def question():
    n1 = random.randint(1,9)
    n2 = random.randint(1,9)
    ops = {'+': operator.add, '-': operator.sub, 'x': operator.mul}
    op = random.choice(ops.keys())
    answer = raw_input("What is %i %s %i?  " % (n1, op, n2))
    try: 
        answer = int(answer)
        if answer == ops[op](n1, n2):
            print "Well done"
            return 1
        else:
            print "incorrect, %i %s %i = %i" % (n1, op, n2, ops[op](n1,n2))
            return 0
    except ValueError:
        print "'%s' does not appear to be a number." % answer
        return 0

我几乎只是逐字地复制了你的代码。唯一的主要区别是添加了try ... except语句和使用raw_input()代替input()。 input()的问题在于它非常不安全,因为Python可以自由地将其输入解释为任何类型,包括变量名,伪造输入可以破坏各种各样的破坏。 raw_input更安全:因为Python总是将输入解释为字符串,所以程序员有权解释输入并预测错误。在这种情况下,try ... except语句尝试将输入的字符串转换为带有int()的整数。 int()如果无法将字符串转换为整数,则会引发ValueError,因此会在失败时执行除ValueError语句之外的操作,将失败告知用户并允许程序恢复而不是崩溃。

既然已经处理了这个问题,我们可以继续讨论问题的实际有趣部分:对条目进行排序。

回到entry()类定义,有两个额外的魔术方法可以帮助创建排行榜, cmp str

class entry():
    def __init__(self, i):
        ...

    def __cmp__(self, other):
        return self.average - other.average

    def __str__(self):
        return "%s | %2.2f" % (self.name, self.average)

    def update_score(self, score, i):
        ...

当Python需要比较两个条目时,会调用 cmp 方法。由于我们希望排行榜按平均分数排序,而不是按字母顺序排列或按总分排序,我们会将此条目的平均值与其他条目的平均值进行比较(Python将负返回值解释为self&lt; ;其他和正回报值为self&gt; other)

str 方法是遇到str(a)或print(a)时调用的方法,其中a是条目类型。由于我们现在必须格式化两个数据,学生的名字和平均值,我们将两个变量放在%符号后面的元组中。 %2.2f告诉Python打印一个小数点后只有2位数的浮点数;任何其他数字都会被忽略。

所有工作都是在编写方法时完成的。因此,编写leaderboard()函数变得微不足道了:

def leaderboard():
    global data
    entries = []
    for i in xrange(0, len(data),50):
        entries += [entry(i)]

    entries.sort()
    entries.reverse()

    for i in entries:
        print i

我们在这里看到了面向对象编程的美妙之处。 xrange循环一遍又一遍地调用条目的 init 方法,用条目填充列表。这个有点复杂的函数安全地存放在入口类声明中,使排行榜功能保持干净且易于阅读。接下来,调用Python内置列表对象的sort()方法,因此我们不需要重新发明轮子来编写另一个排序算法。这会调用我们的 cmp 函数,确保Python能够按照预期的平均分数正确地对条目进行排序。接下来,我们反转()列表,因为我们首先想要得分最高。同样,不需要自己编写这个函数;它伴随着Python中的列表类型。最后,我们打印排行榜。这会调用输入类的 str 功能,因此在排行榜功能本身中我们不必担心在数据文件中找到正确的数据。很干净。

在这里做得很多,现在要做的就是写主循环:

while True:
    print """\
Welcome to Simple-Maths Questions program
Please type one of the following:
Play | Leaderboard | Exit"""
    command = raw_input()
    command = command.upper()
    if command in "PLAY":
        play()
    elif command in "LEADERBOARD":
        leaderboard()
    elif command in "EXIT":
        break #You don't have to call quit() to terminate the program, just reach EOF.
    elif command in "ADD NAME":
        add_student()
    else:
        print "unknown command"

同样,raw_input()比input()更安全。关键字将转换为全部大写,并且只查找输入是否在关键字中,因此输入不区分大小写,并且不需要键入完整关键字(这是调试的节省时间)。此外,还有隐藏的命令&#34; ADD NAME&#34 ;;我没有想解释这个功能。而那,呃,我几乎要说的全部。

这是完成的代码:

import time, random, operator
directory_name = r"C:\Users\YOU\yourDirectoryHere\\"
#When you change the directory name, be SURE to include
#the double backslash at the end. (and keep the r)
datafile = open(directory_name + "scores.txt")
data = bytearray(datafile.read())
datafile.close()

backup_directory = directory_name
def backup():
    #Note: I didn't explain this function. It just randomly
    #makes backups of the data file in case something unexpected happens.
    global data, backup_directory
    datafile = open(backup_directory + str(int(time.time())) + '.txt', 'w')
    datafile.write("Remove this timestamp, message, and newline before restoring this data %s\n" % time.asctime())
    datafile.write(data)
    datafile.close()

class entry():
    def __init__(self, i):
        #i is the location of the student's entry in the data.
        global data
        self.name = data[i:i + 30]
        self.total = int(data[i + 30:i + 40])
        self.attempts = int(data[i + 40:i + 49])
        if self.attempts:
            self.average = float(self.total) / self.attempts
        else: self.average = 0.0

    def __cmp__(self, other):
        #BUGGED CODE return self.average - other.average
        #FIXED BELOW
        if self.average > other.average: return 1
        elif self.average < other.average: return -1
        else: return 0

    def __str__(self):
        return "%s | %2.2f" % (self.name, self.average)

    def update_score(self, score, i):
        global data, directory_name
        self.total += score
        self.attempts += 1
        self.average = float(self.total) / self.attempts
        data[i + 30: i + 40] = "%10i" % self.total
        data[i + 40: i + 49] = "%9i" % self.attempts
        datafile = open(directory_name + "scores.txt",'w')
        datafile.write(data)
        datafile.close()

        if not random.randrange(5):
            backup()


def find_entry(name):
    global data
    for i in xrange(0,len(data),50):
        if data[i:i + 30] == "%30s" % name:
            return i
    print "Name not found. Please check the spelling of your name."
    print "If you spelled your name correctly please ask your teacher for help."
    return None

def question():
    n1 = random.randint(1,9)
    n2 = random.randint(1,9)
    ops = {'+': operator.add, '-': operator.sub, 'x': operator.mul}
    op = random.choice(ops.keys())
    answer = raw_input("What is %i %s %i?  " % (n1, op, n2))
    try: 
        answer = int(answer)
        if answer == ops[op](n1, n2):
            print "Well done"
            return 1
        else:
            print "incorrect, %i %s %i = %i" % (n1, op, n2, ops[op](n1,n2))
            return 0
    except:
        print "'%s' does not appear to be a number." % answer
        return 0

def play():
    global data
    entry_num = None
    while entry_num == None:
        entry_num = find_entry(raw_input("Please enter your name and surname initial (e.g. William G): "))

    student_entry = entry(entry_num)
    score = 0
    for i in xrange(10):
        score += question()

    student_entry.update_score(score, entry_num)
    print "Your score this time was %i / 10" % score

def leaderboard():
    global data
    entries = []
    for i in xrange(0, len(data),50):
        entries += [entry(i)]

    entries.sort()
    entries.reverse()

    for i in entries:
        print i

def add_student():
    #This wasn't explained either. It just appends a properly formatted 50 character
    #long entry to the bytearray, initialized with the inputted name and zeroes.
    global data
    while True:
        student = raw_input("Add a student, press enter to quit: ")
        if student:
            cancel = raw_input("Confirm '%s'. Press enter to confirm, any key to cancel: " % student)
            if not cancel:
                data += bytearray("%30s%10i%9i\n" % (student,0,0))
            else: print "cancelled"
        else:
            break
    datafile = open(directory_name + "scores.txt",'w')
    datafile.write(data)
    datafile.close()

while True:
    print """\
Welcome to Simple-Maths Questions program
Please type one of the following:
Play | Leaderboard | Exit"""
    command = raw_input()
    command = command.upper()
    if command in "PLAY":
        play()
    elif command in "LEADERBOARD":
        leaderboard()
    elif command in "EXIT":
        break
    elif command in "ADD NAME":
        add_student()
    else:
        print "unknown command"

注意:我还没有对此进行详尽的测试,所以请自行进行一些测试以确保其有效。

注2:修订的cmp功能; .sort与近距离浮动的角色表现不正常。号。

答案 1 :(得分:0)

您可以通过多种方式保存此内容。您可以将玩家信息保存为csv。

示例格式可以是:name,N,Score

这里N是学生参加考试的次数。有助于找到平均值。

现在,每当您想要操纵特定学生的分数时,请加载该行并进行操作。

如果新分数高于分数,请将其替换并递增N.

如果较低,则得分=(N *得分+ New_Score)/(N + 1)。在此之后增加N.

对于文件操作,请查看Python中的csv模块。

其中一个选项是让按需生成排行榜。 因此,制作一个排行榜方法,它采用您生成的csv,使用适当的密钥(在名称和分数上)并返回排序列表。