DOCUMENT();
loadMacros("PG.pl",
"PGstandard.pl",
"MathObjects.pl",
);
#Context()->strings->add( none=>{caseSensitive=>1});
#Context()->strings->add('(a+b)'=>{});
TEXT(beginproblem());
BEGIN_TEXT
Type \((a+b)\)
\{ans_rule()\}. $BR
Be sure to include the parantheses and
match the case, but you can put in extra space if you like.
END_TEXT
#ANS(String('a')->cmp());
#ANS(Formula('a+b')->cmp());
ANS(str_cmp('(a+b)'));
ENDDOCUMENT();
The first, and simplest in this case, is to use
ANS(str_cmp('(a+b)',filters=>['remove_whitespace']));rather than a plain
str_cmp()
. This will remove the standard filters (which includes a case-insensitive filter that forces everything to upper case), and replaces them with the whitespace-removal filter instead.
This will accept (a+b)
or ( a + b)
as a correct answer.
Note that by doing a string comparison you are forcing the answer to be in a particular form. For example, (b+a)
will not be accepted. I'm not sure if you care about that or not, but my next answers address this question.
Davide
I have a context (attached) that allows students (and professors) to enter arbitrary strings without parser error messages. You can then use your own custom checker to determine when the answer is correct.
For example:
loadMacros("contextArbitraryString.pl"); Context("ArbitraryString"); ANS(Compute("(a + b)")->cmp(checker => sub { my ($correct,$student,$ans) = @_; $correct = $correct->value; # get perl string from String object $student = $student->value; # ditto $correct =~ s/ +//g; $student =~ s/ +//g; # remove spaces return $correct eq $student; }));This is equivalent to the
str_cmp()
example I gave previously. Of course, you can make your checker more sophisticated:
loadMacros("contextArbitraryString.pl"); Context("ArbitraryString"); ANS(Compute("(a + b)")->cmp(checker => sub { my ($correct,$student,$ans) = @_; $student = $student->value; $student =~ s/ +//g; return $student eq "(a+b)" || $student eq "(b+a)"; }));so that you can get the answer correct in either order.
Note that this gives no helpful error messages if the answer is ill formed (like (a+
with missing operands and parentheses). For that, you need to work a little harder (see next message).
Davide
Entering the answer " (a + b)" (without
the quotes) gives the error:
Unexpected character '('
(which I could fix using withPreFilter).
This is not what I'd expect, but then I
have all of seven or eight hours experience
with the MathObjects code, and I don't
really understand it at all, yet.
cheers,
-wb
Not sure what you mean by fixing it using withPreFilter. Do you mean the version with ->withPreFilter('erase')->withPostFilter('erase') makes the error message go away?
Davide
cribbed from the original question, and the commented
ANS bit is essentially cribbed from the message to which
I replied. (That's the one that gave rise to the error.) The
uncommented ANS bit is what seems to me to work in
general (or at least more generally); the modification was
essentially cribbed from pg/lib/Value/AnswerChecker.pm.
cheers,
-wb
Davide
of the box. (I'm a little surprised that the anchors made
that difference, so I have some reading to do.)
cheers,
-wb
^
that was the problem. The patterns are used within a loop that uses the \G item to maintain the current location within the string, and I had though that ^
was relative to that, but it isn't. It wasn't necessary to have them in the string pattern (I had used them only to reinforce the idea that it would be matching the entire student input). it does mean, however, that the student's answer isn't quite exactly what they typed, because initial spaces are trimmed (by the parser as part of the parsing process). I suspect that is not critical to very many applications, and if it is, one can always use $ans->{student_ans}
instead.Davide
Context("Numeric")->variables->are(a=>'Real', b=>'Real');would do that. Then if other variables are used, or if parentheses don't balance, and so on, the student will get an error message. You could then use
ANS(Formula("(a+b)")->cmp);but this has several problems. First, the parentheses will not be required, since
a+b
and (a+b)
are equivalent as mathematical formulas, and second, they also equal 2a+b-a
and a lot of other expressions that you don't want.
To solve the first of these, we need to modify how the parentheses operate. I have made a new context in which parentheses are not removed (see attached file). This solves only part of the problem, since even though the parens are now retained as far as displaying the formula is concerned, that doesn't change the fact that the resulting formula is equivalent to the non-parenthesized one.
To fix that, you use a custom checker like the one above, but this time you let the MathObject stringify itself (which included the parentheses), and compare the resulting strings. Since these are obtained from the parsed expression structure, there is no need to worry about extra spaces, or other anomalies. For example:
loadMacros("contextKeepParens.pl"); Context("KeepParens")->variables->are(a=>"Real", b=>"Real"); ANS(Compute("(a+b)")->cmp(checker => sub { my ($correct,$student,$ans) = @_; return $student eq "(a+b)" || $correct eq "(b+a)"; }));Note that using
eq
rather than ==
performs a string comparison, rather than a mathematical equivalence check, so this fixes both the issue of requiring parentheses, and also the problem of allowing forms that you don't want.
There is still a slight problem, however, which is that Formula object install pre- and post-filters that perform a check to see if the student's previous answer is equivalent to the current one. This can cause unwanted messages when the answers aren't equivalent in the way you want them to be (e.g, when the parentheses are missing). To solve this, we remove the filters:
loadMacros("contextKeepParens.pl"); Context("KeepParens")->variables->are(a=>"Real", b=>"Real"); ANS(Compute("(a+b)")->cmp(checker => sub { my ($correct,$student,$ans) = @_; return $student eq "(a+b)" || $correct eq "(b+a)"; })->withPreFilter('erase')->withPostFilter('erase'));(at the expense of not telling the student when there are equivalent answers).
[As an aside, the equivalence check does use the custom checker to tell when the past answer is the same as the current one. It passes the past answer as $correct and the current one as $student, but since this checker doesn't actually make use of $correct, this throws off the equivalence check. Because you are comparing the results as strings, and in several forms, it is difficult to base the check on the actual $correct
value.]
This approach has the advantage of providing syntax check and error messages, while still accepting only the forms you want and requiring the parentheses. That is not the case for the string checks. Note that you could also add message about missing parentheses, and so on, if you wished.
The disadvantage of this approach is that you need to include all the equivalent forms explicitly. If you had something like "(a+(b+c))" that would probably be prohibitive. In that case, you would probably want to walk the parse tree by hand to check it.
Hope that helps.
Davide