## WeBWorK Problems

### Checking factors and multiples ### Checking factors and multiples

by Imre Tuba -
Number of replies: 1
I am new to programming exercises in Webwork. I'd like to find out how to implement the following problem in WebWork right.

Find three different factors of 45.

Of course, I want the number randomized. To make sure it has enough factors, I generate it as a product of two random integers in [2,13].

Here are a few problems I've run into and my ideas for solving them.

1. There is no answer checker to check if a number is a factor of another. So I'll need to write my own, along the lines of

sub mycheck_factor {
my ($number,$student, $ansHash) = @_; return($number % $student == 0); } 2. I need to check that the three factors are indeed distinct and award partial credit for each correct answer that is distinct from the others. 3. I'd want to fine tune my answer checked so it displays a meaningful correct answer when the assignment closes. Here is my code which currently asks for and checks only one factor: DOCUMENT(); loadMacros( "PGstandard.pl", "MathObjects.pl" ); Context("Numeric");$a = Real(random(2,13));
$b = Real(random(2,13));$c = $a *$b;

TEXT(beginproblem());
Context()->texStrings;
BEGIN_TEXT
Enter three factors of the number $c: \{ ans_rule(10) \}, \{ ans_rule(10) \}, \{ ans_rule(10) \}. END_TEXT Context()->normalStrings; sub mycheck_factor { my ($number, $student,$ansHash) = @_;
return(($student==int($student)) && (int($number) % int($student) == 0));
}

ANS($c->cmp( checker=>~~&mycheck_factor)); ANS($c->cmp( checker=>~~&mycheck_factor));
ANS($c->cmp( checker=>~~&mycheck_factor)); ENDDOCUMENT(); I have the following concerns. 1. The way the numbers are converted to real first and then back to integer seems a rather inelegant hack. I have to set them up as real, since that's the only type of number MathObject knows and without setting it,$c->cmp doesn't really know what to do (gives error message 'Can't call method "cmp" without a package or object reference'). On the other hand, the % operator in Perl refuses to work with real numbers.

2. Since the student could enter any kind of number, it is necessary to check if their answer is integer and grade it down if it is not. If they enter a noninteger number, it would be nice to send back a message that divisibility is only defined for integers. I suspect it is possible to do this and the moodle page on Custom Answer Checkers mentions something about including
Value->Error("message") after the error, but I can't figure out how to use this.

3. Is there an elegant/efficient way to check that the three numbers entered by the student are distinct, and award 1/3 of the credit for each correct distinct answer?

4. How can I change what is displayed as a correct answer once the homework has closed? Right now, the problem would display $c three times. Could I somehow replace$c with the string "e.g. 1, $a,$c?"

Thank you,

Imre ### Re: Checking factors and multiples

by Davide Cervone -
Imre:

There are two approaches that I can think of that would work for your situation. The first is to use the MultiAnswer object, which lets you provide a single answer checker for multiple answer blanks (the checker has access to the student answers for all the blanks, and returns a score for each). This is implemented in the pg/macros/parserMultiAnswer.pl file, and there is documentation in the comments at the top of that file. There are a number of options that control how the parts are to be displayed in the results table when a student enters answers, and there is control over error messages and partial credit.

Here is an example of using this approach. It turns out that the error message mechanism in MultiAnswer objects is not quite general enough to handle both partial credit and a message at the same time (setting an error message automatically gives 0 credit), so that should really be improved. In the meantime, the same effect can be had by using a post-filter to either do the partial credit or add the error messages. I chose to have it add the messages, since it was a little easier (IMHO).

    DOCUMENT();

"PGstandard.pl",
"MathObjects.pl",
);

TEXT(beginproblem);

#############################################

Context("LimitedNumeric");
Parser::Number::NoDecimals;

($a,$b) = num_sort(random(2,13),random(2,13));
$n =$a * $b;$mp = MultiAnswer(Real($a),Real($b),Real($n))->with( singleResult => 1, separator => ", ", tex_separator => ',\,', allowBlankAnswers => 1, checker => sub { my ($correct,$student,$self,$ans) = @_;$ans->{distinctFactors} = 1; $ans->{nonFactors} = []; my @N = (map {$_->value} @$student); foreach my$i (0..$self->length-1) { next if$N[$i] eq ''; foreach my$j (0..$i-1) { if ($N[$j] ne '' &&$N[$i] ==$N[$j]) {$ans->{distinctFactors} = 0;
$N[$i] = '';
break;
}
}
}
my @score = (0) x $self->length; foreach my$i (0..$self->length-1) { next if$N[$i] eq ''; if ($n % $N[$i] == 0) {$score[$i] = 1}
else {push(@{$ans->{nonFactors}},$N[$i])} } return @score; } ); my$cmp = ($mp->cmp)->withPostFilter(sub { my$ans = shift;
if (!$ans->{isPreview}) { my @errors; push(@errors,"Your factors should all be distinct") unless$ans->{distinctFactors};
foreach my $m (@{$ans->{nonFactors}}) {push(@errors,"$m is not a factor of$n")}
$ans->{ans_message} = join("<BR>",@errors); } return$ans;
});

#############################################

BEGIN_TEXT
Give three distinct factors for $$n$$:
\{$mp->ans_rule(5)\}, \{$mp->ans_rule(5)\}, and \{$mp->ans_rule(5)\}. END_TEXT ############################################# ANS($cmp);

#############################################

ENDDOCUMENT();


Here we use the LimitedNumeric context so that only numbers (not operations or function calls) can be entered by the student, and we turn on the NoDecimals check so that only integers are allowed to be entered. This avoids the problem you were having of dealing with real numbers; we are guaranteed that the student answers are integers.

The MultiAnswer object is given the three correct answers (these are needed for the correct answer display, as you noticed in your version of the problem). It is set to appear as a single result (i.e., only one row in the results table for all three entries). The three factors are displayed with a comma separating them, and this also true for the factors in the correct answer (the usual separator is a semicolon, and we override that here). It also allows the answer checker to run when some answers are blank, so you can give partial credit in that case.

Finally, the checker is provided. It gets the actual underlying perl reals from the student's Real() objects, and looks through them to see whether any are duplicated, and checks whether they are actually factors of the number in question. Fields of the AnwserHash are set to indicate when error messages are needed. The score is an array of scores, one for each answer the student provides; the percentages are handled automatically by the MultiAnswer object.

Once the MultiAnswer object is initialized, its answer checker is obtained (MultiAnswer->cmp returns an ARRAY of checkers, since when singleResult is not set, there will be multiple answer checkers), but in this case, there is only one, so we add a post-filter to it to add the error messages when there were any. Note that this is only done when not in Preview mode, since you don't want to give away actual information in that case.

The answer rules in the problem are created by the MultiPart object itself (note the $mp usage in the BEGIN_TEXT/END_TEXT block. Finally, the answer checker is passed to ANS(). The need for the post-filter is a bit unsatisfying, so one you might consider a second alternative: a List() object with a custom list checker. In this case, the student enters the factors in a single blank but separated by commas.  DOCUMENT(); loadMacros( "PGstandard.pl", "MathObjects.pl", "parserMultiAnswer.pl", ); TEXT(beginproblem); ############################################# Context("LimitedNumeric"); Parser::Number::NoDecimals; Context()->operators->redefine(','); ($a,$b) = num_sort(random(2,13),random(2,13));$n = $a *$b;

$factors = List($a,$b,$n);

#############################################

BEGIN_TEXT
Give three distinct factors for $$n$$ separated by commas: \{ans_rule\}
END_TEXT

#############################################

$showPartialCorrectAnswers = 1; ANS($factors->cmp(
showHints => 0,
entry_type => "factor",
list_checker => sub {
my ($correct,$student,$ans) = @_; my @errors = (); my @N = (map {$_->value} @$student); foreach my$i (0..$#N) { next if$N[$i] eq ''; foreach my$j (0..$i-1) { if ($N[$j] ne '' &&$N[$i] ==$N[$j]) { push(@errors,"Your factors should all be distinct") unless$ans->{isPreview} || @errors;
$N[$i] = '';
break;
}
}
}
my $score = 0; foreach my$i (0..$#N) { if ($N[$i] ne '') { if ($n % $N[$i] == 0) {$score++} elsif (!$ans->{isPreview})
{push(@errors,"$N[$i] is not a factor of $n")} } } return ($score,@errors);
}
));

#############################################

ENDDOCUMENT();


In this case, we need to add the comma operator back into the LimitedNumeric context so that the student can enter a factor list. The factors are stored in a List() object, and the List's cmp() routine is passed a custom list_checker. This checker does essentially the same task as the previous one, but instead of setting flags in the AnswerHash, it stores messages in @errors and returns them along with the score. The List checker then issues the error messages and sets the score based on the length of the lists. This avoids the need of having a separate post-filter for handling the messages.

This approach uses only a single answer blank, and there are advantages and disadvantages to that. If you want to reinforce that the student has to enter three factors, then showing the three blanks may be valuable. If you want the student to have to think about how many entries he or she may need to type (for example, if you asked for all factors, and don't want to give away how many there are), then you might like the List approach better. Note that when the student has entered correct answer, but is missing some, he or she will get a message indicating that more answers are needed (this is taken care of by the List checker automatically); similarly, if he or she enters too many answers, an error may be issued about that as well.

Note, however, that by indicating which entries are nor correct factors, it is possible for the student simply to type in lots of numbers until he or she hits on the factors. In practice, I doubt that will be a serious problem, however, for numbers of the size you are using.

Hope that helps.

Davide