diff --git a/lang/en/vpl.php b/lang/en/vpl.php index 3fc71bb31153fab287cb9af845fa541cea19fb57..a23c695fddbd86fcaa514fe50634f70198882ebc 100644 --- a/lang/en/vpl.php +++ b/lang/en/vpl.php @@ -128,6 +128,7 @@ $string ['gradeoptions'] = 'Grade options'; $string ['grader'] = "Evaluator"; $string ['gradercomments'] = 'Assessment report'; $string ['graderemoved'] = 'The grade has been removed'; +$string ['graphcomputationfailed'] = 'This graph data could not be computed.<br>The files data may be too large.'; $string ['groupwork'] = 'Group work'; $string ['inconsistentgroup'] = 'You are not member of only one group (0 o >1)'; $string ['incorrect_file_name'] = 'Incorrect file name'; diff --git a/similarity/diff.class.php b/similarity/diff.class.php index 709df0850f57dc9fc2469a6040fecfb854ea8015..21d2ab501cedb8f6b15a6c74519c1492976a39be 100644 --- a/similarity/diff.class.php +++ b/similarity/diff.class.php @@ -406,7 +406,7 @@ class vpl_diff { return $n1 + $n2; } $queue = array_fill(0, $n1 + $n2 + 1, array()); - $done = array_fill(0, $n1 + 1, array_fill(0, $n2 + 1, false)); + $done = array_fill(0, $n1 + 1, array()); $z = new stdClass(); $z->i1 = 0; $z->i2 = 0; @@ -422,7 +422,7 @@ class vpl_diff { $z = array_pop($queue[$priority]); $i1 = $z->i1; $i2 = $z->i2; - if ($done[$i1][$i2]) { + if (isset($done[$i1][$i2])) { continue; } else { $done[$i1][$i2] = true; @@ -472,31 +472,112 @@ class vpl_diff { * Computes diff between two files. * @param string $filedata1 * @param string $filedata2 + * @param int $timelimit The maximum amount of time to spend on this computation (in milliseconds). + * If provided and the time limit is exceeded, an exception will be thrown. * @return int The diff in number of chars. */ - static public function compute_filediff($filedata1, $filedata2) { + static public function compute_filediff($filedata1, $filedata2, $timelimit=0) { if (strlen($filedata1) == 0 || strlen($filedata2) == 0) { return strlen($filedata1) + strlen($filedata2); } + $starttime = microtime(true); $lines1 = explode("\n", $filedata1); $lines2 = explode("\n", $filedata2); - $linesdiff = self::calculatediff($lines1, $lines2, false); - $totaldiff = 0; - foreach ($linesdiff as $diff) { - switch ($diff->type) { - case '=' : - break; - case '<' : - $totaldiff += strlen( $lines1[$diff->ln1 - 1] ) + 1; - break; - case '>' : - $totaldiff += strlen( $lines2[$diff->ln2 - 1] ) + 1; - break; - default : - $totaldiff += self::compute_linediff( $lines1[$diff->ln1 - 1], $lines2[$diff->ln2 - 1] ); - break; + $n1 = count($lines1); + $n2 = count($lines2); + $queue = array_fill(0, $n1 + $n2 + 1, array()); + $prev = array_fill(0, $n1 + 1, array()); + $z = new stdClass(); + $z->i1 = 0; + $z->i2 = 0; + $z->d = 0; + $z->prev = 0; + $queue[0][] = $z; + $priority = 0; + + // A-star search of shortest diff. + while (true) { + if ($timelimit > 0 && microtime(true)-$starttime > $timelimit/1000) { + // Time out. + throw new Exception(); + } + while (count($queue[$priority]) == 0) { + $priority++; + } + $z = array_pop($queue[$priority]); + $i1 = $z->i1; + $i2 = $z->i2; + if (isset($prev[$i1][$i2])) { + continue; + } else { + $prev[$i1][$i2] = $z->prev; + } + + // Identical substring: 0 distance. + while ($i1 < $n1 && $i2 < $n2 && $lines1[$i1] == $lines2[$i2]) { + $i1++; + $i2++; + } + + if ($i1 == $n1 && $i2 == $n2) { + // Found shortest diff amongst lines. + // Backtrack to compute total diff as a sum of lines diff. + $totaldiff = 0; + while ($i1 > 0 && $i2 > 0) { + if (!isset($prev[$i1][$i2])) { + $i1--; + $i2--; + } else { + switch ($prev[$i1][$i2]) { + case 1 : + $totaldiff += strlen( $lines1[$i1-1] ) + 1; + $i1--; + break; + case 2 : + $totaldiff += strlen( $lines2[$i2-1] ) + 1; + $i2--; + break; + case 3 : + $totaldiff += self::compute_linediff( $lines1[$i1 - 1], $lines2[$i2 - 1] ); + case 0 : + $i1--; + $i2--; + break; + } + } + } + return $totaldiff; + } + + // Line addition in file1. + if ($i1 < $n1) { + $z1 = new stdClass(); + $z1->i1 = $i1 + 1; + $z1->i2 = $i2; + $z1->d = $z->d + 1; + $z1->prev = 1; + $queue[$z1->d + abs(($n1 - $z1->i1) - ($n2 - $z1->i2))][] = $z1; + } + + // Line addition in file2. + if ($i2 < $n2) { + $z2 = new stdClass(); + $z2->i1 = $i1; + $z2->i2 = $i2 + 1; + $z2->d = $z->d + 1; + $z2->prev = 2; + $queue[$z2->d + abs(($n1 - $z2->i1) - ($n2 - $z2->i2))][] = $z2; + } + + // Line change. + if ($i1 < $n1 && $i2 < $n2) { + $z3 = new stdClass(); + $z3->i1 = $i1 + 1; + $z3->i2 = $i2 + 1; + $z3->d = $z->d + 1; + $z3->prev = 3; + $queue[$z3->d + abs(($n1 - $z3->i1) - ($n2 - $z3->i2))][] = $z3; } } - return $totaldiff; } } diff --git a/styles.css b/styles.css index bbd9957565b6cba4b9b3d5c2ace787575cd31bdb..776b8efbd3c8168d28f496b08f21d82839a8f2db 100644 --- a/styles.css +++ b/styles.css @@ -8,6 +8,17 @@ display: inline-block; } +.path-mod-vpl.pagelayout-incourse .chart-area.nograph { + text-align: center; + vertical-align: top; + top: 2em; + position: relative; +} + +.path-mod-vpl.pagelayout-incourse .chart-area.nograph b { + font-size: .9em; +} + .path-mod-vpl .chart-area canvas { width: 100%; height: 100%; diff --git a/views/vpl_grapher.class.php b/views/vpl_grapher.class.php index 4769428c36c1ba329eb6afdf08a8634950252f07..d5a5c35660ff21bcd72cab31b6e3b637d22dff0a 100644 --- a/views/vpl_grapher.class.php +++ b/views/vpl_grapher.class.php @@ -133,55 +133,62 @@ class vpl_grapher { if (count( $this->submissions ) == 0 || $this->userid === null) { return; } - $submissionslist = array_reverse( $this->submissions ); + try { + $title = $this->username . ' - '; + if ($diff) { + $title .= get_string( 'filesdiffevolution', VPL ); + } else { + $title .= get_string( 'filessizeevolution', VPL ); + } - $interval = self::compute_xaxis_legend_interval(count( $submissionslist ), 20); + $submissionslist = array_reverse( $this->submissions ); + $interval = self::compute_xaxis_legend_interval(count( $submissionslist ), 20); - $i = 1; - $subsn = array(); - $series = array(); - $names = array(); - $totalseries = array(); - if ($diff) { - $reqfiles = $this->vpl->get_fgm('required')->getAllFiles(); - } - foreach ($submissionslist as $subinstance) { - $submission = new mod_vpl_submission( $this->vpl, $subinstance ); - $files = $submission->get_submitted_files(); - $total = 0; - foreach ($files as $name => $data) { - if (!isset($series[$name])) { - $names[] = $name; - $series[$name] = array_fill(0, $i - 1, null); + $i = 1; + $subsn = array(); + $series = array(); + $names = array(); + $totalseries = array(); + if ($diff) { + $reqfiles = $this->vpl->get_fgm('required')->getAllFiles(); + } + foreach ($submissionslist as $subinstance) { + $submission = new mod_vpl_submission( $this->vpl, $subinstance ); + $files = $submission->get_submitted_files(); + $total = 0; + foreach ($files as $name => $data) { + if (!isset($series[$name])) { + $names[] = $name; + $series[$name] = array_fill(0, $i - 1, null); + } + if ($diff && isset($reqfiles[$name])) { + $value = vpl_diff::compute_filediff($reqfiles[$name], $data, 500); + } else { + $value = strlen( $data ); + } + $series[$name][] = $value; + $total += $value; } - if ($diff && isset($reqfiles[$name])) { - $value = vpl_diff::compute_filediff($reqfiles[$name], $data); - } else { - $value = strlen( $data ); + foreach ($series as &$serie) { + if (count($serie) < $i) { + $serie[] = null; + } } - $series[$name][] = $value; - $total += $value; + $totalseries[] = $total; + $subsn[] = $i % $interval == 0 ? $i : ''; + $i++; } - foreach ($series as &$serie) { - if (count($serie) < $i) { - $serie[] = null; - } + if ($diff && count($names) > 1) { + $names[] = get_string('total'); + $series[get_string('total')] = $totalseries; } - $totalseries[] = $total; - $subsn[] = $i % $interval == 0 ? $i : ''; - $i++; - } - if ($diff && count($names) > 1) { - $names[] = get_string('total'); - $series[get_string('total')] = $totalseries; - } - $title = $this->username . ' - '; - if ($diff) { - $title .= get_string( 'filesdiffevolution', VPL ); - } else { - $title .= get_string( 'filessizeevolution', VPL ); + self::draw( $title, get_string( 'submissions', VPL ) , get_string( "sizeb" ), $subsn, $series, $names ); + } catch (Exception $e) { + echo '<span class="chart-area nograph">'; + echo '<b>' . $title . '</b><br>'; + echo get_string('graphcomputationfailed', VPL); + echo '</span>'; } - self::draw( $title, get_string( 'submissions', VPL ) , get_string( "sizeb" ), $subsn, $series, $names ); } static protected function new_working_period($workduration, &$firstwork, $intervals) {