From 59d7a77da8b7eb1aeb8839aec7ab949c4aa7a882 Mon Sep 17 00:00:00 2001 From: Gordon Bateson Date: Mon, 11 Jan 2016 18:46:24 +0900 Subject: [PATCH] MDL-79863 qtype_ordering: qtype_ordering add new gradetype: LONGEST_ORDERED_SUBSET --- question/type/ordering/edit_ordering_form.php | 4 +- .../type/ordering/lang/en/qtype_ordering.php | 6 +- question/type/ordering/question.php | 149 +++++++++++++----- question/type/ordering/renderer.php | 28 +++- question/type/ordering/version.php | 4 +- 5 files changed, 147 insertions(+), 44 deletions(-) diff --git a/question/type/ordering/edit_ordering_form.php b/question/type/ordering/edit_ordering_form.php index a7cc0590735..2a0c50b37b6 100644 --- a/question/type/ordering/edit_ordering_form.php +++ b/question/type/ordering/edit_ordering_form.php @@ -47,6 +47,7 @@ class qtype_ordering_edit_form extends question_edit_form { const RELATIVE_NEXT_INCLUDE_LAST = 2; const RELATIVE_ONE_PREVIOUS_AND_NEXT = 3; const RELATIVE_ALL_PREVIOUS_AND_NEXT = 4; + const LONGEST_ORDERED_SUBSET = 5; // this functionality is currently disabled // because it is not fully functional @@ -112,7 +113,8 @@ class qtype_ordering_edit_form extends question_edit_form { self::RELATIVE_NEXT_EXCLUDE_LAST => get_string('relativenextexcludelast', $plugin), self::RELATIVE_NEXT_INCLUDE_LAST => get_string('relativenextincludelast', $plugin), self::RELATIVE_ONE_PREVIOUS_AND_NEXT => get_string('relativeonepreviousandnext', $plugin), - self::RELATIVE_ALL_PREVIOUS_AND_NEXT => get_string('relativeallpreviousandnext', $plugin) + self::RELATIVE_ALL_PREVIOUS_AND_NEXT => get_string('relativeallpreviousandnext', $plugin), + self::LONGEST_ORDERED_SUBSET => get_string('longestorderedsubset', $plugin) ); $mform->addElement('select', $name, $label, $options); $mform->addHelpButton($name, $name, $plugin); diff --git a/question/type/ordering/lang/en/qtype_ordering.php b/question/type/ordering/lang/en/qtype_ordering.php index be8584a29f4..9344ebabec7 100644 --- a/question/type/ordering/lang/en/qtype_ordering.php +++ b/question/type/ordering/lang/en/qtype_ordering.php @@ -38,10 +38,14 @@ $string['gradingtype_help'] = 'Choose the type of grading calculation. : An item is considered correct if both the previous and next items are the same as they are in the correct answer. The first item should have no previous item, and the last item should have no next item. Thus, there are two possible points for each item, and the highest possible score for the question is **twice** the number of items displayed to the student. **Relative to ALL previous and next items** -: An item is considered correct if it is preceded by all the same items as it is in the correct answer, and it is followed by all the same items as it is in the correct answer. The order of the previous items does not matter, and nor does the order of the following items. Thus, if ***n*** items are displayed to the student, the number of marks available for each item is ***(n - 1)***, and the highest mark availabe for the question is ***n x (n - 1)***, which is the same as ***(n² - n)***.'; +: An item is considered correct if it is preceded by all the same items as it is in the correct answer, and it is followed by all the same items as it is in the correct answer. The order of the previous items does not matter, and nor does the order of the following items. Thus, if ***n*** items are displayed to the student, the number of marks available for each item is ***(n - 1)***, and the highest mark availabe for the question is ***n x (n - 1)***, which is the same as ***(n² - n)***. + +**Longest ordered subset** +: The grade is the number of items in the longest ordered subset of items. The highest possible grade is the same as the number of items displayed. A subset must have at least two items. Subsets do not need to start at the first item (but they can) and they do not need to be contiguous (but they can be). Where there are multiple subsets of equal length, items in the subset that is found first, when searching from left to right, will be displayed as correct. Other items will be marked as incorrect.'; $string['horizontal'] = 'Horizontal'; $string['layouttype_help'] = 'Choose whether to display the items vertically or horizontally.'; $string['layouttype'] = 'Layout of items'; +$string['longestorderedsubset'] = 'Longest ordered subset'; $string['noresponsedetails'] = 'Sorry, no details of the response to this question are available.'; $string['noscore'] = 'No score'; $string['notenoughanswers'] = 'Ordering questions must have more than {$a} answers.'; diff --git a/question/type/ordering/question.php b/question/type/ordering/question.php index 5cf99bbfa44..e08331c5da9 100644 --- a/question/type/ordering/question.php +++ b/question/type/ordering/question.php @@ -200,6 +200,12 @@ class qtype_ordering_question extends question_graded_automatically { $countanswers += count($answerids->next); } break; + + case 5: // LONGEST_ORDERED_SUBSET + $subset = $this->get_ordered_subset(); + $countcorrect = count($subset); + $countanswers = count($this->currentresponse); + break; } if ($countanswers==0) { $fraction = 0; @@ -209,45 +215,6 @@ class qtype_ordering_question extends question_graded_automatically { return array($fraction, question_state::graded_state_for_fraction($fraction)); } - public function get_next_answerids($answerids, $last_item=false) { - $nextanswerids = array(); - $i_max = count($answerids); - $i_max--; - if ($last_item) { - $nextanswerid = 0; - } else { - $nextanswerid = $answerids[$i_max]; - $i_max--; - } - for ($i=$i_max; $i>=0; $i--) { - $thisanswerid = $answerids[$i]; - $nextanswerids[$thisanswerid] = $nextanswerid; - $nextanswerid = $thisanswerid; - } - return $nextanswerids; - } - - public function get_previous_and_next_answerids($answerids, $all=false) { - $prevnextanswerids = array(); - $next = $answerids; - $prev = array(); - while ($answerid = array_shift($next)) { - if ($all) { - $prevnextanswerids[$answerid] = (object)array( - 'prev' => $prev, - 'next' => $next - ); - } else { - $prevnextanswerids[$answerid] = (object)array( - 'prev' => array(empty($prev) ? 0 : $prev[0]), - 'next' => array(empty($next) ? 0 : $next[0]) - ); - } - array_unshift($prev, $answerid); - } - return $prevnextanswerids; - } - public function check_file_access($qa, $options, $component, $filearea, $args, $forcedownload) { if ($component=='question') { if ($filearea=='answer') { @@ -342,4 +309,108 @@ class qtype_ordering_question extends question_graded_automatically { default: return ''; // shouldn't happen !! } } + + public function get_next_answerids($answerids, $last_item=false) { + $nextanswerids = array(); + $i_max = count($answerids); + $i_max--; + if ($last_item) { + $nextanswerid = 0; + } else { + $nextanswerid = $answerids[$i_max]; + $i_max--; + } + for ($i=$i_max; $i>=0; $i--) { + $thisanswerid = $answerids[$i]; + $nextanswerids[$thisanswerid] = $nextanswerid; + $nextanswerid = $thisanswerid; + } + return $nextanswerids; + } + + public function get_previous_and_next_answerids($answerids, $all=false) { + $prevnextanswerids = array(); + $next = $answerids; + $prev = array(); + while ($answerid = array_shift($next)) { + if ($all) { + $prevnextanswerids[$answerid] = (object)array( + 'prev' => $prev, + 'next' => $next + ); + } else { + $prevnextanswerids[$answerid] = (object)array( + 'prev' => array(empty($prev) ? 0 : $prev[0]), + 'next' => array(empty($next) ? 0 : $next[0]) + ); + } + array_unshift($prev, $answerid); + } + return $prevnextanswerids; + } + + public function get_ordered_subset() { + $positions = $this->get_ordered_positions($this->correctresponse, + $this->currentresponse); + $subsets = $this->get_ordered_subsets($positions, count($positions)); + $countcorrect = 1; // i.e. ignore single item subsets + $subsetcorrect = array(); + foreach ($subsets as $subset) { + $count = count($subset); + if ($count > $countcorrect) { + $countcorrect = $count; + $subsetcorrect = $subset; + } + } + return $subsetcorrect; + } + + public function get_ordered_positions($correctresponse, $currentresponse) { + $positions = array(); + foreach ($currentresponse as $answerid) { + $positions[] = array_search($answerid, $correctresponse); + } + return $positions; + } + + public function get_ordered_subsets($positions, $i_max, $i_min=0, $previous=-1) { + + // $subsets is the collection of all subsets within $positions + $subsets = array(); + + // $subset is the main (=earliest or leftmost) subset within $positions + $subset = array(); + + for ($i=$i_min; $i<$i_max; $i++) { + $current = $positions[$i]; + + // if $current is out of sequence, skip it + if ($current < $previous) { + continue; + } + + // if $current is not the position immediately after + // $previous, then we have a non-contiguous sequence + if ($current > ($previous + 1)) { + + // fetch all the subsets in the tail of $positions, + // and prepend $subset-so-far onto each tail subset + $tailsets = $this->get_ordered_subsets($positions, $i_max, $i+1, $previous); + foreach ($tailsets as $tailset) { + $subsets[] = array_merge($subset, $tailset); + } + } + + // add $i to the main subset + $subset[] = $i; + + // update the $previous value + $previous = $current; + } + if (count($subset)) { + // put the main $subset first + array_unshift($subsets, $subset); + } + return $subsets; + } } diff --git a/question/type/ordering/renderer.php b/question/type/ordering/renderer.php index 01fe535cca8..a9e76ce4915 100644 --- a/question/type/ordering/renderer.php +++ b/question/type/ordering/renderer.php @@ -184,6 +184,7 @@ class qtype_ordering_renderer extends qtype_with_combined_feedback_renderer { case 2: $gradingtype = get_string('relativenextincludelast', $plugin); break; case 3: $gradingtype = get_string('relativeonepreviousandnext', $plugin); break; case 4: $gradingtype = get_string('relativeallpreviousandnext', $plugin); break; + case 5: $gradingtype = get_string('longestorderedsubset', $plugin); break; } // format grading type, e.g. Grading type: Relative to next item, excluding last item @@ -222,7 +223,11 @@ class qtype_ordering_renderer extends qtype_with_combined_feedback_renderer { $scoredetails .= html_writer::end_tag('ol'); // format gradedetails, e.g. 4 /6 = 67% - $gradedetails = round(100 * $totalscore / $totalmaxscore, 0); + if ($totalscore==0 || $totalmaxscore==0) { + $gradedetails = 0; + } else { + $gradedetails = round(100 * $totalscore / $totalmaxscore, 0); + } $gradedetails = "$totalscore / $totalmaxscore = $gradedetails%"; $gradedetails = get_string('gradedetails', $plugin).': '.$gradedetails; $gradedetails = html_writer::tag('p', $gradedetails, array('class' => 'gradedetails')); @@ -296,6 +301,18 @@ class qtype_ordering_renderer extends qtype_with_combined_feedback_renderer { $this->correctinfo = $question->get_previous_and_next_answerids($question->correctresponse, true); $this->currentinfo = $question->get_previous_and_next_answerids($question->currentresponse, true); break; + case 5: // LONGEST_ORDERED_SUBSET + $this->correctinfo = $question->correctresponse; + $this->currentinfo = $question->currentresponse; + $subset = $question->get_ordered_subset(); + foreach ($this->currentinfo as $position => $answerid) { + if (array_search($position, $subset)===false) { + $this->currentinfo[$position] = 0; + } else { + $this->currentinfo[$position] = 1; + } + } + break; } } @@ -352,6 +369,15 @@ class qtype_ordering_renderer extends qtype_with_combined_feedback_renderer { $score += count($next); } break; + + case 5: // LONGEST_ORDERED_SUBSET + if (isset($correctinfo[$position])) { + if (isset($currentinfo[$position])) { + $score = $currentinfo[$position]; + } + $maxscore = 1; + } + break; } if ($maxscore===null) { diff --git a/question/type/ordering/version.php b/question/type/ordering/version.php index cf56b2efca2..5587d1e2cc8 100644 --- a/question/type/ordering/version.php +++ b/question/type/ordering/version.php @@ -31,5 +31,5 @@ $plugin->cron = 0; $plugin->component = 'qtype_ordering'; $plugin->maturity = MATURITY_STABLE; $plugin->requires = 2010112400; // Moodle 2.0 -$plugin->version = 2016010238; -$plugin->release = '2016-01-02 (38)'; +$plugin->version = 2016011139; +$plugin->release = '2016-01-11 (39)';