WeBWorK Problems

Student answers and order of their answers

Re: Student answers and order of their answers

by Davide Cervone -
Number of replies: 0
I finally got a chance to write up an example of how to do what you are asking. As Danny points out, it is not trivial, and so the code has some subtitles. A lot of that is due to the fact that it can give partial credit and report warnings about which answers are not correct, if you want, and that takes some effort. It is controlled by the $showPartialCorrectAnswers variable (and also whether the "Preview Answers" or "Submit Answers" button was pressed).

Here is the example code:


loadMacros(
  "PGchoicemacros.pl",
  "parserMultiAnswer.pl"
);

$showPartialCorrectAnswers = 1;

Context("Numeric")->strings->add('infinite' => {}, 'removable' => {});

$ma = MultiAnswer("0,2", "infinite, removable")->with(
  checker => sub {
    my ($c, $s, $self, $ans) = @_;
    my ($clist, $ctype) = @$c;
    my ($slist, $stype) = @$s;
    Value->Error('Number of x-values and discontinuity types must be the same')
      unless $slist->length == $stype->length;

    my $check = $clist->cmp->evaluate($slist);
    $self->setMessage(1,$check->{ans_message}) if $check->{ans_message};
    if ($check->score < 1) {
      $self->setMessage(2,"(Disconinutites not tested until x-coordinates are correct)")
        if $check->score && !$ans->{isPreview};
      return ($check->score, 0);
    }

    my @sx = $slist->value;
    my $sn = scalar(@sx);
    my @si = invert(PGsort(sub {$sx[$_[0]] < $sx[$_[1]]}, (0..$sn-1)));
    my @errors = ();
    foreach my $i (0..$sn-1) {
      push(@errors,"Your ".$ctype->NameForNumber($i+1)." discontinuity is incorrect")
        unless $ctype->extract($si[$i]+1) == $stype->extract($i+1);
    }

    my $score = ($sn - scalar(@errors)) / $sn;
    $score = 0 if !$showPartialCorrectAnswers && @errors;
    @errors = ("All your discontinuities are incorrect") if $score == 0;
    $self->setMessage(2,join($BR,@errors))
      if @errors && $showPartialCorrectAnswers && !$ans->{isPreview};
    
    return (1, $score);
  }
);

BEGIN_TEXT
\{$ma->ans_rule()\} and \{$ma->ans_rule()\}
END_TEXT

ANS($ma->cmp);

This uses a MultiAnswer object to tie the two lists together. You provide a custom checker that determines when the two answers are correct. The checker here extracts the correct and student answers from the data that it is passed, and first checks to make sure the student has the same number of answers in both lists. it produces an error message if not (this is true even when Preview Answers is pressed; if you don't want that, it can be changed).

Then the custom checker compares the correct x-values to the student x-values using the standard list checker for lists of numbers (this is independent of the order). If a message was generated during the checking (e.g., an indication of which entry in the list is wrong), this is transferred to the answer message for the x-values in the MultiAnswer object. If the two lists are not the same, the discontinuity values are not checked, and a message is generated to warn the student about that. (This is because some discontinuities might be correct, but we aren't checking that, so simply saying "incorrect" might be confusing. A most sophisticated checker would score the ones that correspond to correct x values, but the information about which x-values are correct is not directly available, as it was handled internally in the cmp->evaluate() call.) Finally, the score for the first x-values is returned with a score of 0 for the discontinuities (since they haven't been checked).

Otherwise, the two lists are equal (though perhaps not in the same order), so the student's x-values are correct. The next few lines extract the individual x-values from the students list, counts them, and then produces are array that indicates which student answer corresponds to each correct answer. This relies on the correct answers being sorted lowest to highest (though a more sophisticated checker could do a similar indexing of the correct answers no mater their order). So $si[$i] is the index of the correct answer that equals the i-th student answer.

We then loop through the student answers for the discontinuity types and check that they are correct, keeping track of error messages if they are not.

The score for the discontinuities is determined from the number of error messages (incorrect answers), and if they are all incorrect, a single message to that effect is used, and the error messages are displayed (when appropriate). Finally, the score for the two parts is returned.

One other issue to be aware of is that the custom checker will only run of both answer blanks have been filled in (and produce no syntax errors, and have the correct types of answers). In particular, the student will not get feedback about the x-coordinates unless the discontinuities are also entered. You can tell the MultiAnswer object to process blank entries, but then you have to be more careful in your custom checker to handle that situation yourself.

So this kind of thing can be complicated, and takes some care.

There are other ways it could have been done, for example, you could ask the student to return a list of pairs, where the pairs are an x-coordiate together with a discontinuity type, e.g., (0,infinite), (2,removable). Then you don't need a MultiAnswer object, and you can use a plain or list checker. But you would not get partial credit for getting the x-coordinate right with the discontinuity wrong (but you could use a post-filter or custom checker to handle that).

Anyway, I hope this helps you get what you need.

Davide