parserRadioMultiAnswer.pl - Radio answer questions with dependent answers.
RadioMultiAnswer objects let you tie a radio answer together with several answer blanks that are dependent on the radio choice. The RadioMultiAnswer produces a single result in the answer results area. This macro requires javascript to function properly.
To create a RadioMultiAnswer pass a list of lists each with an sprintf-style string and answers, followed by the index of the correct part to RadioMultiAnswer() in the order they will appear in the problem. For example:
$rma = RadioMultiAnswer([
['The unique solution is \(x=\) %s and \(y=\) %s.', 5, 6],
['There are an infinite number of solutions parameterized by '
. '\(x=\) %s and \(y=\) %s.', '23-3t', 't']
['There are no solutions.']
], 0);
The sprintf '%s'
format specifiers are where the answer rules for the answers listed in the part will be placed. If '%s*'
is used instead of the '%s'
format specifier, then an answer array will be used instead of a single answer rule for that answer. In each part there should be one of these format specifiers for each answer in that part.
Then, use $rma->ans_rule to create the radio parts and answer blanks inside. Note that you only call ans_rule once for each RadioMultiAnswer object. You can pass the width of all of the blanks, which defaults to 20 otherwise. For example:
BEGIN_TEXT
Solve the system of linear equations \(x+3y = 23\) and \(2x+y=16\).
\{$rma->ans_rule(10)\}
END_TEXT
Then, call $rma->cmp to produce the answer evaluator for the RadioMultiAnswer.
For PGML:
BEGIN_PGML
Solve the system of linear equations [`x+3y=23`] and [`2x+y=16`].
[__________]{$rma}
END_PGML
You may provide a checker routine that will be called to determine if the answers are correct or not. If one is not provided a default checker will be used. The default checker returns 1 if the student selects the correct radio answer, and all answers in that part are equal to correct answers in that part. The checker will only be called if the student answers have no syntax errors and their types match the types of the correct answers, so you don't have to worry about handling bad data from the student (at least as far as type checking goes).
The checker routine should accept four parameters: a reference to the array of correct answers, a reference to the array of student answers, a reference to the RadioMultiAnswer itself, and a reference to the answer hash. It should do whatever checking it needs to do and then return a score for the RadioMultiAnswer as a whole (every answer blank will be given the same score). You can add error messages in the checker routine by calling the RadioMultiAnswer's appendMessage() method. For example:
$rma->appendMessage('The function can't be the identity');
You can also call Value::Error() in the checker routine to generate an error and die.
The checker routine can be supplied either when the RadioMultiAnswer is created, or when the cmp() method is called. For example:
$rma = RadioMultiAnswer(...,
checker => sub {
my ($correct, $student, $self, $ans) = @_; # get the parameters
my ($radio_cor, $a_cor, $b_cor) = @$correct; # extract the correct answers
my ($radio_stu, $a_stu, $b_stu) = @$student; # extract the student answers
return ($radio_cor == $radio_stu
&& $a_cor->[0] == $a_stu->[0]
&& $a_cor->[1] == $a_stu->[1]);
}
);
ANS($rma->cmp);
or
$rma = RadioMultiAnswer(...);
sub check {
my ($correct, $student, $self, $ans) = @_; # get the parameters
my ($radio_cor, $a_cor, $b_cor) = @$correct; # extract the correct answers
my ($radio_stu, $a_stu, $b_stu) = @$student; # extract the student answers
return ($radio_cor == $radio_stu
&& $a_cor->[0] == $a_stu->[0]
&& $a_cor->[1] == $a_stu->[1]);
};
ANS($rma->cmp(checker => ~~&check));
See the checker option below for more details.
RadioMultiAnswer([['First part %s, %s', $answer1, $answer2],
['Second part %s, %s', $answer3, $answer4],
['Third part']], 0);
RadioMultiAnswer(...)->with(...);
Create a new RadioMultiAnswer item from a list of lists each of which has a first element that is a sprintf style string and the remaining elements of each list are items. The items are converted to Value items, if they aren't already.
There are a number of options that you can set. These can be passed directly to the constructor or set as parameters to the with() method called on the RadioMultiAnswer object.
A subroutine to be called to check the student answers. The routine is passed four parameters: a reference to the array of correct answers, a reference to the array of student answers, a reference to the RadioMultiAnswer object itself, and a reference to the checker's answer hash. The routine should return a score from 0 to 1. If this is not defined, then this will be set to a default checker that returns 1 if the student selects the correct radio answer, and all answers in that part are equal to correct answers in that part, and 0 otherwise.
The structures of the array of student answers and the array of correct answers are the same. The first entry of each will be a number from 1 up to the number of radio answers in the problem. The remaining entries will be array references to the answers for each part. So the first entry can be used to access the index of the array containing the answers for the correct part. For example, $correct->[$correct->[0]] and $student->[$correct->[0]]. Note that the student answers in the parts that are not selected will always be blank. This is enforced by javascript.
So, for the CONSTRUCTOR example shown above the correct answer array will be
[ 1, [ $answer1, $answer2 ], [ $answer3, $answer4 ], [] ]
and if the student selects the incorrect second part, then the student answer array will be:
[ 2, [ '', '' ], [ '5t+2', 't' ], [] ]
where the entries in the latter arrays are not actually strings but are MathObjects.
Whether to use named rules or default rule names. Use named rules if you need to intersperse other rules with the one for the RadioMultiAnswer, in which case you must use NAMED_ANS not ANS.
This is a hash of options that will be passed to the cmp method. For example, cmpOpts => { weight => 0.5 }
. This option is provided to make it more convenient to pass options to cmp when utilizing PGML.
Whether the types of the student and correct answers must match exactly or just pass the usual type-match error checking (in which case, you should check the types before you use the data).
Whether to remove the blank-check pre-filter from the answer checkers that is used for type checking the student's answers.
The string to use between entries in the results area.
The string to use between entries in the preview area.
A reference to a list of sprintf-style strings used to format the students answers for the results area. If undefined, the separator parameter (above) is used to form the string.
A reference to a list of sprintf-style string used to format the students answer previews when singleResults mode is in effect. If undefined, the tex_separator (above) is used to form the string.
A number or a nested list that gives the sizes of the answer blanks. If this is a number then all answer rules will use that for the size. If this is a nested list, then it should contain a list of sizes for each rule in each part. If there are not enough sizes in each sub list, then a default of 20 will be used. If this is not defined, then WeBWorK defaults will be used. The sizes of the answer blanks can also be set via the argument to ans_rule. The same types of arguments are accepted there.
This determines what label to show for each choice. The default is "ABC", which results in upper case alphabetic labels, starting with A. If the value is "123" then the choices will be labeled with numbers. The value of labels may also be a list of labels for each choice (e.g. [label1,label2,...]
). Note that if you give a list of labels and do not supply enough labels for the number of radio choices in your problem, expect inconsistent labelling.
Values are the form of the student answer that will be displayed in the past answers table for the radio button choices part of the answer. By default these are B0, B1, etc. However, that can be changed with this option. The value of the option should be a reference to an array containing the values for the choices. For example:
values => [ 'first choice', 'second choice', ... ]
If a choice is not represented in the hash, then Bn
will be used for the value instead where n
is the 0 based index of the choice.
These values can be any descriptive string that is unique for the choice, but care should be taken to ensure that these values do not indicate which choice is the correct answer.
${BBOLD}%s.${EBOLD}
)Specifies a format string to use when displaying labels before the choice text. It is an sprintf string that contains '%s'
where the label should go. The default value produces the label followed by a period in bold.
Specifies whether labels should be displayed after the radio button and before its text. This makes the association between the choices and the label used as an answer more explicit.
The index (starting at zero) of the radio button to be checked initially. By default this is undefined, which means that none of the radio buttons are initially checked.
If this is set to 1 or "shift" then it is possible to uncheck a radio button by clicking it when it is checked. If this is set to "shift", unchecking requires the shift key to be pressed.