How to seed some random numbers in python_w_input questions

How to seed some random numbers in python_w_input questions

by Michael Fairbank -
Number of replies: 2

I'd like my students to be able run a random number generator in their answer code, but their random number generators to be seeded, so that coderunner's auto-marker works consistently.

A typical question I'd like to create is to ask the students to "Write a piece of code that challenges the user to answer a random times-table question, e.g. it asks the user 'What is 3*4?'.  The two numbers you choose to display the user should be randomly chosen integers between 1 and 12, i.e. they should not always be 3 and 4.  Your program should then validate that the user responds with the right answer".


The solution I want the students to produce is as follows:

#import random
#random.seed(4)# Doesn't work properly.
first_number=random.randrange(1,13)
second_number=random.randrange(1,13)
their_answer=int(input("What is "+str(first_number)+" * "+str(second_number)+"? "))
if their_answer==first_number*second_number:
    print("Correct")
else:
    print("Incorrect. The answer should have been",first_number*second_number)

However for the auto-marker to work, how can I set the seed to the python random.randrange function so that we always get the same sequence of integers?

If I put the seed in the student-question answer (as above, but commented out), then first_number and second_number would always be the same in all of the unit tests!  This means students could replace the line "if their_answer==first_number*second_number:" by "if their_answer==12:" say. 

If I set the seed in the prototype then it also seems to reset the seed at the start of every unit test.  

I want to set the seed just once before all unit tests are run.  (e.g. so unit test 1 always picks 3 and 4 as its random numbers; but unit test 2 always picks 5 and 9, say, as its random numbers).


My current prototype is as follows, with my additions in bold here:

__saved_input__ = input
def input(prompt=''):
    s = __saved_input__(prompt)
    print(s)
    return s

import random
random.seed(1) # This isn't working right.  It sets the seed to 1 at the start of every unit test.   


{{ STUDENT_ANSWER }}
SEPARATOR = "#<ab@17943918#@>#"

{% for TEST in TESTCASES %}
{{ TEST.testcode }}
{% if not loop.last %}
print(SEPARATOR)
{% endif %}
{% endfor %}


Does anyone know a solution to this problem?
Thanks!

In reply to Michael Fairbank

Re: How to seed some random numbers in python_w_input questions

by Mike McDowell -
Hi Mike,

I move the student answer into the testcase loop, then inject add my random seed for each test in the TEST.extra field. This is what I'm using for my python questions:

import random, os

# Grabs input items from tests    
__saved_input__ = input
def input(prompt=''):
    s = __saved_input__(prompt)
    print(s)
    return s

# Run the tests as a loop
{% for TEST in TESTCASES %}

# Execute Global Extra (BEFORE Student Code Is Run)
{{ QUESTION.globalextra }}

# Add any precode for tests as needed
{{ TEST.extra }}

# Inspect student code (replace while True, etc)
student_code = """{{ STUDENT_ANSWER | e('py') }}"""

#### Put an if prior to this if you want to check for anything
# eg. if "import" in student_code: print_this_and_exit()   else run this try:
if "quit(" in student_code:
    print("You cannot use quit() in your code or it will fail the tests")
elif "exit(" in student_code:
    print("You cannot use exit() in your code or it will fail the tests")
else:
    try:
        exec(student_code)
    except Exception as e:
        print(f"\nError running your code:\n{e}")

# Run any Test Code here (AFTER the student runs code)
{{ TEST.testcode }}

SEPARATOR = "##"

{% if not loop.last %}
print(SEPARATOR)
{% endif %}
{% endfor %}
@17943918#@>
In reply to Mike McDowell

Re: How to seed some random numbers in python_w_input questions

by Michael Fairbank -
Thank you Mike. That works. A key thing I had to do was to put some code in TEST.extra, so under "Extra template data", for test case 1, I now have "random.seed(1)", and for test case 2, I now have "random.seed(2)", etc.

Then I modified the template like you showed and I move the {{ TEST.extra }} and {{ STUDENT_ANSWER }} into the main loop. So the template now looks like this:

__saved_input__ = input
def input(prompt=''):
s = __saved_input__(prompt)
print(s)
return s

import random

SEPARATOR = "##"

{% for TEST in TESTCASES %}
# Add any precode for tests as needed
{{ TEST.extra }}
{{ STUDENT_ANSWER }}
{{ TEST.testcode }}
{% if not loop.last %}
print(SEPARATOR)
{% endif %}
{% endfor %}


Thank you for your help.