Browse Source

Optimize problem statistics

pull/6/head
Menci 6 years ago
parent
commit
9bf6bdb137
  1. 2
      libs/judger.js
  2. 2
      models/common.ts
  3. 16
      models/judge_state.ts
  4. 296
      models/problem.ts
  5. 33
      models/submission_statistics.ts

2
libs/judger.js

@ -179,7 +179,7 @@ async function connect() {
judge_state.max_memory = convertedResult.memory; judge_state.max_memory = convertedResult.memory;
judge_state.result = convertedResult.result; judge_state.result = convertedResult.result;
await judge_state.save(); await judge_state.save();
await judge_state.updateRelatedInfo(); await judge_state.updateRelatedInfo(false);
} else if (result.type == interface.ProgressReportType.Compiled) { } else if (result.type == interface.ProgressReportType.Compiled) {
if (!judge_state) return; if (!judge_state) return;
judge_state.compilation = result.progress; judge_state.compilation = result.progress;

2
models/common.ts

@ -112,7 +112,7 @@ export default class Model extends TypeORM.BaseEntity {
return await queryBuilder.getMany(); return await queryBuilder.getMany();
} }
static async queryPage(paginater: Paginater, where, order, largeData) { static async queryPage(paginater: Paginater, where, order, largeData = false) {
if (!paginater.pageCnt) return []; if (!paginater.pageCnt) return [];
const queryBuilder = where instanceof TypeORM.SelectQueryBuilder const queryBuilder = where instanceof TypeORM.SelectQueryBuilder

16
models/judge_state.ts

@ -12,6 +12,7 @@ const Judger = syzoj.lib('judger');
@TypeORM.Entity() @TypeORM.Entity()
@TypeORM.Index(['type', 'type_info']) @TypeORM.Index(['type', 'type_info'])
@TypeORM.Index(['type', 'is_public']) @TypeORM.Index(['type', 'is_public'])
@TypeORM.Index(['problem_id', 'type', 'pending', 'score'])
export default class JudgeState extends Model { export default class JudgeState extends Model {
@TypeORM.PrimaryGeneratedColumn() @TypeORM.PrimaryGeneratedColumn()
id: number; id: number;
@ -114,6 +115,10 @@ export default class JudgeState extends Model {
// No need to await them. // No need to await them.
this.user.refreshSubmitInfo(); this.user.refreshSubmitInfo();
this.problem.resetSubmissionCount(); this.problem.resetSubmissionCount();
if (!newSubmission) {
this.problem.updateStatistics(this.user_id);
}
} else if (this.type === 1) { } else if (this.type === 1) {
let contest = await Contest.findById(this.type_info); let contest = await Contest.findById(this.type_info);
await contest.newSubmission(this); await contest.newSubmission(this);
@ -138,16 +143,7 @@ export default class JudgeState extends Model {
this.task_id = require('randomstring').generate(10); this.task_id = require('randomstring').generate(10);
await this.save(); await this.save();
await this.problem.resetSubmissionCount(); await this.updateRelatedInfo(false);
if (oldStatus === 'Accepted') {
await this.user.refreshSubmitInfo();
await this.user.save();
}
if (this.type === 1) {
let contest = await Contest.findById(this.type_info);
await contest.newSubmission(this);
}
try { try {
await Judger.judge(this, this.problem, 1); await Judger.judge(this, this.problem, 1);

296
models/problem.ts

@ -1,181 +1,3 @@
const statisticsStatements = {
fastest:
'\
SELECT \
DISTINCT(`user_id`) AS `user_id`, \
( \
SELECT \
`id` \
FROM `judge_state` `inner_table` \
WHERE `problem_id` = `outer_table`.`problem_id` AND `user_id` = `outer_table`.`user_id` AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `total_time` ASC \
LIMIT 1 \
) AS `id`, \
( \
SELECT \
`total_time` \
FROM `judge_state` `inner_table` \
WHERE `problem_id` = `outer_table`.`problem_id` AND `user_id` = `outer_table`.`user_id` AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `total_time` ASC \
LIMIT 1 \
) AS `total_time` \
FROM `judge_state` `outer_table` \
WHERE \
`problem_id` = __PROBLEM_ID__ AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `total_time` ASC \
',
slowest:
' \
SELECT \
DISTINCT(`user_id`) AS `user_id`, \
( \
SELECT \
`id` \
FROM `judge_state` `inner_table` \
WHERE `problem_id` = `outer_table`.`problem_id` AND `user_id` = `outer_table`.`user_id` AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `total_time` DESC \
LIMIT 1 \
) AS `id`, \
( \
SELECT \
`total_time` \
FROM `judge_state` `inner_table` \
WHERE `problem_id` = `outer_table`.`problem_id` AND `user_id` = `outer_table`.`user_id` AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `total_time` DESC \
LIMIT 1 \
) AS `total_time` \
FROM `judge_state` `outer_table` \
WHERE \
`problem_id` = __PROBLEM_ID__ AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `total_time` DESC \
',
shortest:
' \
SELECT \
DISTINCT(`user_id`) AS `user_id`, \
( \
SELECT \
`id` \
FROM `judge_state` `inner_table` \
WHERE `problem_id` = `outer_table`.`problem_id` AND `user_id` = `outer_table`.`user_id` AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `code_length` ASC \
LIMIT 1 \
) AS `id`, \
( \
SELECT \
`code_length` \
FROM `judge_state` `inner_table` \
WHERE `problem_id` = `outer_table`.`problem_id` AND `user_id` = `outer_table`.`user_id` AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `code_length` ASC \
LIMIT 1 \
) AS `code_length` \
FROM `judge_state` `outer_table` \
WHERE \
`problem_id` = __PROBLEM_ID__ AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `code_length` ASC \
',
longest:
' \
SELECT \
DISTINCT(`user_id`) AS `user_id`, \
( \
SELECT \
`id` \
FROM `judge_state` `inner_table` \
WHERE `problem_id` = `outer_table`.`problem_id` AND `user_id` = `outer_table`.`user_id` AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `code_length` DESC \
LIMIT 1 \
) AS `id`, \
( \
SELECT \
`code_length` \
FROM `judge_state` `inner_table` \
WHERE `problem_id` = `outer_table`.`problem_id` AND `user_id` = `outer_table`.`user_id` AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `code_length` DESC \
LIMIT 1 \
) AS `code_length` \
FROM `judge_state` `outer_table` \
WHERE \
`problem_id` = __PROBLEM_ID__ AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `code_length` DESC \
',
earliest:
' \
SELECT \
DISTINCT(`user_id`) AS `user_id`, \
( \
SELECT \
`id` \
FROM `judge_state` `inner_table` \
WHERE `problem_id` = `outer_table`.`problem_id` AND `user_id` = `outer_table`.`user_id` AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `submit_time` ASC \
LIMIT 1 \
) AS `id`, \
( \
SELECT \
`submit_time` \
FROM `judge_state` `inner_table` \
WHERE `problem_id` = `outer_table`.`problem_id` AND `user_id` = `outer_table`.`user_id` AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `submit_time` ASC \
LIMIT 1 \
) AS `submit_time` \
FROM `judge_state` `outer_table` \
WHERE \
`problem_id` = __PROBLEM_ID__ AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `submit_time` ASC \
',
min:
' \
SELECT \
DISTINCT(`user_id`) AS `user_id`, \
( \
SELECT \
`id` \
FROM `judge_state` `inner_table` \
WHERE `problem_id` = `outer_table`.`problem_id` AND `user_id` = `outer_table`.`user_id` AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `max_memory` ASC \
LIMIT 1 \
) AS `id`, \
( \
SELECT \
`max_memory` \
FROM `judge_state` `inner_table` \
WHERE `problem_id` = `outer_table`.`problem_id` AND `user_id` = `outer_table`.`user_id` AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `max_memory` ASC \
LIMIT 1 \
) AS `max_memory` \
FROM `judge_state` `outer_table` \
WHERE \
`problem_id` = __PROBLEM_ID__ AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `max_memory` ASC \
',
max:
' \
SELECT \
DISTINCT(`user_id`) AS `user_id`, \
( \
SELECT \
`id` \
FROM `judge_state` `inner_table` \
WHERE `problem_id` = `outer_table`.`problem_id` AND `user_id` = `outer_table`.`user_id` AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `max_memory` ASC \
LIMIT 1 \
) AS `id`, \
( \
SELECT \
`max_memory` \
FROM `judge_state` `inner_table` \
WHERE `problem_id` = `outer_table`.`problem_id` AND `user_id` = `outer_table`.`user_id` AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `max_memory` ASC \
LIMIT 1 \
) AS `max_memory` \
FROM `judge_state` `outer_table` \
WHERE \
`problem_id` = __PROBLEM_ID__ AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `max_memory` DESC \
'
};
import * as TypeORM from "typeorm"; import * as TypeORM from "typeorm";
import Model from "./common"; import Model from "./common";
@ -187,6 +9,7 @@ import JudgeState from "./judge_state";
import Contest from "./contest"; import Contest from "./contest";
import ProblemTag from "./problem_tag"; import ProblemTag from "./problem_tag";
import ProblemTagMap from "./problem_tag_map"; import ProblemTagMap from "./problem_tag_map";
import SubmissionStatictics, { StatisticsType } from "./submission_statistics";
import * as fs from "fs-extra"; import * as fs from "fs-extra";
import * as path from "path"; import * as path from "path";
@ -204,6 +27,18 @@ enum ProblemType {
Interaction = "interaction" Interaction = "interaction"
} }
const statisticsTypes = {
fastest: ['total_time', 'ASC'],
slowest: ['total_time', 'DESC'],
shortest: ['code_length', 'ASC'],
longest: ['code_length', 'DESC'],
min: ['max_memory', 'ASC'],
max: ['max_memory', 'DESC'],
earliest: ['submit_time', 'ASC']
};
const statisticsCodeOnly = ["fastest", "slowest", "min", "max"];
@TypeORM.Entity() @TypeORM.Entity()
export default class Problem extends Model { export default class Problem extends Model {
static cache = true; static cache = true;
@ -505,18 +340,50 @@ export default class Problem extends Model {
}); });
} }
// type: fastest / slowest / shortest / longest / earliest async updateStatistics(user_id) {
async countStatistics(type) { await Promise.all(Object.keys(statisticsTypes).map(async type => {
let statement = statisticsStatements[type]; if (this.type === ProblemType.SubmitAnswer && statisticsCodeOnly.includes(type)) return;
if (!statement) return null;
await syzoj.utils.lock(['Problem::UpdateStatistics', this.id, type], async () => {
const [column, order] = statisticsTypes[type];
const result = await JudgeState.createQueryBuilder()
.select([column, "id"])
.where("user_id = :user_id", { user_id })
.andWhere("status = :status", { status: "Accepted" })
.andWhere("problem_id = :problem_id", { problem_id: this.id })
.orderBy({ [column]: order })
.take(1)
.getRawMany();
const resultRow = result[0];
if (!resultRow || resultRow[column] == null) return;
const baseColumns = {
user_id,
problem_id: this.id,
type: type as StatisticsType
};
const entityManager = TypeORM.getManager(); let record = await SubmissionStatictics.findOne(baseColumns);
if (!record) {
record = SubmissionStatictics.create(baseColumns);
}
record.key = resultRow[column];
record.submission_id = resultRow["id"];
statement = statement.replace('__PROBLEM_ID__', this.id); await record.save();
return JudgeState.countQuery(statement); });
}));
}
async countStatistics(type) {
return await SubmissionStatictics.count({
problem_id: this.id,
type: type
});
} }
// type: fastest / slowest / shortest / longest / earliest
async getStatistics(type, paginate) { async getStatistics(type, paginate) {
const entityManager = TypeORM.getManager(); const entityManager = TypeORM.getManager();
@ -528,18 +395,29 @@ export default class Problem extends Model {
suffixSum: null suffixSum: null
}; };
let statement = statisticsStatements[type]; const order = statisticsTypes[type][1];
if (!statement) return null; const ids = (await SubmissionStatictics.queryPage(paginate, {
problem_id: this.id,
statement = statement.replace('__PROBLEM_ID__', this.id); type: type
}, {
let a; '`key`': order
if (!paginate.pageCnt) a = []; })).map(x => x.submission_id);
else a = (await entityManager.query(statement + `LIMIT ${paginate.perPage} OFFSET ${(paginate.currPage - 1) * paginate.perPage}`));
statistics.judge_state = ids.length ? await JudgeState.createQueryBuilder()
statistics.judge_state = await a.mapAsync(async x => JudgeState.findById(x.id)); .whereInIds(ids)
.orderBy(`FIELD(id,${ids.join(',')})`)
a = (await entityManager.query('SELECT `score`, COUNT(*) AS `count` FROM `judge_state` WHERE `problem_id` = __PROBLEM_ID__ AND `type` = 0 AND `pending` = 0 GROUP BY `score`'.replace('__PROBLEM_ID__', this.id.toString()))); .getMany()
: [];
JudgeState.createQueryBuilder()
.select('score')
.addSelect('COUNT(*)', 'count')
.where('problem_id = :problem_id', { problem_id: this.id })
.andWhere('type = 0')
.andWhere('pending = false')
.groupBy('score')
.getRawMany()
let a = (await entityManager.query('SELECT `score`, COUNT(*) AS `count` FROM `judge_state` WHERE `problem_id` = __PROBLEM_ID__ AND `type` = 0 AND `pending` = 0 GROUP BY `score`'.replace('__PROBLEM_ID__', this.id.toString())));
let scoreCount = []; let scoreCount = [];
for (let score of a) { for (let score of a) {
@ -549,6 +427,11 @@ export default class Problem extends Model {
if (scoreCount[0] === undefined) scoreCount[0] = 0; if (scoreCount[0] === undefined) scoreCount[0] = 0;
if (scoreCount[100] === undefined) scoreCount[100] = 0; if (scoreCount[100] === undefined) scoreCount[100] = 0;
if (a[null as any]) {
a[0] += a[null as any];
delete a[null as any];
}
statistics.scoreDistribution = []; statistics.scoreDistribution = [];
for (let i = 0; i < scoreCount.length; i++) { for (let i = 0; i < scoreCount.length; i++) {
if (scoreCount[i] !== undefined) statistics.scoreDistribution.push({ score: i, count: parseInt(scoreCount[i]) }); if (scoreCount[i] !== undefined) statistics.scoreDistribution.push({ score: i, count: parseInt(scoreCount[i]) });
@ -627,11 +510,11 @@ export default class Problem extends Model {
const entityManager = TypeORM.getManager(); const entityManager = TypeORM.getManager();
id = parseInt(id); id = parseInt(id);
await entityManager.query('UPDATE `problem` SET `id` = ' + id + ' WHERE `id` = ' + this.id); await entityManager.query('UPDATE `problem` SET `id` = ' + id + ' WHERE `id` = ' + this.id);
await entityManager.query('UPDATE `judge_state` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id); await entityManager.query('UPDATE `judge_state` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id);
await entityManager.query('UPDATE `problem_tag_map` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id); await entityManager.query('UPDATE `problem_tag_map` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id);
await entityManager.query('UPDATE `article` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id); await entityManager.query('UPDATE `article` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id);
await entityManager.query('UPDATE `SubmissionStatictics` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id);
let contests = await Contest.find(); let contests = await Contest.find();
for (let contest of contests) { for (let contest of contests) {
@ -698,9 +581,10 @@ export default class Problem extends Model {
problemTagCache.del(this.id); problemTagCache.del(this.id);
await entityManager.query('DELETE FROM `judge_state` WHERE `problem_id` = ' + this.id); await entityManager.query('DELETE FROM `judge_state` WHERE `problem_id` = ' + this.id);
await entityManager.query('DELETE FROM `problem_tag_map` WHERE `problem_id` = ' + this.id); await entityManager.query('DELETE FROM `problem_tag_map` WHERE `problem_id` = ' + this.id);
await entityManager.query('DELETE FROM `article` WHERE `problem_id` = ' + this.id); await entityManager.query('DELETE FROM `article` WHERE `problem_id` = ' + this.id);
await entityManager.query('DELETE FROM `submission_statictics` WHERE `problem_id` = ' + this.id);
await this.destroy(); await this.destroy();
} }

33
models/submission_statistics.ts

@ -0,0 +1,33 @@
import * as TypeORM from "typeorm";
import Model from "./common";
export enum StatisticsType {
FASTEST = "fastest",
SLOWEST = "slowest",
SHORTEST = "shortest",
LONGEST = "longest",
MEMORY_MIN = "min",
MEMORY_MAX = "max",
EARLIEST = "earliest"
}
@TypeORM.Entity()
@TypeORM.Index(['problem_id', 'type', 'key'])
export default class SubmissionStatistics extends Model {
static cache = false;
@TypeORM.PrimaryColumn({ type: "integer" })
problem_id: number;
@TypeORM.PrimaryColumn({ type: "integer" })
user_id: number;
@TypeORM.PrimaryColumn({ type: "enum", enum: StatisticsType })
type: StatisticsType;
@TypeORM.Column({ type: "integer" })
key: number;
@TypeORM.Column({ type: "integer" })
submission_id: number;
};
Loading…
Cancel
Save