From e07d692551a48cfc94969bee679785e91e500877 Mon Sep 17 00:00:00 2001 From: Jaimos Skriletz Date: Tue, 5 Nov 2024 18:05:26 -0700 Subject: [PATCH 1/3] Add cmpOpts setting to parserMultiAnswer. Add a convenience option to parserMultiAnswer to send the HASH of options listed in cmpOpts to all answers cmp methods. This is the same option copied from parserRadioMultiAnswer. --- macros/parsers/parserMultiAnswer.pl | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/macros/parsers/parserMultiAnswer.pl b/macros/parsers/parserMultiAnswer.pl index 299d7705d..094f12041 100644 --- a/macros/parsers/parserMultiAnswer.pl +++ b/macros/parsers/parserMultiAnswer.pl @@ -62,6 +62,7 @@ sub new { part => 0, singleResult => 0, namedRules => 0, + cmpOpts => undef, checkTypes => 1, allowBlankAnswers => 0, tex_separator => $separator . '\,', @@ -89,8 +90,10 @@ sub setCmpFlags { # the individual answer checkers. # sub cmp { - my $self = shift; - my %options = @_; + my ($self, %options) = @_; + + %options = (%options, %{ $self->{cmpOpts} }) if ref($self->{cmpOpts}) eq 'HASH'; + foreach my $id ('checker', 'separator') { if (defined($options{$id})) { $self->{$id} = $options{$id}; @@ -501,6 +504,12 @@ =head2 namedRules if you need to intersperse other rules with the ones for the C. In this case, you must use C instead of C. Default: 0. +=head2 cmpOpts + +This is a hash of options that will be passed to the cmp method. For example, +C<< cmpOpts => { weight => 0.5 } >>. This option is provided to make it more convenient to pass +options to cmp when utilizing PGML. Default: undef (no options are sent). + =head2 checkTypes Specifies whether the types of the student and professor's answers must match exactly From 22b9199425d6a4fba3ce473d33feaaf9392cfcc3 Mon Sep 17 00:00:00 2001 From: Jaimos Skriletz Date: Tue, 5 Nov 2024 21:14:15 -0700 Subject: [PATCH 2/3] Add default checker to parserMultiAnswer. This adds a simple default checker to parserMultiAnswer that is used if no checker is provided. The checker checks if each answer part is correct using the overloaded `==` operator. It can also be configured to either return which parts are correct, allowing for partial credit, or only return 0 or 1 depending on if all answer parts are correct. The `partialCredit` option can be used to control which behavior to use, and defaults to the value of `$showPartialCorrectAnswers`. This is just for convince when using MultiAnswer with a simple checker. --- macros/parsers/parserMultiAnswer.pl | 33 ++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/macros/parsers/parserMultiAnswer.pl b/macros/parsers/parserMultiAnswer.pl index 094f12041..89e10fa87 100644 --- a/macros/parsers/parserMultiAnswer.pl +++ b/macros/parsers/parserMultiAnswer.pl @@ -71,6 +71,7 @@ sub new { format => undef, context => $context, single_ans_messages => [], + partialCredit => $main::showPartialCorrectAnswers, }, $class; } @@ -100,7 +101,23 @@ sub cmp { delete $options{$id}; } } - die "You must supply a checker subroutine" unless ref($self->{checker}) eq 'CODE'; + + unless (ref($self->{checker}) eq 'CODE') { + $self->{checker} = sub { + my ($correct, $student, $self, $ans) = @_; + my @scores; + + for (0 .. $self->length - 1) { + push(@scores, $correct->[$_] == $student->[$_] ? 1 : 0); + } + return \@scores if $self->{partialCredit}; + for (@scores) { + return 0 unless $_; + } + return 1; + } + } + if ($self->{allowBlankAnswers}) { foreach my $cmp (@{ $self->{cmp} }) { $cmp->install_pre_filter('erase'); @@ -477,9 +494,9 @@ =head1 ATTRIBUTES C objects have the following attributes: -=head2 checker (required) +=head2 checker -A coderef to be called to check student answers. This is the only required attribute. +A coderef to be called to check student answers. The C routine receives four parameters: a reference to the array of correct answers, a reference to the array of student answers, a reference to the C object itself, @@ -493,6 +510,16 @@ =head2 checker (required) } $multianswer_obj = $multianswer_obj->with(checker=>~~&always_right); +If a C is not provided, a default checker is used. The default checker checks if each +answer is equal to its correct answer (using the overloaded C<==> operator). If C<< partialCredit => 1 >>, +the checker returns an array of 0s and 1s listing which answers are correct giving partial credit. +If C<< partialCredit => 0 >>, the checker only returns 1 if all answers are correct, otherwise returns 0. + +=head2 partialCredit + +This is used with the default checker to determine if the default checker should reward partial +credit, based on the number of correct answers, or not. Default: C<$showPartialCorrectAnswers>. + =head2 singleResult Indicates whether to show only one entry in the results table (C<< singleResult => 1 >>) From 29f85de8ee2f5ea8e33f7c406a8cb9e48eb83a26 Mon Sep 17 00:00:00 2001 From: Jaimos Skriletz Date: Wed, 6 Nov 2024 13:00:51 -0700 Subject: [PATCH 3/3] parserMultiAnswer, check if user checker is a CODE ref. Check if any user defined checker is a subroutine, and give an error message if not, instead of sliently overwriting it with a default checker. --- macros/parsers/parserMultiAnswer.pl | 1 + 1 file changed, 1 insertion(+) diff --git a/macros/parsers/parserMultiAnswer.pl b/macros/parsers/parserMultiAnswer.pl index 89e10fa87..dfc3f24f0 100644 --- a/macros/parsers/parserMultiAnswer.pl +++ b/macros/parsers/parserMultiAnswer.pl @@ -103,6 +103,7 @@ sub cmp { } unless (ref($self->{checker}) eq 'CODE') { + die "Your checker must be a subroutine." if defined($self->{checker}); $self->{checker} = sub { my ($correct, $student, $self, $ans) = @_; my @scores;