| … | |
… | |
| 19 | |
19 | |
| 20 | sub cmp_defaults {( |
20 | sub cmp_defaults {( |
| 21 | showTypeWarnings => 1, |
21 | showTypeWarnings => 1, |
| 22 | showEqualErrors => 1, |
22 | showEqualErrors => 1, |
| 23 | ignoreStrings => 1, |
23 | ignoreStrings => 1, |
|
|
24 | studentsMustReduceUnions => 1, |
|
|
25 | showUnionReduceWarnings => 1, |
| 24 | )} |
26 | )} |
| 25 | |
27 | |
| 26 | sub cmp { |
28 | sub cmp { |
| 27 | my $self = shift; |
29 | my $self = shift; |
| 28 | my $ans = new AnswerEvaluator; |
30 | my $ans = new AnswerEvaluator; |
| … | |
… | |
| 60 | StringifyAsTeX => 0, # reset this, just in case. |
62 | StringifyAsTeX => 0, # reset this, just in case. |
| 61 | no_parameters => 1, # don't let students enter parameters |
63 | no_parameters => 1, # don't let students enter parameters |
| 62 | showExtraParens => 1, # make student answer painfully unambiguous |
64 | showExtraParens => 1, # make student answer painfully unambiguous |
| 63 | reduceConstants => 0, # don't combine student constants |
65 | reduceConstants => 0, # don't combine student constants |
| 64 | reduceConstantFunctions => 0, # don't reduce constant functions |
66 | reduceConstantFunctions => 0, # don't reduce constant functions |
|
|
67 | ($ans->{studentsMustReduceUnions} ? |
|
|
68 | (reduceUnions => 0, reduceSets => 0, |
|
|
69 | reduceUnionsForComparison => $ans->{showUnionReduceWarnings}, |
|
|
70 | reduceSetsForComparison => $ans->{showUnionReduceWarnings}) : |
|
|
71 | (reduceUnions => 1, reduceSets => 1, |
|
|
72 | reduceUnionsForComparison => 1, reduceSetsForComparison => 1)), |
| 65 | ($ans->{requireParenMatch}? (): ignoreEndpointTypes => 1), # for Intervals |
73 | ($ans->{requireParenMatch}? (): ignoreEndpointTypes => 1), # for Intervals |
| 66 | $self->cmp_contextFlags($ans), # any additional ones from the object itself |
74 | $self->cmp_contextFlags($ans), # any additional ones from the object itself |
| 67 | ); |
75 | ); |
| 68 | my $inputs = $self->getPG('$inputs_ref',{action=>""}); |
76 | my $inputs = $self->getPG('$inputs_ref',{action=>""}); |
| 69 | $ans->{isPreview} = $inputs->{previewAnswers} || ($inputs->{action} =~ m/^Preview/); |
77 | $ans->{isPreview} = $inputs->{previewAnswers} || ($inputs->{action} =~ m/^Preview/); |
| … | |
… | |
| 232 | # |
240 | # |
| 233 | sub cmp_postprocess {} |
241 | sub cmp_postprocess {} |
| 234 | sub cmp_contextFlags {return ()} |
242 | sub cmp_contextFlags {return ()} |
| 235 | |
243 | |
| 236 | # |
244 | # |
|
|
245 | # For reducing Unions, Sets and Intervals |
|
|
246 | # |
|
|
247 | sub cmp_checkUnionReduce { |
|
|
248 | my $self = shift; my $ans = shift; |
|
|
249 | return unless $ans->{studentsMustReduceUnions} && |
|
|
250 | $ans->{showUnionReduceWarnings} && |
|
|
251 | !$ans->{isPreview}; |
|
|
252 | my $student = $ans->{student_value}; |
|
|
253 | return unless defined($student) && !Value::isFormula($student); |
|
|
254 | if ($student->type eq 'Union' && $student->length >= 2) { |
|
|
255 | my $reduced = $student->reduce; |
|
|
256 | return "Your union can be written in a simpler form" |
|
|
257 | unless $reduced->type eq 'Union' && $reduced->length == $student->length; |
|
|
258 | my @R = $reduced->value; my @S = sort {$a <=> $b} $student->value; |
|
|
259 | foreach my $i (0..$#R) { |
|
|
260 | return "Your union can be written in a simpler form" |
|
|
261 | unless $R[$i] == $S[$i]; |
|
|
262 | } |
|
|
263 | } elsif ($student->type eq 'Set') { |
|
|
264 | my $reduced = $student->reduce; |
|
|
265 | return "Your set must have no redundant elements" |
|
|
266 | unless $reduced->length == $student->length; |
|
|
267 | } |
|
|
268 | return; |
|
|
269 | } |
|
|
270 | |
|
|
271 | # |
| 237 | # create answer rules of various types |
272 | # create answer rules of various types |
| 238 | # |
273 | # |
| 239 | sub ans_rule {shift; pgCall('ans_rule',@_)} |
274 | sub ans_rule {shift; pgCall('ans_rule',@_)} |
| 240 | sub named_ans_rule {shift; pgCall('NAMED_ANS_RULE',@_)} |
275 | sub named_ans_rule {shift; pgCall('NAMED_ANS_RULE',@_)} |
| 241 | sub named_ans_rule_extension {shift; pgCall('NAMED_ANS_RULE_EXTENSION',@_)} |
276 | sub named_ans_rule_extension {shift; pgCall('NAMED_ANS_RULE_EXTENSION',@_)} |
| … | |
… | |
| 777 | if $other->type =~ m/^(Point|List)$/; |
812 | if $other->type =~ m/^(Point|List)$/; |
| 778 | $other->type =~ m/^(Interval|Union|Set)$/; |
813 | $other->type =~ m/^(Interval|Union|Set)$/; |
| 779 | } |
814 | } |
| 780 | |
815 | |
| 781 | # |
816 | # |
|
|
817 | # Check for unreduced unions and sets |
|
|
818 | # |
|
|
819 | sub cmp_equal { |
|
|
820 | my $self = shift; my $ans = shift; |
|
|
821 | my $error = $self->cmp_checkUnionReduce($ans); |
|
|
822 | if ($error) {$self->cmp_Error($ans,$error); return} |
|
|
823 | $self->SUPER::cmp_equal($ans); |
|
|
824 | } |
|
|
825 | |
|
|
826 | # |
| 782 | # Check for wrong enpoints and wrong type of endpoints |
827 | # Check for wrong enpoints and wrong type of endpoints |
| 783 | # |
828 | # |
| 784 | sub cmp_postprocess { |
829 | sub cmp_postprocess { |
| 785 | my $self = shift; my $ans = shift; |
830 | my $self = shift; my $ans = shift; |
| 786 | return unless $ans->{score} == 0 && !$ans->{isPreview}; |
831 | return unless $ans->{score} == 0 && !$ans->{isPreview}; |
| … | |
… | |
| 830 | )} |
875 | )} |
| 831 | |
876 | |
| 832 | # |
877 | # |
| 833 | # Use the list checker if the student answer is a set |
878 | # Use the list checker if the student answer is a set |
| 834 | # otherwise use the standard compare (to get better |
879 | # otherwise use the standard compare (to get better |
| 835 | # error messages |
880 | # error messages). But check for unreduced unions |
|
|
881 | # and sets first. |
| 836 | # |
882 | # |
| 837 | sub cmp_equal { |
883 | sub cmp_equal { |
| 838 | my ($self,$ans) = @_; |
884 | my ($self,$ans) = @_; |
|
|
885 | my $error = $self->cmp_checkUnionReduce($ans); |
|
|
886 | if ($error) {$self->cmp_Error($ans,$error); return} |
| 839 | Value::List::cmp_equal(@_) if $ans->{student_value}->type eq 'Set'; |
887 | return Value::List::cmp_equal(@_) if $ans->{student_value}->type eq 'Set'; |
| 840 | Value::cmp_equal(@_); |
888 | $self->SUPER::cmp_equal($ans); |
| 841 | } |
889 | } |
| 842 | |
890 | |
| 843 | ############################################################# |
891 | ############################################################# |
| 844 | |
892 | |
| 845 | package Value::Union; |
893 | package Value::Union; |
| … | |
… | |
| 865 | list_type => 'an interval, set or union', |
913 | list_type => 'an interval, set or union', |
| 866 | short_type => 'a union', |
914 | short_type => 'a union', |
| 867 | entry_type => 'an interval or set', |
915 | entry_type => 'an interval or set', |
| 868 | )} |
916 | )} |
| 869 | |
917 | |
| 870 | sub cmp_equal {Value::List::cmp_equal(@_)} |
918 | # |
|
|
919 | # Check for unreduced sets and unions |
|
|
920 | # |
|
|
921 | sub cmp_equal { |
|
|
922 | my $self = shift; my $ans = shift; |
|
|
923 | my $error = $self->cmp_checkUnionReduce($ans); |
|
|
924 | if ($error) {$self->cmp_Error($ans,$error); return} |
|
|
925 | Value::List::cmp_equal($self,$ans); |
|
|
926 | } |
| 871 | |
927 | |
| 872 | ############################################################# |
928 | ############################################################# |
| 873 | |
929 | |
| 874 | package Value::List; |
930 | package Value::List; |
| 875 | |
931 | |
| … | |
… | |
| 904 | # Handle removal of outermost parens in correct answer. |
960 | # Handle removal of outermost parens in correct answer. |
| 905 | # |
961 | # |
| 906 | sub cmp { |
962 | sub cmp { |
| 907 | my $self = shift; |
963 | my $self = shift; |
| 908 | my $cmp = $self->SUPER::cmp(@_); |
964 | my $cmp = $self->SUPER::cmp(@_); |
|
|
965 | $cmp->{rh_ans}{showUnionReduceWarnings} = 0; |
| 909 | if ($cmp->{rh_ans}{removeParens}) { |
966 | if ($cmp->{rh_ans}{removeParens}) { |
| 910 | $self->{open} = $self->{close} = ''; |
967 | $self->{open} = $self->{close} = ''; |
| 911 | $cmp->ans_hash(correct_ans => $self->stringify) |
968 | $cmp->ans_hash(correct_ans => $self->stringify) |
| 912 | unless defined($self->{correct_ans}); |
969 | unless defined($self->{correct_ans}); |
| 913 | } |
970 | } |
| … | |
… | |
| 1005 | $value =~ s/ or /s or /; # fix "interval or union" |
1062 | $value =~ s/ or /s or /; # fix "interval or union" |
| 1006 | push(@errors,"There should be more ${value}s in your $stype") |
1063 | push(@errors,"There should be more ${value}s in your $stype") |
| 1007 | if ($score < $maxscore && $score == $m); |
1064 | if ($score < $maxscore && $score == $m); |
| 1008 | push(@errors,"There should be fewer ${value}s in your $stype") |
1065 | push(@errors,"There should be fewer ${value}s in your $stype") |
| 1009 | if ($score < $maxscore && $score == $M && !$showHints); |
1066 | if ($score < $maxscore && $score == $M && !$showHints); |
|
|
1067 | } |
|
|
1068 | |
|
|
1069 | # |
|
|
1070 | # If all the entries are in error, don't give individual messages |
|
|
1071 | # |
|
|
1072 | if ($score == 0) { |
|
|
1073 | my $i = 0; |
|
|
1074 | while ($i <= $#errors) { |
|
|
1075 | if ($errors[$i++] =~ m/^Your .* is incorrect$/) |
|
|
1076 | {splice(@errors,--$i,1)} |
|
|
1077 | } |
| 1010 | } |
1078 | } |
| 1011 | |
1079 | |
| 1012 | # |
1080 | # |
| 1013 | # Finalize the score |
1081 | # Finalize the score |
| 1014 | # |
1082 | # |
| … | |
… | |
| 1210 | |
1278 | |
| 1211 | # |
1279 | # |
| 1212 | # Use the list checker if the formula is a list or union |
1280 | # Use the list checker if the formula is a list or union |
| 1213 | # Otherwise use the normal checker |
1281 | # Otherwise use the normal checker |
| 1214 | # |
1282 | # |
| 1215 | if ($self->type =~ m/^(List|Union)$/) { |
1283 | if ($self->type =~ m/^(List|Union|Set)$/) { |
| 1216 | Value::List::cmp_equal($self,$ans); |
1284 | Value::List::cmp_equal($self,$ans); |
| 1217 | } else { |
1285 | } else { |
| 1218 | $self->SUPER::cmp_equal($ans); |
1286 | $self->SUPER::cmp_equal($ans); |
| 1219 | } |
1287 | } |
| 1220 | } |
1288 | } |