Commit cce63f6e by Jason Bau

Revert "Fix randomization bug for multiple questions per problem"

This reverts commit 0c583d9e.
parent f4837d91
...@@ -179,12 +179,9 @@ class LoncapaProblem(object): ...@@ -179,12 +179,9 @@ class LoncapaProblem(object):
self.inputs = {} self.inputs = {}
# Run response late_transforms last (see MultipleChoiceResponse) # Run response late_transforms last (see MultipleChoiceResponse)
# Sort the responses to be in *_1 *_2 ... order. for response in self.responders.values():
responses = self.responders.values()
responses = sorted(responses, key = lambda resp: int(resp.id[resp.id.rindex('_')+1:]) )
for response in responses:
if hasattr(response, 'late_transforms'): if hasattr(response, 'late_transforms'):
response.late_transforms(self) response.late_transforms()
self.extracted_tree = self._extract_html(self.tree) self.extracted_tree = self._extract_html(self.tree)
......
...@@ -776,14 +776,13 @@ class MultipleChoiceResponse(LoncapaResponse): ...@@ -776,14 +776,13 @@ class MultipleChoiceResponse(LoncapaResponse):
else: else:
choice.set("name", name) choice.set("name", name)
def late_transforms(self, problem): def late_transforms(self):
""" """Rearrangements run late in the __init__ process.
Rearrangements run late in the __init__ process.
Cannot do these at response init time, as not enough Cannot do these at response init time, as not enough
other stuff exists at that time. other stuff exists at that time.
""" """
self.do_shuffle(self.xml) self.do_shuffle(self.xml)
self.do_answer_pool(self.xml, problem) self.do_answer_pool(self.xml)
def get_score(self, student_answers): def get_score(self, student_answers):
""" """
...@@ -883,21 +882,7 @@ class MultipleChoiceResponse(LoncapaResponse): ...@@ -883,21 +882,7 @@ class MultipleChoiceResponse(LoncapaResponse):
rng.shuffle(middle) rng.shuffle(middle)
return head + middle + tail return head + middle + tail
def get_rng(self, problem): def do_answer_pool(self, tree):
"""
Get the random number generator to be shared by responses
off the problem, creating it on the problem if needed.
"""
# Multiple answer-pool questions share one rng stored on the problem.
# If each question got its own rng, the structure
# could appear predictable to the student, e.g. (c) keeps being the
# correct choice.
# TODO: could make the sharing some sort of authoring option
if not hasattr(problem, 'shared_rng'):
problem.shared_rng = random.Random(self.context['seed'])
return problem.shared_rng
def do_answer_pool(self, tree, problem):
""" """
Implements the answer-pool subsetting operation in-place on the tree. Implements the answer-pool subsetting operation in-place on the tree.
Allows for problem questions with a pool of answers, from which answer options shown to the student Allows for problem questions with a pool of answers, from which answer options shown to the student
...@@ -909,7 +894,15 @@ class MultipleChoiceResponse(LoncapaResponse): ...@@ -909,7 +894,15 @@ class MultipleChoiceResponse(LoncapaResponse):
Calling this a second time does nothing. Calling this a second time does nothing.
""" """
choicegroups = tree.xpath("choicegroup[@answer-pool]") choicegroups = tree.xpath("choicegroup[@answer-pool]")
rng = self.get_rng(problem)
# Uses self.seed -- but want to randomize every time reaches this problem,
# so problem's "randomization" should be set to "always"
rnd = random.Random(self.context['seed'])
# TODO: currently, each choicegroup uses the seed from the problem.
# This has the unfortunate effect of making choicegroups look similar
# when we have many in one problem. Could perturb the rnd a little
# per choicegroup so they look different.
for choicegroup in choicegroups: for choicegroup in choicegroups:
num_str = choicegroup.get('answer-pool') num_str = choicegroup.get('answer-pool')
...@@ -931,7 +924,7 @@ class MultipleChoiceResponse(LoncapaResponse): ...@@ -931,7 +924,7 @@ class MultipleChoiceResponse(LoncapaResponse):
choicegroup.remove(choice) choicegroup.remove(choice)
# Sample from the answer pool to get the subset choices and solution id # Sample from the answer pool to get the subset choices and solution id
(solution_id, subset_choices) = self.sample_from_answer_pool(choices_list, rng, num_choices) (solution_id, subset_choices) = self.sample_from_answer_pool(choices_list, rnd, num_choices)
# Add back in randomly selected choices # Add back in randomly selected choices
for choice in subset_choices: for choice in subset_choices:
...@@ -947,7 +940,7 @@ class MultipleChoiceResponse(LoncapaResponse): ...@@ -947,7 +940,7 @@ class MultipleChoiceResponse(LoncapaResponse):
if solution.get('explanation-id') != solution_id: if solution.get('explanation-id') != solution_id:
solutionset.remove(solution) solutionset.remove(solution)
def sample_from_answer_pool(self, choices, rng, num_pool): def sample_from_answer_pool(self, choices, rnd, num_pool):
""" """
Takes in: Takes in:
1. list of choices 1. list of choices
...@@ -980,15 +973,15 @@ class MultipleChoiceResponse(LoncapaResponse): ...@@ -980,15 +973,15 @@ class MultipleChoiceResponse(LoncapaResponse):
num_incorrect = min(num_incorrect, len(incorrect_choices)) num_incorrect = min(num_incorrect, len(incorrect_choices))
# Select the one correct choice # Select the one correct choice
index = rng.randint(0, len(correct_choices) - 1) index = rnd.randint(0, len(correct_choices) - 1)
correct_choice = correct_choices[index] correct_choice = correct_choices[index]
solution_id = correct_choice.get('explanation-id') solution_id = correct_choice.get('explanation-id')
# Put together the result, pushing most of the work onto rng.shuffle() # Put together the result, pushing most of the work onto rnd.shuffle()
subset_choices = [correct_choice] subset_choices = [correct_choice]
rng.shuffle(incorrect_choices) rnd.shuffle(incorrect_choices)
subset_choices += incorrect_choices[:num_incorrect] subset_choices += incorrect_choices[:num_incorrect]
rng.shuffle(subset_choices) rnd.shuffle(subset_choices)
return (solution_id, subset_choices) return (solution_id, subset_choices)
......
...@@ -359,13 +359,12 @@ class CapaAnswerPoolTest(unittest.TestCase): ...@@ -359,13 +359,12 @@ class CapaAnswerPoolTest(unittest.TestCase):
""") """)
problem = new_loncapa_problem(xml_str) problem = new_loncapa_problem(xml_str, seed=723)
the_html = problem.get_html() the_html = problem.get_html()
print the_html print the_html
str1 = r"<div>.*\[.*'wrong-3'.*'correct-2'.*'wrong-2'.*'wrong-4'.*\].*</div>" str1 = r"<div>.*\[.*'wrong-3'.*'correct-2'.*'wrong-2'.*'wrong-4'.*\].*</div>"
str2 = r"<div>.*\[.*'wrong-2'.*'wrong-1'.*'correct-2'.*\].*</div>" # rng shared str2 = r"<div>.*\[.*'correct-2'.*'wrong-2'.*'wrong-3'.*\].*</div>"
# str2 = r"<div>.*\[.*'correct-2'.*'wrong-2'.*'wrong-3'.*\].*</div>" # rng independent
str3 = r"<div>\{.*'1_solution_2'.*\}</div>" str3 = r"<div>\{.*'1_solution_2'.*\}</div>"
str4 = r"<div>\{.*'1_solution_4'.*\}</div>" str4 = r"<div>\{.*'1_solution_4'.*\}</div>"
...@@ -448,12 +447,11 @@ class CapaAnswerPoolTest(unittest.TestCase): ...@@ -448,12 +447,11 @@ class CapaAnswerPoolTest(unittest.TestCase):
problem = new_loncapa_problem(xml_str, seed=9) problem = new_loncapa_problem(xml_str, seed=9)
the_html = problem.get_html() the_html = problem.get_html()
print the_html
str1 = r"<div>.*\[.*'wrong-4'.*'wrong-3'.*'correct-1'.*\].*</div>" str1 = r"<div>.*\[.*'wrong-4'.*'wrong-3'.*'correct-1'.*\].*</div>"
str2 = r"<div>.*\[.*'wrong-2'.*'wrong-3'.*'wrong-4'.*'correct-2'.*\].*</div>" str2 = r"<div>.*\[.*'wrong-1'.*'wrong-4'.*'wrong-3'.*'correct-1'.*\].*</div>"
str3 = r"<div>\{.*'1_solution_1'.*\}</div>" str3 = r"<div>\{.*'1_solution_1'.*\}</div>"
str4 = r"<div>\{.*'1_solution_4'.*\}</div>" str4 = r"<div>\{.*'1_solution_3'.*\}</div>"
self.assertRegexpMatches(the_html, str1) self.assertRegexpMatches(the_html, str1)
self.assertRegexpMatches(the_html, str2) self.assertRegexpMatches(the_html, str2)
...@@ -465,72 +463,6 @@ class CapaAnswerPoolTest(unittest.TestCase): ...@@ -465,72 +463,6 @@ class CapaAnswerPoolTest(unittest.TestCase):
self.assertRegexpMatches(without_new_lines, str1 + r".*" + str2) self.assertRegexpMatches(without_new_lines, str1 + r".*" + str2)
self.assertRegexpMatches(without_new_lines, str3 + r".*" + str4) self.assertRegexpMatches(without_new_lines, str3 + r".*" + str4)
def test_answer_pool_random_consistent(self):
"""
The point of this test is to make sure that the exact randomization
per seed does not change.
"""
xml_str = textwrap.dedent("""
<problem>
<multiplechoiceresponse>
<choicegroup type="MultipleChoice" answer-pool="2">
<choice correct="false">wrong-1</choice>
<choice correct="false">wrong-2</choice>
<choice correct="true">correct-1</choice>
<choice correct="false">wrong-3</choice>
<choice correct="false">wrong-4</choice>
<choice correct="true">correct-2</choice>
<choice correct="true">correct-3</choice>
</choicegroup>
</multiplechoiceresponse>
<multiplechoiceresponse>
<choicegroup type="MultipleChoice" answer-pool="3">
<choice correct="false">wrong-1</choice>
<choice correct="false">wrong-2</choice>
<choice correct="true">correct-1</choice>
<choice correct="false">wrong-3</choice>
<choice correct="false">wrong-4</choice>
<choice correct="true">correct-2</choice>
<choice correct="true">correct-3</choice>
</choicegroup>
</multiplechoiceresponse>
<multiplechoiceresponse>
<choicegroup type="MultipleChoice" answer-pool="2">
<choice correct="false">wrong-1</choice>
<choice correct="false">wrong-2</choice>
<choice correct="true">correct-1</choice>
<choice correct="false">wrong-3</choice>
<choice correct="false">wrong-4</choice>
<choice correct="true">correct-2</choice>
<choice correct="true">correct-3</choice>
</choicegroup>
</multiplechoiceresponse>
<multiplechoiceresponse>
<choicegroup type="MultipleChoice" answer-pool="3">
<choice correct="false">wrong-1</choice>
<choice correct="false">wrong-2</choice>
<choice correct="true">correct-1</choice>
<choice correct="false">wrong-3</choice>
<choice correct="false">wrong-4</choice>
<choice correct="true">correct-2</choice>
<choice correct="true">correct-3</choice>
</choicegroup>
</multiplechoiceresponse>
</problem>
""")
problem = new_loncapa_problem(xml_str)
the_html = problem.get_html()
str1 = (r"<div>.*\[.*'correct-2'.*'wrong-2'.*\].*</div>.*" +
r"<div>.*\[.*'wrong-1'.*'correct-2'.*'wrong-4'.*\].*</div>.*" +
r"<div>.*\[.*'correct-1'.*'wrong-4'.*\].*</div>.*" +
r"<div>.*\[.*'wrong-1'.*'wrong-2'.*'correct-1'.*\].*</div>")
without_new_lines = the_html.replace("\n", "")
self.assertRegexpMatches(without_new_lines, str1)
def test_answer_pool_and_no_answer_pool(self): def test_answer_pool_and_no_answer_pool(self):
xml_str = textwrap.dedent(""" xml_str = textwrap.dedent("""
<problem> <problem>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment