WeBWorK Main Forum

raw student input

raw student input

by joel robbin -
Number of replies: 9
Can I access the exact string a student typed? I am trying to make the following work.


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();
In reply to joel robbin

Re: raw student input

by Davide Cervone -
There are several approaches you could take. I will address each in a separate message.

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

In reply to joel robbin

Re: raw student input

by Davide Cervone -
It is also possible to use MathObjects to handle this situation, but you have to be fairly careful. You tried to define a string for the answer you expected, and that will work (except for the space removal), but you have to remember that the MathObject checker does more than compare the string. If the student doesn't type the string you intended, MathObject will parse the string as an expression and give error messages about that, and that may not be what you want. For example, it would complain about undefined variables if you type any other expression involving "a" and "b".

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

In reply to Davide Cervone

Re: raw student input

by William Boshuck -
I had a small problem with this example.
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

In reply to William Boshuck

Re: raw student input

by Davide Cervone -
Can you send the complete code that you are using? It is hard to tell where the problem is without that. The error message indicates that the open parenthesis is not defined in the current Context, which suggests that something has gone wrong with that. It is possible that the file I sent is the culprit, but I'd like to see exactly what you have done.

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
In reply to Davide Cervone

Re: raw student input

by William Boshuck -
Sure, it is attached. The problem text is essentially
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

In reply to William Boshuck

Re: raw student input

by Davide Cervone -
OK, it turns out to have been a problem with contextArbitraryString.pl. I've attached an updated copy. Try that and see if that doesn't work better for you.

Davide
In reply to Davide Cervone

Re: raw student input

by William Boshuck -
Yes, thank you. That does what I'd expect, and out
of the box. (I'm a little surprised that the anchors made
that difference, so I have some reading to do.)

cheers,
-wb
In reply to William Boshuck

Re: raw student input

by Davide Cervone -
It was really the initial ^ 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
In reply to joel robbin

Re: raw student input

by Davide Cervone -
One of the nice things about the MathObjects library is that it produces error messages that can help the student when they enter answers that are syntactically incorrect (like when they don't balance parentheses, and so on). It would be nice to allow MathObjects to produce them for your questions as well. For that, you would need to add "a" and "b" as variables:

    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