2
\$\begingroup\$

I have created a quiz in Python that will generate a series of 10 questions to test the basic arithmetic skills of primary school children. To do this, I have used a while loop and random functions to generate random numbers and operations.

name = input("What is your name? ")
print("Hello, "+name+". You will be completing a quiz that will ask you 10 questions which will test you on adding, subtracting and multiplying two numbers together. Try your best at each question and good luck!")

import random
from operator import add, sub, mul

count = 0
score = 0
while count <= 9:
    ops = (add, sub, mul)
    op = random.choice(ops)
    x = random.randint(1,10)
    y = random.randint(1,10)

    if op == add:
        print("What is", x, "+",y, "? ")
        question_add = int(input())
        answer_add = op(x,y)
        if question_add == answer_add:
            print("Well done, this is correct!")
            score = score + 1
            count = count + 1
        else:
            print("Sorry, but this is incorrect.")
            count = count + 1

    elif op == sub:
        print("What is", x, "-", y, "? ")
        question_sub = int(input())
        answer_sub = op(x,y)
        if question_sub == answer_sub:
            print("Well done, this is correct!")
            score = score + 1
            count = count + 1
        else:
            print("Sorry, but this is incorrect.")
            count = count + 1

    elif op == mul:
        print("What is", x, "x", y, "? ")
        question_mul = int(input())
        answer_mul = op(x,y)
        if question_mul == answer_mul:
            print("Well done, this is correct!")
            score = score + 1
            count = count + 1
        else:
            print("Sorry, but this is incorrect.")
            count = count + 1

    if count == 10:
        print("Well done "+name+"! You have completed the quiz. Your final score out of 10 is "+str(score)+".")

I have done it, however, I do not think that this is the most efficient way to write this program. Can this program be condensed down further into fewer lines of code?

\$\endgroup\$
0

4 Answers 4

3
\$\begingroup\$

Looping

You want to perform an action 10 times. The loop counter never matters for your code, you just need to ask 10 questions. The way you wrote the code, you have:

count = count + 1

in 6 places, all of which are necessary, otherwise you would loop too many times. That is very error prone. If you added support for division, you would have to write it two more times. Good rule of thumb is never repeat yourself!

The best way to loop 10 times in Python, when you don't care about the loop count, is:

for _ in range(10):
    # stuff

No need to worry about incrementing count. Let range() do it for you.

Don't Repeat Yourself

The code for adding, subtracting, and multiplying is the same. The only difference is what operation you do and what operation you print. You mostly already have that refactored. You do use op() to perform the operation. So let's just go one step further:

ops = (('+', add), ('-', sub), ('*',mul))
op_name, op = random.choice(ops)

And then, I'm pretty much copying your logic for add with just one real change (and renaming some variables):

print("What is", x, op_name ,y, "? ")  # op_name, not +
actual = int(input())
expected = op(x,y)
if actual == expected:
    print("Well done, this is correct!")
    score += 1
else:
    print("Sorry, but this is incorrect.")

I also renamed your variables to actual and expected, instead of question and answer - since question didn't really refer to a question. Also += 1 is a more concise way of expressing incrementing by one.

Tell the user the answer?

If the user gets it wrong, do you want to tell them the correct answer?

Finishing up

You don't need to check count (or, now, _) to know when you're done. Just wait for the loop to end, at which point you can print the score. So the flow looks like:

score = 0
for _ in range(10):
    # prompt a problem and potentially increment score

print("Well done "+name+"! You have completed the quiz. Your final score out of 10 is "+str(score)+".")

Though typically you'd express the score as X/10, not out of 10 is X. That just reads funny.

\$\endgroup\$
1
\$\begingroup\$

You've gotten some good answers regarding how to simplify your code with respect to the overall logic and the use of operations. I would just like to focus a little on printing.

First of all, the recommended version for string interpolation is the following:

x, op_name, y = 123, "+", 321
print("What is {} {} {}?".format(x, op_name, y))

And then there is that really long text at the beginning. Having that long text is not a good option, and given the right output settings you'll have a bit of a problem reading that.

A little based on my own attempts, and some based upon reading "Proper indentation for Python multiline strings", here are some alternatives which tries to print a text with multiple lines (and at the same time making it look nice in code). Two exceptions to expected output: 1) The first multiline which has erroneous indentation (it's correct but not the intended output), and 2) where the dedent() doesn't seem to do it's job:

from textwrap import dedent

if __name__ == '__main__':
    main()

def main():
    """Hello {name} **docstring**,

    You will be completing a quiz
    with long instructions
    """

    name = "holroy"

    print("""Hello {name} ***first multiline***,

        You will be completing a quiz
        with long instructions
        """.format(name= name))

    print( """\
Hello {name} ***second multiline**,

You will be completing a quiz
with long instructions
""".format(name= name))


    print("Hello {name} **implicit concatenation**,\n" "\n"
          "You will be completing a quiz\n"
          "with long instructions\n".format(name= name))

    # Uses the functions docstring
    print(dedent(main.__doc__).format(name= name))


    print(dedent("""\
        Hello {name} **dedented multiline**

        You will be completing a quiz
        with long instructions
        """.format(name= name)))

Out of these I believe the last one, dedented multiline, or the implicit concatenation (with internal \n) are my favourites. (But it's also kind of cool that you could use __doc__ to get the docstring of the function, and possibly use that!)

The two first multiline options fails either on outputted indentation, or not so nice looking code (the code indentation is disturbed).

\$\endgroup\$
1
\$\begingroup\$

I have reviewed your code and have made the following changes:

  • Modularised the program
  • Made logical changes to the variable names
  • Rewritten parts to make it more concise

Edit (suggested by comments):

  • Modularising the program just means to put it into many subroutines. Put it into subroutines that make sense. So asking the user their name and checking it is one part, and thus one subroutine. This makes it easier to debug and also makes it easier to edit and comprehend later on.

  • Logical names for variables just means that it is easier to understand and call them later on.

  • Having concise code is equally important. Not only does it mean that your code is easier to read and understand, it also means it takes less time to compile (which is important with longer scripts)

def name_enter():
    global name
    name = ""
    while name == "" or len(name) > 25 or not re.match(r'^[A-Za-z0-9-]*$', name):
    name = input("Please enter your name: ")
    enter_class()


def enter_class():  
    global class_choice     
    class_choice = None     
    while class_choice not in ["1","3","2"]:    
        class_choice = input("Please enter you class (1, 2, 3): ") 
        print("\nClass entered was " + class_choice)
    mathsquestion()

def mathsquestion():
    global qa, score
    qa, score = 0, 0
    for qa in range(0,10):
        qa = qa + 1
        print("The question you are currently on is: ", qa)
        n1, n2, userans = random.randrange(12), random.randrange(12), ""
        opu = random.choice(["-","+","x"])
        if opu == "+":
            while userans == "" or not re.match(r'^[0-9,-]*$', userans):
                 userans = input("Please solve this: %d" % (n1) + " + %d" % (n2) + " = ") 
             prod = n1 + n2
        elif opu == "-":
            while userans == "" or not re.match(r'^[0-9,-]*$', userans):
                userans = input("Please solve this: %d" % (n1) + " - %d" % (n2) + " = ") 
            prod = n1 - n2
        else:
            while userans == "" or not re.match(r'^[0-9,-]*$', userans):
                userans = input("Please solve this: %d" % (n1) + " x %d" % (n2) + " = ") 
            prod = n1 * n2
    userans = int(userans)
    prod = int(prod)
    if prod == userans:
        score = score + 1
        print("Well done, you have got the question correct. Your score is now: %d" % (score))
    else:
        print("Unfortunatly that is incorrect. The answer you entered was %d" % (userans) + " and the answer is actually %d" % (prod))

    print("Your final score is: %d" % (score))
\$\endgroup\$
0
-2
\$\begingroup\$

You can use a dictionary for your operations and a for-loop for count:

import random
from operator import add, sub, mul

name = input("What is your name? ")
print("Hello, {}. You will be completing a quiz that will ask you 10 questions which will test you on adding, subtracting and multiplying two numbers together. Try your best at each question and good luck!".format(name))

score = 0
for count in range(10):
    ops = {'+': add, '-': sub, 'x': mul}
    op = random.choice(ops.keys())
    x = random.randint(1,10)
    y = random.randint(1,10)

    print("What is {} {} {}? ".format(x, op, y))
    question = int(input())
    answer = ops[op](x,y)
    if question == answer:
        print("Well done, this is correct!")
        score += 1
    else:
        print("Sorry, but this is incorrect.")

print("Well done {}! You have completed the quiz. Your final score out of 10 is {}.".format(name, score))
\$\endgroup\$
4
  • \$\begingroup\$ I have tested this code but after I type in my name it gives me an error: \$\endgroup\$
    – s.zain
    Oct 3, 2015 at 17:37
  • \$\begingroup\$ Traceback (most recent call last): line 10, in <module> op = random.choice(ops) \$\endgroup\$
    – s.zain
    Oct 3, 2015 at 17:38
  • \$\begingroup\$ Good, but explain your reasoning and why this is an improvement over the past code. \$\endgroup\$
    – Caridorc
    Oct 3, 2015 at 17:59
  • \$\begingroup\$ @s.zain See here for why this is a problem. Daniel is likely using Python 2 and was able to use obs.keys() directly as a list, but you'd need list(obs.keys()). \$\endgroup\$ Oct 5, 2015 at 14:20

Not the answer you're looking for? Browse other questions tagged or ask your own question.