WeBWorK Problems

Checking submitted answers into a formula

Re: Checking submitted answers into a formula

by Davide Cervone -
Number of replies: 0
Robin is right, the MultiAnswer object is what you want, here. I don't really care for the example from the wiki, however, as the use of ref is not really he right way to do the type checking.

Here is my example of the problem that you requested (where three points are required):

    DOCUMENT();

    loadMacros(
      "PGstandard.pl",
      "MathObjects.pl",
      "parserMultiAnswer.pl",
    );
    
    Context("Point");
    Parser::BOP::equality->Allow;     # Allow equalities in formulas
    
    $f = Compute("x^2 + y^2 = 1");    # The equation the students must match
    
    #
    #  The is the MultiAnswer object, with three correct points
    #  (only used for showing the correct answer).  It allows 
    #  blank answers, so students can add one point at a
    #  time, and it checks that no two points are equal.
    #
    $ma = MultiAnswer("(1,0)","(0,1)","(-1,0)")->with(
      function => $f,
      allowBlankAnswers => 1,
      checker => sub {
         my ($correct,$student,$self) = @_;
         my @student = @$student;    # The array of student answers
         my $n = scalar(@student);   # How many are in the array
         my @correct = (0) x $n;     # An array of that many zeros (assume none correct)
         foreach my $i (0..$n-1) {   # Loop through the student answers
            next unless $student[$i]->classMatch("Point");  # Go on if the answer is blank
            my ($x,$y) = $student[$i]->value;               # Get the x and y coordinates
            if ($self->{function}->eval(x=>$x,y=>$y)) {     # If the equation is satisfied (1 if equal, 0 if not)
              $correct[$i] = 1;                             # Indicate answer is correct
              foreach my $j (0..$i-1) {                     # Look through previous points
                if ($student[$j] == $student[$i]) {         # If this point equals a previous one
                  $self->setMessage($i+1,"This point is the same as the ".Value->NameForNumber($j+1)." one");
                  $correct[$i] = 0;                         # Give a warning and mark incorrect
                  break;                                    # Stop looking through previous points
                }
              }
            }
         }
         return @correct;    # Return the array indicating which are correct
      }
    );
    
    Context()->texStrings;
    BEGIN_TEXT
    Three distinct points that lie on \($f\) are:$BR
    \{$ma->ans_rule(15)\}, \{$ma->ans_rule(15)\}, and \{$ma->ans_rule(15)\}
    END_TEXT
    Context()->normalStrings;
    
    ANS($ma->cmp);
    
    ENDDOCUMENT();
Note that this checker works for any number of points. The checker itself could be packaged into a separate macro file for re-use, if so desired. For example, the macro file could contain
    $points_on_function = sub {
      my ($correct,$student,$self) = @_;
      ...
      return @correct;
    };
then you could use it as
    MultiAnswer("(1,0)","(0,1"),"(-1,0)")->with(
      function => $f,
      allowBlankAnswers => 1,
      checker => $points_on_function,
    );
In fact, in that case, you could do something like
    sub PointsOnFunction {
      my $f = shift;
      return MultiAnswer(@_)->with(
         function => Compute($f),
         allowBlankAnswers => 1,
         checker => $points_on_function,
      );
    }
in the macro file, and then just do
    PointsOnFunction("x^2+y^2=1","(1,0)","(0,1)","(-1,0)");
in the main program. Note that none of the approaches above checks that the correct answers are actually on the function given, so we assume you have them right. :-)

Hope that does the trick.

Davide