[system] / trunk / webwork / system / courseScripts / AnswerHash.pm Repository:
ViewVC logotype

Annotation of /trunk/webwork/system/courseScripts/AnswerHash.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 173 - (view) (download) (as text)

1 : sam 2 ##########################################################################
2 :     ## AnswerHash Package
3 :     ##
4 :     ## Provides a data structure for answer hashes. Currently just a wrapper
5 :     ## for the hash, but that might change
6 :    
7 :     =pod
8 :    
9 :     For the most part AnswerHash is an object which contains data. It has only a few methods.
10 :     The data which is automatically initiallized by the constructor new is given here:
11 :    
12 :     $new_answer_hash = { 'score' => 0,
13 :     'correct_ans' => "No correct answer specified",
14 :     'student_ans' => undef,
15 :     'original_student_ans', => undef,
16 :     'type' => 'Undefined answer evaluator type',
17 :     'ans_message' => '',
18 :    
19 :     'preview_text_string' => undef,
20 :     'preview_latex_string' => undef,
21 :     'error_flag' => undef,
22 :     'error_message' => '',
23 :    
24 :     };
25 :    
26 :    
27 :    
28 :     Methods:
29 :     new
30 :    
31 :     setKeys $rh_ans->setKeys{score=>1}; Sets elements in the AnswerHash.
32 :     There is a check to make sure that the
33 :     key is one of the values listed above.
34 :    
35 :     $rh_ans->{non_standard_value} = 'oops';
36 :     Add an element to the AnswerHash.
37 :     No checks are made. Can be used (cautiously)
38 :     to customize and extend the AnswerHash type.
39 :    
40 :     OR
41 :    
42 :     AND
43 :    
44 :     =cut
45 :    
46 :     BEGIN {
47 :     be_strict(); # an alias for use strict. This means that all global variable must contain main:: as a prefix.
48 :    
49 :     }
50 :    
51 :     package AnswerHash;
52 :     # initialization fields
53 :     my %fields = ( 'score' => undef,
54 :     'correct_ans' => undef,
55 :     'student_ans' => undef,
56 :     'ans_message' => undef,
57 :     'type' => undef,
58 :     'preview_text_string' => undef,
59 :     'preview_latex_string' => undef,
60 :     'original_student_ans' => undef
61 :     );
62 :    
63 :     ## Initializing constructor
64 :     sub new {
65 :     my $class = shift @_;
66 :    
67 :     my $self = { 'score' => 0,
68 :     'correct_ans' => 'No correct answer specified',
69 :     'student_ans' => undef,
70 :     'ans_message' => '',
71 :     'type' => 'Undefined answer evaluator type',
72 :     'preview_text_string' => undef,
73 :     'preview_latex_string' => undef,
74 :     'original_student_ans' => undef,
75 :     'error_flag' => undef,
76 :     'error_message' => '',
77 :    
78 :     }; # return a reference to a hash.
79 :    
80 :     bless $self, $class;
81 :     $self -> setKeys(@_);
82 :    
83 :     return $self;
84 :     }
85 :    
86 :     ## IN: a hash
87 :     ## Checks to make sure that the keys are valid,
88 :     ## then sets their value
89 :     sub setKeys {
90 :     my $self = shift;
91 :     my %inits = @_;
92 :     foreach my $item (keys %inits) {
93 :     if ( exists $fields{$item} ) {
94 :     $self -> {$item} = $inits{$item};
95 :     }
96 :     else {
97 :     warn "AnswerHash cannot automatically initialize an item named $item";
98 :     }
99 :     }
100 :     }
101 :    
102 :     # access methods
103 :     sub data { #$rh_ans->data('foo') is a synonym for $rh_ans->{student_ans}='foo'
104 :     my $self = shift;
105 :     $self->input(@_);
106 :     }
107 :    
108 :     sub input { #$rh_ans->input('foo') is a synonym for $rh_ans->{student_ans}='foo'
109 :     my $self = shift;
110 :     my $input = shift;
111 :     $self->{student_ans} = $input if defined($input);
112 :     $self->{student_ans}
113 :     }
114 :     sub score {
115 :     my $self = shift;
116 :     my $score = shift;
117 :     $self->{score} = $score if defined($score);
118 :     $self->{score}
119 :     }
120 :    
121 :     # error methods
122 :     sub throw_error {
123 :     my $self = shift;
124 :     my $flag = shift;
125 :     my $message = shift;
126 :     $self->{error_message} .= " $message " if defined($message);
127 :     $self->{error_flag} = $flag if defined($flag);
128 :     $self->{error_flag}
129 :     }
130 :     sub catch_error {
131 :     my $self = shift;
132 :     my $flag = shift;
133 :     return('') unless defined($self->{error_flag});
134 :     return $self->{error_flag} unless $flag; # empty input catches all errors.
135 :     return $self->{error_flag} if $self->{error_flag} eq $flag;
136 :     return ''; # nothing to catch
137 :     }
138 :     sub clear_error {
139 :     my $self = shift;
140 :     my $flag = shift;
141 :     if (defined($flag) and $flag =~/\S/ and defined($self->{error_flag}) and $flag eq $self->{error_flag}) {
142 :     $self->{error_flag} = undef;
143 :     $self->{error_message} = undef;
144 :     }
145 :     $self;
146 :     }
147 :     sub error_flag {
148 :     my $self = shift;
149 :     my $flag = shift;
150 :     $self->{error_flag} = $flag if defined($flag);
151 :     $self->{error_flag}
152 :     }
153 :     sub error_message {
154 :     my $self = shift;
155 :     my $message = shift;
156 :     $self->{error_message} = $message if defined($message);
157 :     $self->{error_message}
158 :     }
159 :    
160 :     # error print out method
161 :    
162 :     sub pretty_print {
163 :     my $r_input = shift;
164 :     my $out = '';
165 :     if ( not ref($r_input) ) {
166 :     $out = $r_input; # not a reference
167 :     } elsif (ref($r_input) =~/hash/i) {
168 :     local($^W) = 0;
169 :     $out .= "<TABLE border = \"2\" cellpadding = \"3\" BGCOLOR = \"#FFFFFF\">";
170 :     foreach my $key (sort keys %$r_input ) {
171 :     $out .= "<tr><TD> $key</TD><TD>=&gt;</td><td>&nbsp;".pretty_print($r_input->{$key}) . "</td></tr>";
172 :     }
173 :     $out .="</table>";
174 :     } elsif (ref($r_input) eq 'ARRAY' ) {
175 :     my @array = @$r_input;
176 :     $out .= "( " ;
177 :     while (@array) {
178 :     $out .= pretty_print(shift @array) . " , ";
179 :     }
180 :     $out .= " )";
181 :     } elsif (ref($r_input) eq 'CODE') {
182 :     $out = "$r_input";
183 :     } else {
184 :     $out = $r_input;
185 :     }
186 :     $out;
187 :     }
188 :    
189 :     # action methods
190 :     sub OR {
191 :     my $self = shift;
192 :    
193 :     my $rh_ans2 = shift;
194 :     my %options = @_;
195 :     return($self) unless defined($rh_ans2) and ref($rh_ans2) eq 'AnswerHash';
196 :    
197 :     my $out_hash = new AnswerHash;
198 :     # score is the maximum of the two scores
199 :     $out_hash->{score} = ( $self->{score} < $rh_ans2->{score} ) ? $rh_ans2->{score} :$self->{score};
200 :     $out_hash->{correct_ans} = join(" OR ", $self->{correct_ans}, $rh_ans2->{correct_ans} );
201 :     $out_hash->{student_ans} = $self->{student_ans};
202 :     $out_hash->{type} = join(" OR ", $self->{type}, $rh_ans2->{type} );
203 :     $out_hash->{preview_text_string} = join(" ", $self->{preview_text_string}, $rh_ans2->{preview_text_string} );
204 :     $out_hash->{original_student_ans} = $self->{original_student_ans};
205 :     $out_hash;
206 :     }
207 :    
208 :     sub AND {
209 :     my $self = shift;
210 :     my $rh_ans2 = shift;
211 :     my %options = @_;
212 :     my $out_hash = new AnswerHash;
213 :     # score is the minimum of the two scores
214 :     $out_hash->{score} = ( $self->{score} > $rh_ans2->{score} ) ? $rh_ans2->{score} :$self->{score};
215 :     $out_hash->{correct_ans} = join(" AND ", $self->{correct_ans}, $rh_ans2->{correct_ans} );
216 :     $out_hash->{student_ans} = $self->{student_ans};
217 :     $out_hash->{type} = join(" AND ", $self->{type}, $rh_ans2->{type} );
218 :     $out_hash->{preview_text_string} = join(" ", $self->{preview_text_string}, $rh_ans2->{preview_text_string} );
219 :     $out_hash->{original_student_ans} = $self->{original_student_ans};
220 :     $out_hash;
221 :     }
222 :    
223 :     package AnswerEvaluator;
224 :    
225 :    
226 :    
227 :    
228 :     sub new {
229 :     my $class = shift @_;
230 :    
231 :     my $self = { pre_filters => [ [\&blank_prefilter] ],
232 :     evaluators => [],
233 :     post_filters => [ [\&blank_postfilter] ],
234 : gage 5 debug => 0,
235 : sam 2 rh_ans => new AnswerHash,
236 :    
237 :     };
238 :    
239 :     bless $self, $class;
240 :     $self->rh_ans(@_); #initialize answer hash
241 :     return $self;
242 :     }
243 :    
244 :     # dereference_array_ans pretty prints an answer which is stored as an anonymous array.
245 :     sub dereference_array_ans {
246 :     my $self = shift;
247 :     my $rh_ans = shift;
248 :     if (defined($rh_ans->{student_ans}) and ref($rh_ans->{student_ans}) eq 'ARRAY' ) {
249 :     $rh_ans->{student_ans} = "( ". join(" , ",@{$rh_ans->{student_ans}} ) . " ) ";
250 :     }
251 :     $rh_ans;
252 :     }
253 : gage 109
254 : sam 2 sub get_student_answer {
255 :     my $self = shift;
256 :     my $input = shift;
257 : gage 109 if (ref($input) =~/AnswerHash/) {
258 :     # in this case nothing needs to be done, since the student's answer is already in an answerhash.
259 :     # This is useful when an AnswerEvaluator is used as a filter in another answer evaluator.
260 :     } elsif ($input =~ /\0/ ) {
261 : sam 2 my @input = split(/\0/,$input);
262 :     $self-> {rh_ans} -> {original_student_ans} = " ( " .join(", ",@input) . " ) ";
263 :     $input = \@input;
264 :     $self-> {rh_ans} -> {student_ans} = $input;
265 :     } else {
266 :     $input = '' unless defined($input);
267 :     $self-> {rh_ans} -> {original_student_ans} = $input;
268 :     $self-> {rh_ans} -> {student_ans} = $input;
269 :     }
270 :    
271 :    
272 :     $input;
273 :     }
274 :    
275 :     sub evaluate {
276 :     my $self = shift;
277 :     $self->get_student_answer(shift @_);
278 :     my $rh_ans = $self ->{rh_ans};
279 : gage 173 warn "<H3> Answer evaluator information: </H3>\n" if defined($self->{debug}) and $self->{debug}>0;
280 : sam 2 my @prefilters = @{$self -> {pre_filters}};
281 :     my $count = -1; # the blank filter is counted as filter 0
282 :     foreach my $i (@prefilters) {
283 :     last if defined( $self->{rh_ans}->{error_flag} );
284 :     my @array = @$i;
285 :     my $filter = shift(@array); # the array now contains the options for the filter
286 : gage 109 my %options = @array;
287 : sam 2 if (defined($self->{debug}) and $self->{debug}>0) {
288 : gage 109
289 : sam 2 $self->{rh_ans}->{rh_options} = \%options; #include the options in the debug information
290 :     warn "before pre-filter: ",++$count, $self->{rh_ans}->pretty_print();
291 :     }
292 :     $rh_ans = &$filter($rh_ans,@array);
293 : gage 173 warn "<h4>Filter Name:", $rh_ans->{_filter_name},"</h4><BR>\n"
294 :     if defined($self->{debug}) and $self->{debug}>0 and defined($rh_ans->{_filter_name});
295 : gage 109 $rh_ans->{_filter_name} = undef;
296 : sam 2 }
297 :     my @evaluators = @{$self -> {evaluators} };
298 :     $count = 0;
299 :     foreach my $i ( @evaluators ) {
300 :     last if defined($self->{rh_ans}->{error_flag});
301 :     my @array = @$i;
302 :     my $evaluator = shift(@array); # the array now contains the options for the filter
303 : gage 109 my %options = @array;
304 : sam 2 if (defined($self->{debug}) and $self->{debug}>0) {
305 :     $self->{rh_ans}->{rh_options} = \%options; #include the options in the debug information
306 :     warn "before evaluator: ",++$count, $self->{rh_ans}->pretty_print();
307 :     }
308 :     $rh_ans = &$evaluator($rh_ans,@array);
309 : gage 173 warn "<h4>Filter Name:", $rh_ans->{_filter_name},"</h4><BR>\n" if defined($self->{debug}) and $self->{debug}>0 and defined($rh_ans->{_filter_name});
310 : gage 109 $rh_ans->{_filter_name} = undef;
311 : sam 2 }
312 :     my @post_filters = @{$self -> {post_filters} };
313 :     $count = -1; # blank filter catcher is filter 0
314 :     foreach my $i ( @post_filters ) {
315 :     last if defined($rh_ans->{done}) and $rh_ans->{done} == 1; # no further action needed
316 :     my @array = @$i;
317 : gage 109
318 : sam 2 my $filter = shift(@array); # the array now contains the options for the filter
319 : gage 109 my %options = @array;
320 : sam 2 if (defined($self->{debug}) and $self->{debug}>0) {
321 :     $self->{rh_ans}->{rh_options} = \%options; #include the options in the debug information
322 : gage 109 warn "before post-filter: ",++$count, $self->{rh_ans}->pretty_print(),"\n";
323 : sam 2 }
324 :    
325 :     $rh_ans = &$filter($rh_ans,@array);
326 : gage 173 warn "<h4>Filter Name:", $rh_ans->{_filter_name},"</h4><BR>\n" if defined($self->{debug}) and $self->{debug}>0 and defined($rh_ans->{_filter_name});
327 : gage 109 $rh_ans->{_filter_name} = undef;
328 : sam 2 }
329 :     $rh_ans = $self->dereference_array_ans($rh_ans);
330 :     # make sure that the student answer is not an array so that it is reported correctly in answer section.
331 : gage 109 warn "<h4>final result: </h4>", $self->{rh_ans}->pretty_print() if defined($self->{debug}) and $self->{debug}>0;
332 : sam 2 $self ->{rh_ans} = $rh_ans;
333 :     $rh_ans;
334 :     }
335 : gage 109 # This next subroutine is for checking the instructor's answer and is not yet in use.
336 : sam 2 sub correct_answer_evaluate {
337 :     my $self = shift;
338 :     $self-> {rh_ans} -> {correct_ans} = shift @_;
339 : gage 109 my $rh_ans = $self ->{rh_ans};
340 : sam 2 my @prefilters = @{$self -> {correct_answer_pre_filters}};
341 :     my $count = -1; # the blank filter is counted as filter 0
342 :     foreach my $i (@prefilters) {
343 :     last if defined( $self->{rh_ans}->{error_flag} );
344 :     my @array = @$i;
345 :     my $filter = shift(@array); # the array now contains the options for the filter
346 :     warn "before pre-filter: ",++$count, $self->{rh_ans}->pretty_print() if defined($self->{debug}) and $self->{debug}>0;
347 :     $rh_ans = &$filter($rh_ans,@array);
348 : gage 109 warn "Filter Name:", $rh_ans->{_filter_name},"<BR>\n" if $self->{debug}>0 and defined($rh_ans->{_filter_name})
349 : sam 2 }
350 :     my @evaluators = @{$self -> {correct_answer_evaluators} };
351 :     $count = 0;
352 :     foreach my $i ( @evaluators ) {
353 :     last if defined($self->{rh_ans}->{error_flag});
354 :     my @array = @$i;
355 :     my $evaluator = shift(@array); # the array now contains the options for the filter
356 :     warn "before evaluator: ",++$count, $self->{rh_ans}->pretty_print() if defined($self->{debug}) and $self->{debug}>0;
357 :     $rh_ans = &$evaluator($rh_ans,@array);
358 :     }
359 :     my @post_filters = @{$self -> {correct_answer_post_filters} };
360 :     $count = -1; # blank filter catcher is filter 0
361 :     foreach my $i ( @post_filters ) {
362 :     last if defined($rh_ans->{done}) and $rh_ans->{done} == 1; # no further action needed
363 :     my @array = @$i;
364 :     my $filter = shift(@array); # the array now contains the options for the filter
365 :     warn "before post-filter: ",++$count, $self->{rh_ans}->pretty_print() if defined($self->{debug}) and $self->{debug}>0;
366 :     $rh_ans = &$filter($rh_ans,@array);
367 : gage 109 warn "Filter Name:", $rh_ans->{_filter_name},"<BR>\n" if $self->{debug}>0 and defined($rh_ans->{_filter_name})
368 : sam 2 }
369 :     $rh_ans = $self->dereference_array_ans($rh_ans);
370 :     # make sure that the student answer is not an array so that it is reported correctly in answer section.
371 :     warn "final result: ", $self->{rh_ans}->pretty_print() if defined($self->{debug}) and $self->{debug}>0;
372 :     $self ->{rh_ans} = $rh_ans;
373 :     $rh_ans;
374 :     }
375 :    
376 :     sub install_pre_filter {
377 :     my $self = shift;
378 :     if (@_ == 0) {
379 :     # do nothing if input is empty
380 :     } elsif ($_[0] eq 'reset' or $_[0] eq 'erase' ) {
381 :     $self->{pre_filters} = [];
382 :     } else {
383 :     push(@{$self->{pre_filters}},[ @_ ]) if @_; #install pre_filter and it's options
384 :     }
385 :     @{$self->{pre_filters}}; # return array of all pre_filters
386 :     }
387 :    
388 :     sub install_evaluator {
389 :     my $self = shift;
390 :     if (@_ == 0) {
391 :     # do nothing if input is empty
392 :     } elsif ($_[0] eq 'reset' or $_[0] eq 'erase' ) {
393 :     $self->{evaluators} = [];
394 :     } else {
395 :     push(@{$self->{evaluators}},[ @_ ]) if @_; #install evaluator and it's options
396 :     }
397 :     @{$self->{'evaluators'}}; # return array of all evaluators
398 :     }
399 :    
400 :     sub install_post_filter {
401 :     my $self = shift;
402 :     if (@_ == 0) {
403 :     # do nothing if input is empty
404 :     } elsif ($_[0] eq 'reset' or $_[0] eq 'erase' ) {
405 :     $self->{post_filters} = [];
406 :     } else {
407 :     push(@{$self->{post_filters}}, [ @_ ]) if @_; #install post_filter and it's options
408 :     }
409 :     @{$self->{post_filters}}; # return array of all post_filters
410 :     }
411 :    
412 :     ## filters for checking the correctAnswer
413 :     sub install_correct_answer_pre_filter {
414 :     my $self = shift;
415 :     if (@_ == 0) {
416 :     # do nothing if input is empty
417 :     } elsif ($_[0] eq 'reset' or $_[0] eq 'erase' ) {
418 :     $self->{correct_answer_pre_filters} = [];
419 :     } else {
420 :     push(@{$self->{correct_answer_pre_filters}},[ @_ ]) if @_; #install correct_answer_pre_filter and it's options
421 :     }
422 :     @{$self->{correct_answer_pre_filters}}; # return array of all correct_answer_pre_filters
423 :     }
424 :    
425 :     sub install_correct_answer_evaluator {
426 :     my $self = shift;
427 :     if (@_ == 0) {
428 :     # do nothing if input is empty
429 :     } elsif ($_[0] eq 'reset' or $_[0] eq 'erase' ) {
430 :     $self->{correct_answer_evaluators} = [];
431 :     } else {
432 :     push(@{$self->{correct_answer_evaluators}},[ @_ ]) if @_; #install evaluator and it's options
433 :     }
434 :     @{$self->{correct_answer_evaluators}}; # return array of all evaluators
435 :     }
436 :    
437 :     sub install_correct_answer_post_filter {
438 :     my $self = shift;
439 :     if (@_ == 0) {
440 :     # do nothing if input is empty
441 :     } elsif ($_[0] eq 'reset' or $_[0] eq 'erase' ) {
442 :     $self->{correct_answer_post_filters} = [];
443 :     } else {
444 :     push(@{$self->{correct_answer_post_filters}}, [ @_ ]) if @_; #install post_filter and it's options
445 :     }
446 :     @{$self->{correct_answer_post_filters}}; # return array of all post_filters
447 :     }
448 :    
449 :     sub ans_hash { #alias for rh_ans
450 :     my $self = shift;
451 :     $self->rh_ans(@_);
452 :     }
453 :     sub rh_ans {
454 :     my $self = shift;
455 :     my %in_hash = @_;
456 :     foreach my $key (keys %in_hash) {
457 :     $self->{rh_ans}->{$key} = $in_hash{$key};
458 :     }
459 :     $self->{rh_ans};
460 :     }
461 :     ######################################################
462 :     #
463 :     # Built in Filters
464 :     #
465 :     ######################################################
466 :    
467 :    
468 :     sub blank_prefilter { # check for blanks
469 :     my $rh_ans = shift;
470 :     # undefined answers are BLANKS
471 :     ( not defined($rh_ans->{student_ans}) ) && do {$rh_ans->throw_error("BLANK", 'The answer is blank');
472 :     return($rh_ans);};
473 :     # answers which are arrays or hashes or some other object reference are NOT blanks
474 :     ( ref($rh_ans->{student_ans} ) ) && do { return( $rh_ans ) };
475 :     # if the answer is a true variable consisting only of white space it is a BLANK
476 :     ( ($rh_ans->{student_ans}) !~ /\S/ ) && do {$rh_ans->throw_error("BLANK", 'The answer is blank');
477 :     return($rh_ans);};
478 :     # If we get to here, we assume that the answer is not a blank. It is defined, not a reference
479 :     # and contains something other than whitespaces.
480 :     $rh_ans;
481 :     };
482 :    
483 :     sub blank_postfilter {
484 :     my $rh_ans=shift;
485 :     return($rh_ans) unless defined($rh_ans->{error_flag}) and $rh_ans->{error_flag} eq 'BLANK';
486 :     $rh_ans->{error_flag} = undef;
487 :     $rh_ans->{error_message} = '';
488 :     $rh_ans->{done} =1; # no further checking is needed.
489 :     $rh_ans;
490 :     };
491 :    
492 :     1;
493 :     #package AnswerEvaluatorMaker;
494 :    

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9