MultiAnswerProblems
MultiAnswer Problems, Linked Answer Blanks
This is the PG code to create a problem with multiple answer blanks which use the answers to all of the answer blanks to decide if the problem is correct.
Note: The example below uses MultiAnswer to check the factorization of a polynomial, and is a good example of how MultiAnswer works. There is a much better way to evaluate polynomial factorization, which is described in FactoringAndExpanding. There is an easy way to allow answers to be entered into several answer blanks in any order which is described at UnorderedAnswers1.
PG problem file  Explanation 

DOCUMENT(); loadMacros( "PGstandard.pl", "MathObjects.pl", "parserMultiAnswer.pl", ); TEXT(beginproblem()); 
Initialization:
In the initialization section of the file, load 
$fac1 = Formula("(1  x)"); $fac2 = Formula("(1 + x)"); $multians = MultiAnswer($fac1, $fac2)>with( singleResult => 0, checker => sub { my ( $correct, $student, $self ) = @_; my ( $f1stu, $f2stu ) = @{$student}; my ( $f1, $f2 ) = @{$correct}; if ( ($f1 == $f1stu && $f2 == $f2stu)  ($f1 == $f2stu && $f2 == $f1stu) ) { return [1,1]; } else { if ($f1 == $f1stu  $f2 == $f1stu) { return [1,0]; } elsif ($f1 == $f2stu  $f2 == $f2stu) { return [0,1]; } else { return [0,0]; } } } ); 
Setup: In the problem setup section of the file we define a MultiAnswer object that knows how to deal with the problem. Here we define an object that will take two answers and check that they are correct (in either order).
First, the
Then, the
The checker routine then returns a reference to a list of results for the problem. In this case there are two answer blanks, so there are two return values. All return values should be 0 or 1, according to whether the answer for that answer blank is correct or not. Note that if we made this an "all or nothing" problem (that is, we set It is possible to set an answer message that will be displayed when the problem is checked, too. For example, if we wanted to set a message when one of the parts was wrong, we could replace the section of the checker code that deals with incorrect answers with: if ($f1 == $f1stu  $f2 == $f1stu) { $self>setMessage(1,"This is correct."); $self>setMessage(2,"Check your answer " . "by using FOIL."); return [1,0]; } elsif ($f1 == $f1stu  $f2 == $f2stu) { $self>setMessage(1,"Check your answer " . "by using FOIL."); $self>setMessage(2,"This is correct."); return [0,1]; } else { return [0,0]; } 
BEGIN_TEXT Factor: \(1x^2 = \big( \) \{$multians>ans_rule(10)\} \( \big) \big( \) \{$multians>ans_rule(10)\} \( \big) \) END_TEXT 
Main Text: In the text section of the problem we proceed as expected, but define the answer blanks using the MultiAnswer object that we defined in the initialization section of the problem. 
ANS( $multians>cmp() ); ENDDOCUMENT(); 
Answer evaluation: And the answer and solution section of the file is straightforward. 
As noted above, there are a number of other options that can be supplied to the MultiAnswer object. allowBlankAnswers
and checkTypes
are described below. Others include:

separator
: If the answer is checked withsingleResult => 1
, this gives the string to use between entries in the results area. By default this is a semicolon, but we could specify, for example,separator => ' and '
. 
tex_separator
: Similar toseparator
, but but for the preview area of the answer display.
Next, consider allowBlankAnswers
and checkTypes
. These allow the answer checker to more flexibly deal with student input, but require that it be more robust to student errors. To illustrate allowBlankAnswers
, suppose we want to check two numbers and their difference, and that we want to give credit for the first two answers even if the difference is not supplied. We could do this with the following checker.
$multians = MultiAnswer($ans1, $ans2, $diff)>with( singleResult => 0, allowBlankAnswers => 1, checker => sub { my ( $correct, $student, $self ) = @_; my ( $a1stu, $a2stu, $diffstu ) = @{$student}; my ( $a1, $a2, $diff ) = @{$correct}; my @ret = ( $a1stu == $a1, $a2stu == $a2, 0); # then check to be sure that we have numbers for both values # before we take their difference if ( ref($a1stu) eq ref($a1) && ref($a2stu) eq ref($a2) ) { $ret[2] = ( $a1stu  $a2stu ) == $diffstu; } # in this case we might want to warn if the difference is # correct but doesn't match the correct difference if ( $ret[2] && $diffstu != $diff ) { $self>setMessage(3,'Your difference is correct, but one ' . 'or both of your numbers is/are wrong. ' . 'Be sure to recalculate your difference ' . 'when you correct the number(s).'); } return [ @ret ]; } );
Lastly, checkTypes
is useful if the answer blanks in the problem have different types (e.g., if the formula 1x and the string "anything" can be entered in either order). In this case there will be trouble checking the answers, because answer checking will be stopped if the type of the student answer doesn't match that of the input answer. To turn off type checking in the MultiAnswer>with()
call we use the checkTypes
flag, and then have to add type checking in the answer checker. For example,
$multians = MultiAnswer($ans1, $ans2)>with( singleResult => 0, checkTypes => 0, checker => sub { my ( $correct, $student, $self ) = @_; my ( $a1stu, $a2stu ) = @{$student}; my ( $a1, $a2 ) = @{$correct}; if ( ( ( ref($a1stu) eq ref($a1) && $a1stu == $a1 ) && ( ref($a2stu) eq ref($a2) && $a2stu == $a2 ) )  ( ( ref($a1stu) eq ref($a2) && $a1stu == $a2 ) && ( ref($a2stu) eq ref($a1) && $a2stu == $a1 ) ) ) { return [1,1]; } elsif ( ( ref($a1stu) eq ref($a1) && $a1stu == $a1 )  ( ref($a1stu) eq ref($a2) && $a1stu == $a2 ) ) { return [1,0]; } elsif ( ( ref($a2stu) eq ref($a1) && $a2stu == $a1 )  ( ref($a2stu) eq ref($a2) && $a2stu == $a2 ) ) { return [0,1]; } else { return [0,0]; } } );
 POD documentation: parserMultiAnswer.pl.html
 PG macro: parserMultiAnswer.pl