HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Multiple Choice Quiz with Stat Tracking

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
trackingwithquizchoicestatmultiple

Problem

I was hoping I could get some of you to look over a program I wrote. I am just beginning to learn about Python (for the last month or so). Therefore, I may not be aware of techniques that would make this program better. Currently I'm studying Python 2.7. Any advice or suggestions you could provide would be appreciated. Thanks again.

```
##Flash Card Program

# Import required modules
import random
import sys
import os
import math

# variables
right_answer_total = float(0)
wrong_answer_total = float(0)
percentage = 100 * (float(right_answer_total) / float(answer_total)) if (right_answer_total > 0 and answer_total > 0) else 0

# dictionary containing the information for the questions & answers
word_drills = {'class': 'Tell Python to make a new kind of thing.',
'object': 'Two meanings: the most basic kind of thing, and any instance of some thing.',
'instance': 'What you get when you tell Python to create a class.',
'def': 'How you define a function inside a class.',
'self': 'Inside the functions in a class, self is a variable for the instance/object being accessed.',
'inheritance': 'The concept that one class can inherit traits from another class, much like you and your parents.',
'composition': 'The concept that a class can be composed of other classes as parts, much like how a car has wheels.',
'attribute': 'A property classes have that are from composition and are usually variables.',
'is-a': 'A phrase to say that something inherits from another, as in a Salmon *** Fish',
'has-a': 'A phrase to say that something is composed of other things or has a trait, as in a Salmon *** mouth.'}

# Main portion of the program
def start():
# For loop that creates a list named keys. It grabs 3 random keys from the dictionary word_drills
keys = [x for x in random.sample(word_drills, 3)]
# User is presented with a question. A value from

Solution

Here's a step by step Pythonization walk-trough.

Trivial simplifications

Before:

answer_total = float(right_answer_total + wrong_answer_total)
percentage = 100 * (float(right_answer_total) / float(answer_total))


After:

percentage = 100 * right_answer_total / float(right_answer_total + wrong_answer_total)


Reasoning: a single division argument (either numerator or denominator) is sufficient to be of a float type so that the float division was performed.

Before:

keys = [x for x in random.sample(word_drills, 3)]


After:

keys = random.sample(word_drills, 3)


Reasoning: [x for x in l] results in a copy of l since neither filtering nor element transformations are performed.

Before:

key1, key2, key3 = keys[0], keys[1], keys[2]
print "\n\n(a)%s   (b)%s   (c)%s" % (key1, key2, key3)
a, b, c = word_drills[key1], word_drills[key2], word_drills[key3]


After:

print "\n\n(a)%s   (b)%s   (c)%s" % tuple(keys)


Reasoning: same effect with less code; variables key1, key2, key3, a, b, c are unnecessary (see below).

Code Refactoring

Looks like you came from functional programming world. Each of your functions ends in calling another function. Since Python has no tail call optimization you will end up in stack overflow eventually.

Thus your algorithm must be revised in a more imperative way. The whole program looks like this:

  • Repeat in an infinite loop:



  • Print statistics



  • Prepare a question



  • Gather user input



  • Exit program if user input was invalid



  • Check whether user's answer was correct



  • Update answer statistics



For the outer loop a while True: construction will do.

Statistics printing is mostly left intact. The function name changed from stat_tracking to print_stats to reflect the innards. Note that there is no tail call to start() since this function will be invoked from start() not vice versa. Also, to avoid global variables, stats are passed as paramenters:

def print_stats(right_answer_total, wrong_answer_total, percentage):
    os.system('cls' if os.name=='nt' else 'clear')
    print "-" * 37
    print "|         Stat Tracking             |"
    print "-" * 37
    print "| Correct | Incorrect |  Percentage |"
    print "-" * 37
    print "|    %d    |     %d     |     %d %%     |" % (right_answer_total, wrong_answer_total, percentage)
    print "-" * 37
    print "\n\n\n"


Bearing simplifications in mind and renaming the variables to reflect their meanings in mind here's the question preparation:

possible_answers = random.sample(word_drills, 3)
correct_answer = random.choice(possible_answers)
question = word_drills[correct_answer]
print "Question:", question
print "\n\n(a)%s   (b)%s   (c)%s" % tuple(possible_answers)


Since the whole program resides in a loop there's no need for exit()ing, a simple break will do:

if selection not in ('a', 'b', 'c'):
    print "That is not a valid selection."
    break


Checking the answer for correctness needs a little bit more explanation.

First of all, the answered_correctly() and not_answered_correctly() are gone. They were mostly the same, only a single line differed.

Getting the user's answer is basically inferring an index from possible_answers. Since we validated selection (see above) we are sure that its value is in "a", "b", "c". "a", "b", "c" correspond to 0, 1, 2 indexes of the possible_answers list respectively. Since "a", "b", "c" are laid out sequentially in ASCII table ord(selection) - ord('a') can be used to convert the string to answer index.

Now figuring out whether user was correct or not is a matter of checking whether answer == correct_answer.

Along with statistics update the code looks like this:

answer = possible_answers[ord(selection) - ord('a')]
if answer == correct_answer:
    print "That's correct!"
    right_answer_total += 1
else:
    print "I'm sorry, that is incorrect..."
    wrong_answer_total += 1

percentage = 100 * right_answer_total / float(right_answer_total + wrong_answer_total)


Final Touches

sys and math modules are unused, so do not import them.

The main entry point function is called main() rather then start(). Though that is just a convention.

To avoid your program starting when it is imported (as import program) rather then started as a program (as python program.py) the following trick is used:

if __name__ == '__main__':
    main()


And here's the whole code:

```
#!/usr/bin/env python

import random
import os

# dictionary containing the information for the questions & answers
word_drills = {'class': 'Tell Python to make a new kind of thing.',
'object': 'Two meanings: the most basic kind of thing, and any instance of some thing.',
'instance': 'What you get when you tell Python to create a class.',
'def': 'How you define a function inside a class.',
'self': 'Inside the functions in a class, self is a va

Code Snippets

answer_total = float(right_answer_total + wrong_answer_total)
percentage = 100 * (float(right_answer_total) / float(answer_total))
percentage = 100 * right_answer_total / float(right_answer_total + wrong_answer_total)
keys = [x for x in random.sample(word_drills, 3)]
keys = random.sample(word_drills, 3)
key1, key2, key3 = keys[0], keys[1], keys[2]
print "\n\n(a)%s   (b)%s   (c)%s" % (key1, key2, key3)
a, b, c = word_drills[key1], word_drills[key2], word_drills[key3]

Context

StackExchange Code Review Q#14838, answer score: 2

Revisions (0)

No revisions yet.