Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 52 additions & 22 deletions macros/parsers/parserMultiAnswer.pl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ package parser::MultiAnswer;
our $separator = ';'; # separator for singleResult previews

my @ans_defaults = (
checker => sub {0},
showCoordinateHints => 0,
showEndpointHints => 0,
showEndTypeHints => 0,
Expand Down Expand Up @@ -90,17 +89,13 @@ sub cmp {
die "Your checker must be a subroutine." if defined($self->{checker});
$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 $self->{scores} if $self->{partialCredit};
for (@{ $self->{scores} }) {
return 0 unless $_ == 1;
}
return 1;
}
};
$self->{checkTypes} = 'compatible' if $self->{checkTypes} && $self->{checkTypes} ne 'exact';
}

if ($self->{allowBlankAnswers}) {
Expand Down Expand Up @@ -281,7 +276,6 @@ sub entry_check {
my $ANS = $self->{cmp}[$i]->evaluate($ans->{student_ans});
$self->{ans}[$i] = $ANS;
$ANS->{type} = $ans->{type};
$ANS->score(0);

foreach my $id (keys %{$ans}) {
$ANS->{$id} = $ans->{$id} unless defined($ANS->{$id});
Expand All @@ -306,21 +300,48 @@ sub perform_check {
my $rh_ans = shift;
my $context = $self->context;
$context->clearError;

#
# Get correct and student answers, and scores.
# Initially mark all answer blanks as wrong.
#
my @correct;
my @student;
foreach my $ans (@{ $self->{ans} }) {
push(@correct, $ans->{correct_value});
push(@student, $ans->{student_value});
return if $ans->{ans_message} || !defined($ans->{student_value});
$self->{scores} = [];
for my $ans (@{ $self->{ans} }) {
push(@correct, $ans->{correct_value});
push(@student, $ans->{student_value});
push(@{ $self->{scores} }, $ans->{score});
$ans->score(0);
}

#
# Check for error messages and correct answer types
#
my $checkTypes = $self->{checkTypes};
$checkTypes = 'equal' if $checkTypes && $checkTypes ne 'compatible';
for my $ans (@{ $self->{ans} }) {
return if ($checkTypes ne 'compatible' && $ans->{ans_message}) || !defined($ans->{student_value});
return
if $self->{checkTypes}
if $checkTypes eq 'equal'
&& $ans->{student_value}->type ne $ans->{correct_value}->type
&& !($self->{allowBlankAnswers} && $ans->{student_ans} !~ m/\S/);
return
if $checkTypes eq 'compatible'
&& !$ans->{correct_value}->typeMatch($ans->{student_value}, $ans)
&& !($self->{allowBlankAnswers} && $ans->{student_ans} !~ m/\S/);
}

#
# Set the isPreview flag.
#
my $inputs = $main::inputs_ref;
$rh_ans->{isPreview} = $inputs->{previewAnswers}
|| ($inputs_{action} && $inputs->{action} =~ m/^Preview/);
|| ($inputs->{action} && $inputs->{action} =~ m/^Preview/);

#
# Call the user's answer checker.
#
Parser::Context->current(undef, $context); # change to multi-answer's context
my $flags = Value::contextSet($context, $self->cmp_contextFlags($rh_ans)); # save old context flags
$context->{answerHash} = $rh_ans; # attach the answerHash
Expand All @@ -329,16 +350,20 @@ sub perform_check {
$context->{answerHash} = undef; # remove answerHash
if (!@result && $context->{error}{flag}) { $self->cmp_error($self->{ans}[0]); return 1 }

#
# Mark the answers as correct or incorrect.
#
my $result = (scalar(@result) > 1 ? [@result] : $result[0] || 0);
if (ref($result) eq 'ARRAY') {
die "Checker subroutine returned the wrong number of results"
if (scalar(@{$result}) != $self->length);
foreach my $i (0 .. $self->length - 1) { $self->{ans}[$i]->score($result->[$i]) }
for my $i (0 .. $self->length - 1) { $self->{ans}[$i]->score($result->[$i]) }
} elsif (Value::matchNumber($result)) {
foreach my $ans (@{ $self->{ans} }) { $ans->score($result) }
for my $ans (@{ $self->{ans} }) { $ans->score($result) }
} else {
die "Checker subroutine should return a number or array of numbers ($result)";
}

return;
}

Expand Down Expand Up @@ -524,9 +549,14 @@ =head2 cmpOpts

=head2 checkTypes

Specifies whether the types of the student and professor's answers must match exactly
(C<< checkTypes => 1 >>) or just pass the usual type-match error checking (in which case, you should
check the types before you use the data). Default: 1.
Specifies whether the types of the student and instructor's answers must match and to what extent.
if C<< checkTypes => 'compatible' >> then the student answers only need to be compatible with the
instructor answers in the sense that they parse into objects that can be compared to the instructor
answers. If C<< checkTypes => 1 >> then the types of the student answers must match the types of the
instructor answers exactly. Otherwise no type checking is done other than the the usual type-match
error checking (in which case, you should check the types before you use the data). Default: 1.
Note that if the default checker is used, i.e., if C<checker> is not set, then C<< checkTypes => 1 >>
is the same as C<< checkTypes => 'compatible' >>.

=head2 allowBlankAnswers

Expand Down
34 changes: 25 additions & 9 deletions macros/parsers/parserRadioMultiAnswer.pl
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,14 @@ =head1 OPTIONS

=item checkTypes (Default: checkTypes => 1)

Whether the types of the student and correct answers must match exactly or just pass the usual
type-match error checking (in which case, you should check the types before you use the data).
Specifies whether the types of the student and instructor's answers must match and to what extent.
if C<< checkTypes => 'compatible' >> then the student answers only need to be compatible with the
instructor answers in the sense that they parse into objects that can be compared to the instructor
answers. If C<< checkTypes => 1 >> then the types of the student answers must match the types of the
instructor answers exactly. Otherwise no type checking is done other than the the usual type-match
error checking (in which case, you should check the types before you use the data). Default: 1.
Note that if the default checker is used, i.e., if C<checker> is not set, then C<< checkTypes => 1 >>
is the same as C<< checkTypes => 'compatible' >>.

=item allowBlankAnswers (Default: allowBlankAnswers => 0)

Expand Down Expand Up @@ -260,7 +266,6 @@ package parser::RadioMultiAnswer;
our $answerPrefix = 'RaDiOMuLtIaNsWeR_'; # answer rule prefix

my @ans_defaults = (
checker => sub {0},
showCoordinateHints => 0,
showEndpointHints => 0,
showEndTypeHints => 0
Expand Down Expand Up @@ -350,16 +355,18 @@ 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) = @_;
return 0 if ($correct->[0] != $student->[0]);

for (0 .. $#{ $correct->[ $correct->[0] ] }) {
return 0 if $correct->[ $correct->[0] ][$_] != $student->[ $correct->[0] ][$_];
return 0 unless $self->{ans}[ $correct->[0] - 1 ][$_]{score};
}

return 1;
};
$self->{checkTypes} = 'compatible' if $self->{checkTypes} && $self->{checkTypes} ne 'exact';
}

if ($self->{allowBlankAnswers}) {
Expand Down Expand Up @@ -531,13 +538,18 @@ sub perform_check {
my ($self, $rh_ans) = @_;
my $context = $self->context;
$context->clearError;
my @correct;
my @student;

my $checkTypes = $self->{checkTypes};
$checkTypes = 'equal' if $checkTypes && $checkTypes ne 'compatible';

# The answers for all parts are sent to the grader. The answers in the incorrect parts from
# the correct answer may be used in the grader. The answers for each part are in a separate
# list. The radio answer is sent as the numerical index of the choice. The count starts at
# one, so that it is the location in the list sent to the grader of the answers for the
# selected part (the radio answer is in position 0).
my @correct;
my @student;

push(@correct, $self->{correct} + 1);
push(@student, $self->getIndexByValue($main::inputs_ref->{ $self->ANS_NAME(0) }) + 1);
my $part_index = 1;
Expand All @@ -551,9 +563,13 @@ sub perform_check {
next if $student[0] != $part_index;
return if $ans->{ans_message} ne '' || !defined $ans->{student_value};
return
if ($self->{checkTypes}
&& $ans->{student_value}->type ne $ans->{correct_value}->type
&& !($self->{allowBlankAnswers} && $ans->{student_ans} !~ m/\S/));
if $checkTypes eq 'equal'
&& $ans->{student_value}->type ne $ans->{correct_value}->type
&& !($self->{allowBlankAnswers} && $ans->{student_ans} !~ m/\S/);
return
if $checkTypes eq 'compatible'
&& !$ans->{correct_value}->typeMatch($ans->{student_value}, $ans)
&& !($self->{allowBlankAnswers} && $ans->{student_ans} !~ m/\S/);
}
push(@correct, [@part_correct]);
push(@student, [@part_student]);
Expand Down
Loading