MDL-79863 qtype_ordering: qtype_ordering add new gradetype: LONGEST_ORDERED_SUBSET

This commit is contained in:
Gordon Bateson 2016-01-11 18:46:24 +09:00 committed by Mathew May
parent ee1a5f57b2
commit 59d7a77da8
5 changed files with 147 additions and 44 deletions

View File

@ -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);

View File

@ -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)***.';
: 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)***.
**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.';

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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)';