Multiple choice quiz for maths and history, with saved score reports

This is a program I have been working on in python. It is meant to be a quiz to some secondary school pupils on two subjects/topics which are maths and history.

So far, it allows a user to try the quiz on one of the subjects and gives them a grade, percentage and score according to what they have answered correctly in the quiz. However, the program seems long-winded with two very big functions as well:

print("Welcome to my quiz!")
def maths():
with open("maths.txt","r") as topic1:
score = 0
difficultyLevel = input("Please select a difficulty level for the  maths quiz:easy, medium or hard:")
print("The maths questions:")
if difficultyLevel == "Easy" or difficultyLevel == "easy":
for x in range(0,3):
print(questionsForMaths[x].rstrip())
userAnswer = input("Choose from the following:").lower()
print ("correct")
score = score + 1
else:
print ("incorrect")
#2nd question
for x in range(5,8):
print(questionsForMaths[x].rstrip())
userAnswer = input("Choose from the following:").lower()
print ("correct")
score = score + 1
else:
print ("incorrect")
#3rd question
for x in range(10,13):
print(questionsForMaths[x].rstrip())
userAnswer = input("Choose from the following:").lower()
print ("correct")
score = score + 1
else:
print ("incorrect")
#4th question
for x in range(15,18):
print(questionsForMaths[x].rstrip())
userAnswer = input("Choose from the following:").lower()
print("correct")
score = score + 1
else:
print("incorrect")
#5th question
for x in range(20,23):
print(questionsForMaths[x].rstrip())
userAnswer = input("Choose from the following:")
print("correct")
score = score + 1
else:
print("incorrect")
elif difficultyLevel == "Medium" or difficultyLevel == "medium":
for x in range(0,4):
print(questionsForMaths[x].rstrip())
userAnswer = input("Choose from the following:").lower()
print ("correct")
score = score + 1
else:
print ("incorrect")
#2nd question
for x in range(5,9):
print(questionsForMaths[x].rstrip())
userAnswer = input("Choose from the following:").lower()
print ("correct")
score = score + 1
else:
print ("incorrect")
#3rd question
for x in range(10,14):
print(questionsForMaths[x].rstrip())
userAnswer = input("Choose from the following:").lower()
print ("correct")
score = score + 1
else:
print ("incorrect")
#4th question
for x in range(15,19):
print(questionsForMaths[x].rstrip())
userAnswer = input("Choose from the following:").lower()
print("correct")
score = score + 1
else:
print("incorrect")
#5th question
for x in range(20,24):
print(questionsForMaths[x].rstrip())
userAnswer = input("Choose from the following:")
print("correct")
score = score + 1
else:
print("incorrect")

elif difficultyLevel == "Hard" or difficultyLevel == "hard":
for x in range(0,5):
print(questionsForMaths[x].rstrip())
userAnswer = input("Choose from the following:").lower()
print ("correct")
score = score + 1
else:
print ("incorrect")
#2nd question
for x in range(5,10):
print(questionsForMaths[x].rstrip())
userAnswer = input("Choose from the following:").lower()
print ("correct")
score = score + 1
else:
print ("incorrect")
#3rd question
for x in range(10,15):
print(questionsForMaths[x].rstrip())
userAnswer = input("Choose from the following:").lower()
print ("correct")
score = score + 1
else:
print ("incorrect")
#4th question
for x in range(15,20):
print(questionsForMaths[x].rstrip())
userAnswer = input("Choose from the following:").lower()
print("correct")
score = score + 1
else:
print("incorrect")
#5th question
for x in range(20,25):
print(questionsForMaths[x].rstrip())
userAnswer = input("Choose from the following:")
print("correct")
score = score + 1
else:
print("incorrect")
percentage = (score/5)*100
print("The percentage of questions correct is", percentage)
if percentage < 40.0:
print("You have failed the maths quiz")
with open("reports.txt","a") as reports:
reports.write("\n" + fullUsername + " " + "achieved a grade of fail" + " " + "in the maths quiz")
elif percentage >= 40.0 and percentage <= 60.0:
print("You have passed the maths quiz")
with open("reports.txt","a") as reports:
reports.write("\n" + fullUsername + " " + "achieved a grade of pass" + " " + "in the maths quiz")
elif percentage >= 60.0 and percentage <= 80.0:
print("You have achieved merit for the maths quiz")
with open("reports.txt","a") as reports:
reports.write("\n" + fullUsername + " " + "achieved a grade of merit" + " " + "in the maths quiz")
elif percentage > 80.0:
print("You have achieved distinction for maths quiz")
with open("reports.txt","a") as reports:
reports.write("\n" + fullUsername + " " + "achieved a grade of distinction" + " " + "in the maths quiz")
with open("reports.txt","a") as reports:
reports.write("\n" + fullUsername + " " + "did the maths quiz and" + " " + "achieved a score of" + " " + str(score))
reports.write("\n" + fullUsername + " " + "did the maths quiz and" + " " + "achieved a percentage of" + " " + str(percentage))

def history():
with open("history.txt","r") as topic2:
score = 0
print("The history questions:")
difficultyLevel = input("Please select a difficulty level for the  history quiz:easy, medium or hard:")
if difficultyLevel == "Easy" or difficultyLevel == "easy":
for y in range(0,3):
print(questionsForHistory[y].rstrip())
userAnswer2 = input("Choose from the following:")
print("correct")
score = score + 1
else:
print("incorrect")
#2nd question
for y in range(5,8):
print(questionsForHistory[y].rstrip())
userAnswer2 = input("Choose from the following:")
print("correct")
score = score + 1
else:
print("incorrect")
#3rd question
for y in range(10,13):
print(questionsForHistory[y].rstrip())
userAnswer2 = input("Choose from the following:")
print("correct")
score = score + 1
else:
print("incorrect")
#4th question
for y in range(15,18):
print(questionsForHistory[y].rstrip())
userAnswer2 = input("Choose from the following:")
print("correct")
score = score + 1
else:
print("incorrect")
#5th question
for y in range(20,23):
print(questionsForHistory[y].rstrip())
userAnswer2 = input("Choose from the following:")
print("correct")
score = score + 1
else:
print("incorrect")
elif difficultyLevel == "Medium" or difficultyLevel == "medium":
for y in range(0,4):
print(questionsForHistory[y].rstrip())
userAnswer2 = input("Choose from the following:")
print("correct")
score = score + 1
else:
print("incorrect")
#2nd question
for y in range(5,9):
print(questionsForHistory[y].rstrip())
userAnswer2 = input("Choose from the following:")
print("correct")
score = score + 1
else:
print("incorrect")
#3rd question
for y in range(10,14):
print(questionsForHistory[y].rstrip())
userAnswer2 = input("Choose from the following:")
print("correct")
score = score + 1
else:
print("incorrect")
#4th question
for y in range(15,19):
print(questionsForHistory[y].rstrip())
userAnswer2 = input("Choose from the following:")
print("correct")
score = score + 1
else:
print("incorrect")
#5th question
for y in range(20,24):
print(questionsForHistory[y].rstrip())
userAnswer2 = input("Choose from the following:")
print("correct")
score = score + 1
else:
print("incorrect")
elif difficultyLevel == "Hard" or difficultyLevel == "hard":
for y in range(0,5):
print(questionsForHistory[y].rstrip())
userAnswer2 = input("Choose from the following:")
print("correct")
score = score + 1
else:
print("incorrect")
#2th question
for y in range(5,10):
print(questionsForHistory[y].rstrip())
userAnswer2 = input("Choose from the following:")
print("correct")
score = score + 1
else:
print("incorrect")
#3rd question
for y in range(10,15):
print(questionsForHistory[y].rstrip())
userAnswer2 = input("Choose from the following:")
print("correct")
score = score + 1
else:
print("incorrect")
#4th question
for y in range(15,20):
print(questionsForHistory[y].rstrip())
userAnswer2 = input("Choose from the following:")
print("correct")
score = score + 1
else:
print("incorrect")
#5th question
for y in range(20,25):
print(questionsForHistory[y].rstrip())
userAnswer2 = input("Choose from the following:")
print("correct")
score = score + 1
else:
print("incorrect")
percentage = (score/5)*100
print("The percentage of questions correct is", percentage)
if percentage < 40.0:
print("You have failed the history quiz")
with open("reports.txt","a") as reports:
reports.write("\n" + fullUsername + " " + "achieved a grade of fail" + " " + "in the history quiz")
elif percentage >= 40.0 and percentage <= 60.0:
print("You have passed the history quiz")
with open("reports.txt","a") as reports:
reports.write("\n" + fullUsername + " " + "achieved a grade of pass" + " " + "in the history quiz")
elif percentage >= 60.0 and percentage <= 80.0:
print("You have achieved merit for the history quiz")
with open("reports.txt","a") as reports:
reports.write("\n" + fullUsername + " " + "achieved a grade of merit" + " " + "in the history quiz")
elif percentage > 80.0:
print("You have achieved distinction for history quiz")
with open("reports.txt","a") as reports:
reports.write("\n" + fullUsername + " " + "achieved a grade of distinction" + " " + "in the history quiz")
with open("reports.txt","a") as reports:
reports.write("\n" + fullUsername + " " + "did the history quiz and" + " " + "achieved a score of" + " " + str(score))
reports.write("\n" + fullUsername + " " + "did the history quiz and" + " " + "achieved a percentage of" + " " + str(percentage))

with open("user account .txt","w") as userFile:
print("Invalid name, try again")
print("try again")
with open("reports.txt","a") as reports:

UserYearGroup = int(input("Enter your year group:"))
while UserYearGroup < 7 or UserYearGroup > 11:
print("Invalid year group, enter again")
UserYearGroup = int(input("Enter your year group:"))
if UserYearGroup >= 7 and UserYearGroup <= 11:
userFile.write("\nYear Group:" + str(UserYearGroup))
print(UserYearGroup)

password = input("Enter your password, it must be at least 6 characters long and no more than 11 characters:")
password = input("Enter your password, it must be at least 6 characters long and no more than 11 characters:")

topicChoice = input("Which topic do you want to test yourself on : its either maths or history?")
his = "history".lower()
ma = "maths".lower()

if topicChoice == his:
history()

elif topicChoice == ma:
maths()


How can I make the program more efficient (i.e. fewer lines of code) and possibly not as repetitive?

• Could you give us some idea what the first few lines of maths.txt look like? – 200_success Nov 1 '17 at 20:52

Lots repeated code, so we can define functions or parameters to be more efficient.

maths and history almost the same codes, so can just delete one and use topic as parameter

For questions part are the same. In different difficultyLevel, only the range for x and expected answer are different (and some little difference for the 3th question in history topic). All these can be more efficient.

Here is my code

user_account_filename = "user account .txt"
report_filename = "reports.txt"

'maths':[1,7,12,16,22],
'history':[2,6,12,17,21]
}

level2questions = {
'easy':3,
'medium':4,
'hard':5
}

question_num = 5

score = 0
questions_filename = topic + '.txt'
difficultyLevel = input("Please select a difficulty level for the " + topic + " quiz:easy, medium or hard:")

if difficultyLevel.lower() in ["easy","medium","hard"]:
difficultyLevel = difficultyLevel.lower()
print("The " + topic + " questions:")

for i in range(question_num):
for x in range(i * 5, i * 5 +\
level2questions[difficultyLevel]):
print(questions[x].rstrip())
userAnswer = input("Choose from the following:").lower()

if i == 2 and topic == "history":
print ("correct")
score = score + 1
else:
print ("incorrect")

#TODO:else

def test_result(result):
report = "\n" + fullUsername + " achieved a grade of " + result + " in the " + topic + " quiz"
if result in ["merit", "distinction"]:
result = "achieved " + result + " for"
else:
result += "ed"
printout = "You have " + result + " the " + topic + " quiz"
return printout, report

percentage = (score/5)*100
print("The percentage of questions correct is", percentage)
if percentage < 40.0:
result = "fail"
elif percentage >= 40.0 and percentage <= 60.0:
result = "pass"
elif percentage >= 60.0 and percentage <= 80.0:
result = "merit"
elif percentage > 80.0:
result = "distinction"

printout, report = test_result(result)
print(printout)

record_prefix = "\n" + fullUsername + " did the " + topic + " quiz and achieved a "
write_file(report_filename, report + record_prefix + "score of " + str(score) + record_prefix + "percentage of " + str(percentage))

def write_file(path,data):
with open(path,'a+') as f:
f.write(data)

def main():
print("Welcome to my quiz!")

print("Invalid name, try again")
print("try again")

#UserYearGroup
#I have modified some code here, add UserYearGroup.isdigit() and delete the if UserYearGroup >= 7 and UserYearGroup <= 11
UserYearGroup = input("Enter your year group:")
while UserYearGroup.isdigit() and not (7 <= int(UserYearGroup) <= 11):
print("Invalid year group, enter again")
UserYearGroup = input("Enter your year group:")
write_file(user_account_filename, "\nYear Group:" + UserYearGroup)
print(UserYearGroup)

password = input("Enter your password, it must be at least 6 characters long and no more than 11 characters:")
while not 6 <= len(password) <= 11:
password = input("Enter your password, it must be at least 6 characters long and no more than 11 characters:")

#TopicChoice
topicChoice = input("Which topic do you want to test yourself on : its either maths or history?")
#deleted his and ma
if topicChoice.lower() in ["history","maths"]:
#TODO:else

if __name__ == "__main__":
main()


Explainations

1.expectedAnswers part:as you can see, in your original code, in maths/history topic, for different difficultyLevel,from question 1 to 5

if userAnswer == questionsForMaths[i].rstrip():
print ("correct")
score = score + 1
else:
print ("incorrect")


this part is repeated, except the i in questionsForMaths[i] (so I guess the i means "expected answer"). And also, in each difficultLevel the is are all the same -- for maths topic from question 1 to 5, i equals 1,7,12,16 and 22; for history topic, i equals 2,6,12,17,21. Thus this is how expectedAnswers works

2.level2questions part: still in different difficultyLevel,from question 1 to 5

for x in range(i,j):
print(questionsForMaths[x].rstrip())


this part is repeated, except the i and j in for x in range(i,j).

For easy difficulty level:

for x in range(0,3):
print(questionsForMaths[x].rstrip())
...
#2nd question
for x in range(5,8):
print(questionsForMaths[x].rstrip())
...
#3rd question
for x in range(10,13):
print(questionsForMaths[x].rstrip())
...
#4th question
for x in range(15,18):
print(questionsForMaths[x].rstrip())
...
#5th question
for x in range(20,23):
print(questionsForMaths[x].rstrip())


For medium difficulty level:

for x in range(0,4):
print(questionsForMaths[x].rstrip())
...
#2nd question
for x in range(5,9):
print(questionsForMaths[x].rstrip())
...
#3rd question
for x in range(10,14):
print(questionsForMaths[x].rstrip())
...
#4th question
for x in range(15,19):
print(questionsForMaths[x].rstrip())
...
#5th question
for x in range(20,24):
print(questionsForMaths[x].rstrip())


For hard difficulty level:

for x in range(0,5):
print(questionsForMaths[x].rstrip())
...
#2nd question
for x in range(5,10):
print(questionsForMaths[x].rstrip())
...
#3rd question
for x in range(10,15):
print(questionsForMaths[x].rstrip())
...
#4th question
for x in range(15,20):
print(questionsForMaths[x].rstrip())
...
#5th question
for x in range(20,25):
print(questionsForMaths[x].rstrip())


We can find something common in x rang here:

Difficult Level |  1st  |  2nd  |  3rd    |  4th    | 5th

Easy            | (0,3) | (5,8) | (10,13) | (15,18) | (20,23)

Medium          | (0,4) | (5,9) | (10,14) | (15,19) | (20,24)

Hard            | (0,5) | (5,10)| (10,15) | (15,20) | (20,25)


So, all 1st question x all start from 0(equal (1-1) * 5), and 2nd start from 5(equal (2-1) * 5), and so on. So we can get x range start from (question_NO - 1) * 5, so this is the i part.

Then let's look at where does x range stop

For "easy" level, 1st question x end in 3(equal 0 + 3), 2nd question end in 8(equal 5 + 3), and so on. So x end in i + 3

Same thing for medium and hard level

"medium" level end in i + 4, "hard" level end in i + 5, this is the j part.

Now we have 3 magic numbers here -- 3 for easy level; 4 for medium level and 5 for hard level

So I use this level2questions to remove repeated code

level2questions = {
'easy':3,
'medium':4,
'hard':5
}


We can get "the magic number" by target level -- level2questions[level]

So x end in i + magic_number that is i + level2questions[level] and replace the i we get before, we can rewrite the for x in range(i,j) to:

for x in range((question_NO - 1) * 5,\
(question_NO - 1) * 5 + level2questions[level]):


One more thing in my code:

for i in range(question_num):
for x in range(i * 5, i * 5 +\
level2questions[difficultyLevel]):
print(questions[x].rstrip())


The question_NO(in my code it is i) start from 0, so just use question_NO replace (question_NO - 1)

3.last two line part: it means, if this code is executed as main programme (Or to say, when you run the python file from terminal), then run the main() function.

You can try it, create another python file and add from [this python file name(without.py)] import *, when you run the "another python file" on terminal, the main() function here won't work

Here are some better explaination for it

https://stackoverflow.com/questions/419163/what-does-if-name-main-do

and docs

First, find out common things, and try to define functions for them. when you need one, just call the function.

In your code, lots repeated code in it. You can first remove those repeated code by define function.

If most code are similiar but only some "key points" are differnt, then you need add parameters in the function, key parameters are for those "key points"

An example: print("You have failed the maths quiz") and print("You have failed the history quiz") except the "math" and "history", all other are same here, so you can define such function

def failed_quiz(topic):
print("You have failed the " + topic + " quiz")


I think this would be a good practice for you, so first, try to "kill" repeat code

Then, find things common in logic. this just as my level2questions part.

I think "create efficient programs" just like organize things in daily life, but we need more practice to get used in code life, anyway you have a good start, just keep coding and read other's code, your code will be better :)

Sorry can't give you resource link, as I don't know any, just keep practice^^

• I tried to use your code above but the 'UserYearGroup' part is still a problem because the program stops running and gives a syntax error. – Z.Zayed Nov 2 '17 at 17:24
• There are also other problems when the above code is ran for example write_file(user_account_filename, "\nYear Group:" + UserYearGroup) TypeError: Can't convert 'int' object to str implicitly. – Z.Zayed Nov 2 '17 at 17:30
• @Z.Zayed I have modified the code, and should without problem in UserYearGroup part now. – Aries_is_there Nov 3 '17 at 2:45
• can you explain to me what the expectedAnswers{} and level2questions{] part of the program does? Also what does the last two lines do in the program, I am not sure what is actually happening at that part either. – Z.Zayed Nov 4 '17 at 23:40
• @Z.Zayed Sure, I will edit my answer with explainations – Aries_is_there Nov 5 '17 at 11:16

The answer above is super long already. so I just start a new one.

Easier Version

Usually while we write code, first list the steps then code.

And keep in mind, One function should do just one thing

So let's analyse your structure first

1.User input basic informations and save them in file

2.User choose topic to test

3.Start test

Input basic informations

This part your code is great, just don't run it keep opening file.

So the structor here like

2.save in file

So here we can define two functions

def read_user_info():
(some rules..)
write_file(filename, content)

def write_file(filename, content):
with open(filename,"a+") as f:
f.write(content)


Just like my main() and write_file() function above.

User choose topic to test

I wrote this part in main() too, as just one line, but better to write another function for it, might be more clear in structure.

Start test

Here is we need Merge maths() and history()

This two functions are similar

The structure of those two functions:

1.open file

#maths
with open("maths.txt","r") as topic1:
...
#history
with open("history.txt","r") as topic2:
...


2.user choose difficulty level

#maths
difficultyLevel = input("Please select a difficulty \
level for the  maths quiz:easy, medium or hard:")
...
#history
difficultyLevel = input("Please select a difficulty \
level for the  history quiz:easy, medium or hard:")
...


3.user answer questions / recode score

#maths
if difficultyLevel == "Easy" or difficultyLevel == "easy":
for x in range(0,3):
print(questionsForMaths[x].rstrip())
userAnswer = input("Choose from the following:").lower()
print ("correct")
score = score + 1
else:
print ("incorrect")
....
(other 4 questions)
...
(other difficulty level)

#history
if difficultyLevel == "Easy" or difficultyLevel == "easy":
for y in range(0,3):
print(questionsForHistory[y].rstrip())
userAnswer2 = input("Choose from the following:")
print("correct")
score = score + 1
else:
print("incorrect")
....
(other 4 questions)
...
(other difficulty level)


4.show result

print("Your total score is", score)
percentage = (score/5)*100
print("The percentage of questions correct is", percentage)
if percentage < 40.0:
...


Thus we can first split it to four functions

def open_file(filename):
...
def choose_difficult_level():
...
...
...


open file

I rewrote your open file code

Yours

with open(filename,"r") as topic:


To

questions = open(questions_filename).readlines()


Just need one-line code, but yours also good, you can keep yours

choose difficult level

Also one-line code

def choose_difficult_level(topic):
difficultyLevel = input("Please select a difficulty \
level for the {} quiz:easy, medium or hard:".format(topic))
return difficultyLevel


Here I use format, it is better coding style in python, you can find and read doctumentary about it.

Let's make this part easier

Basic structor like this

def answer_quesions(topic,difficultyLevel):
#get questions for differnet topic
score = 0
if difficultyLevel == "Easy" or difficultyLevel == "easy":
...
(do easy questions and record score)
elif difficultyLevel == "Medium" or difficultyLevel == "medium":
...
(do medium questions and record score)
elif difficultyLevel == "Hard" or difficultyLevel == "hard":
...
(do hard questions and record score)
return score


It is ok, if it is better for you to understand, just use

if toptic == "maths":
(do maths questions)
elif topic == "history":
(do history questions)


Compare to just code in this function(and make this function super long), it is butter to define functions for different level and different topic

So you can define functions just like this

def math_easy_questions():
def history_easy_questions():


and call then in your answer_quesions

But I still recommend you try to understand my code, the expectedAnswers and level2questions part

show result

Here we can do two things

1.replace all 'maths' and 'history' to 'topic'

2.use string to recode all things need to write in file, then write the string in file(just write in file once)

def show_result(topic, score,fullUsername):
percentage = (score/5)*100
print("The percentage of questions correct is", percentage)
reports=""
if percentage < 40.0:
#Change
#print("You have failed the maths quiz")
print("You have failed the {} quiz".format(topic))
#Delete
#with open("reports.txt","a") as reports:
#    reports.write("\n" + fullUsername + " " + "achieved a grade of fail" + " " + "in the maths quiz")

• It's bad practice to open a file and not call close on it. The with statement should stay. – Daniel Jan 13 '18 at 13:38