i'm working on a simple math programme for a school maths class where kids writes their name in and then programme asks 10 easy maths questions. After that, kids' names and scores would be stored in different text file, according to their classes.
Here are my questions.
At the moment, their names are stored in this format: William W - 10
so first part is their name and last part is their score. I'm not sure if there's better way of doing this.
How do i make it so that if same kid does the test again, it would average with previous score?
Also, how do i make it so that the kid's score will over write if kid gets higher score? (which would be different text file to one with average score)
i'm trying to make a ranking board where kid with highest point shows up in the top and lowest on the bottom.
When shown on leader board, i want it to look like this.
- William W - 10
- Nikko E - 9
etc. i have searched everywhere and i don't seem to find a answer. It would be nice if someone could be share their knowledge on this. Thank you
Here is my unfinished code. I'm new to python XD I need to add extra things to the leader board: average, highest and alphabetical order with each student's highest score for the tests.
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()
You can save this in multiple ways. You can save information of players as a csv.
An example format could be : name, N, Score
Here N is the number of times the student has given the test. Helpful for finding the average.
Now every time you want manipulate the score of a particular student, load in the row and manipulate it.
If the new score is higher than Score, replace it and increment N.
If its lower, Score = (N*Score + New_Score)/(N+1). Increment N after this.
For file manipulation, check out the csv module in Python.
One of the options is to let the leaderboard to be generated on demand. So, make a leaderboard method which takes the csv you generated, use appropriate keys(on name and score) and return the sorted list.
Well, I planned to just write the relevant parts of the program but I ended up writing an entire working program instead. Since I don't know how much Python you know I'm just going to explain the entire program; sorry if I have a somewhat pedantic and/or pompous air. Feel free to just skip to the bottom and copy the code.
The code I wrote demonstrates 4 Python idioms: object oriented design, bytearray/string slicing and formatting, the xrange() function, and error catching.
I think that csv would not be a good angle to approach the problem with. I opted to store the data as a text table. Each line takes the following form: 30 characters name | 10 characters total score | 9 characters attempts | 1 newline The benefit of this is that each line is exactly 50 characters wide. So, unlike the csv approach, we don't have to scan through for commas to find relevent data; we just go through the data in increments of 50 characters. This makes for much more readable code, and it's not like text storage space is at a premium these days.
Example: Chell R earned 42 points in 5 attempts becomes:
Since we're going to be sorting this data later, it would be advantageous to store both the student's name and his/her score in an object. This object makes sorting much simpler; because the name is permanently attatched to the score, we don't have to worry about making sure the name data follows the score data as the data is sorted. It just happens implicitly.
This is the first portion of the code:
The method defined under init is known as a magic method. This means that unlike the regular method update_score, you don't call the method yourself. It's automatically called by Python; in this case it's called when an entry object is created.
Five things to point out: 1: Just to quickly explain a syntax annoyance, each method must have self as the first argument and each variable stored in the object needs a self. prefix.
2: Notice that the data read from the file is converted to the bytearray type. Bytearrays are useful because unlike strings they are mutable, so we can update students' scores when neccessary without inefficiently recreating the entire string. I opted to pass data as a global variable instead of a regular argument in order to avoid having to pass it through several layers of functions.
3: The expression foo = bar[x:y] is a slice operation. This means "Fill variable foo with characters x through y-1 of bar". I used it in the init function in order to initialize the entry's name and score values. The reverse also works, as seen in the update_score method.
4: Another useful trick for string or bytearray formatting inherits from C. Since each total score entry must take up 10 characters in order to keep each entry 50 characters wide, use the string "%10i" % self.total in order to force the interger to a 10 character width.
5: Finally, just a quick note that I wrote float(self.total) / self.attempts. Dividing an integer by an integer results in a rounded, inaccurate integer.
The next part of the program is the main play() function, which calls find_entry():
The xrange function is first seen in the find_entry function. This provides a much more readable, pythonic way to iterate than while loops. "for i in xrange(0,len(data),50):" is equivalent to. "i = 0; While i < len(data): ... i += 50. Anyway, all it does is go through the data 50 characters at a time (since each entry is 50 characters wide) and check if the first 30 characters match up with the inputted name. (Note that I use data[i:i + 30] == "%30s" % name. The inputted name must be pumped up to 30 characters, otherwise the equality operator will always fail). It returns the index i of the found entry if successful and returns None to indicate failure.
Next we'll examine the main play() function:
We see here that the fact that find_entry returns None upon failure is put to good use. The while loop means the program keeps on trying until a valid name is inputted. Also note the use of the raw_input function. This forces Python to interpret the input as a string. This'll make more sense in the next function, the question function:
I pretty much just copied your code verbatim for this part. The only major difference is the addition of the try... except statement and the use of raw_input() in lieu of input(). The issue with input() is that it's pretty unsafe, since Python is free to interpret its input as any type, including a variable name, a bogus input can wreck all sorts of havoc. raw_input is much safer: since Python always interprets the input as a string the programmer gets the right to interpret the input and anticipate errors. In this case, the try... except statement tries to convert the inputted string to an integer with int(). int() raises ValueError if it fails to convert the string to an integer, so an except ValueError statement would execute instead upon failure, communicating the failure to the user and allowing the program to recover instead of crashing.
Now that that's dealt with, we can move on to the actually interesting part of the question: sorting the entries.
Going back to the entry() class definition, there are two additional magic methods which facilitate leaderboard creation, cmp and str:
The cmp method is called when Python needs to compare two entries. Since we want the leaderboards to be sorted by average score, not alphabetically or by total score, we return a comparison of this entry's average with the other entry's average (Python interprets a negative return value as self < other and a positive return value as self > other)
The str method is what's called when str(a) or print(a) is encountered, where a is an entry type. Since we now must format two pieces of data, the student's name and average, we put the two variables in a tuple after the % sign. The %2.2f tells Python to print a floating point number with only 2 digit after the decimal point; any further digits are ignored.
All the work was done writing the methods. Writing the leaderboard() function thus becomes a trivial affair:
We see the beauty of object oriented programming at work here. The xrange loop calls the entry's init method over and over again, populating the list with entries. This somewhat complex function is safely stowed away in the entry class declaration, keeping the leaderboard function clean and easy to read. Next, the sort() method of Python's built in list object is called, so we don't need to reinvent the wheel writing another sorting algorithm. This calls our cmp function, ensuring that Python will correctly sort the entries by average score as intended. Next, we reverse() the list, since we want the highest scores first. Again, no need to write this function ourselves; it comes along with the list type in Python. Finally, we print the leaderboard. This calls the entry class's str function, so in the leaderboard function itself we don't need to worry about finding the correct data in the data file. Nice and clean.
Pretty much done here, now all there's to do is write the main loop:
Again, raw_input() is safer than input(). The keyword is converted to all caps and only finds whether the input is in the keyword, so the input is not case sensitive and there's no need to type the full keyword (This was a timesaver for debugging). Also, there's the hidden command "ADD NAME"; I didn't feel like explaining that function. And that's, uh, pretty much all I have to say.
And here is the completed code:
Note: I haven't exactly exhaustively bug tested this, so please do a bit of testing yourself to make sure this works.
NOTE 2: Amended cmp function; .sort behaved erratically with close floating pt. numbers.