Forum archive 2000-2006

Arnold K. Pizer - str_cmp no longer accepts an empty string as an answer.

Arnold K. Pizer - str_cmp no longer accepts an empty string as an answer.

by Arnold Pizer -
Number of replies: 0
inactiveTopicstr_cmp no longer accepts an empty string as an answer. topic started 9/18/2005; 7:29:40 PM
last post 9/23/2005; 10:50:10 PM
userArnold K. Pizer - str_cmp no longer accepts an empty string as an answer.  blueArrow
9/18/2005; 7:29:40 PM (reads: 986, responses: 11)
Hi Susan,

Answers are now being handled by Davide's Cervone's new parser. I'll post this to the discussion group and I'm sure Davide will respond.

Arnie

Hi, I tried logging into the discussion group on webwork.rochester.edu to see if anyone else could answer this question, but I wasn't able to log in. I have some old problems with multiple answer boxes, some of which are supposed to be left blank for the correct answer. Now webwork scores them as incorrect, even when the correct answer as shown when selecting the "show correct answer" box is an empty box. Before, ANS(str_cmp("")) would work; now it doesn't. Any ideas? Thanks, Susan Diesel

<| Post or View Comments |>


userJohn Jones - Re: str_cmp no longer accepts an empty string as an answer.  blueArrow
9/18/2005; 8:11:42 PM (reads: 1099, responses: 0)
I don't think Parser is handling string answers.

A bug already reported to bugzilla which may be related is ANS(str_cmp("0")), which treats the answer as "", but doesn't accept 0 (or "") as correct.

John

<| Post or View Comments |>


userDavide P. Cervone - Re: str_cmp no longer accepts an empty string as an answer.  blueArrow
9/18/2005; 8:36:07 PM (reads: 1063, responses: 0)
Arnie and Susan:

John is right, the Parser is not used for str_cmp, so this is not a Parser issue. I think the problem is that there is a pre-filter that prevents the answer checker from running when the student answer is blank and marks the answer as incorrect automatically. You could try something like

    $cmp = str_cmp("");
$cmp->install_pre_filter('erase');
which will remove all the prefilters, but this will also remove the filters installed by str_cmp. It would probably be good to replace the remove_whitespace filter with something like
    $cmp->install_pre_filter(~~&remove_whitespace);
though I haven't tried it.

Davide

<| Post or View Comments |>


userJohn Jones - Re: str_cmp no longer accepts an empty string as an answer.  blueArrow
9/18/2005; 9:37:39 PM (reads: 1078, responses: 0)
Hi,

I see that the other bug I mentioned with answers of "0" has been fixed.

John

<| Post or View Comments |>


userMichael Gage - Re: str_cmp no longer accepts an empty string as an answer.  blueArrow
9/19/2005; 11:54:53 AM (reads: 1067, responses: 2)
The str_cmp function was one of the oldest and didn't even use the AnswerEvaluator objects, much less the Parser object. The change in behavior is due to the rewrite of str_cmp to use the AnswerEvaluator objects, (but not yet, the Parser objects).

What should be done next depends on a fact that I haven't had time to check out fully. The first is

All of the answer checkers now interpret empty answers or blank answers as non-attempts. (I'm pretty sure this is true of all of the AnswerEvaluator object based checkers, not quite so sure of the Parser based checkers).

I think it is useful to be able to distinguish the state "no answer has (yet) been given" . The only reasonable way I can think of to do this is to use an empty answer to represent "no reply". To avoid confusion this should mean that answers which contain no printing characters (i.e. blanks) should also mean no reply.

This means that problems would have to be written in such a way that a blank was never a correct answer. On the face of it this actually seems like a good idea. Otherwise you won't be able to tell the difference between a correct answer and no answer at all. There might be examples of problems that would make me change my mind. So far I'm aware of the problem: intersection of A and B and you are to fill in the elements, or leave it blank if the intersection is empty. One could have the student enter the word "empty" if the intersection was empty.

The alternative, which was the historical situation: namely that blank means no reply for every answerEvaluator except for the str_cmp evaluator. The lack of consistency bothers me a little, but I'd be willing to be inconsistent if there was a good reason -- namely examples of problems for which the answer really should be blank and there is no reasonable alternative.

After all, these problems are written in perl, and perl is filled with context related special cases. That's why people love and/or hate it!!

I'll try and determine whether or not things really are consistent at the moment (that is blank ALWAYS means no reply). We can all try to think of examples where the answer really should be blank. if the only reason is "that is the way it was historically", then I think we can quickly find most of the old problems and edit them to work with the consistent set of answer evaluators. There can't be very many of them.

There may be other places where str_cmp's behavior has changed, because it is now an AnswerEvaluator object instead of a subroutine, as it was formerly.

Working to make the PG language consistent -- but not too consistent,

-- Mike

<| Post or View Comments |>


userJohn Jones - Re: str_cmp no longer accepts an empty string as an answer.  blueArrow
9/19/2005; 1:48:53 PM (reads: 1039, responses: 0)
Hi Mike,

The uses I know of where one wants to allow blank answers as part of a correct submission are where the problem doesn't want to give away the number of needed answer blanks. I don't know of any problems where all answers blank is the right answer - the problems I am thinking of all have multiple blanks, only some of which should be left blank.

The first examples I saw like this were for problems which want the roots of a polynomial and gives one answer blank per root. Other examples come from solving systems of linear equations. There you may want to have a way of entering a unique solution, a family of solutions, or that the system is inconsistent (i.e., not give away anything as to what might happen with the system). If there are three variables, it is natural to have entry blanks for x, y, and z, and maybe another blank for the multiple choice part (of how many solutions there are). If the linear system is inconsistent, then you may want to let students leave x, y, and z entries as blank.

I would deal with both of these situations differently if it were my class (e.g., using list type evaluators for the first scenerio), but some teachers like questions like the questions as they are.

Some problems stick with blank=wrong convention by having instructions like 'if this situation occurs, enter "x" in each of the unused entry blanks'. When we used problems like that, we ended up with some very frustrated students. They struggled, eventually got the right answer, but didn't have the right things in the unneeded answer boxes. They felt that they did the problem right, but webwork was marking their answer wrong. Generally, this is a common complaint (right answers marked wrong). Usually it is unjustified, but this scenerio was one where the student was right. They had solved the math problem and they were not getting credit because of the rules for entering their answer. It is one thing if the rules are the same as with paper homework (e.g., following order of operations), but that wasn't the case here.

Wanting to diffuse the sentiment of "webwork is too frustrating; I did problems right but it marked my answers as wrong", we have problems where blank might be the right answer for part of a problem. It seems to come the closest to letting the questions and answers be as it would be with paper homework.

John

<| Post or View Comments |>


userDavide P. Cervone - Re: str_cmp no longer accepts an empty string as an answer.  blueArrow
9/19/2005; 2:12:25 PM (reads: 1156, responses: 1)
The Parser-based answer checkers are honest AnswerEvaluator objects, so yes, they use the blank answer filter that "new AnswerEvaluator" inserts. (There is a special case, however, when the ans_array methods are used, since these produce multiple answer blanks, and we want to process the non-empty extra ones even when the first one is empty.) So I think everything is consistent in the main set of answer checkers.

Early on (before I had list-based answer checkers), I used empty answer blanks for situations where I didn't want to give away the number of items the student needed to enter. E.g, f(x) = ... is undefined for the following values of x: ___, ___, ___ (leave unneeded blanks empty). This is now better handled with a list-based checker, so I'm converting those as they come up.

Now that WW handles partial credit within a single answer blank, I don't see a need to have multiple blanks like this. The error reporting and correct/incorrect markers work a little better with separate blanks, but it usually causes students confusion to have to leave things blank, and the wording of the problem is more awkward. Now that WW reports "n blanks were left empty" warnings, that is an extra issue that would have to be addressed. The list-based checker is usually a better solution.

It is probably possible to use the Parser's MultiPart object (see pg/macros/parserMultiPart.pl) to provide for blank entries if you really wanted them. The MultiPart object also removes the blank check, since it needs to be called even when the main answer-blank it is tied to is empty (some of the additional blanks might not be empty). So that would be one way to bypass the check. One nice feature of this is you could count the blank entries as correct only when all the other ones are correct. Otherwise, the student could leave everything empty and be told which blank ones were correct, and so could deduce the number of non-empty answers he needed to enter. Hardly any point in providing the answer blanks in that case.

I had a blank_cmp that worked this way that is part of the unordered-answer implementation that I did early on (before the list checkers were available). It allowed you to do unordered lists using multiple answer blanks, were some could be left empty. It still works, but is basically obsolete now.

Davide

<| Post or View Comments |>


userSusan Diesel - Re: str_cmp no longer accepts an empty string as an answer.  blueArrow
9/19/2005; 4:20:25 PM (reads: 1245, responses: 0)
Thanks for all the discussion on this topic. The problems I'm revising are several years old, and worked with all earlier releases of WW, so it was a surprise to find tried and true exercises no longer working correctly. The types of problems where this occurs are indeed ones where I don't want to give away the number of answers, or where the number of answers may vary depending on values chosen for the problem. It looks like in the short term I will need to revised problems that expect some blank answer to be correct to instruct students to enter "this box intentionally left blank" or the equivalent. There is so much new in v2 that I will have to do some studying now.

Susan Diesel

<| Post or View Comments |>


userMichael Gage - Re: str_cmp no longer accepts an empty string as an answer.  blueArrow
9/19/2005; 9:31:54 PM (reads: 1063, responses: 3)
Hi Susan,

If you have a list of problems that you plan to convert, I'd be interested in having you post the list or email them to me. If you are not in too much of a hurry I can probably help, particularly with any which are in the library databaes and it will give me concrete ideas of how to balance consistency and desirable special cases.

I'm particularly interested in the case where the str_cmp answer evaluator is involved. I think that Davide has done a good job of listing the options for answers which have several parts -- such as the number of roots of a polynomial.

-- Mike

<| Post or View Comments |>


userSusan Diesel - Re: str_cmp no longer accepts an empty string as an answer.  blueArrow
9/22/2005; 1:36:42 PM (reads: 1138, responses: 2)
I modified the problems I was concerned about, but for the future I would like some advice about how to account for empty answer boxes. Here is a simple example, from my course in developmental math:

BEGIN_TEXT

$BR

List all prime numbers less than 100. Enter answers in increasing order, filling boxes from left to right, without skipping any primes. Leave blank any answer boxes at the end that you do not need. $PAR {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} $BR {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} $BR {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} $BR {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} $BR {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} $BR {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} $BR {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} $BR {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} $BR {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} $BR {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)} {ans_rule(5)}

$BR

END_TEXT

ANS(num_cmp(2)); ANS(num_cmp(3)); ANS(num_cmp(5)); ANS(num_cmp(7)); ANS(num_cmp(11)); ANS(num_cmp(13)); ANS(num_cmp(17)); ANS(num_cmp(19)); ANS(num_cmp(23)); ANS(num_cmp(29)); ANS(num_cmp(31)); ANS(num_cmp(37)); ANS(num_cmp(41)); ANS(num_cmp(43)); ANS(num_cmp(47)); ANS(num_cmp(53)); ANS(num_cmp(59)); ANS(num_cmp(61)); ANS(num_cmp(67)); ANS(num_cmp(71)); ANS(num_cmp(73)); ANS(num_cmp(79)); ANS(num_cmp(83)); ANS(num_cmp(89)); ANS(num_cmp(91)); ANS(num_cmp(97)); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp("")); ANS(str_cmp(""));

ENDDOCUMENT();

One reason I like the empty boxes is that one number per box means they get credit for each correct prime that is in order. The developmental students who see this problem are likely to be confused by syntax for entering a list into a single answer box. And, if they submit the answers with empty boxes they see how many primes there are, so they can use that information to refine and improve on their next attempt (almost no one gets all of them on the first try).

<| Post or View Comments |>


userDavide P. Cervone - Re: str_cmp no longer accepts an empty string as an answer.  blueArrow
9/23/2005; 6:57:21 PM (reads: 1228, responses: 1)
One approach is to use the following subroutine:
    sub blank_cmp {
my $cmp = str_cmp("",@_);
$cmp->install_pre_filter('erase');
$cmp->install_pre_filter(~~&remove_whitespace);
$cmp;
}

and then use ANS(blank_cmp) in place of the str_cmp calls that you have been using. You will still have the issue of the warning message that WeBWorK will issue if there are blank entries. A little hack to disable that is to include

    TEXT(MODES(HTML=>"<STYLE>div.ResultsAlert {display:none}</STYLE>",tex=>''));
after the TEXT(beginproblem) somewhere. This will make the warning message disappear (though it may still show up for a moment as the problemis loaded).

On the other hand, I don't think your approach to this problem is the most effective one. Let me address some of the comments you have made above:

 

One reason I like the empty boxes is that one number per box means they get credit for each correct prime that is in order.

Since WeBWorK handles partial credit within a single answer, a list-based answer checker can do this as well.

 

The developmental students who see this problem are likely to be confused by syntax for entering a list into a single answer box.

I find it hard to belive that students who are capable of understanding prime numbers can't figure out how to type commas between their answers. Is that really what you are suggesting? My experience with our students (admittedly not developmental students) is that they are far less comfortable about leaving answers blank than about entering lists.

 

if they submit the answers with empty boxes they see how many primes there are

Then what is the purpose of having 100 boxes to start with? Why not just give them the right number initially, since they can find out how many there are by just hitting the submit button. Is their not having that information on their first attempt really important? I'm not sure the overhead of having 100 boxes (and worse, 100 rows in the results table at the top of the page when they submit answers) is worth the marginal value of letting them think that all 100 might be prime. I would think that having to scroll past over seventy blank rows of empty preview boxes in order to get down to the entry blanks again would be a source of frustration (it would be for me), especially if they had to scroll back up to the top to read the error messages again.

 

they can use that information to refine and improve on their next attempt

This would also be true for a list-based checker. Here is an example of how this could be done. This one is fairly sophisticated, and tries to model the effect that you are getting, but without the need for 100 entry blanks. It will give credit for the correct primes up to the first incorrect entry in the list. Furthermore, it will tell you helpful information about the first wrong entry.

(I had an earlier version that gave credit for any correct primes, and told you which entries in teh list were incorrect, but this would mean you could just enter every number from 2 to 99 and WW would tell you exactly which are primes. You can still get WW to tell you the right answers by entering successive integers until it tells you you got the next one, but that is also true of your implementation as well.)

 


    DOCUMENT();        # This should be the first executable line in the problem.


loadMacros(
"PG.pl",
"PGbasicmacros.pl",
"PGanswermacros.pl",
"Parser.pl",
);


TEXT(beginproblem);


Context("Numeric");


$Primes = List(2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,
53,59,61,67,71,73,79,83,89,91,97);


BEGIN_TEXT


$BCENTER
List all the prime numbers that are less than 100:
$PAR


\{ans_box(3,60)\}
$PAR


${BITALIC}(Enter you answers in increasing
order, separated by commas.)${EITALIC}
$ECENTER


END_TEXT


ANS($Primes->cmp(showLengthHints => 0, list_checker => sub {
my $correct = shift; my $student = shift; my $ans = shift;
my @correct = @{$correct}; my @student = @{$student};
my $n = scalar(@student); my $m = scalar(@correct);
my $score = 0; my @errors; my $i;


#
# Split preview so it is not too long
#
if ($n > 8) {
my @rows; $i = 0;
while ($i < $n) {push(@rows,join(",\,",@student[$i..min($i+7,$n-1)])); $i += 8}
$ans->{preview_latex_string} =
"\begin{array}{c}".join(",\\",@rows)."\end{array}";
}


#
# Check that everything is a positive integer
#
for ($i = 0; $i < $n; $i++) {
if ($student[$i]->class ne 'Real') {
push(@errors,"Your ".$Primes->NameForNumber($i+1).
" answer is not a Number (it looks like ".Value::showClass($student[$i]).")");
} else {
$student[$i] = $student[$i]->value;
if ($student[$i] !~ m/^-?[0-9]+$/) {
push(@errors,"$student[$i] is not an integer");
} elsif ($student[$i] < 1) {
push(@errors,"$student[$i] is not a positive integer");
} elsif ($student[$i] > 100) {
push(@errors,"$student[$i] is bigger than 100");
}
}
}
return ($score,@errors) if scalar(@errors);


#
# Check for numbers being in ascending order
#
for ($i = 0; $i < $n-1; $i++) {
if ($student[$i] > $student[$i+1]) {
push(@errors,"Your numbers are not listed in increasing order")
unless $ans->{isPreview};
return ($score,@errors);
}
}


#
# Find the first wrong number
#
$i = 0;
while ($i < $n && $i < $m) {
if ($student[$i] != $correct[$i]) {
unless ($ans->{isPreview}) {
if ($student[$i] < $correct[$i]) {
push(@errors,"$student[$i] is not prime");
} elsif ($i == 0) {
push(@errors,"There is a prime before $student[$i]");
} else {
push(@errors,"There is prime between $student[$i-1] and $student[$i]");
}
}
return($score,@errors);
}
$i++; $score++;
}
if (!$ans->{isPreview}) {
push(@errors,"There are more primes after $student[$n-1]") if $i < $m;
push(@errors,"$student[$i] is not prime") if $i < $n;
}


return ($score,@errors);
}));


$showPartialCorrectAnswers = 1;


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


END_PROBLEM();
ENDDOCUMENT(); # This should be the last executable line in the problem.

 


This creates a List object with the correct answers, and an answer box for the students to type in their list. The answer checker uses a custom list checker to implement the special rules for this problem.

The checker routine gets the data about the correct answers and the student answers. The first thing that happens is we modify the answer preview to break it into several lines (otherwise the preview will get too wide and make the results table too wide). The next thing is to run through the student answers to check that they actually are numbers, and that the numbers are positive integers. Appropirate error message are issued for any entries that are not positive integers.

Next, we check that the student answers are in ascending order, and report an error if not.

Then we look through the student and correct answers to find the first number that is not the same (i.e., the first wrong entry). An error is issued either pointing out that the student answer isn't prime, or that there are primes missing before the students number.

Finally, we report errors for any extra numbers the student has entered, or about missing primes at the end if the student has entered a correct , but too short, list.

This example has all the features that your original does, but has the advantage of not taking up so much screen space, and of giving more informative error messages.

Davide

PS, it turns out the the MultiPart object can't be used for this after all (at least not currently). I'll look into what changes must be made to allow the Parser (and so the MultiPart object) to handle blank answers.

<| Post or View Comments |>


userDavide P. Cervone - Re: str_cmp no longer accepts an empty string as an answer.  blueArrow
9/23/2005; 10:50:10 PM (reads: 1352, responses: 0)
OK, I have fixed the Parser to handle emtpy strings. You can use
    ANS(String("")->cmp)
to get a blank string checker. I have also modified Problem.pm to not count empty answers that are marked correct as blank answers. This means that you won't get the "n unanswered questions" warning (not the contradictory "all the answers are correct/at least one is NOT correct" messages). Finally, I modified the MultiPart object to work with blank answers.

This allows you to have the mutliple-answer-blank problem like you wanted, but still get the better error messages from my list-based example. Also, this allows you to mark the blank answers as incorrect until all the other answers are correct (so you don't give away the number of non-blank entries until everything is correct).

I still like my list-based solution better, but if you really insist on multiple answer blanks, here is a version of the primes problem that does that. One more problem that I noticed when I was working on this is that when you use multiple blanks, and you find that you have missed one of the primes, you have to retype all the following ones in order to insert the missing prime. That is a pain. With the list-based version, you just type the new value with no need to retype any others -- much more convenient.

 


    DOCUMENT();        # This should be the first executable line in the problem.


loadMacros(
"PG.pl",
"PGbasicmacros.pl",
"PGanswermacros.pl",
"Parser.pl",
"parserMultiPart.pl",
);


TEXT(beginproblem);
BEGIN_PROBLEM();


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


Context("Numeric");


$Primes = MultiPart(
2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,
53,59,61,67,71,73,79,83,89,91,97,("") x 74
)->with(checkTypes => 0, allowBlankAnswers => 1);


@rows = ();
for ($i = 0; $i < 10; $i++) {
@rules = ();
for ($j = 0; $j < 10; $j++) {
push(@rules,$Primes->ans_rule(5));
}
push(@rows,join(" ",@rules));
}


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


BEGIN_TEXT


$BCENTER
List all the prime numbers that are less than 100:
$PAR


\{join($BR,@rows)\}


$PAR


${BITALIC}(Enter you answers in increasing
order, separated by commas.)${EITALIC}
$ECENTER


END_TEXT


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


ANS($Primes->cmp(checker => sub {
my $correct = shift; my $student = shift;
my $self = shift; my $ans = shift;
my @correct = @{$correct}; my @student = @{$student};
my $n = scalar(@student); my $m = scalar(@correct);
my @score = (0)x$m;
while ($n > 0 && $student[$n-1] eq '') {$n--}
while ($m > 0 && $correct[$m-1] eq '') {$m--}
my $score = 0; my $errors = 0; my $i;
return 0 if $n == 0;


#
# Check that everything is a positive integer
#
for ($i = 0; $i < $n; $i++) {
if ($student[$i] eq '') {
$student[$i] = ''; # make sure it is not a String object;
} elsif ($student[$i]->class ne 'Real') {
$self->setMessage($i+1,"Your answer is not a number (it looks like ".
lc(Value::showClass($student[$i])).")");
} else {
$student[$i] = $student[$i]->value;
if ($student[$i] !~ m/^-?[0-9]+$/) {
$self->setMessage($i+1,"$student[$i] is not an integer");
$errors = 1;
} elsif ($student[$i] < 1) {
$self->setMessage($i+1,"$student[$i] is not a positive integer");
$errors = 1;
} elsif ($student[$i] > 100) {
$self->setMessage($i+1,"$student[$i] is bigger than 100");
$errors = 1;
}
}
}
return 0 if $errors;


#
# Check for numbers being in ascending order
# with not intervening blanks
#
for ($i = 0; $i < $n-1; $i++) {
next if ($student[$i] eq '' || $student[$i+1] eq '');
if ($student[$i] > $student[$i+1]) {
$self->setMessage($i+2,"This answer is less than the previous one")
unless $ans->{isPreview};
return 0;
}
}


#
# Find the first wrong number
#
$i = 0;
while ($i < $n && $i < $m) {
last if ($student[$i] eq '');
if ($student[$i] != $correct[$i]) {
unless ($ans->{isPreview}) {
if ($student[$i] < $correct[$i]) {
$self->setMessage($i+1,"$student[$i] is not prime");
} elsif ($i == 0) {
$self->setMessage($i+1,"There is a prime before $student[$i]");
} else {
$self->setMessage($i+1,"There is prime between $student[$i-1] and $student[$i]");
}
}
return [@score];
}
$score[$i++] = 1;
}
if (!$ans->{isPreview}) {
$self->setMessage($n+1,"There are more primes after $student[$n-1]") if $i < $m;
$self->setMessage($i+1,"$student[$i] is not prime") if $i < $n && $student[$i] ne '';
}


#
# Mark the blanks as correct if everything else is OK
#
if ($i == $m) {while ($i < scalar(@score)) {$score[$i++] = 1}}


return [@score];
}));


$showPartialCorrectAnswers = 1;


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


ENDDOCUMENT(); # This should be the last executable line in the problem.

 


This one works much the same as the previous verion, but it uses the MultiPart setMessage function to associate error messages with the various answers. It also has to mark the extra blank problems correct when the rest of the answers are correct.

Davide

<| Post or View Comments |>