題目部分
題目是整個OJ系統的練習基礎,無論是平時學生的練習還是比賽時的準備用題,題目系統在OJ中都是至關重要的。在controllers
資料夾下,負責題目部分的程式碼檔案分別為:problem_set.php
,problem.php
,problem_statistics.php
,problem_data_manage.php
,problem_statement_manage.php
,problem_managers_manage.php
。以下是具體的詳細內容
題目列表
在problem_set.php
中,檔案主要用於管理和展示題目列表。以下是程式碼的詳細解釋:
1. 引入庫
requirePHPLib('form');
requirePHPLib('judger');
requirePHPLib('data');
這些函式用於引入需要的庫檔案,分別用於表單處理、評測系統和資料操作。
2. 新增題目按鈕
if (isSuperUser($myUser)) {
$new_problem_form = new UOJForm('new_problem');
$new_problem_form->handle = function() {
DB::query("insert into problems (title, is_hidden, submission_requirement) values ('New Problem', 1, '{}')");
$id = DB::insert_id();
DB::query("insert into problems_contents (id, statement, statement_md) values ($id, '', '')");
dataNewProblem($id);
};
$new_problem_form->submit_button_config['align'] = 'right';
$new_problem_form->submit_button_config['class_str'] = 'btn btn-primary';
$new_problem_form->submit_button_config['text'] = UOJLocale::get('problems::add new');
$new_problem_form->submit_button_config['smart_confirm'] = '';
$new_problem_form->runAtServer();
}
- 檢查當前使用者是否是超級使用者,如果是,則顯示一個用於新增題目的表單。
- 當表單提交時,插入一條新題目記錄到
problems
表,並獲取新題目的ID。 - 插入一條空的題目內容記錄到
problems_contents
表。 - 表單按鈕的樣式和文字配置。
3. 列印每個題目
function echoProblem($problem) {
global $myUser;
if (isProblemVisibleToUser($problem, $myUser)) {
echo '<tr class="text-center">';
if ($problem['submission_id']) {
echo '<td class="success">';
} else {
echo '<td>';
}
echo '#', $problem['id'], '</td>';
echo '<td class="text-left">';
if ($problem['is_hidden']) {
echo ' <span class="text-danger">[隱藏]</span> ';
}
echo '<a href="/problem/', $problem['id'], '">', $problem['title'], '</a>';
if (isset($_COOKIE['show_tags_mode'])) {
foreach (queryProblemTags($problem['id']) as $tag) {
echo '<a class="uoj-problem-tag">', '<span class="badge badge-pill badge-secondary">', HTML::escape($tag), '</span>', '</a>';
}
}
echo '</td>';
if (isset($_COOKIE['show_submit_mode'])) {
$perc = $problem['submit_num'] > 0 ? round(100 * $problem['ac_num'] / $problem['submit_num']) : 0;
echo <<<EOD
<td><a href="/submissions?problem_id={$problem['id']}&min_score=100&max_score=100">×{$problem['ac_num']}</a></td>
<td><a href="/submissions?problem_id={$problem['id']}">×{$problem['submit_num']}</a></td>
<td>
<div class="progress bot-buffer-no">
<div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="$perc" aria-valuemin="0" aria-valuemax="100" style="width: $perc%; min-width: 20px;">{$perc}%</div>
</div>
</td>
EOD;
}
echo '<td class="text-left">', getClickZanBlock('P', $problem['id'], $problem['zan']), '</td>';
echo '</tr>';
}
}
echoProblem
函式用於輸出每個題目的HTML。- 判斷題目是否對當前使用者可見,如果可見,則輸出題目的ID、標題、標籤、提交統計和點贊數。
4. 構建查詢條件
$cond = array();
$search_tag = null;
$cur_tab = isset($_GET['tab']) ? $_GET['tab'] : 'all';
if ($cur_tab == 'template') {
$search_tag = "模板題";
}
if (isset($_GET['tag'])) {
$search_tag = $_GET['tag'];
}
if ($search_tag) {
$cond[] = "'".DB::escape($search_tag)."' in (select tag from problems_tags where problems_tags.problem_id = problems.id)";
}
if (isset($_GET["search"])) {
$cond[]="title like '%".DB::escape($_GET["search"])."%' or id like '%".DB::escape($_GET["search"])."%'";
}
if ($cond) {
$cond = join($cond, ' and ');
} else {
$cond = '1';
}
- 根據URL引數構建查詢條件,用於篩選題目列表。
5. 定義表頭
$header = '<tr>';
$header .= '<th class="text-center" style="width:5em;">ID</th>';
$header .= '<th>'.UOJLocale::get('problems::problem').'</th>';
if (isset($_COOKIE['show_submit_mode'])) {
$header .= '<th class="text-center" style="width:5em;">'.UOJLocale::get('problems::ac').'</th>';
$header .= '<th class="text-center" style="width:5em;">'.UOJLocale::get('problems::submit').'</th>';
$header .= '<th class="text-center" style="width:150px;">'.UOJLocale::get('problems::ac ratio').'</th>';
}
$header .= '<th class="text-center" style="width:180px;">'.UOJLocale::get('appraisal').'</th>';
$header .= '</tr>';
- 定義題目列表的表頭,根據使用者的顯示設定調整顯示內容。
6. 定義分頁器和表格樣式
$pag_config = array('page_len' => 100);
$pag_config['col_names'] = array('*');
$pag_config['table_name'] = "problems left join best_ac_submissions on best_ac_submissions.submitter = '{$myUser['username']}' and problems.id = best_ac_submissions.problem_id";
$pag_config['cond'] = $cond;
$pag_config['tail'] = "order by id asc";
$pag = new Paginator($pag_config);
$div_classes = array('table-responsive');
$table_classes = array('table', 'table-bordered', 'table-hover', 'table-striped');
- 設定分頁器的配置,包括每頁顯示的題目數量、查詢的表名和條件等。
7. 輸出HTML內容
<?php echoUOJPageHeader(UOJLocale::get('problems')) ?>
<div class="row">
<div class="col-sm-4">
<?= HTML::tablist($tabs_info, $cur_tab, 'nav-pills') ?>
</div>
<div class="col-sm-4 order-sm-9 checkbox text-right">
<label class="checkbox-inline" for="input-show_tags_mode"><input type="checkbox" id="input-show_tags_mode" <?= isset($_COOKIE['show_tags_mode']) ? 'checked="checked" ': ''?>/> <?= UOJLocale::get('problems::show tags') ?></label>
<label class="checkbox-inline" for="input-show_submit_mode"><input type="checkbox" id="input-show_submit_mode" <?= isset($_COOKIE['show_submit_mode']) ? 'checked="checked" ': ''?>/> <?= UOJLocale::get('problems::show statistics') ?></label>
</div>
<div class="col-sm-4 order-sm-5">
<?php echo $pag->pagination(); ?>
</div>
</div>
<div class="top-buffer-sm"></div>
<script type="text/javascript">
$('#input-show_tags_mode').click(function() {
if (this.checked) {
$.cookie('show_tags_mode', '', {path: '/problems'});
} else {
$.removeCookie('show_tags_mode', {path: '/problems'});
}
location.reload();
});
$('#input-show_submit_mode').click(function() {
if (this.checked) {
$.cookie('show_submit_mode', '', {path: '/problems'});
} else {
$.removeCookie('show_submit_mode', {path: '/problems'});
}
location.reload();
});
</script>
<?php
echo '<div class="', join($div_classes, ' '), '">';
echo '<table class="', join($table_classes, ' '), '">';
echo '<thead>';
echo $header;
echo '</thead>';
echo '<tbody>';
foreach ($pag->get() as $idx => $row) {
echoProblem($row);
echo "\n";
}
if ($pag->isEmpty()) {
echo '<tr><td class="text-center" colspan="233">'.UOJLocale::get('none').'</td></tr>';
}
echo '</tbody>';
echo '</table>';
echo '</div>';
if (isSuperUser($myUser)) {
$new_problem_form->printHTML();
}
echo $pag->
pagination();
?>
<?php echoUOJPageFooter() ?>
- 輸出頁面頭部、標籤欄、分頁導航、題目列表表格和底部。
- 使用JavaScript控制標籤和統計資訊的顯示。
題目頁面與提交
題目頁面與提交頁面下主要有”描述“、”提交“和”管理“(此功能只有管理員才能看到)功能。
當然,下面是對這段PHP程式碼的詳細解釋:
引入必要的PHP庫
requirePHPLib('form');
requirePHPLib('judger');
requirePHPLib('form')
:引入處理表單的庫。requirePHPLib('judger')
:引入處理評測的庫。
驗證題目ID並查詢題目資訊
if (!validateUInt($_GET['id']) || !($problem = queryProblemBrief($_GET['id']))) {
become404Page();
}
$problem_content = queryProblemContent($problem['id']);
- 驗證
id
是否為正整數,且查詢題目的簡要資訊。 - 如果題目不存在或ID無效,返回404頁面。
- 查詢題目詳細內容。
處理競賽相關資訊
$contest = validateUInt($_GET['contest_id']) ? queryContest($_GET['contest_id']) : null;
if ($contest != null) {
genMoreContestInfo($contest);
$problem_rank = queryContestProblemRank($contest, $problem);
if ($problem_rank == null) {
become404Page();
} else {
$problem_letter = chr(ord('A') + $problem_rank - 1);
}
}
- 驗證
contest_id
是否為正整數,且查詢競賽資訊。 - 如果競賽存在,生成更多競賽資訊並查詢題目在競賽中的排名。
- 如果題目不在競賽中,返回404頁面。
- 計算題目在競賽中的字母標識。
檢查使用者許可權和題目可見性
$is_in_contest = false;
$ban_in_contest = false;
if ($contest != null) {
if (!hasContestPermission($myUser, $contest)) {
if ($contest['cur_progress'] == CONTEST_NOT_STARTED) {
become404Page();
} elseif ($contest['cur_progress'] == CONTEST_IN_PROGRESS) {
if ($myUser == null || !hasRegistered($myUser, $contest)) {
becomeMsgPage("<h1>比賽正在進行中</h1><p>很遺憾,您尚未報名。比賽結束後再來看吧~</p>");
} else {
$is_in_contest = true;
DB::update("update contests_registrants set has_participated = 1 where username = '{$myUser['username']}' and contest_id = {$contest['id']}");
}
} else {
$ban_in_contest = !isProblemVisibleToUser($problem, $myUser);
}
}
} else {
if (!isProblemVisibleToUser($problem, $myUser)) {
become404Page();
}
}
- 初始化
is_in_contest
和ban_in_contest
為false
。 - 如果競賽存在且使用者無許可權:
- 競賽未開始,返回404頁面。
- 競賽進行中,且使用者未登入或未報名,顯示比賽進行中的提示資訊。
- 如果使用者已報名,設定
is_in_contest
為true
並更新資料庫。 - 競賽已結束,檢查題目是否對使用者可見,設定
ban_in_contest
。
- 如果沒有競賽,檢查題目是否對使用者可見,不可見則返回404頁面。
獲取提交需求和額外配置
$submission_requirement = json_decode($problem['submission_requirement'], true);
$problem_extra_config = getProblemExtraConfig($problem);
$custom_test_requirement = getProblemCustomTestRequirement($problem);
- 解析題目的提交需求和自定義測試需求。
- 獲取題目的額外配置。
處理自定義測試狀態請求
if ($custom_test_requirement && $_GET['get'] == 'custom-test-status-details' && Auth::check()) {
if ($custom_test_submission == null) {
echo json_encode(null);
} else if ($custom_test_submission['status'] != 'Judged') {
echo json_encode(array(
'judged' => false,
'html' => getSubmissionStatusDetails($custom_test_submission)
));
} else {
ob_start();
$styler = new CustomTestSubmissionDetailsStyler();
if (!hasViewPermission($problem_extra_config['view_details_type'], $myUser, $problem, $submission)) {
$styler->fade_all_details = true;
}
echoJudgementDetails($custom_test_submission_result['details'], $styler, 'custom_test_details');
$result = ob_get_contents();
ob_end_clean();
echo json_encode(array(
'judged' => true,
'html' => getSubmissionStatusDetails($custom_test_submission),
'result' => $result
));
}
die();
}
- 如果有自定義測試需求,並且GET請求引數
get
為custom-test-status-details
且使用者已登入:- 如果沒有自定義測試提交記錄,返回
null
。 - 如果提交記錄的狀態不是
Judged
,返回評測狀態詳情。 - 否則,獲取評測詳情並返回評測結果。
- 如果沒有自定義測試提交記錄,返回
檢查是否可以使用ZIP上傳
$can_use_zip_upload = true;
foreach ($submission_requirement as $req) {
if ($req['type'] == 'source code') {
$can_use_zip_upload = false;
}
}
- 檢查提交需求中是否包含原始碼,如果包含則不允許使用ZIP上傳。
處理ZIP檔案上傳
function handleUpload($zip_file_name, $content, $tot_size) {
global $problem, $contest, $myUser, $is_in_contest;
$content['config'][] = array('problem_id', $problem['id']);
if ($is_in_contest && $contest['extra_config']["contest_type"] != 'IOI' && !isset($contest['extra_config']["problem_{$problem['id']}"])) {
$content['final_test_config'] = $content['config'];
$content['config'][] = array('test_sample_only', 'on');
}
$esc_content = DB::escape(json_encode($content));
$language = '/';
foreach ($content['config'] as $row) {
if (strEndWith($row[0], '_language')) {
$language = $row[1];
break;
}
}
if ($language != '/') {
Cookie::set('uoj_preferred_language', $language, time() + 60 * 60 * 24 * 365, '/');
}
$esc_language = DB::escape($language);
$result = array();
$result['status'] = "Waiting";
$result_json = json_encode($result);
if ($is_in_contest) {
DB::query("insert into submissions (problem_id, contest_id, submit_time, submitter, content, language, tot_size, status, result, is_hidden) values (${problem['id']}, ${contest['id']}, now(), '${myUser['username']}', '$esc_content', '$esc_language', $tot_size, '${result['status']}', '$result_json', 0)");
} else {
DB::query("insert into submissions (problem_id, submit_time, submitter, content, language, tot_size, status, result, is_hidden) values (${problem['id']}, now(), '${myUser['username']}', '$esc_content', '$esc_language', $tot_size, '${result['status']}', '$result_json', {$problem['is_hidden']})");
}
}
- 處理ZIP檔案上傳:
- 根據上傳內容配置更新提交記錄。
- 處理語言設定並儲存。
- 將提交記錄插入資料庫。
處理自定義測試上傳
function handleCustomTestUpload($zip_file_name, $content, $tot_size) {
global $problem, $contest, $myUser;
$content['config'][] = array('problem_id', $problem['id']);
$content['config'][] = array('custom_test', 'on');
$esc_content = DB::escape(json_encode($content));
$language = '/';
foreach ($content['config'] as $row) {
if (strEndWith($row[0], '_language')) {
$language = $row[1];
break;
}
}
if ($language != '/') {
Cookie::set('uoj_preferred_language', $language, time() + 60 * 60 * 24 * 365, '/');
}
$esc_language = DB::escape($language);
$result = array();
$result['status'] = "Waiting";
$result_json = json_encode($result);
DB::insert("insert into custom_test_submissions (problem_id, submit_time, submitter, content, status, result) values ({$problem['id']}, now(), '{$myUser['username']}', '$esc_content', '{$result['status']}', '$result_json')");
}
- 類似ZIP檔案上傳的處理方式,處理自定義測試上傳並插入資料庫。
建立並處理表單
if ($can_use_zip_upload) {
$zip_answer_form = newZipSubmissionForm('zip_answer',
$submission_requirement,
'uojRandAvaiableSubmissionFileName',
'handleUpload');
$zip_answer_form->runAtServer();
}
$answer_form = newSubmissionForm('answer',
$submission_requirement,
'handleUpload');
$answer_form->runAtServer();
if ($custom_test_requirement) {
$custom_test_form = newSubmissionForm('custom_test',
$custom_test_requirement,
'handleCustomTestUpload');
$custom_test_form->runAtServer();
}
- 根據題目的提交需求,建立並處理不同型別的表單:
- 如果允許ZIP上傳,建立ZIP提交表單。
- 建立普通提交表單。
- 如果有自定義測試需求,建立自定義測試表單。
管理員題目描述處理
這段程式碼實現了一個問題管理頁面,允許具有相應許可權的使用者編輯問題的內容、標籤和可見性
引入必要的庫
requirePHPLib('form');
這行程式碼引入了form
庫,提供表單處理功能。
驗證和獲取問題資訊
if (!validateUInt($_GET['id']) || !($problem = queryProblemBrief($_GET['id']))) {
become404Page();
}
validateUInt($_GET['id'])
:驗證GET引數id
是否為無符號整數。queryProblemBrief($_GET['id'])
:查詢問題簡要資訊。如果問題不存在或驗證失敗,呼叫become404Page()
,返回404頁面。
if (!hasProblemPermission($myUser, $problem)) {
become403Page();
}
hasProblemPermission($myUser, $problem)
:檢查當前使用者是否有許可權管理這個問題。如果沒有許可權,呼叫become403Page()
,返回403頁面。
獲取問題詳細內容和標籤
$problem_content = queryProblemContent($problem['id']);
$problem_tags = queryProblemTags($problem['id']);
queryProblemContent($problem['id'])
:獲取問題的詳細內容。queryProblemTags($problem['id'])
:獲取問題的標籤。
初始化編輯器
$problem_editor = new UOJBlogEditor();
$problem_editor->name = 'problem';
$problem_editor->blog_url = "/problem/{$problem['id']}";
$problem_editor->cur_data = array(
'title' => $problem['title'],
'content_md' => $problem_content['statement_md'],
'content' => $problem_content['statement'],
'tags' => $problem_tags,
'is_hidden' => $problem['is_hidden']
);
$problem_editor->label_text = array_merge($problem_editor->label_text, array(
'view blog' => '檢視題目',
'blog visibility' => '題目可見性'
));
- 建立一個新的
UOJBlogEditor
例項用於編輯問題內容。 - 設定編輯器名稱、URL和當前資料(標題、內容、標籤和可見性)。
- 更新編輯器的標籤文字(如“檢視題目”和“題目可見性”)。
定義儲存操作
$problem_editor->save = function($data) {
global $problem, $problem_tags;
DB::update("update problems set title = '".DB::escape($data['title'])."' where id = {$problem['id']}");
DB::update("update problems_contents set statement = '".DB::escape($data['content'])."', statement_md = '".DB::escape($data['content_md'])."' where id = {$problem['id']}");
if ($data['tags'] !== $problem_tags) {
DB::delete("delete from problems_tags where problem_id = {$problem['id']}");
foreach ($data['tags'] as $tag) {
DB::insert("insert into problems_tags (problem_id, tag) values ({$problem['id']}, '".DB::escape($tag)."')");
}
}
if ($data['is_hidden'] != $problem['is_hidden'] ) {
DB::update("update problems set is_hidden = {$data['is_hidden']} where id = {$problem['id']}");
DB::update("update submissions set is_hidden = {$data['is_hidden']} where problem_id = {$problem['id']}");
DB::update("update hacks set is_hidden = {$data['is_hidden']} where problem_id = {$problem['id']}");
}
};
- 定義
save
函式以處理編輯器儲存操作。 - 更新問題標題和內容(包括Markdown格式和HTML格式)。
- 如果標籤發生變化,刪除舊標籤並插入新標籤。
- 如果可見性發生變化,更新問題、提交的可見性。
題目資料管理
在problem_data_manage.php
中,包含著對於一道題目資料上傳的邏輯操作。這裡擷取部分重要程式碼進行解釋
資料上傳程式碼
<div class="modal fade" id="UploadDataModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="myModalLabel">上傳資料</h4>
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
</div>
<div class="modal-body">
<form action="" method="post" enctype="multipart/form-data" role="form">
<div class="form-group">
<label for="exampleInputFile">上傳zip檔案</label>
<input type="file" name="problem_data_file" id="problem_data_file">
<p class="help-block">說明:請將所有資料放置於壓縮包根目錄內。若壓縮包內僅存在資料夾而不存在檔案,則會將這些一級子資料夾下的內容移動到根目錄下,然後這些一級子資料夾刪除;若這些子資料夾記憶體在同名檔案,則會發生隨機替換,僅保留一個副本。</p>
</div>
<input type="hidden" name="problem_data_file_submit" value="submit">
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-success">上傳</button>
</form>
<button type="button" class="btn btn-secondary" data-dismiss="modal">關閉</button>
</div>
</div>
</div>
</div>
- 使用者可以點選上傳資料按鈕,彈出此模態框。
- 使用者可以選擇一個ZIP檔案,並點選模態框底部的上傳按鈕來提交選擇的檔案。
- 提交表單時,資料將被提交到當前頁面(
action=""
表示當前頁面),並使用POST方法傳輸資料。 - 上傳的ZIP檔案將會作為名為
problem_data_file
的檔案上傳欄位提交。 - 提交完成後,可以點選模態框右上角的關閉按鈕或底部的關閉按鈕來關閉模態框。
線上顯示測試資料
這段程式碼定義了一個名為 getDataDisplayer()
的函式,它根據問題的配置資訊動態生成並返回一個 DataDisplayer
物件,用於顯示不同型別的資料檔案和資訊。讓我們逐步解釋其功能和實現細節:
1. 全域性變數宣告:
global $data_dir;
global $problem;
- 這些變數包含了資料目錄路徑
$data_dir
和與問題相關的資訊$problem
。
2. 獲取允許顯示的檔案列表:
$allow_files = array_flip(array_filter(scandir($data_dir), function($x){return $x !== '.' && $x !== '..';}));
- 使用
scandir($data_dir)
獲取$data_dir
目錄下的所有檔案和目錄。 - 使用
array_filter
過濾掉當前目錄 ('.'
) 和上級目錄 ('..'
)。 - 使用
array_flip
將檔名作為鍵,便於後續快速查詢檔案是否存在。
3. 定義獲取檔案顯示函式的匿名函式:
$getDisplaySrcFunc = function($name) use($allow_files) {
return function() use($name, $allow_files) {
$src_name = $name . '.cpp';
if (isset($allow_files[$src_name])) {
echoFilePre($src_name);
} else {
echoFileNotFound($src_name);
}
if (isset($allow_files[$name])) {
echoFilePre($name);
} else {
echoFileNotFound($name);
}
};
};
$getDisplaySrcFunc
是一個匿名函式,接受一個檔名$name
,返回一個函式。- 返回的函式根據
$name
和$name . '.cpp'
在$allow_files
中查詢檔案是否存在,存在則呼叫echoFilePre($file_name)
顯示檔案預覽,否則呼叫echoFileNotFound($file_name)
顯示檔案未找到的資訊。
4. 讀取問題的配置檔案 problem.conf
:
$problem_conf = getUOJConf("$data_dir/problem.conf");
if ($problem_conf === -1) {
// 處理無法讀取問題配置檔案的情況
}
if ($problem_conf === -2) {
// 處理問題配置檔案格式錯誤的情況
}
- 使用
getUOJConf("$data_dir/problem.conf")
函式讀取問題的配置檔案內容。 - 根據返回值
-1
或-2
處理無法讀取或格式錯誤的情況,分別顯示相應的錯誤資訊。
5. 根據配置檔案中的資訊設定顯示器和處理器:
- 根據
use_builtin_judger
的設定和其他配置資訊動態生成DataDisplayer
物件,並設定相應的資料顯示和處理方法。
// 處理使用內建判題器的情況
if ($problem_conf['use_builtin_judger'] == 'on') {
// 設定顯示器用於顯示測試資料和額外測試資料
// 設定顯示器用於顯示checker等其他型別資料
// 根據hackable屬性設定顯示器用於顯示standard和validator
// 根據interaction_mode設定顯示器用於顯示interactor
}
題目資料統計
在單個題目的主頁面的右上角中,有一個“統計”按鈕。點選按鈕即可顯示當前題目的相關資料資訊。下面列舉一些重要邏輯程式碼:
題目資料相關資訊的處理
// 分數分佈資料的獲取
function scoreDistributionData() {
$data = array();
$result = DB::select("select score, count(*) from submissions where problem_id = {$_GET['id']} and score is not null group by score");
$is_res_empty = true;
$has_score_0 = false;
$has_score_100 = false;
while ($row = DB::fetch($result, MYSQLI_NUM)) {
if ($row[0] == 0) {
$has_score_0 = true;
} else if ($row[0] == 100) {
$has_score_100 = true;
}
$score = $row[0] * 100;
$data[] = array('score' => $score, 'count' => $row[1]);
}
if (!$has_score_0) {
array_unshift($data, array('score' => 0, 'count' => 0));
}
if (!$has_score_100) {
$data[] = array('score' => 10000, 'count' => 0);
}
return $data;
}
// 分數分佈資料的預處理
$data = scoreDistributionData();
$pre_data = $data;
$suf_data = $data;
for ($i = 0; $i < count($data); $i++) {
$data[$i]['score'] /= 100;
}
for ($i = 1; $i < count($data); $i++) {
$pre_data[$i]['count'] += $pre_data[$i - 1]['count'];
}
for ($i = count($data) - 1; $i > 0; $i--) {
$suf_data[$i - 1]['count'] += $suf_data[$i]['count'];
}
// 提交排序選擇的處理
$submissions_sort_by_choice = !isset($_COOKIE['submissions-sort-by-code-length']) ? 'time' : 'tot_size';
- 從資料庫中查詢特定問題 (
problem_id = $_GET['id']
) 的提交資料,並統計每個分數的提交次數。 - 如果沒有分數為0或100的記錄,手動新增這些分數和對應的次數。
- 返回一個陣列
$data
,包含分數和對應的提交次數。 - 使用
scoreDistributionData()
函式獲取分數分佈資料並儲存在$data
陣列中。 - 複製
$data
到$pre_data
和$suf_data
,用於預處理。 $data
陣列中的每個分數除以100,以得到真實的分數值。$pre_data
陣列中每個元素的count
屬性表示該分數及之前所有分數的累計提交次數。$suf_data
陣列中每個元素的count
屬性表示該分數及之後所有分數的累計提交次數。- 檢查是否設定了名為
submissions-sort-by-code-length
的 Cookie。 - 如果未設定該 Cookie,將
$submissions_sort_by_choice
設定為'time'
,表示按時間排序。 - 如果設定了該 Cookie,將
$submissions_sort_by_choice
設定為'tot_size'
,表示按程式碼長度排序。
使用Morris資料視覺化
程式碼使用了 Morris.js 庫來生成一個折線圖,並對資料進行了一些配置和處理。
new Morris.Line({
element: 'score-distribution-chart-suf', // 圖表要渲染的元素的 ID
data: <?= json_encode($suf_data) ?>, // 圖表使用的資料,使用 PHP 的 json_encode 將 PHP 陣列轉換為 JavaScript 物件
xkey: 'score', // X 軸上的資料欄位
ykeys: ['count'], // Y 軸上的資料欄位
labels: ['number'], // 資料欄位的標籤,用於圖例和提示資訊
lineColors: function(row, sidx, type) { // 定義折線的顏色
if (type == 'line') {
return '#0b62a4'; // 折線顏色
}
return getColOfScore(row.src.score / 100); // 如果有其他型別的資料,使用自定義函式獲取顏色
},
xLabelFormat: function(x) { // 定義 X 軸標籤的格式化函式
return (x.getTime() / 100).toString(); // 將時間轉換為指定格式的字串
},
hoverCallback: function(index, options, content, row) { // 定義滑鼠懸停提示資訊的回撥函式
var scr = row.score / 100; // 獲取分數,並轉換為實際的分數值
return '<div class="morris-hover-row-label">' + 'score: ≥' + scr + '</div>' + // 返回自定義的懸停提示資訊的 HTML
'<div class="morris-hover-point">' + '<a href="/submissions?problem_id=' + <?= $problem['id'] ?> + '&min_score=' + scr + '">' + 'number: ' + row.count + '</a>' + '</div>';
},
resize: true // 是否支援自動調整大小
});