Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
bf5bac8f
Commit
bf5bac8f
authored
Feb 19, 2014
by
Nick Parlante
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #2652 from edx/nick/fix-shuffle-bug
Fix randomization bug for multiple questions per problem
parents
4bbbd400
0c583d9e
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
105 additions
and
27 deletions
+105
-27
common/lib/capa/capa/capa_problem.py
+5
-2
common/lib/capa/capa/responsetypes.py
+28
-21
common/lib/capa/capa/tests/test_answer_pool.py
+72
-4
No files found.
common/lib/capa/capa/capa_problem.py
View file @
bf5bac8f
...
@@ -179,9 +179,12 @@ class LoncapaProblem(object):
...
@@ -179,9 +179,12 @@ class LoncapaProblem(object):
self
.
inputs
=
{}
self
.
inputs
=
{}
# Run response late_transforms last (see MultipleChoiceResponse)
# Run response late_transforms last (see MultipleChoiceResponse)
for
response
in
self
.
responders
.
values
():
# Sort the responses to be in *_1 *_2 ... order.
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
()
response
.
late_transforms
(
self
)
self
.
extracted_tree
=
self
.
_extract_html
(
self
.
tree
)
self
.
extracted_tree
=
self
.
_extract_html
(
self
.
tree
)
...
...
common/lib/capa/capa/responsetypes.py
View file @
bf5bac8f
...
@@ -775,13 +775,14 @@ class MultipleChoiceResponse(LoncapaResponse):
...
@@ -775,13 +775,14 @@ class MultipleChoiceResponse(LoncapaResponse):
else
:
else
:
choice
.
set
(
"name"
,
name
)
choice
.
set
(
"name"
,
name
)
def
late_transforms
(
self
):
def
late_transforms
(
self
,
problem
):
"""Rearrangements run late in the __init__ process.
"""
Cannot do these at response init time, as not enough
Rearrangements run late in the __init__ process.
other stuff exists at that time.
Cannot do these at response init time, as not enough
other stuff exists at that time.
"""
"""
self
.
do_shuffle
(
self
.
xml
)
self
.
do_shuffle
(
self
.
xml
)
self
.
do_answer_pool
(
self
.
xml
)
self
.
do_answer_pool
(
self
.
xml
,
problem
)
def
get_score
(
self
,
student_answers
):
def
get_score
(
self
,
student_answers
):
"""
"""
...
@@ -881,7 +882,21 @@ class MultipleChoiceResponse(LoncapaResponse):
...
@@ -881,7 +882,21 @@ class MultipleChoiceResponse(LoncapaResponse):
rng
.
shuffle
(
middle
)
rng
.
shuffle
(
middle
)
return
head
+
middle
+
tail
return
head
+
middle
+
tail
def
do_answer_pool
(
self
,
tree
):
def
get_rng
(
self
,
problem
):
"""
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
...
@@ -893,15 +908,7 @@ class MultipleChoiceResponse(LoncapaResponse):
...
@@ -893,15 +908,7 @@ 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'
)
...
@@ -923,7 +930,7 @@ class MultipleChoiceResponse(LoncapaResponse):
...
@@ -923,7 +930,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
,
rn
d
,
num_choices
)
(
solution_id
,
subset_choices
)
=
self
.
sample_from_answer_pool
(
choices_list
,
rn
g
,
num_choices
)
# Add back in randomly selected choices
# Add back in randomly selected choices
for
choice
in
subset_choices
:
for
choice
in
subset_choices
:
...
@@ -939,7 +946,7 @@ class MultipleChoiceResponse(LoncapaResponse):
...
@@ -939,7 +946,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
,
rn
d
,
num_pool
):
def
sample_from_answer_pool
(
self
,
choices
,
rn
g
,
num_pool
):
"""
"""
Takes in:
Takes in:
1. list of choices
1. list of choices
...
@@ -972,15 +979,15 @@ class MultipleChoiceResponse(LoncapaResponse):
...
@@ -972,15 +979,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
=
rn
d
.
randint
(
0
,
len
(
correct_choices
)
-
1
)
index
=
rn
g
.
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 rn
d
.shuffle()
# Put together the result, pushing most of the work onto rn
g
.shuffle()
subset_choices
=
[
correct_choice
]
subset_choices
=
[
correct_choice
]
rn
d
.
shuffle
(
incorrect_choices
)
rn
g
.
shuffle
(
incorrect_choices
)
subset_choices
+=
incorrect_choices
[:
num_incorrect
]
subset_choices
+=
incorrect_choices
[:
num_incorrect
]
rn
d
.
shuffle
(
subset_choices
)
rn
g
.
shuffle
(
subset_choices
)
return
(
solution_id
,
subset_choices
)
return
(
solution_id
,
subset_choices
)
...
...
common/lib/capa/capa/tests/test_answer_pool.py
View file @
bf5bac8f
...
@@ -359,12 +359,13 @@ class CapaAnswerPoolTest(unittest.TestCase):
...
@@ -359,12 +359,13 @@ class CapaAnswerPoolTest(unittest.TestCase):
"""
)
"""
)
problem
=
new_loncapa_problem
(
xml_str
,
seed
=
723
)
problem
=
new_loncapa_problem
(
xml_str
)
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>.*\[.*'correct-2'.*'wrong-2'.*'wrong-3'.*\].*</div>"
str2
=
r"<div>.*\[.*'wrong-2'.*'wrong-1'.*'correct-2'.*\].*</div>"
# rng shared
# 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>"
...
@@ -447,11 +448,12 @@ class CapaAnswerPoolTest(unittest.TestCase):
...
@@ -447,11 +448,12 @@ 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-
1'.*'wrong-4'.*'wrong-3'.*'correct-1
'.*\].*</div>"
str2
=
r"<div>.*\[.*'wrong-
2'.*'wrong-3'.*'wrong-4'.*'correct-2
'.*\].*</div>"
str3
=
r"<div>\{.*'1_solution_1'.*\}</div>"
str3
=
r"<div>\{.*'1_solution_1'.*\}</div>"
str4
=
r"<div>\{.*'1_solution_
3
'.*\}</div>"
str4
=
r"<div>\{.*'1_solution_
4
'.*\}</div>"
self
.
assertRegexpMatches
(
the_html
,
str1
)
self
.
assertRegexpMatches
(
the_html
,
str1
)
self
.
assertRegexpMatches
(
the_html
,
str2
)
self
.
assertRegexpMatches
(
the_html
,
str2
)
...
@@ -463,6 +465,72 @@ class CapaAnswerPoolTest(unittest.TestCase):
...
@@ -463,6 +465,72 @@ 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>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment