## WeBWorK Problems

### Unordered list Arbitrary strings ionic equations ### Unordered list Arbitrary strings ionic equations

by Eric Stroyan -
Number of replies: 3
I am attempting to write a problem that takes a student answer for the ions resulting when a compound dissolves.
Students are given a compound. e.g.
Be^{+2}(aq) + 2F^{-1}(aq).
I have stored the correct answer in a List as Be^{+2}(aq),2F^{-1}(aq).
I have tried a custom answer checker that I hoped removed white-space and changed )+ to a comma.
Basically, I want to check the results without having to make the students give products in any order.
The code does give the correct list when checking 'correct answers, but does not appear to parse the student input.
What have I missed? (Tons probably, but I'm used to that.)
Thanks.

#
#Show products of aqueous ionization
#
DOCUMENT();
"PGstandard.pl",
"MathObjects.pl",
"contextArbitraryString.pl",
"parserFunction.pl",
"unionLists.pl",
"PGcourse.pl"
);

#
#identify number of ions in a formula name
#
%compounds=(
1=>{name=>"Beryllium Fluoride",
formula=>"BeF_{2}",
sol=>"insoluble",
ions=>[{ion=>"Be^{+2}", name=>"beryllium", count=>1},
{ion=>"F^{-1}", name=>"fluoride", count=>2}],
composition=>[
{atom=>4, count=>1},
{atom=>9, count=>2},]},
);

TEXT(&beginproblem);
Context("ArbitraryString");
$showPartialCorrectAnswers = 1; # #Get info from database #$count=keys %compounds;
@items=(1..$count);$S = list_random(@items);
$name =$compounds{$S}{name};$form = $compounds{$S}{formula};
$i=0.; for$who (@{$compounds{$S}{ions}})
{$n=$i;
$ion_at[$i]=$who->{ion};$N_ion[$i] =$who->{count};
$i=$i+1;
}
$i=0; # # answers for dissolved compound #$cation = $N_ion.$ion_at."(aq)";
$anion =$N_ion.$ion_at."(aq)";$answer=List($cation,$anion);
#
#Students are given examples of expected TeX input of formulae
#
BEGIN_TEXT
For this question you must enter your symbol/formula in LaTeX format.$BR Examples are shown in the table below.$BR
\{image("LaTeX_formulae.png")\}$BR Complete the following. You must balance properly.$BR

$$form(aq) \longrightarrow$$ \{ans_rule(20)\}

END_TEXT

ANS(List($answer)->cmp( list_checker => sub { my ($correct,$student,$ansHash,$value) = @_; my$n = scalar(@$student); # number of student answers my$score = 0; # number of correct student answers
for ($i = 0;$i < $n;$i++) {
$cor =$correct ->[$i];$stu= $student->[$i];
$stu=~ s/ //g; # remove whitespace$stu=~ s/~~)+/,/g; # remove )+ and replace with ,
if($cor eq$stu){$score++;} } return$score;
}
));

ENDDOCUMENT(); ### Re: Unordered list Arbitrary strings ionic equations

by Davide Cervone -
I've been wanting to get back to this for a while, and finally had a chance to write something up.

The approach you are taking has a number of problems. The main issue is that your checker does not handle handle the student answer correctly, and really can't. Your $answer is a List object, but the ArbitraryString context can only produce a String object as a result, so the $student variable will be a reference to an array of a single string. That means $n will always be 1, and you will only ever check the first term of the correct answer, never the second or later ones. • It appears you are trying to turn the student answer into a list, but that you are not doing this properly. First of all the substitution s/~~)+/,/g doesn't do what you think it does. It replaces one or more parentheses by a comma; it doesn't convert a parenthesis followed by a plus sign to a comma. That is because + is a special character in regular expressions, so would have to be quoted to match against a literal plus sign. Also, you will lose the parentheses in this substitution, so perhaps you meant s/~~)~~+/),/g. But even with that, the code won't work because $stu is now a string consisting of all the terms separated by commas, and you are checking one correct term against this string of all the student answers. You would need to split the string into its components at the commas and extract the one you need. (To do that, it would be best to split it once outside the loop so you don't go through the same process over and over again.)

But even if you did that, this doesn't do what you say you want to accomplish, which is to allow the student terms to be in any order, as you are checking the first correct with the first student part, and never any of the other student parts. Checking an unordered list is harder than that, in general.

Because your correct answer is a List, it will display as the terms separated by commas (not plus signs) if the student requests the correct answer. That will mean that the correct answer will be shown in a form that is not what the student should type. (This could be handled by changing the separator used for lists to a plus sign, but that is a hack).

Finally, if a student uses commas instead of plus signs, the answer would be marked as correct (if you followed my suggestions above, since you would first convert pluses to commas, then split at the commas, so if they had commas to start with, there would be no why to know that they didn't come from plusses).

So for all these reasons, I think you are going to have difficulty getting this approach to work.

My recommendation is to no use List MathObjects at all, but simply use the ArbitraryString context to use String objects as it is designed to do. Then use its custom checker to split the string on the plus signs (to get Perl arrays of the ions), sort those lists alphabetically (so they will be in the same order if correct), join them again by plus signs, and compare the results as single strings (rather than looking through them). This will not let you give partial credit (that could be done, but takes more work), but will make it possible to give credit for any order.

Here is the critical code that does the work:

$cation =$N_ion.$ion_at."(aq)";$anion = $N_ion.$ion_at."(aq)";
$answer = Compute("$cation + $anion"); sub normalizeList { my$string = shift;
$string =~ s/~~s//g;$string =~ s/(^|(?<=~~+))1(?=[A-Z])//g;
my @list = lex_sort(split(/(?<![{^])~~+/, $string)); return join(' + ', @list); } ANS($answer->cmp(
checker => sub {
my ($correct,$student,$ans) = @_; return normalizeList($correct) eq normalizeList($student); } )); This uses a function noramilzeList() to convert the correct and student answers into a string that represents the sum of the terms in lexicographic order, and the checker simply compares these two lists as strings. We use $answer = compute(...) to turn the original sum into the MathObject String. The normalizeList() function includes code to remove a leading 1 from an element so that students don't have to type that (but allows it if they do).

While this does the job, it is still not a great approach, as it doesn't give the student any help with syntax errors, and doesn't allow you to provide partial credit. The best approach would be to develop a context that properly parses the chemical formulas that you are using. The contextReaction.pl includes a context that does most of what you need. The only missing part is the powers and the (aq) (I'm not a chemist, so I don't know what that means or what to call it). I don't think it would be too hard to add those to the reaction context, at least for someone familiar with MathObject contexts.  