题目部分
题目是整个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 // 是否支持自动调整大小
});
标签:逻辑,题目,contest,content,实训,problem,data,id
From: https://www.cnblogs.com/hakurokawashiro/p/18263667