WeBWorK Problems

Partition of integers

Partition of integers

by Daniel Bergh -
Number of replies: 3
How would you implement the following problem in WeBWork using MathObjects?

The student is asked to enter a partition of an integer. The answer should be of the form 5 + 3 + 3 + 2. It should be ok to permute the terms in any order, but it should not be ok to use other operators than +.

One possible solution would be to start with a numeric context. You change
the pattern for numbers by putting an apropriate regular expression in
Context()->{pattern}{number}

such that it only accept digits 0-9 and such that the first digit is non-zero. Then you disable all functions and operators except "+" (which is slightly teadious, since you seem to need to list all operators you want to disable). You should also disable delimiters used for various lists.

Then you need to add a custom answer checker that somehow extracts the list of the terms, sorts them and compare to the similar thing extracted from the reference object. The easiest way is probably to reparse the raw string with a regular expression, but this is ugly, since it duplicates the work just done. An alternative way would be to traverse the parse tree and collect the values, but I don't know where to put this code. Should one override the add method on Value::Real such that it produces a list instead of a number? This seems way too hacky for my taste...

Now the answer preview probably still will show the sum of the elements rather than the partition. I have no idea how to shut this of. Suggestions?

All in all, it seems like awfully lot of work to solve a pretty simple problem. Which makes me wonder if it really is how it is supposed to be done...

I am really interested in a more complex problem, which I stated in the post

http://webwork.maa.org/moodle/mod/forum/discuss.php?d=3283

but I thought it would be a good idéa to do something simplier first, in order to get a better feeling about how the stuff works.

In reply to Daniel Bergh

Re: Partition of integers

by Alex Jordan -
You might be able to employ bizarroArithmetic.pl to achieve this (currently in the development branch on github, so you will have to copy it and add it to your macros folder). Use regular Numeric context and disable all operators except +, and follow the instructions to turn + into something other than its usual meaning. Associativity and commutativity of answers are OK, and 5+3+3+2 will be different from 6+3+3+1, etc. There's a close-to-0 chance a studnet could get credit for the decimal value of 5+3+3+2, but if you also used Parser::Number::NoDecimals(); then you wouldn't need to worry about it.

Regarding displaying of answers in the correct answer field, there are flags to control that like formatStudentAnswer, reduceConstants, and reduceConstantFunctions. Also each MathObject has properties you can set like correct_ans and correct_ans_latex_string. If I need to set these I do so in the ANS call, as in
ANS($mathobject->cmp(correct_ans_latex_string => $someString));
In reply to Daniel Bergh

Re: Partition of integers

by Davide Cervone -
How would you implement the following problem in WeBWork using MathObjects?

I have put together a Partition context that does this. The attached contextPartition.pl file contains the context. the idea is to create Partition object that knows how to compare partitions, and make a new implementation for + in the context so that it produces partition objects rather than real numbers.

You are right that you could modify the number pattern to prevent decimals, but I think Alex's suggestion of using Parser::Number::NoDecimals is better, as this allows the parser to recognize decimal numbers and give appropriate error messages (rather than "Unexpected character '.'"). This is the kind of thing that I mean by having the context know about other possible answers, not just ones in the correct form.

disable all functions and operators except "+" (which is slightly teadious, since you seem to need to list all operators you want to disable).

The attached file gives one way to do this easier (undefined them all and then redefine the ones you want).

You should also disable delimiters used for various lists.

You could, or you could let the class produce error messages for the lists when they are used in appropriately. Leaving them in lets you create lists of lists, if you need them (like a list of pairs that are a word with a partition).

Then you need to add a custom answer checker that somehow extracts the list of the terms, sorts them and compare to the similar thing extracted from the reference object. ... [One] way would be to traverse the parse tree and collect the values, but I don't know where to put this code.

If you create the Partition class that implements addition of numbers to form partitions, and comparison between partitions (and string and TeX representations of partitions), then you don't have to make a custom checker, as the standard MathObject answer checker will call the object's comparison method automatically. You just have to tell the parser's plus operator to build the Partition object rather than a Real object. That is done by changing the class associated with the + operator. See the attached code for an example.

Should one override the add method on Value::Real such that it produces a list instead of a number?

Right idea, but wrong method. You don't modify the Real class, you modify the context's definition for + and what class it is tied to.

As an aside, it is not a good idea to modify the Value::Real class itself, since WeBWorK uses mod_perl, so those changes would be persistent and affect later problems that use the same httpd child process. Instead, you should create a subclass of Value::Real and override sub add there, then tell the Context to use your class rather than the standard Real class, e.g.,

   Context()->{value}{Real} = "my::Real";
assuming your class is my::Real. MathObjects copies the context when you say Context("Numeric") and so changes you make to that are not persistent and so don't affect other problems using the same child.

Now the answer preview probably still will show the sum of the elements rather than the partition. I have no idea how to shut this of. Suggestions?

If you have the plus create your Partition object, then the output will be the output from that object rather than from the real number that you would normally get from the sum.

As Alex points out, there is also some additional control over what is put in the "Entered" column. The Context flag formatStudentAnswer can be set to evaluated, reduced, or parsed. The first will give you the final number (assuming the student entered a formula that returns a number), the third will give the original formula entered by the student. The second form is only pertinent for Formulas, where the result will be simplified by the reduction rules available in the context (removal of "+ 0" or "1*" or "^1" or "/1" and such things).

For a custom object like the Partition object in the attached file, the object's string() and TeX() methods provide the strings to be used.

Anyway, this does provide a somewhat simpler example than the Permutation one, which needed two classes, and a new list type for the Parser.

In reply to Daniel Bergh

Re: Partition of integers

by Davide Cervone -
For completeness, here is an example of how to write the custom checker that walks the parse tree to collect the terms, without having to create your own Context or MathObject classes.
    $context = Context("Numeric");
    
    #
    #  Remove everything by addition and commas
    #  from the context
    #
    Parser::Number::NoDecimals();
    $context->variables->clear();
    $context->strings->clear();
    $context->constants->clear();
    $context->functions->disable("all");
    $context->operators->undefine($context->operators->names);
    $context->operators->redefine(["fn",",","+"]);
    $context->operators->remove(" ");
    
    #
    #  Keep student answers unevaluated
    #
    $context->flags->set(
      reduceConstants => 0,
      formatStudentAnswer => 'parsed',
    );
    
    BEGIN_TEXT
    Enter a partition: \{ans_rule\}
    END_TEXT
    
    #
    #  Turn a tree of addition into an array
    #  (should really check that numbers are
    #  positive, since zero could appear here).
    #
    sub getTerms {
      my $bop = shift;
      return $bop->{value} if $bop->class eq "Number";
      Value->Error("Formula doesn't appear to be a partition")
        unless $bop->class eq "BOP" && $bop->{bop} eq "+";
      return (getTerms($bop->{lop}),getTerms($bop->{rop}));
    }

    #
    #  Use a custom checker that collects the numbers
    #  from the partitions and compares the sorted results.
    #
    ANS(Compute("1 + 3 + 5")->cmp(
      cmp_class => "a Partition",
      checker => sub {
        my ($correct,$student,$ans) = @_;
        my $S = join('+',num_sort(getTerms($ans->{student_formula}{tree})));
        my $C = join('+',num_sort(getTerms($correct->{original_formula}{tree})));
        return $C eq $S;
      }
    ));
Hope that helps clarify things.