WeBWorK Problems

custom checker for interval answers

Re: custom checker for interval answers

by Davide Cervone -
Number of replies: 0
First, I would recommend that you use the answerHints.pl macros to make this easier. They are designed for exactly this situation (where you want to display a message for a particular wrong answer). Here is a snippet that does that:
    loadMacros("answerHints.pl");
    ...
    Context("Interval");
    ...
    $dom = Compute("(-inf, $b) U ($b, $c) U ($c, inf)");
    $dom_bad = Compute("(-inf, $b) U ($c, inf)");
    ...
    ANS($dom->cmp->withPostFilter(AnswerHints(
      $dom_bad => "Hint:  Yes, x can be less than $b or " .
                  "greater than $c, but what about numbers " .
                  "between $b and $c?"
    )));

It is also possible to make your original approach work, though it is harder. The one thing you need to know is that the domains you are using are not actually Interval objects, they are Union objects. (Note that the Interval() command will "upgrade" to a Union automatically, but I've changed it to Compute() above to avoid making it look like it is supposed to be an interval).

The reason this is important is because the Union object is a subclass of the List object (it is basically an unordered list of Intervals). That means the checker routine is called on the individual list elements (in this case the intervals) rather than on the list (or union) as a whole. So your equality is never true, and the message never generated.

To solve that, you would need to use a list_checker rather than a checker. This gets that array of intervals as the $correct and $student answers, rather than a MathObject itself, but you can use $ans->{correct_value} and $ans->{student_value} to get the MathObjects if you need them (as you do in your case).

So your modified version would be:

    ANS($dom->cmp(list_checker => sub {
      my ($correct, $student, $ans) = @_;
      $correct = $ans->{correct_value};
      $student = $ans->{student_value};
      if ($student == $dom_bad) {
        Value::Error("Hint: Yes, x can be less than $b and " .
        "greater than $c, but what about numbers " .
        "between $b and $c?");
      }
      return ($correct == $student);
    }));
Two things to note: First, I have changed the order of the final comparison to put the correct answer on the left. It doesn't really matter in this case, but would if they were functions, and so it is a good habit to get into. Second, I removed the unneeded return 0 after the error message is generated. Value::Error actually throws an error condition (which is trapped by the main answer checker), and that exits the routine, so the return statement is not needed.

Your original had the pre- and post-filters removed, but I'm not sure why you want that. If you need to do that for some reason, then you will need to be more careful in your custom checker, because you will have to deal with the possibility of there being NO answer provided (one of the pre-filters you removed is the blank answer checker). In that case, the modified routine is:

    ANS($dom->cmp(list_checker => sub {
      my ($correct, $student, $ans) = @_;
      return 0 unless $ans->{original_student_answer =~ /~~S/;
      $correct = $ans->{correct_value};
      $student = $ans->{student_value};
      if ($student == $dom_bad) {
        Value::Error("Hint: Yes, x can be less than $b and " .
        "greater than $c, but what about numbers " .
        "between $b and $c?");
      }
      return ($correct == $student);
    })->withPreFilter('erase')->withPostFilter('erase');
This checks to make sure the student answer contains some non-space character before proceeding. That is not necessary under normal circumstances, since the blank filter usually blocks empty answers from getting passed through, but removing the pre-filters means you have to work harder yourself.

Anyway, hope that does what you need.

Davide