@xcpcio/core 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +7 -0
- package/dist/index.cjs +716 -0
- package/dist/index.d.ts +162 -0
- package/dist/index.mjs +676 -0
- package/package.json +73 -0
- package/src/contest.ts +191 -0
- package/src/image.ts +13 -0
- package/src/index.ts +9 -0
- package/src/problem.ts +157 -0
- package/src/rank.ts +122 -0
- package/src/resolver-operation.ts +22 -0
- package/src/resolver.ts +120 -0
- package/src/submission-status.ts +151 -0
- package/src/submission.ts +87 -0
- package/src/team.ts +116 -0
- package/src/utils/dayjs.ts +61 -0
- package/src/utils/index.ts +1 -0
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xcpcio/core",
|
|
3
|
+
"description": "XCPCIO Core",
|
|
4
|
+
"version": "0.2.1",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Dup4 <lyuzhi.pan@gmail.com>",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"ICPC",
|
|
9
|
+
"CCPC"
|
|
10
|
+
],
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/xcpcio/xcpcio.git"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/xcpcio/xcpcio",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/xcpcio/xcpcio/issues"
|
|
18
|
+
},
|
|
19
|
+
"main": "./dist/index.mjs",
|
|
20
|
+
"module": "./dist/index.mjs",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"require": "./dist/index.cjs",
|
|
25
|
+
"import": "./dist/index.mjs",
|
|
26
|
+
"types": "./dist/index.d.ts"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"typesVersions": {
|
|
30
|
+
"*": {
|
|
31
|
+
"*": [
|
|
32
|
+
"./dist/*",
|
|
33
|
+
"./dist/index.d.ts"
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"src",
|
|
39
|
+
"dist"
|
|
40
|
+
],
|
|
41
|
+
"sideEffects": false,
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@babel/types": "^7.22.4",
|
|
44
|
+
"@types/lodash": "^4.14.195",
|
|
45
|
+
"@types/node": "^17.0.45",
|
|
46
|
+
"@typescript-eslint/eslint-plugin": "^5.59.9",
|
|
47
|
+
"@typescript-eslint/parser": "^5.59.9",
|
|
48
|
+
"bumpp": "^7.2.0",
|
|
49
|
+
"eslint": "^8.42.0",
|
|
50
|
+
"esmo": "^0.14.1",
|
|
51
|
+
"npm-run-all": "^4.1.5",
|
|
52
|
+
"pnpm": "^7.33.0",
|
|
53
|
+
"prettier": "^2.8.8",
|
|
54
|
+
"taze": "^0.10.2",
|
|
55
|
+
"typescript": "^4.9.5",
|
|
56
|
+
"unbuild": "^0.7.6",
|
|
57
|
+
"vite": "^4.3.9",
|
|
58
|
+
"vitest": "^0.32.0"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"dayjs": "^1.11.8",
|
|
62
|
+
"lodash": "^4.17.21",
|
|
63
|
+
"@xcpcio/types": "0.2.1"
|
|
64
|
+
},
|
|
65
|
+
"scripts": {
|
|
66
|
+
"build": "unbuild",
|
|
67
|
+
"dev": "unbuild --stub",
|
|
68
|
+
"start": "esmo src/index.ts",
|
|
69
|
+
"test": "vitest",
|
|
70
|
+
"lint": "run-p lint:*",
|
|
71
|
+
"lint:build": "# tsc --noEmit"
|
|
72
|
+
}
|
|
73
|
+
}
|
package/src/contest.ts
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { Contest as IContest, ContestState, Image, VERSION, StatusTimeDisplay } from "@xcpcio/types";
|
|
2
|
+
|
|
3
|
+
import { Problem, Problems, createProblems, createProblemsByProblemIds } from "./problem";
|
|
4
|
+
import { dayjs, createDayJS, getTimeDiff } from "./utils";
|
|
5
|
+
|
|
6
|
+
export class Contest {
|
|
7
|
+
name = "";
|
|
8
|
+
|
|
9
|
+
startTime: dayjs.Dayjs;
|
|
10
|
+
endTime: dayjs.Dayjs;
|
|
11
|
+
freezeTime: dayjs.Dayjs;
|
|
12
|
+
|
|
13
|
+
totalDurationTimestamp: number;
|
|
14
|
+
freezeDurationTimestamp: number;
|
|
15
|
+
unFreezeDurationTimestamp: number;
|
|
16
|
+
|
|
17
|
+
penalty: number;
|
|
18
|
+
|
|
19
|
+
problems: Problems;
|
|
20
|
+
problemsMap: Map<string, Problem>;
|
|
21
|
+
|
|
22
|
+
statusTimeDisplay: StatusTimeDisplay;
|
|
23
|
+
|
|
24
|
+
badge?: string;
|
|
25
|
+
medal?: Record<string, Record<string, number>>;
|
|
26
|
+
organization?: string;
|
|
27
|
+
|
|
28
|
+
group?: Record<string, string>;
|
|
29
|
+
tag?: Record<string, string>;
|
|
30
|
+
|
|
31
|
+
logo?: Image;
|
|
32
|
+
banner?: Image;
|
|
33
|
+
boardLink?: string;
|
|
34
|
+
|
|
35
|
+
version = VERSION;
|
|
36
|
+
|
|
37
|
+
constructor() {
|
|
38
|
+
this.startTime = createDayJS();
|
|
39
|
+
this.endTime = createDayJS();
|
|
40
|
+
this.freezeTime = createDayJS();
|
|
41
|
+
|
|
42
|
+
this.totalDurationTimestamp = 0;
|
|
43
|
+
this.freezeDurationTimestamp = 0;
|
|
44
|
+
this.unFreezeDurationTimestamp = 0;
|
|
45
|
+
|
|
46
|
+
// 20 mins
|
|
47
|
+
this.penalty = 20 * 60;
|
|
48
|
+
|
|
49
|
+
this.problems = [];
|
|
50
|
+
this.problemsMap = new Map<string, Problem>();
|
|
51
|
+
|
|
52
|
+
this.statusTimeDisplay = {
|
|
53
|
+
correct: true,
|
|
54
|
+
incorrect: true,
|
|
55
|
+
pending: true,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
getContestDuration(timeFormat = "HH:mm:ss"): string {
|
|
60
|
+
return dayjs.duration(this.endTime.diff(this.startTime)).format(timeFormat);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
getContestState(): ContestState {
|
|
64
|
+
const now = createDayJS();
|
|
65
|
+
|
|
66
|
+
if (now.isBefore(this.startTime)) {
|
|
67
|
+
return ContestState.PENDING;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (now.isSameOrAfter(this.endTime)) {
|
|
71
|
+
return ContestState.FINISHED;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (now.isSameOrAfter(this.freezeTime)) {
|
|
75
|
+
return ContestState.FROZEN;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return ContestState.RUNNING;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
getContestPendingTime(): string {
|
|
82
|
+
let baseTime = createDayJS();
|
|
83
|
+
if (baseTime.isAfter(this.startTime)) {
|
|
84
|
+
baseTime = this.startTime;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return getTimeDiff(Math.floor(dayjs.duration(this.startTime.diff(baseTime)).asSeconds()));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
getContestRemainingTime(endTime: dayjs.Dayjs): string {
|
|
91
|
+
let baseTime = dayjs();
|
|
92
|
+
if (baseTime.isAfter(endTime)) {
|
|
93
|
+
baseTime = endTime;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return getTimeDiff(Math.floor(dayjs.duration(endTime.diff(baseTime)).asSeconds()));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
getContestElapsedTime(): string {
|
|
100
|
+
let baseTime = dayjs();
|
|
101
|
+
if (baseTime.isAfter(this.endTime)) {
|
|
102
|
+
baseTime = this.endTime;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return getTimeDiff(Math.floor(dayjs.duration(baseTime.diff(this.startTime)).asSeconds()));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
getContestProgressRatio(): number {
|
|
109
|
+
const baseTime = dayjs();
|
|
110
|
+
|
|
111
|
+
if (this.startTime.isSameOrAfter(baseTime)) {
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (this.endTime.isSameOrBefore(baseTime)) {
|
|
116
|
+
return 100;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const total = this.endTime.diff(this.startTime, "s");
|
|
120
|
+
const pass = baseTime.diff(this.startTime, "s");
|
|
121
|
+
|
|
122
|
+
return Math.round((pass * 100) / total);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function createContest(contestJSON: IContest): Contest {
|
|
127
|
+
const c = new Contest();
|
|
128
|
+
|
|
129
|
+
c.name = contestJSON.contest_name;
|
|
130
|
+
|
|
131
|
+
c.startTime = createDayJS(contestJSON.start_time);
|
|
132
|
+
c.endTime = createDayJS(contestJSON.end_time);
|
|
133
|
+
|
|
134
|
+
c.totalDurationTimestamp = c.endTime.unix() - c.startTime.unix();
|
|
135
|
+
|
|
136
|
+
{
|
|
137
|
+
// default value
|
|
138
|
+
c.freezeTime = c.endTime;
|
|
139
|
+
c.freezeDurationTimestamp = 0;
|
|
140
|
+
|
|
141
|
+
if (contestJSON.frozen_time !== undefined && contestJSON.frozen_time != null) {
|
|
142
|
+
const frozenTime = Number(contestJSON.frozen_time);
|
|
143
|
+
|
|
144
|
+
c.freezeTime = createDayJS(c.endTime.unix() - frozenTime);
|
|
145
|
+
c.freezeDurationTimestamp = frozenTime;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (contestJSON.freeze_time !== undefined && contestJSON.freeze_time !== null) {
|
|
149
|
+
c.freezeTime = createDayJS(contestJSON.freeze_time);
|
|
150
|
+
c.freezeDurationTimestamp = c.endTime.unix() - c.freezeTime.unix();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
c.unFreezeDurationTimestamp = c.totalDurationTimestamp - c.freezeDurationTimestamp;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
c.penalty = contestJSON.penalty;
|
|
157
|
+
|
|
158
|
+
{
|
|
159
|
+
if (contestJSON.problem_id !== undefined && contestJSON.problem_id !== null) {
|
|
160
|
+
c.problems = createProblemsByProblemIds(contestJSON.problem_id, contestJSON.balloon_color);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (contestJSON.problems !== undefined && contestJSON.problems !== null) {
|
|
164
|
+
c.problems = createProblems(contestJSON.problems);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
c.problemsMap = new Map(c.problems.map((p) => [p.id, p]));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (contestJSON.status_time_display !== undefined && contestJSON.status_time_display !== null) {
|
|
171
|
+
c.statusTimeDisplay = {
|
|
172
|
+
correct: Boolean(contestJSON.status_time_display.correct ?? false),
|
|
173
|
+
incorrect: Boolean(contestJSON.status_time_display.incorrect ?? false),
|
|
174
|
+
pending: Boolean(contestJSON.status_time_display.pending ?? false),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
c.badge = contestJSON.badge;
|
|
179
|
+
c.medal = contestJSON.medal;
|
|
180
|
+
c.organization = contestJSON.organization;
|
|
181
|
+
|
|
182
|
+
c.group = contestJSON.group;
|
|
183
|
+
c.tag = contestJSON.tag;
|
|
184
|
+
|
|
185
|
+
c.banner = contestJSON.banner;
|
|
186
|
+
|
|
187
|
+
c.logo = contestJSON.logo;
|
|
188
|
+
c.boardLink = contestJSON.board_link;
|
|
189
|
+
|
|
190
|
+
return c;
|
|
191
|
+
}
|
package/src/image.ts
ADDED
package/src/index.ts
ADDED
package/src/problem.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { Problem as IProblem, Problems as IProblems, BalloonColor } from "@xcpcio/types";
|
|
2
|
+
|
|
3
|
+
import { Submissions } from "./submission";
|
|
4
|
+
|
|
5
|
+
export interface ProblemStatistics {
|
|
6
|
+
acceptedNum: number;
|
|
7
|
+
rejectedNum: number;
|
|
8
|
+
pendingNum: number;
|
|
9
|
+
|
|
10
|
+
submittedNum: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class Problem {
|
|
14
|
+
id: string;
|
|
15
|
+
label: string;
|
|
16
|
+
|
|
17
|
+
name: string;
|
|
18
|
+
|
|
19
|
+
timeLimit?: string;
|
|
20
|
+
memoryLimit?: string;
|
|
21
|
+
|
|
22
|
+
balloonColor?: BalloonColor;
|
|
23
|
+
|
|
24
|
+
statistics: ProblemStatistics;
|
|
25
|
+
|
|
26
|
+
constructor() {
|
|
27
|
+
this.id = "";
|
|
28
|
+
this.label = "";
|
|
29
|
+
|
|
30
|
+
this.name = "";
|
|
31
|
+
|
|
32
|
+
this.statistics = {
|
|
33
|
+
acceptedNum: 0,
|
|
34
|
+
rejectedNum: 0,
|
|
35
|
+
pendingNum: 0,
|
|
36
|
+
submittedNum: 0,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type Problems = Array<Problem>;
|
|
42
|
+
|
|
43
|
+
export function createProblem(problemJSON: IProblem): Problem {
|
|
44
|
+
const p = new Problem();
|
|
45
|
+
|
|
46
|
+
p.id = String(problemJSON.id);
|
|
47
|
+
p.label = problemJSON.label;
|
|
48
|
+
|
|
49
|
+
p.name = problemJSON.name ?? "";
|
|
50
|
+
|
|
51
|
+
p.timeLimit = problemJSON.time_limit;
|
|
52
|
+
p.memoryLimit = problemJSON.memory_limit;
|
|
53
|
+
|
|
54
|
+
p.balloonColor = problemJSON.balloon_color;
|
|
55
|
+
|
|
56
|
+
return p;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function createProblems(problemsJSON: IProblems): Problems {
|
|
60
|
+
return problemsJSON.map((pJSON) => {
|
|
61
|
+
const p = new Problem();
|
|
62
|
+
|
|
63
|
+
p.id = pJSON.id;
|
|
64
|
+
p.label = pJSON.label;
|
|
65
|
+
|
|
66
|
+
p.name = pJSON.name ?? "";
|
|
67
|
+
|
|
68
|
+
p.timeLimit = pJSON.time_limit;
|
|
69
|
+
p.memoryLimit = pJSON.memory_limit;
|
|
70
|
+
|
|
71
|
+
p.balloonColor = pJSON.balloon_color;
|
|
72
|
+
|
|
73
|
+
return p;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function createProblemsByProblemIds(problemIds: string[], balloonColors?: BalloonColor[]): Problems {
|
|
78
|
+
const problems = problemIds.map((label: string, index: number) => {
|
|
79
|
+
const p = new Problem();
|
|
80
|
+
p.id = String(index);
|
|
81
|
+
p.label = label;
|
|
82
|
+
|
|
83
|
+
return p;
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (balloonColors !== undefined && balloonColors !== null) {
|
|
87
|
+
for (const index in balloonColors) {
|
|
88
|
+
problems[index].balloonColor = balloonColors[index];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return problems;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export class TeamProblemStatistics {
|
|
96
|
+
isFirstSolved: boolean;
|
|
97
|
+
|
|
98
|
+
isSolved: boolean;
|
|
99
|
+
solvedTimestamp: number;
|
|
100
|
+
|
|
101
|
+
isSubmitted: boolean;
|
|
102
|
+
lastSubmitTimestamp: number;
|
|
103
|
+
|
|
104
|
+
failedCount: number;
|
|
105
|
+
pendingCount: number;
|
|
106
|
+
ignoreCount: number;
|
|
107
|
+
totalCount: number;
|
|
108
|
+
|
|
109
|
+
submissions: Submissions;
|
|
110
|
+
problem: Problem;
|
|
111
|
+
|
|
112
|
+
contestPenalty: number;
|
|
113
|
+
|
|
114
|
+
constructor(options?: { teamProblemStatistics?: TeamProblemStatistics }) {
|
|
115
|
+
this.isFirstSolved = options?.teamProblemStatistics?.isFirstSolved ?? false;
|
|
116
|
+
|
|
117
|
+
this.isSolved = options?.teamProblemStatistics?.isSolved ?? false;
|
|
118
|
+
this.solvedTimestamp = options?.teamProblemStatistics?.solvedTimestamp ?? 0;
|
|
119
|
+
|
|
120
|
+
this.isSubmitted = options?.teamProblemStatistics?.isSubmitted ?? false;
|
|
121
|
+
this.lastSubmitTimestamp = options?.teamProblemStatistics?.lastSubmitTimestamp ?? 0;
|
|
122
|
+
|
|
123
|
+
this.failedCount = options?.teamProblemStatistics?.failedCount ?? 0;
|
|
124
|
+
this.pendingCount = options?.teamProblemStatistics?.pendingCount ?? 0;
|
|
125
|
+
this.ignoreCount = options?.teamProblemStatistics?.ignoreCount ?? 0;
|
|
126
|
+
this.totalCount = options?.teamProblemStatistics?.totalCount ?? 0;
|
|
127
|
+
|
|
128
|
+
this.submissions = options?.teamProblemStatistics?.submissions ?? [];
|
|
129
|
+
this.problem = options?.teamProblemStatistics?.problem ?? new Problem();
|
|
130
|
+
|
|
131
|
+
this.contestPenalty = options?.teamProblemStatistics?.contestPenalty ?? 20 * 60;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
get isAccepted() {
|
|
135
|
+
return this.isSolved;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
get isWrongAnswer() {
|
|
139
|
+
return !this.isSolved && this.pendingCount === 0 && this.failedCount > 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
get isPending() {
|
|
143
|
+
return !this.isSolved && this.pendingCount > 0;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
get isUnSubmitted() {
|
|
147
|
+
return this.totalCount === 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
get penalty() {
|
|
151
|
+
if (this.isSolved === false) {
|
|
152
|
+
return 0;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return Math.floor(this.solvedTimestamp / 60) * 60 + this.failedCount * this.contestPenalty;
|
|
156
|
+
}
|
|
157
|
+
}
|
package/src/rank.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import _ from "lodash";
|
|
2
|
+
|
|
3
|
+
import { Contest } from "./contest";
|
|
4
|
+
import { Team, Teams } from "./team";
|
|
5
|
+
import { Submission, Submissions } from "./submission";
|
|
6
|
+
import { TeamProblemStatistics } from "./problem";
|
|
7
|
+
|
|
8
|
+
export class Rank {
|
|
9
|
+
readonly contest: Contest;
|
|
10
|
+
|
|
11
|
+
teams: Teams;
|
|
12
|
+
teamsMap: Map<string, Team>;
|
|
13
|
+
|
|
14
|
+
submissions: Submissions;
|
|
15
|
+
submissionsMap: Map<string, Submission>;
|
|
16
|
+
|
|
17
|
+
firstSolvedSubmissions: Map<string, Submissions>;
|
|
18
|
+
|
|
19
|
+
constructor(contest: Contest, teams: Teams, submissions: Submissions) {
|
|
20
|
+
this.contest = contest;
|
|
21
|
+
|
|
22
|
+
this.teams = _.cloneDeep(teams);
|
|
23
|
+
this.teamsMap = new Map(this.teams.map((t) => [t.id, t]));
|
|
24
|
+
|
|
25
|
+
this.submissions = _.cloneDeep(submissions).sort(Submission.compare);
|
|
26
|
+
this.submissionsMap = new Map(this.submissions.map((s) => [s.id, s]));
|
|
27
|
+
|
|
28
|
+
this.firstSolvedSubmissions = new Map(this.contest.problems.map((p) => [p.id, []]));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
buildRank(options?: { timestamp?: number }) {
|
|
32
|
+
(() => {
|
|
33
|
+
for (const t of this.teams) {
|
|
34
|
+
t.problemStatistics = this.contest.problems.map((p) => {
|
|
35
|
+
const ps = new TeamProblemStatistics();
|
|
36
|
+
ps.problem = p;
|
|
37
|
+
ps.contestPenalty = this.contest.penalty;
|
|
38
|
+
|
|
39
|
+
return ps;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
t.problemStatisticsMap = new Map(t.problemStatistics.map((ps) => [ps.problem.id, ps]));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.firstSolvedSubmissions = new Map(this.contest.problems.map((p) => [p.id, []]));
|
|
46
|
+
|
|
47
|
+
for (const s of this.submissions) {
|
|
48
|
+
const teamId = s.teamId;
|
|
49
|
+
const problemId = s.problemId;
|
|
50
|
+
const team = this.teamsMap.get(teamId);
|
|
51
|
+
const problem = this.contest.problemsMap.get(problemId);
|
|
52
|
+
|
|
53
|
+
if (team === undefined || problem === undefined) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (options?.timestamp !== undefined && options?.timestamp !== null) {
|
|
58
|
+
if (s.timestamp > options.timestamp) {
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const problemStatistics = team.problemStatisticsMap.get(problemId) as TeamProblemStatistics;
|
|
64
|
+
const submissions = problemStatistics.submissions;
|
|
65
|
+
const firstSolvedSubmissions = this.firstSolvedSubmissions.get(problemId) as Array<Submission>;
|
|
66
|
+
|
|
67
|
+
submissions.push(s);
|
|
68
|
+
problem.statistics.submittedNum++;
|
|
69
|
+
|
|
70
|
+
if (problemStatistics.isSolved) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (s.isIgnore || s.isNotCalculatedPenaltyStatus()) {
|
|
75
|
+
problemStatistics.ignoreCount++;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
problemStatistics.isSubmitted = true;
|
|
80
|
+
problemStatistics.lastSubmitTimestamp = s.timestamp;
|
|
81
|
+
problemStatistics.totalCount++;
|
|
82
|
+
|
|
83
|
+
if (s.isAccepted()) {
|
|
84
|
+
problemStatistics.isSolved = true;
|
|
85
|
+
problemStatistics.solvedTimestamp = s.timestamp;
|
|
86
|
+
|
|
87
|
+
problem.statistics.acceptedNum++;
|
|
88
|
+
|
|
89
|
+
if (
|
|
90
|
+
firstSolvedSubmissions.length === 0 ||
|
|
91
|
+
firstSolvedSubmissions[firstSolvedSubmissions.length - 1].timestamp === s.timestamp
|
|
92
|
+
) {
|
|
93
|
+
problemStatistics.isFirstSolved = true;
|
|
94
|
+
firstSolvedSubmissions.push(s);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (s.isRejected()) {
|
|
99
|
+
problemStatistics.failedCount++;
|
|
100
|
+
problem.statistics.rejectedNum++;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (s.isPending()) {
|
|
104
|
+
problemStatistics.pendingCount++;
|
|
105
|
+
problem.statistics.pendingNum++;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
this.teams.forEach((t) => t.calcSolvedData());
|
|
110
|
+
this.teams.sort(Team.compare);
|
|
111
|
+
|
|
112
|
+
{
|
|
113
|
+
let rank = 1;
|
|
114
|
+
for (const t of this.teams) {
|
|
115
|
+
t.rank = rank++;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
})();
|
|
119
|
+
|
|
120
|
+
return this;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { TeamProblemStatistics } from "./problem";
|
|
2
|
+
import { Team } from "./team";
|
|
3
|
+
|
|
4
|
+
export class ResolverOperation {
|
|
5
|
+
id: number;
|
|
6
|
+
|
|
7
|
+
team: Team;
|
|
8
|
+
problemIx: number;
|
|
9
|
+
|
|
10
|
+
beforeTeamProblemStatistics: TeamProblemStatistics;
|
|
11
|
+
afterTeamProblemStatistics: TeamProblemStatistics;
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
this.id = 0;
|
|
15
|
+
|
|
16
|
+
this.team = new Team();
|
|
17
|
+
this.problemIx = 0;
|
|
18
|
+
|
|
19
|
+
this.beforeTeamProblemStatistics = new TeamProblemStatistics();
|
|
20
|
+
this.afterTeamProblemStatistics = new TeamProblemStatistics();
|
|
21
|
+
}
|
|
22
|
+
}
|
package/src/resolver.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import _ from "lodash";
|
|
2
|
+
|
|
3
|
+
import { Rank } from "./rank";
|
|
4
|
+
import { Contest } from "./contest";
|
|
5
|
+
import { Team, Teams } from "./team";
|
|
6
|
+
import { Submission, Submissions } from "./submission";
|
|
7
|
+
import { TeamProblemStatistics } from "./problem";
|
|
8
|
+
import { ResolverOperation } from "./resolver-operation";
|
|
9
|
+
|
|
10
|
+
export class Resolver extends Rank {
|
|
11
|
+
finalRank: Rank;
|
|
12
|
+
operations: Array<ResolverOperation>;
|
|
13
|
+
|
|
14
|
+
beforeFreezeSubmissions: Submissions;
|
|
15
|
+
afterFreezeSubmissions: Submissions;
|
|
16
|
+
|
|
17
|
+
constructor(contest: Contest, teams: Teams, submissions: Submissions) {
|
|
18
|
+
submissions.sort(Submission.compare);
|
|
19
|
+
|
|
20
|
+
let beforeFreezeSubmissions = submissions;
|
|
21
|
+
let afterFreezeSubmissions = submissions;
|
|
22
|
+
|
|
23
|
+
{
|
|
24
|
+
const ix = _.sortedIndex(
|
|
25
|
+
submissions.map((s) => s.timestamp),
|
|
26
|
+
contest.unFreezeDurationTimestamp,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
beforeFreezeSubmissions = submissions.slice(0, ix + 1);
|
|
30
|
+
afterFreezeSubmissions = submissions.slice(ix, -1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
super(contest, teams, beforeFreezeSubmissions);
|
|
34
|
+
|
|
35
|
+
this.finalRank = new Rank(contest, teams, submissions);
|
|
36
|
+
this.operations = [];
|
|
37
|
+
|
|
38
|
+
this.beforeFreezeSubmissions = beforeFreezeSubmissions;
|
|
39
|
+
this.afterFreezeSubmissions = afterFreezeSubmissions;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
buildResolver() {
|
|
43
|
+
this.buildRank();
|
|
44
|
+
this.finalRank.buildRank();
|
|
45
|
+
|
|
46
|
+
for (const s of this.afterFreezeSubmissions) {
|
|
47
|
+
const teamId = s.teamId;
|
|
48
|
+
const problemId = s.problemId;
|
|
49
|
+
const team = this.teamsMap.get(teamId);
|
|
50
|
+
const problem = this.contest.problemsMap.get(problemId);
|
|
51
|
+
|
|
52
|
+
if (team === undefined || problem === undefined) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const problemStatistics = team.problemStatisticsMap.get(problemId) as TeamProblemStatistics;
|
|
57
|
+
|
|
58
|
+
problemStatistics.pendingCount++;
|
|
59
|
+
problemStatistics.totalCount++;
|
|
60
|
+
if (!problemStatistics.isAccepted) {
|
|
61
|
+
problemStatistics.lastSubmitTimestamp = s.timestamp;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
{
|
|
66
|
+
const teams_ = _.cloneDeep(this.teams);
|
|
67
|
+
|
|
68
|
+
for (let i = this.teams.length - 1; i >= 0; ) {
|
|
69
|
+
const team = teams_[i];
|
|
70
|
+
const teamId = team.id;
|
|
71
|
+
|
|
72
|
+
let handleCnt = 0;
|
|
73
|
+
let problemIx = -1;
|
|
74
|
+
for (const p of team.problemStatistics) {
|
|
75
|
+
problemIx++;
|
|
76
|
+
|
|
77
|
+
if (!p.isPending) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
handleCnt++;
|
|
82
|
+
|
|
83
|
+
const beforeTeamProblemStatistics = this.teamsMap.get(teamId)?.problemStatistics[
|
|
84
|
+
problemIx
|
|
85
|
+
] as TeamProblemStatistics;
|
|
86
|
+
const afterTeamProblemStatistics = this.finalRank.teamsMap.get(teamId)?.problemStatistics[
|
|
87
|
+
problemIx
|
|
88
|
+
] as TeamProblemStatistics;
|
|
89
|
+
|
|
90
|
+
const op = new ResolverOperation();
|
|
91
|
+
op.id = this.operations.length;
|
|
92
|
+
op.team = this.teamsMap.get(teamId) as Team;
|
|
93
|
+
op.problemIx = problemIx;
|
|
94
|
+
|
|
95
|
+
op.beforeTeamProblemStatistics = beforeTeamProblemStatistics;
|
|
96
|
+
op.afterTeamProblemStatistics = afterTeamProblemStatistics;
|
|
97
|
+
|
|
98
|
+
this.operations.push(op);
|
|
99
|
+
|
|
100
|
+
team.problemStatistics[problemIx] = afterTeamProblemStatistics;
|
|
101
|
+
team.calcSolvedData();
|
|
102
|
+
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
{
|
|
107
|
+
let j = i;
|
|
108
|
+
while (j > 0 && Team.compare(teams_[j], teams_[j - 1]) < 0) {
|
|
109
|
+
[teams_[j], teams_[j - 1]] = [teams_[j - 1], teams_[j]];
|
|
110
|
+
j--;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (handleCnt === 0) {
|
|
115
|
+
i--;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|