WeBWorK Problems

Student answers and order of their answers

Student answers and order of their answers

by Teresa Adams -
Number of replies: 2
I have two questions which are kind of similar.

Question 1:
I am trying to code a question on continuity for f(x)=x(x^2-2). It's a two part answer where they would first answer the x-value then the type of discontinuity as the second answer. The student could answer "0, 2" with "removable, infinite" for the second part or "2, 0" with "infinite, removable" as the second part and both be correct. How do I code that while making sure "0, 2" with "infinite, removable" is incorrect?

Question 2:
Can I code the question above in such a way that I could ask the student how many (if any) discontinuities and based on their answer have that many answer boxes show up? I would still want it so that the order for x wouldn't matter. So that if they put 2 in the first answer box and picked infinite for from the first popup it would be marked correct or (if they put 1/removable in the first set).

In reply to Teresa Adams

Re: Student answers and order of their answers

by Danny Glin -
This is something that gets requested somewhat regularly, and it's something I'd like to have in several contexts (e.g. finding and classifying critical points, listing eigenvalues and their corresponding eigenvectors). Unfortunately there is not a graceful way to do this right now. Here are a few thoughts on ways to ask this based on what's currently available.

One approach is to set up a MultiAnswer problem, where all of the answers are linked. You would then have to write a custom answer checker that checks if all of the x-values are distinct, and that each one matches the corresponding description.

The above suggestion doesn't address the issue of having to tell the student how many answers there are. One way to approach this is with a scaffolding problem. You could set up the first part of your problem as "how many discontinuities does the function have", or even "list the x-values of the discontinuities of the function". Once they get that correct, they could move on to the second part, which is to classify the discontinuities.

There may be better ways to do this, but that's what I'm able to come up with.

In reply to Teresa Adams

Re: Student answers and order of their answers

by Davide Cervone -
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:


$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;
      if @errors && $showPartialCorrectAnswers && !$ans->{isPreview};
    return (1, $score);

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


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.