@xcpcio/core 0.3.3 → 0.4.0
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/dist/index.cjs +154 -17
- package/dist/index.d.ts +37 -5
- package/dist/index.mjs +149 -18
- package/package.json +18 -19
- package/src/contest-index.ts +123 -0
- package/src/contest.ts +6 -4
- package/src/image.ts +1 -1
- package/src/index.ts +1 -0
- package/src/problem.ts +42 -9
- package/src/rank.ts +36 -16
- package/src/resolver.ts +8 -6
- package/src/submission.ts +4 -3
- package/src/team.ts +31 -7
- package/src/utils/calc.ts +7 -0
- package/src/utils/dayjs.ts +12 -14
- package/src/utils/index.ts +1 -0
package/dist/index.cjs
CHANGED
|
@@ -27,6 +27,13 @@ const minMax__default = /*#__PURE__*/_interopDefaultLegacy(minMax);
|
|
|
27
27
|
const relativeTime__default = /*#__PURE__*/_interopDefaultLegacy(relativeTime);
|
|
28
28
|
const ___default = /*#__PURE__*/_interopDefaultLegacy(_);
|
|
29
29
|
|
|
30
|
+
function calcDict(attemptedNum, solvedNum) {
|
|
31
|
+
if (solvedNum === 0) {
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
return Math.floor((attemptedNum - solvedNum) * 100 / attemptedNum);
|
|
35
|
+
}
|
|
36
|
+
|
|
30
37
|
dayjs__default.extend(duration__default);
|
|
31
38
|
dayjs__default.extend(utc__default);
|
|
32
39
|
dayjs__default.extend(timezone__default);
|
|
@@ -52,8 +59,9 @@ function getTimestamp(time) {
|
|
|
52
59
|
}
|
|
53
60
|
function getTimeDiff(seconds) {
|
|
54
61
|
const two = (a) => {
|
|
55
|
-
if (a < 10)
|
|
56
|
-
return
|
|
62
|
+
if (a < 10) {
|
|
63
|
+
return `0${a}`;
|
|
64
|
+
}
|
|
57
65
|
return String(a);
|
|
58
66
|
};
|
|
59
67
|
const h = Math.floor(seconds / 3600);
|
|
@@ -62,17 +70,113 @@ function getTimeDiff(seconds) {
|
|
|
62
70
|
return [two(h), two(m), two(s)].join(":");
|
|
63
71
|
}
|
|
64
72
|
|
|
73
|
+
class ContestIndexConfig {
|
|
74
|
+
constructor() {
|
|
75
|
+
this.contestName = "";
|
|
76
|
+
this.startTime = createDayJS();
|
|
77
|
+
this.endTime = createDayJS();
|
|
78
|
+
this.freezeTime = createDayJS();
|
|
79
|
+
this.totalDurationTimestamp = 0;
|
|
80
|
+
this.freezeDurationTimestamp = 0;
|
|
81
|
+
this.unFreezeDurationTimestamp = 0;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
class ContestIndex {
|
|
85
|
+
constructor() {
|
|
86
|
+
this.config = new ContestIndexConfig();
|
|
87
|
+
this.boardLink = "";
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function createContestIndex(contestIndexJSON) {
|
|
91
|
+
const c = new ContestIndex();
|
|
92
|
+
const cc = c.config;
|
|
93
|
+
const cjc = contestIndexJSON.config;
|
|
94
|
+
cc.contestName = cjc.contest_name;
|
|
95
|
+
cc.startTime = createDayJS(cjc.start_time);
|
|
96
|
+
cc.endTime = createDayJS(cjc.end_time);
|
|
97
|
+
cc.totalDurationTimestamp = cc.endTime.unix() - cc.startTime.unix();
|
|
98
|
+
{
|
|
99
|
+
cc.freezeTime = cc.endTime;
|
|
100
|
+
cc.freezeDurationTimestamp = 0;
|
|
101
|
+
if (cjc.frozen_time !== void 0 && cjc.frozen_time != null) {
|
|
102
|
+
const frozenTime = Number(cjc.frozen_time);
|
|
103
|
+
cc.freezeTime = createDayJS(cc.endTime.unix() - frozenTime);
|
|
104
|
+
cc.freezeDurationTimestamp = frozenTime;
|
|
105
|
+
}
|
|
106
|
+
cc.unFreezeDurationTimestamp = cc.totalDurationTimestamp - cc.freezeDurationTimestamp;
|
|
107
|
+
}
|
|
108
|
+
cc.logo = cjc.logo;
|
|
109
|
+
c.boardLink = contestIndexJSON.board_link;
|
|
110
|
+
return c;
|
|
111
|
+
}
|
|
112
|
+
function createContestIndexList(contestListJSON) {
|
|
113
|
+
const contestIndexList = [];
|
|
114
|
+
const dfs = (contestList) => {
|
|
115
|
+
if (Object.prototype.hasOwnProperty.call(contestList, "config")) {
|
|
116
|
+
contestIndexList.push(createContestIndex(contestList));
|
|
117
|
+
} else {
|
|
118
|
+
for (const k in contestList) {
|
|
119
|
+
dfs(contestList[k]);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
dfs(contestListJSON);
|
|
124
|
+
contestIndexList.sort((a, b) => {
|
|
125
|
+
if (a.config.startTime.isBefore(b.config.startTime)) {
|
|
126
|
+
return 1;
|
|
127
|
+
}
|
|
128
|
+
if (a.config.startTime.isAfter(b.config.startTime)) {
|
|
129
|
+
return -1;
|
|
130
|
+
}
|
|
131
|
+
if (a.config.endTime.isBefore(b.config.endTime)) {
|
|
132
|
+
return 1;
|
|
133
|
+
}
|
|
134
|
+
if (a.config.endTime.isAfter(b.config.endTime)) {
|
|
135
|
+
return -1;
|
|
136
|
+
}
|
|
137
|
+
if (a.config.contestName < b.config.contestName) {
|
|
138
|
+
return 1;
|
|
139
|
+
}
|
|
140
|
+
if (a.config.contestName > b.config.contestName) {
|
|
141
|
+
return -1;
|
|
142
|
+
}
|
|
143
|
+
return 0;
|
|
144
|
+
});
|
|
145
|
+
return contestIndexList;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
class ProblemStatistics {
|
|
149
|
+
constructor() {
|
|
150
|
+
this.acceptedNum = 0;
|
|
151
|
+
this.rejectedNum = 0;
|
|
152
|
+
this.pendingNum = 0;
|
|
153
|
+
this.submittedNum = 0;
|
|
154
|
+
this.attemptedNum = 0;
|
|
155
|
+
this.ignoreNum = 0;
|
|
156
|
+
this.firstSolveSubmissions = [];
|
|
157
|
+
this.lastSolveSubmissions = [];
|
|
158
|
+
}
|
|
159
|
+
reset() {
|
|
160
|
+
this.acceptedNum = 0;
|
|
161
|
+
this.rejectedNum = 0;
|
|
162
|
+
this.pendingNum = 0;
|
|
163
|
+
this.submittedNum = 0;
|
|
164
|
+
this.firstSolveSubmissions = [];
|
|
165
|
+
this.lastSolveSubmissions = [];
|
|
166
|
+
}
|
|
167
|
+
get dict() {
|
|
168
|
+
if (this.acceptedNum === 0) {
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
return calcDict(this.attemptedNum, this.acceptedNum);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
65
174
|
class Problem {
|
|
66
175
|
constructor() {
|
|
67
176
|
this.id = "";
|
|
68
177
|
this.label = "";
|
|
69
178
|
this.name = "";
|
|
70
|
-
this.statistics =
|
|
71
|
-
acceptedNum: 0,
|
|
72
|
-
rejectedNum: 0,
|
|
73
|
-
pendingNum: 0,
|
|
74
|
-
submittedNum: 0
|
|
75
|
-
};
|
|
179
|
+
this.statistics = new ProblemStatistics();
|
|
76
180
|
}
|
|
77
181
|
}
|
|
78
182
|
function createProblem(problemJSON) {
|
|
@@ -281,20 +385,29 @@ class Team {
|
|
|
281
385
|
this.group = [];
|
|
282
386
|
this.tag = [];
|
|
283
387
|
this.rank = 0;
|
|
388
|
+
this.organizationRank = -1;
|
|
284
389
|
this.solvedProblemNum = 0;
|
|
390
|
+
this.attemptedProblemNum = 0;
|
|
285
391
|
this.penalty = 0;
|
|
286
392
|
this.problemStatistics = [];
|
|
287
393
|
this.problemStatisticsMap = /* @__PURE__ */ new Map();
|
|
288
394
|
}
|
|
289
|
-
penaltyToMinute() {
|
|
395
|
+
get penaltyToMinute() {
|
|
290
396
|
return Math.floor(this.penalty / 60);
|
|
291
397
|
}
|
|
398
|
+
get dict() {
|
|
399
|
+
const attemptedNum = this.attemptedProblemNum;
|
|
400
|
+
const solvedNum = this.solvedProblemNum;
|
|
401
|
+
return calcDict(attemptedNum, solvedNum);
|
|
402
|
+
}
|
|
292
403
|
calcSolvedData() {
|
|
293
404
|
this.solvedProblemNum = 0;
|
|
294
405
|
this.penalty = 0;
|
|
406
|
+
this.attemptedProblemNum = 0;
|
|
295
407
|
for (const p of this.problemStatistics) {
|
|
296
408
|
if (p.isAccepted) {
|
|
297
409
|
this.solvedProblemNum++;
|
|
410
|
+
this.attemptedProblemNum += p.failedCount + 1;
|
|
298
411
|
this.penalty += p.penalty;
|
|
299
412
|
}
|
|
300
413
|
}
|
|
@@ -319,17 +432,18 @@ function createTeam(teamJSON) {
|
|
|
319
432
|
t.id = teamJSON.id ?? teamJSON.team_id ?? "";
|
|
320
433
|
t.name = teamJSON.name ?? teamJSON.team_name ?? "";
|
|
321
434
|
t.organization = teamJSON.organization ?? "";
|
|
435
|
+
t.badge = teamJSON.badge;
|
|
322
436
|
t.group = teamJSON.group ?? [];
|
|
323
437
|
t.tag = teamJSON.group ?? [];
|
|
324
438
|
t.coach = teamJSON.coach;
|
|
325
439
|
t.members = teamJSON.members;
|
|
326
|
-
if (teamJSON.official === true) {
|
|
440
|
+
if (Boolean(teamJSON.official) === true) {
|
|
327
441
|
t.group.push("official");
|
|
328
442
|
}
|
|
329
|
-
if (teamJSON.unofficial === true) {
|
|
443
|
+
if (Boolean(teamJSON.unofficial) === true) {
|
|
330
444
|
t.group.push("unofficial");
|
|
331
445
|
}
|
|
332
|
-
if (teamJSON.girl === true) {
|
|
446
|
+
if (Boolean(teamJSON.girl) === true) {
|
|
333
447
|
t.group.push("girl");
|
|
334
448
|
}
|
|
335
449
|
t.group = [...new Set(t.group)];
|
|
@@ -528,7 +642,6 @@ class Rank {
|
|
|
528
642
|
this.teamsMap = new Map(this.teams.map((t) => [t.id, t]));
|
|
529
643
|
this.submissions = ___default.cloneDeep(submissions).sort(Submission.compare);
|
|
530
644
|
this.submissionsMap = new Map(this.submissions.map((s) => [s.id, s]));
|
|
531
|
-
this.firstSolvedSubmissions = new Map(this.contest.problems.map((p) => [p.id, []]));
|
|
532
645
|
}
|
|
533
646
|
buildRank(options) {
|
|
534
647
|
(() => {
|
|
@@ -541,7 +654,9 @@ class Rank {
|
|
|
541
654
|
});
|
|
542
655
|
t.problemStatisticsMap = new Map(t.problemStatistics.map((ps) => [ps.problem.id, ps]));
|
|
543
656
|
}
|
|
544
|
-
this.
|
|
657
|
+
this.contest.problems.forEach((p) => {
|
|
658
|
+
p.statistics.reset();
|
|
659
|
+
});
|
|
545
660
|
for (const s of this.submissions) {
|
|
546
661
|
const teamId = s.teamId;
|
|
547
662
|
const problemId = s.problemId;
|
|
@@ -557,13 +672,13 @@ class Rank {
|
|
|
557
672
|
}
|
|
558
673
|
const problemStatistics = team.problemStatisticsMap.get(problemId);
|
|
559
674
|
const submissions = problemStatistics.submissions;
|
|
560
|
-
const firstSolvedSubmissions = this.firstSolvedSubmissions.get(problemId);
|
|
561
675
|
submissions.push(s);
|
|
562
676
|
problem.statistics.submittedNum++;
|
|
563
677
|
if (problemStatistics.isSolved) {
|
|
564
678
|
continue;
|
|
565
679
|
}
|
|
566
680
|
if (s.isIgnore || s.isNotCalculatedPenaltyStatus()) {
|
|
681
|
+
problem.statistics.ignoreNum++;
|
|
567
682
|
problemStatistics.ignoreCount++;
|
|
568
683
|
continue;
|
|
569
684
|
}
|
|
@@ -574,10 +689,15 @@ class Rank {
|
|
|
574
689
|
problemStatistics.isSolved = true;
|
|
575
690
|
problemStatistics.solvedTimestamp = s.timestamp;
|
|
576
691
|
problem.statistics.acceptedNum++;
|
|
577
|
-
|
|
692
|
+
problem.statistics.attemptedNum += problemStatistics.failedCount + 1;
|
|
693
|
+
if (problem.statistics.firstSolveSubmissions.length === 0 || problem.statistics.firstSolveSubmissions[problem.statistics.firstSolveSubmissions.length - 1].timestamp === s.timestamp) {
|
|
578
694
|
problemStatistics.isFirstSolved = true;
|
|
579
|
-
|
|
695
|
+
problem.statistics.firstSolveSubmissions.push(s);
|
|
580
696
|
}
|
|
697
|
+
while (problem.statistics.lastSolveSubmissions.length > 0) {
|
|
698
|
+
problem.statistics.lastSolveSubmissions.pop();
|
|
699
|
+
}
|
|
700
|
+
problem.statistics.lastSolveSubmissions.push(s);
|
|
581
701
|
}
|
|
582
702
|
if (s.isRejected()) {
|
|
583
703
|
problemStatistics.failedCount++;
|
|
@@ -596,6 +716,17 @@ class Rank {
|
|
|
596
716
|
t.rank = rank++;
|
|
597
717
|
}
|
|
598
718
|
}
|
|
719
|
+
if (this.contest.organization) {
|
|
720
|
+
let rank = 1;
|
|
721
|
+
const se = /* @__PURE__ */ new Set();
|
|
722
|
+
for (const t of this.teams) {
|
|
723
|
+
if (!se.has(t.organization)) {
|
|
724
|
+
se.add(t.organization);
|
|
725
|
+
t.organizationRank = rank;
|
|
726
|
+
rank++;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
599
730
|
})();
|
|
600
731
|
return this;
|
|
601
732
|
}
|
|
@@ -691,13 +822,19 @@ class Resolver extends Rank {
|
|
|
691
822
|
|
|
692
823
|
exports.dayjs = dayjs__default;
|
|
693
824
|
exports.Contest = Contest;
|
|
825
|
+
exports.ContestIndex = ContestIndex;
|
|
826
|
+
exports.ContestIndexConfig = ContestIndexConfig;
|
|
694
827
|
exports.Problem = Problem;
|
|
828
|
+
exports.ProblemStatistics = ProblemStatistics;
|
|
695
829
|
exports.Rank = Rank;
|
|
696
830
|
exports.Resolver = Resolver;
|
|
697
831
|
exports.Submission = Submission;
|
|
698
832
|
exports.Team = Team;
|
|
699
833
|
exports.TeamProblemStatistics = TeamProblemStatistics;
|
|
834
|
+
exports.calcDict = calcDict;
|
|
700
835
|
exports.createContest = createContest;
|
|
836
|
+
exports.createContestIndex = createContestIndex;
|
|
837
|
+
exports.createContestIndexList = createContestIndexList;
|
|
701
838
|
exports.createDayJS = createDayJS;
|
|
702
839
|
exports.createProblem = createProblem;
|
|
703
840
|
exports.createProblems = createProblems;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
import dayjs from 'dayjs';
|
|
2
2
|
export { default as dayjs } from 'dayjs';
|
|
3
|
-
import { SubmissionStatus, Submission as Submission$1, Submissions as Submissions$1, BalloonColor, Problem as Problem$1, Problems as Problems$1, StatusTimeDisplay,
|
|
3
|
+
import { Image, ContestIndex as ContestIndex$1, SubmissionStatus, Submission as Submission$1, Submissions as Submissions$1, BalloonColor, Problem as Problem$1, Problems as Problems$1, StatusTimeDisplay, ContestState, Contest as Contest$1, Team as Team$1, Teams as Teams$1 } from '@xcpcio/types';
|
|
4
|
+
|
|
5
|
+
declare class ContestIndexConfig {
|
|
6
|
+
contestName: string;
|
|
7
|
+
startTime: dayjs.Dayjs;
|
|
8
|
+
endTime: dayjs.Dayjs;
|
|
9
|
+
freezeTime: dayjs.Dayjs;
|
|
10
|
+
totalDurationTimestamp: number;
|
|
11
|
+
freezeDurationTimestamp: number;
|
|
12
|
+
unFreezeDurationTimestamp: number;
|
|
13
|
+
logo?: Image;
|
|
14
|
+
constructor();
|
|
15
|
+
}
|
|
16
|
+
declare class ContestIndex {
|
|
17
|
+
config: ContestIndexConfig;
|
|
18
|
+
boardLink: string;
|
|
19
|
+
constructor();
|
|
20
|
+
}
|
|
21
|
+
type ContestIndexList = Array<ContestIndex>;
|
|
22
|
+
declare function createContestIndex(contestIndexJSON: ContestIndex$1): ContestIndex;
|
|
23
|
+
declare function createContestIndexList(contestListJSON: any): ContestIndexList;
|
|
24
|
+
|
|
25
|
+
declare function calcDict(attemptedNum: number, solvedNum: number): number;
|
|
4
26
|
|
|
5
27
|
declare function createDayJS(time?: Date | string | number | undefined): dayjs.Dayjs;
|
|
6
28
|
declare function getTimestamp(time: number | dayjs.Dayjs): number;
|
|
@@ -24,11 +46,18 @@ type Submissions = Array<Submission>;
|
|
|
24
46
|
declare function createSubmission(submissionJSON: Submission$1): Submission;
|
|
25
47
|
declare function createSubmissions(submissionsJSON: Submissions$1): Submissions;
|
|
26
48
|
|
|
27
|
-
|
|
49
|
+
declare class ProblemStatistics {
|
|
28
50
|
acceptedNum: number;
|
|
29
51
|
rejectedNum: number;
|
|
30
52
|
pendingNum: number;
|
|
31
53
|
submittedNum: number;
|
|
54
|
+
attemptedNum: number;
|
|
55
|
+
ignoreNum: number;
|
|
56
|
+
firstSolveSubmissions: Submissions;
|
|
57
|
+
lastSolveSubmissions: Submissions;
|
|
58
|
+
constructor();
|
|
59
|
+
reset(): void;
|
|
60
|
+
get dict(): number;
|
|
32
61
|
}
|
|
33
62
|
declare class Problem {
|
|
34
63
|
id: string;
|
|
@@ -104,17 +133,21 @@ declare class Team {
|
|
|
104
133
|
id: string;
|
|
105
134
|
name: string;
|
|
106
135
|
organization: string;
|
|
136
|
+
badge?: Image;
|
|
107
137
|
group: Array<string>;
|
|
108
138
|
tag: Array<string>;
|
|
109
139
|
coach?: string | Array<string>;
|
|
110
140
|
members?: string | Array<string>;
|
|
111
141
|
rank: number;
|
|
142
|
+
organizationRank: number;
|
|
112
143
|
solvedProblemNum: number;
|
|
144
|
+
attemptedProblemNum: number;
|
|
113
145
|
penalty: number;
|
|
114
146
|
problemStatistics: Array<TeamProblemStatistics>;
|
|
115
147
|
problemStatisticsMap: Map<string, TeamProblemStatistics>;
|
|
116
148
|
constructor();
|
|
117
|
-
penaltyToMinute(): number;
|
|
149
|
+
get penaltyToMinute(): number;
|
|
150
|
+
get dict(): number;
|
|
118
151
|
calcSolvedData(): void;
|
|
119
152
|
static compare(lhs: Team, rhs: Team): number;
|
|
120
153
|
}
|
|
@@ -128,7 +161,6 @@ declare class Rank {
|
|
|
128
161
|
teamsMap: Map<string, Team>;
|
|
129
162
|
submissions: Submissions;
|
|
130
163
|
submissionsMap: Map<string, Submission>;
|
|
131
|
-
firstSolvedSubmissions: Map<string, Submissions>;
|
|
132
164
|
constructor(contest: Contest, teams: Teams, submissions: Submissions);
|
|
133
165
|
buildRank(options?: {
|
|
134
166
|
timestamp?: number;
|
|
@@ -159,4 +191,4 @@ declare function isRejected(status: SubmissionStatus): boolean;
|
|
|
159
191
|
declare function isPending(status: SubmissionStatus): boolean;
|
|
160
192
|
declare function isNotCalculatedPenaltyStatus(status: SubmissionStatus): boolean;
|
|
161
193
|
|
|
162
|
-
export { Contest, Problem, ProblemStatistics, Problems, Rank, Resolver, Submission, Submissions, Team, TeamProblemStatistics, Teams, createContest, createDayJS, createProblem, createProblems, createProblemsByProblemIds, createSubmission, createSubmissions, createTeam, createTeams, getImageSource, getTimeDiff, getTimestamp, isAccepted, isNotCalculatedPenaltyStatus, isPending, isRejected, stringToSubmissionStatus };
|
|
194
|
+
export { Contest, ContestIndex, ContestIndexConfig, ContestIndexList, Problem, ProblemStatistics, Problems, Rank, Resolver, Submission, Submissions, Team, TeamProblemStatistics, Teams, calcDict, createContest, createContestIndex, createContestIndexList, createDayJS, createProblem, createProblems, createProblemsByProblemIds, createSubmission, createSubmissions, createTeam, createTeams, getImageSource, getTimeDiff, getTimestamp, isAccepted, isNotCalculatedPenaltyStatus, isPending, isRejected, stringToSubmissionStatus };
|
package/dist/index.mjs
CHANGED
|
@@ -11,6 +11,13 @@ import relativeTime from 'dayjs/plugin/relativeTime';
|
|
|
11
11
|
import { VERSION, ContestState, SubmissionStatus } from '@xcpcio/types';
|
|
12
12
|
import _ from 'lodash';
|
|
13
13
|
|
|
14
|
+
function calcDict(attemptedNum, solvedNum) {
|
|
15
|
+
if (solvedNum === 0) {
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
18
|
+
return Math.floor((attemptedNum - solvedNum) * 100 / attemptedNum);
|
|
19
|
+
}
|
|
20
|
+
|
|
14
21
|
dayjs.extend(duration);
|
|
15
22
|
dayjs.extend(utc);
|
|
16
23
|
dayjs.extend(timezone);
|
|
@@ -36,8 +43,9 @@ function getTimestamp(time) {
|
|
|
36
43
|
}
|
|
37
44
|
function getTimeDiff(seconds) {
|
|
38
45
|
const two = (a) => {
|
|
39
|
-
if (a < 10)
|
|
40
|
-
return
|
|
46
|
+
if (a < 10) {
|
|
47
|
+
return `0${a}`;
|
|
48
|
+
}
|
|
41
49
|
return String(a);
|
|
42
50
|
};
|
|
43
51
|
const h = Math.floor(seconds / 3600);
|
|
@@ -46,17 +54,113 @@ function getTimeDiff(seconds) {
|
|
|
46
54
|
return [two(h), two(m), two(s)].join(":");
|
|
47
55
|
}
|
|
48
56
|
|
|
57
|
+
class ContestIndexConfig {
|
|
58
|
+
constructor() {
|
|
59
|
+
this.contestName = "";
|
|
60
|
+
this.startTime = createDayJS();
|
|
61
|
+
this.endTime = createDayJS();
|
|
62
|
+
this.freezeTime = createDayJS();
|
|
63
|
+
this.totalDurationTimestamp = 0;
|
|
64
|
+
this.freezeDurationTimestamp = 0;
|
|
65
|
+
this.unFreezeDurationTimestamp = 0;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
class ContestIndex {
|
|
69
|
+
constructor() {
|
|
70
|
+
this.config = new ContestIndexConfig();
|
|
71
|
+
this.boardLink = "";
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function createContestIndex(contestIndexJSON) {
|
|
75
|
+
const c = new ContestIndex();
|
|
76
|
+
const cc = c.config;
|
|
77
|
+
const cjc = contestIndexJSON.config;
|
|
78
|
+
cc.contestName = cjc.contest_name;
|
|
79
|
+
cc.startTime = createDayJS(cjc.start_time);
|
|
80
|
+
cc.endTime = createDayJS(cjc.end_time);
|
|
81
|
+
cc.totalDurationTimestamp = cc.endTime.unix() - cc.startTime.unix();
|
|
82
|
+
{
|
|
83
|
+
cc.freezeTime = cc.endTime;
|
|
84
|
+
cc.freezeDurationTimestamp = 0;
|
|
85
|
+
if (cjc.frozen_time !== void 0 && cjc.frozen_time != null) {
|
|
86
|
+
const frozenTime = Number(cjc.frozen_time);
|
|
87
|
+
cc.freezeTime = createDayJS(cc.endTime.unix() - frozenTime);
|
|
88
|
+
cc.freezeDurationTimestamp = frozenTime;
|
|
89
|
+
}
|
|
90
|
+
cc.unFreezeDurationTimestamp = cc.totalDurationTimestamp - cc.freezeDurationTimestamp;
|
|
91
|
+
}
|
|
92
|
+
cc.logo = cjc.logo;
|
|
93
|
+
c.boardLink = contestIndexJSON.board_link;
|
|
94
|
+
return c;
|
|
95
|
+
}
|
|
96
|
+
function createContestIndexList(contestListJSON) {
|
|
97
|
+
const contestIndexList = [];
|
|
98
|
+
const dfs = (contestList) => {
|
|
99
|
+
if (Object.prototype.hasOwnProperty.call(contestList, "config")) {
|
|
100
|
+
contestIndexList.push(createContestIndex(contestList));
|
|
101
|
+
} else {
|
|
102
|
+
for (const k in contestList) {
|
|
103
|
+
dfs(contestList[k]);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
dfs(contestListJSON);
|
|
108
|
+
contestIndexList.sort((a, b) => {
|
|
109
|
+
if (a.config.startTime.isBefore(b.config.startTime)) {
|
|
110
|
+
return 1;
|
|
111
|
+
}
|
|
112
|
+
if (a.config.startTime.isAfter(b.config.startTime)) {
|
|
113
|
+
return -1;
|
|
114
|
+
}
|
|
115
|
+
if (a.config.endTime.isBefore(b.config.endTime)) {
|
|
116
|
+
return 1;
|
|
117
|
+
}
|
|
118
|
+
if (a.config.endTime.isAfter(b.config.endTime)) {
|
|
119
|
+
return -1;
|
|
120
|
+
}
|
|
121
|
+
if (a.config.contestName < b.config.contestName) {
|
|
122
|
+
return 1;
|
|
123
|
+
}
|
|
124
|
+
if (a.config.contestName > b.config.contestName) {
|
|
125
|
+
return -1;
|
|
126
|
+
}
|
|
127
|
+
return 0;
|
|
128
|
+
});
|
|
129
|
+
return contestIndexList;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
class ProblemStatistics {
|
|
133
|
+
constructor() {
|
|
134
|
+
this.acceptedNum = 0;
|
|
135
|
+
this.rejectedNum = 0;
|
|
136
|
+
this.pendingNum = 0;
|
|
137
|
+
this.submittedNum = 0;
|
|
138
|
+
this.attemptedNum = 0;
|
|
139
|
+
this.ignoreNum = 0;
|
|
140
|
+
this.firstSolveSubmissions = [];
|
|
141
|
+
this.lastSolveSubmissions = [];
|
|
142
|
+
}
|
|
143
|
+
reset() {
|
|
144
|
+
this.acceptedNum = 0;
|
|
145
|
+
this.rejectedNum = 0;
|
|
146
|
+
this.pendingNum = 0;
|
|
147
|
+
this.submittedNum = 0;
|
|
148
|
+
this.firstSolveSubmissions = [];
|
|
149
|
+
this.lastSolveSubmissions = [];
|
|
150
|
+
}
|
|
151
|
+
get dict() {
|
|
152
|
+
if (this.acceptedNum === 0) {
|
|
153
|
+
return 0;
|
|
154
|
+
}
|
|
155
|
+
return calcDict(this.attemptedNum, this.acceptedNum);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
49
158
|
class Problem {
|
|
50
159
|
constructor() {
|
|
51
160
|
this.id = "";
|
|
52
161
|
this.label = "";
|
|
53
162
|
this.name = "";
|
|
54
|
-
this.statistics =
|
|
55
|
-
acceptedNum: 0,
|
|
56
|
-
rejectedNum: 0,
|
|
57
|
-
pendingNum: 0,
|
|
58
|
-
submittedNum: 0
|
|
59
|
-
};
|
|
163
|
+
this.statistics = new ProblemStatistics();
|
|
60
164
|
}
|
|
61
165
|
}
|
|
62
166
|
function createProblem(problemJSON) {
|
|
@@ -265,20 +369,29 @@ class Team {
|
|
|
265
369
|
this.group = [];
|
|
266
370
|
this.tag = [];
|
|
267
371
|
this.rank = 0;
|
|
372
|
+
this.organizationRank = -1;
|
|
268
373
|
this.solvedProblemNum = 0;
|
|
374
|
+
this.attemptedProblemNum = 0;
|
|
269
375
|
this.penalty = 0;
|
|
270
376
|
this.problemStatistics = [];
|
|
271
377
|
this.problemStatisticsMap = /* @__PURE__ */ new Map();
|
|
272
378
|
}
|
|
273
|
-
penaltyToMinute() {
|
|
379
|
+
get penaltyToMinute() {
|
|
274
380
|
return Math.floor(this.penalty / 60);
|
|
275
381
|
}
|
|
382
|
+
get dict() {
|
|
383
|
+
const attemptedNum = this.attemptedProblemNum;
|
|
384
|
+
const solvedNum = this.solvedProblemNum;
|
|
385
|
+
return calcDict(attemptedNum, solvedNum);
|
|
386
|
+
}
|
|
276
387
|
calcSolvedData() {
|
|
277
388
|
this.solvedProblemNum = 0;
|
|
278
389
|
this.penalty = 0;
|
|
390
|
+
this.attemptedProblemNum = 0;
|
|
279
391
|
for (const p of this.problemStatistics) {
|
|
280
392
|
if (p.isAccepted) {
|
|
281
393
|
this.solvedProblemNum++;
|
|
394
|
+
this.attemptedProblemNum += p.failedCount + 1;
|
|
282
395
|
this.penalty += p.penalty;
|
|
283
396
|
}
|
|
284
397
|
}
|
|
@@ -303,17 +416,18 @@ function createTeam(teamJSON) {
|
|
|
303
416
|
t.id = teamJSON.id ?? teamJSON.team_id ?? "";
|
|
304
417
|
t.name = teamJSON.name ?? teamJSON.team_name ?? "";
|
|
305
418
|
t.organization = teamJSON.organization ?? "";
|
|
419
|
+
t.badge = teamJSON.badge;
|
|
306
420
|
t.group = teamJSON.group ?? [];
|
|
307
421
|
t.tag = teamJSON.group ?? [];
|
|
308
422
|
t.coach = teamJSON.coach;
|
|
309
423
|
t.members = teamJSON.members;
|
|
310
|
-
if (teamJSON.official === true) {
|
|
424
|
+
if (Boolean(teamJSON.official) === true) {
|
|
311
425
|
t.group.push("official");
|
|
312
426
|
}
|
|
313
|
-
if (teamJSON.unofficial === true) {
|
|
427
|
+
if (Boolean(teamJSON.unofficial) === true) {
|
|
314
428
|
t.group.push("unofficial");
|
|
315
429
|
}
|
|
316
|
-
if (teamJSON.girl === true) {
|
|
430
|
+
if (Boolean(teamJSON.girl) === true) {
|
|
317
431
|
t.group.push("girl");
|
|
318
432
|
}
|
|
319
433
|
t.group = [...new Set(t.group)];
|
|
@@ -512,7 +626,6 @@ class Rank {
|
|
|
512
626
|
this.teamsMap = new Map(this.teams.map((t) => [t.id, t]));
|
|
513
627
|
this.submissions = _.cloneDeep(submissions).sort(Submission.compare);
|
|
514
628
|
this.submissionsMap = new Map(this.submissions.map((s) => [s.id, s]));
|
|
515
|
-
this.firstSolvedSubmissions = new Map(this.contest.problems.map((p) => [p.id, []]));
|
|
516
629
|
}
|
|
517
630
|
buildRank(options) {
|
|
518
631
|
(() => {
|
|
@@ -525,7 +638,9 @@ class Rank {
|
|
|
525
638
|
});
|
|
526
639
|
t.problemStatisticsMap = new Map(t.problemStatistics.map((ps) => [ps.problem.id, ps]));
|
|
527
640
|
}
|
|
528
|
-
this.
|
|
641
|
+
this.contest.problems.forEach((p) => {
|
|
642
|
+
p.statistics.reset();
|
|
643
|
+
});
|
|
529
644
|
for (const s of this.submissions) {
|
|
530
645
|
const teamId = s.teamId;
|
|
531
646
|
const problemId = s.problemId;
|
|
@@ -541,13 +656,13 @@ class Rank {
|
|
|
541
656
|
}
|
|
542
657
|
const problemStatistics = team.problemStatisticsMap.get(problemId);
|
|
543
658
|
const submissions = problemStatistics.submissions;
|
|
544
|
-
const firstSolvedSubmissions = this.firstSolvedSubmissions.get(problemId);
|
|
545
659
|
submissions.push(s);
|
|
546
660
|
problem.statistics.submittedNum++;
|
|
547
661
|
if (problemStatistics.isSolved) {
|
|
548
662
|
continue;
|
|
549
663
|
}
|
|
550
664
|
if (s.isIgnore || s.isNotCalculatedPenaltyStatus()) {
|
|
665
|
+
problem.statistics.ignoreNum++;
|
|
551
666
|
problemStatistics.ignoreCount++;
|
|
552
667
|
continue;
|
|
553
668
|
}
|
|
@@ -558,10 +673,15 @@ class Rank {
|
|
|
558
673
|
problemStatistics.isSolved = true;
|
|
559
674
|
problemStatistics.solvedTimestamp = s.timestamp;
|
|
560
675
|
problem.statistics.acceptedNum++;
|
|
561
|
-
|
|
676
|
+
problem.statistics.attemptedNum += problemStatistics.failedCount + 1;
|
|
677
|
+
if (problem.statistics.firstSolveSubmissions.length === 0 || problem.statistics.firstSolveSubmissions[problem.statistics.firstSolveSubmissions.length - 1].timestamp === s.timestamp) {
|
|
562
678
|
problemStatistics.isFirstSolved = true;
|
|
563
|
-
|
|
679
|
+
problem.statistics.firstSolveSubmissions.push(s);
|
|
564
680
|
}
|
|
681
|
+
while (problem.statistics.lastSolveSubmissions.length > 0) {
|
|
682
|
+
problem.statistics.lastSolveSubmissions.pop();
|
|
683
|
+
}
|
|
684
|
+
problem.statistics.lastSolveSubmissions.push(s);
|
|
565
685
|
}
|
|
566
686
|
if (s.isRejected()) {
|
|
567
687
|
problemStatistics.failedCount++;
|
|
@@ -580,6 +700,17 @@ class Rank {
|
|
|
580
700
|
t.rank = rank++;
|
|
581
701
|
}
|
|
582
702
|
}
|
|
703
|
+
if (this.contest.organization) {
|
|
704
|
+
let rank = 1;
|
|
705
|
+
const se = /* @__PURE__ */ new Set();
|
|
706
|
+
for (const t of this.teams) {
|
|
707
|
+
if (!se.has(t.organization)) {
|
|
708
|
+
se.add(t.organization);
|
|
709
|
+
t.organizationRank = rank;
|
|
710
|
+
rank++;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
583
714
|
})();
|
|
584
715
|
return this;
|
|
585
716
|
}
|
|
@@ -673,4 +804,4 @@ class Resolver extends Rank {
|
|
|
673
804
|
}
|
|
674
805
|
}
|
|
675
806
|
|
|
676
|
-
export { Contest, Problem, Rank, Resolver, Submission, Team, TeamProblemStatistics, createContest, createDayJS, createProblem, createProblems, createProblemsByProblemIds, createSubmission, createSubmissions, createTeam, createTeams, getImageSource, getTimeDiff, getTimestamp, isAccepted, isNotCalculatedPenaltyStatus, isPending, isRejected, stringToSubmissionStatus };
|
|
807
|
+
export { Contest, ContestIndex, ContestIndexConfig, Problem, ProblemStatistics, Rank, Resolver, Submission, Team, TeamProblemStatistics, calcDict, createContest, createContestIndex, createContestIndexList, createDayJS, createProblem, createProblems, createProblemsByProblemIds, createSubmission, createSubmissions, createTeam, createTeams, getImageSource, getTimeDiff, getTimestamp, isAccepted, isNotCalculatedPenaltyStatus, isPending, isRejected, stringToSubmissionStatus };
|
package/package.json
CHANGED
|
@@ -1,31 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xcpcio/core",
|
|
3
|
+
"version": "0.4.0",
|
|
3
4
|
"description": "XCPCIO Core",
|
|
4
|
-
"version": "0.3.3",
|
|
5
|
-
"license": "MIT",
|
|
6
5
|
"author": "Dup4 <lyuzhi.pan@gmail.com>",
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
"CCPC"
|
|
10
|
-
],
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://github.com/xcpcio/xcpcio",
|
|
11
8
|
"repository": {
|
|
12
9
|
"type": "git",
|
|
13
10
|
"url": "git+https://github.com/xcpcio/xcpcio.git"
|
|
14
11
|
},
|
|
15
|
-
"homepage": "https://github.com/xcpcio/xcpcio",
|
|
16
12
|
"bugs": {
|
|
17
13
|
"url": "https://github.com/xcpcio/xcpcio/issues"
|
|
18
14
|
},
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
"keywords": [
|
|
16
|
+
"ICPC",
|
|
17
|
+
"CCPC"
|
|
18
|
+
],
|
|
19
|
+
"sideEffects": false,
|
|
22
20
|
"exports": {
|
|
23
21
|
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
24
23
|
"require": "./dist/index.cjs",
|
|
25
|
-
"import": "./dist/index.mjs"
|
|
26
|
-
"types": "./dist/index.d.ts"
|
|
24
|
+
"import": "./dist/index.mjs"
|
|
27
25
|
}
|
|
28
26
|
},
|
|
27
|
+
"main": "./dist/index.mjs",
|
|
28
|
+
"module": "./dist/index.mjs",
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
29
30
|
"typesVersions": {
|
|
30
31
|
"*": {
|
|
31
32
|
"*": [
|
|
@@ -38,7 +39,11 @@
|
|
|
38
39
|
"src",
|
|
39
40
|
"dist"
|
|
40
41
|
],
|
|
41
|
-
"
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"dayjs": "^1.11.8",
|
|
44
|
+
"lodash": "^4.17.21",
|
|
45
|
+
"@xcpcio/types": "0.4.0"
|
|
46
|
+
},
|
|
42
47
|
"devDependencies": {
|
|
43
48
|
"@babel/types": "^7.22.4",
|
|
44
49
|
"@types/lodash": "^4.14.195",
|
|
@@ -50,18 +55,12 @@
|
|
|
50
55
|
"esmo": "^0.14.1",
|
|
51
56
|
"npm-run-all": "^4.1.5",
|
|
52
57
|
"pnpm": "^7.33.0",
|
|
53
|
-
"prettier": "^2.8.8",
|
|
54
58
|
"taze": "^0.10.2",
|
|
55
59
|
"typescript": "^4.9.5",
|
|
56
60
|
"unbuild": "^0.7.6",
|
|
57
61
|
"vite": "^4.3.9",
|
|
58
62
|
"vitest": "^0.32.0"
|
|
59
63
|
},
|
|
60
|
-
"dependencies": {
|
|
61
|
-
"dayjs": "^1.11.8",
|
|
62
|
-
"lodash": "^4.17.21",
|
|
63
|
-
"@xcpcio/types": "0.3.3"
|
|
64
|
-
},
|
|
65
64
|
"scripts": {
|
|
66
65
|
"build": "unbuild",
|
|
67
66
|
"dev": "unbuild --stub",
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type dayjs from "dayjs";
|
|
2
|
+
|
|
3
|
+
import type { ContestIndex as IContestIndex, Image } from "@xcpcio/types";
|
|
4
|
+
|
|
5
|
+
import { createDayJS } from "./utils";
|
|
6
|
+
|
|
7
|
+
export class ContestIndexConfig {
|
|
8
|
+
contestName: string;
|
|
9
|
+
|
|
10
|
+
startTime: dayjs.Dayjs;
|
|
11
|
+
endTime: dayjs.Dayjs;
|
|
12
|
+
freezeTime: dayjs.Dayjs;
|
|
13
|
+
|
|
14
|
+
totalDurationTimestamp: number;
|
|
15
|
+
freezeDurationTimestamp: number;
|
|
16
|
+
unFreezeDurationTimestamp: number;
|
|
17
|
+
|
|
18
|
+
logo?: Image;
|
|
19
|
+
|
|
20
|
+
constructor() {
|
|
21
|
+
this.contestName = "";
|
|
22
|
+
|
|
23
|
+
this.startTime = createDayJS();
|
|
24
|
+
this.endTime = createDayJS();
|
|
25
|
+
this.freezeTime = createDayJS();
|
|
26
|
+
|
|
27
|
+
this.totalDurationTimestamp = 0;
|
|
28
|
+
this.freezeDurationTimestamp = 0;
|
|
29
|
+
this.unFreezeDurationTimestamp = 0;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class ContestIndex {
|
|
34
|
+
config: ContestIndexConfig;
|
|
35
|
+
boardLink: string;
|
|
36
|
+
|
|
37
|
+
constructor() {
|
|
38
|
+
this.config = new ContestIndexConfig();
|
|
39
|
+
this.boardLink = "";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type ContestIndexList = Array<ContestIndex>;
|
|
44
|
+
|
|
45
|
+
export function createContestIndex(contestIndexJSON: IContestIndex): ContestIndex {
|
|
46
|
+
const c = new ContestIndex();
|
|
47
|
+
const cc = c.config;
|
|
48
|
+
const cjc = contestIndexJSON.config;
|
|
49
|
+
|
|
50
|
+
cc.contestName = cjc.contest_name;
|
|
51
|
+
|
|
52
|
+
cc.startTime = createDayJS(cjc.start_time);
|
|
53
|
+
cc.endTime = createDayJS(cjc.end_time);
|
|
54
|
+
|
|
55
|
+
cc.totalDurationTimestamp = cc.endTime.unix() - cc.startTime.unix();
|
|
56
|
+
|
|
57
|
+
{
|
|
58
|
+
// default value
|
|
59
|
+
cc.freezeTime = cc.endTime;
|
|
60
|
+
cc.freezeDurationTimestamp = 0;
|
|
61
|
+
|
|
62
|
+
if (cjc.frozen_time !== undefined && cjc.frozen_time != null) {
|
|
63
|
+
const frozenTime = Number(cjc.frozen_time);
|
|
64
|
+
|
|
65
|
+
cc.freezeTime = createDayJS(cc.endTime.unix() - frozenTime);
|
|
66
|
+
cc.freezeDurationTimestamp = frozenTime;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
cc.unFreezeDurationTimestamp = cc.totalDurationTimestamp - cc.freezeDurationTimestamp;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
cc.logo = cjc.logo;
|
|
73
|
+
|
|
74
|
+
c.boardLink = contestIndexJSON.board_link;
|
|
75
|
+
|
|
76
|
+
return c;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function createContestIndexList(contestListJSON: any): ContestIndexList {
|
|
80
|
+
const contestIndexList = [] as ContestIndexList;
|
|
81
|
+
|
|
82
|
+
const dfs = (contestList: any) => {
|
|
83
|
+
if (Object.prototype.hasOwnProperty.call(contestList, "config")) {
|
|
84
|
+
contestIndexList.push(createContestIndex(contestList));
|
|
85
|
+
} else {
|
|
86
|
+
for (const k in contestList) {
|
|
87
|
+
dfs(contestList[k]);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
dfs(contestListJSON);
|
|
93
|
+
|
|
94
|
+
contestIndexList.sort((a: ContestIndex, b: ContestIndex) => {
|
|
95
|
+
if (a.config.startTime.isBefore(b.config.startTime)) {
|
|
96
|
+
return 1;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (a.config.startTime.isAfter(b.config.startTime)) {
|
|
100
|
+
return -1;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (a.config.endTime.isBefore(b.config.endTime)) {
|
|
104
|
+
return 1;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (a.config.endTime.isAfter(b.config.endTime)) {
|
|
108
|
+
return -1;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (a.config.contestName < b.config.contestName) {
|
|
112
|
+
return 1;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (a.config.contestName > b.config.contestName) {
|
|
116
|
+
return -1;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return 0;
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
return contestIndexList;
|
|
123
|
+
}
|
package/src/contest.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { Contest as IContest,
|
|
1
|
+
import type { Contest as IContest, Image, StatusTimeDisplay } from "@xcpcio/types";
|
|
2
|
+
import { ContestState, VERSION } from "@xcpcio/types";
|
|
2
3
|
|
|
3
|
-
import { Problem, Problems
|
|
4
|
-
import {
|
|
4
|
+
import type { Problem, Problems } from "./problem";
|
|
5
|
+
import { createProblems, createProblemsByProblemIds } from "./problem";
|
|
6
|
+
import { createDayJS, dayjs, getTimeDiff } from "./utils";
|
|
5
7
|
|
|
6
8
|
export class Contest {
|
|
7
9
|
name = "";
|
|
@@ -164,7 +166,7 @@ export function createContest(contestJSON: IContest): Contest {
|
|
|
164
166
|
c.problems = createProblems(contestJSON.problems);
|
|
165
167
|
}
|
|
166
168
|
|
|
167
|
-
c.problemsMap = new Map(c.problems.map(
|
|
169
|
+
c.problemsMap = new Map(c.problems.map(p => [p.id, p]));
|
|
168
170
|
}
|
|
169
171
|
|
|
170
172
|
if (contestJSON.status_time_display !== undefined && contestJSON.status_time_display !== null) {
|
package/src/image.ts
CHANGED
package/src/index.ts
CHANGED
package/src/problem.ts
CHANGED
|
@@ -1,13 +1,51 @@
|
|
|
1
|
-
import { Problem as IProblem, Problems as IProblems
|
|
1
|
+
import type { BalloonColor, Problem as IProblem, Problems as IProblems } from "@xcpcio/types";
|
|
2
2
|
|
|
3
|
-
import { Submissions } from "./submission";
|
|
3
|
+
import type { Submissions } from "./submission";
|
|
4
|
+
import { calcDict } from "./utils";
|
|
4
5
|
|
|
5
|
-
export
|
|
6
|
+
export class ProblemStatistics {
|
|
6
7
|
acceptedNum: number;
|
|
7
8
|
rejectedNum: number;
|
|
8
9
|
pendingNum: number;
|
|
9
10
|
|
|
10
11
|
submittedNum: number;
|
|
12
|
+
attemptedNum: number;
|
|
13
|
+
ignoreNum: number;
|
|
14
|
+
|
|
15
|
+
firstSolveSubmissions: Submissions;
|
|
16
|
+
lastSolveSubmissions: Submissions;
|
|
17
|
+
|
|
18
|
+
constructor() {
|
|
19
|
+
this.acceptedNum = 0;
|
|
20
|
+
this.rejectedNum = 0;
|
|
21
|
+
this.pendingNum = 0;
|
|
22
|
+
|
|
23
|
+
this.submittedNum = 0;
|
|
24
|
+
this.attemptedNum = 0;
|
|
25
|
+
this.ignoreNum = 0;
|
|
26
|
+
|
|
27
|
+
this.firstSolveSubmissions = [];
|
|
28
|
+
this.lastSolveSubmissions = [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
reset() {
|
|
32
|
+
this.acceptedNum = 0;
|
|
33
|
+
this.rejectedNum = 0;
|
|
34
|
+
this.pendingNum = 0;
|
|
35
|
+
|
|
36
|
+
this.submittedNum = 0;
|
|
37
|
+
|
|
38
|
+
this.firstSolveSubmissions = [];
|
|
39
|
+
this.lastSolveSubmissions = [];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get dict() {
|
|
43
|
+
if (this.acceptedNum === 0) {
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return calcDict(this.attemptedNum, this.acceptedNum);
|
|
48
|
+
}
|
|
11
49
|
}
|
|
12
50
|
|
|
13
51
|
export class Problem {
|
|
@@ -29,12 +67,7 @@ export class Problem {
|
|
|
29
67
|
|
|
30
68
|
this.name = "";
|
|
31
69
|
|
|
32
|
-
this.statistics =
|
|
33
|
-
acceptedNum: 0,
|
|
34
|
-
rejectedNum: 0,
|
|
35
|
-
pendingNum: 0,
|
|
36
|
-
submittedNum: 0,
|
|
37
|
-
};
|
|
70
|
+
this.statistics = new ProblemStatistics();
|
|
38
71
|
}
|
|
39
72
|
}
|
|
40
73
|
|
package/src/rank.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import _ from "lodash";
|
|
2
2
|
|
|
3
|
-
import { Contest } from "./contest";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import type { Contest } from "./contest";
|
|
4
|
+
import type { Teams } from "./team";
|
|
5
|
+
import { Team } from "./team";
|
|
6
|
+
import type { Submissions } from "./submission";
|
|
7
|
+
import { Submission } from "./submission";
|
|
6
8
|
import { TeamProblemStatistics } from "./problem";
|
|
7
9
|
|
|
8
10
|
export class Rank {
|
|
@@ -14,18 +16,14 @@ export class Rank {
|
|
|
14
16
|
submissions: Submissions;
|
|
15
17
|
submissionsMap: Map<string, Submission>;
|
|
16
18
|
|
|
17
|
-
firstSolvedSubmissions: Map<string, Submissions>;
|
|
18
|
-
|
|
19
19
|
constructor(contest: Contest, teams: Teams, submissions: Submissions) {
|
|
20
20
|
this.contest = contest;
|
|
21
21
|
|
|
22
22
|
this.teams = _.cloneDeep(teams);
|
|
23
|
-
this.teamsMap = new Map(this.teams.map(
|
|
23
|
+
this.teamsMap = new Map(this.teams.map(t => [t.id, t]));
|
|
24
24
|
|
|
25
25
|
this.submissions = _.cloneDeep(submissions).sort(Submission.compare);
|
|
26
|
-
this.submissionsMap = new Map(this.submissions.map(
|
|
27
|
-
|
|
28
|
-
this.firstSolvedSubmissions = new Map(this.contest.problems.map((p) => [p.id, []]));
|
|
26
|
+
this.submissionsMap = new Map(this.submissions.map(s => [s.id, s]));
|
|
29
27
|
}
|
|
30
28
|
|
|
31
29
|
buildRank(options?: { timestamp?: number }) {
|
|
@@ -39,10 +37,12 @@ export class Rank {
|
|
|
39
37
|
return ps;
|
|
40
38
|
});
|
|
41
39
|
|
|
42
|
-
t.problemStatisticsMap = new Map(t.problemStatistics.map(
|
|
40
|
+
t.problemStatisticsMap = new Map(t.problemStatistics.map(ps => [ps.problem.id, ps]));
|
|
43
41
|
}
|
|
44
42
|
|
|
45
|
-
this.
|
|
43
|
+
this.contest.problems.forEach((p) => {
|
|
44
|
+
p.statistics.reset();
|
|
45
|
+
});
|
|
46
46
|
|
|
47
47
|
for (const s of this.submissions) {
|
|
48
48
|
const teamId = s.teamId;
|
|
@@ -62,7 +62,6 @@ export class Rank {
|
|
|
62
62
|
|
|
63
63
|
const problemStatistics = team.problemStatisticsMap.get(problemId) as TeamProblemStatistics;
|
|
64
64
|
const submissions = problemStatistics.submissions;
|
|
65
|
-
const firstSolvedSubmissions = this.firstSolvedSubmissions.get(problemId) as Array<Submission>;
|
|
66
65
|
|
|
67
66
|
submissions.push(s);
|
|
68
67
|
problem.statistics.submittedNum++;
|
|
@@ -72,6 +71,7 @@ export class Rank {
|
|
|
72
71
|
}
|
|
73
72
|
|
|
74
73
|
if (s.isIgnore || s.isNotCalculatedPenaltyStatus()) {
|
|
74
|
+
problem.statistics.ignoreNum++;
|
|
75
75
|
problemStatistics.ignoreCount++;
|
|
76
76
|
continue;
|
|
77
77
|
}
|
|
@@ -85,14 +85,21 @@ export class Rank {
|
|
|
85
85
|
problemStatistics.solvedTimestamp = s.timestamp;
|
|
86
86
|
|
|
87
87
|
problem.statistics.acceptedNum++;
|
|
88
|
+
problem.statistics.attemptedNum += problemStatistics.failedCount + 1;
|
|
88
89
|
|
|
89
90
|
if (
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
problem.statistics.firstSolveSubmissions.length === 0
|
|
92
|
+
|| problem.statistics.firstSolveSubmissions[problem.statistics.firstSolveSubmissions.length - 1].timestamp === s.timestamp
|
|
92
93
|
) {
|
|
93
94
|
problemStatistics.isFirstSolved = true;
|
|
94
|
-
|
|
95
|
+
problem.statistics.firstSolveSubmissions.push(s);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
while (problem.statistics.lastSolveSubmissions.length > 0) {
|
|
99
|
+
problem.statistics.lastSolveSubmissions.pop();
|
|
95
100
|
}
|
|
101
|
+
|
|
102
|
+
problem.statistics.lastSolveSubmissions.push(s);
|
|
96
103
|
}
|
|
97
104
|
|
|
98
105
|
if (s.isRejected()) {
|
|
@@ -106,7 +113,7 @@ export class Rank {
|
|
|
106
113
|
}
|
|
107
114
|
}
|
|
108
115
|
|
|
109
|
-
this.teams.forEach(
|
|
116
|
+
this.teams.forEach(t => t.calcSolvedData());
|
|
110
117
|
this.teams.sort(Team.compare);
|
|
111
118
|
|
|
112
119
|
{
|
|
@@ -115,6 +122,19 @@ export class Rank {
|
|
|
115
122
|
t.rank = rank++;
|
|
116
123
|
}
|
|
117
124
|
}
|
|
125
|
+
|
|
126
|
+
if (this.contest.organization) {
|
|
127
|
+
let rank = 1;
|
|
128
|
+
const se = new Set<string>();
|
|
129
|
+
|
|
130
|
+
for (const t of this.teams) {
|
|
131
|
+
if (!se.has(t.organization)) {
|
|
132
|
+
se.add(t.organization);
|
|
133
|
+
t.organizationRank = rank;
|
|
134
|
+
rank++;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
118
138
|
})();
|
|
119
139
|
|
|
120
140
|
return this;
|
package/src/resolver.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import _ from "lodash";
|
|
2
2
|
|
|
3
3
|
import { Rank } from "./rank";
|
|
4
|
-
import { Contest } from "./contest";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
4
|
+
import type { Contest } from "./contest";
|
|
5
|
+
import type { Teams } from "./team";
|
|
6
|
+
import { Team } from "./team";
|
|
7
|
+
import type { Submissions } from "./submission";
|
|
8
|
+
import { Submission } from "./submission";
|
|
9
|
+
import type { TeamProblemStatistics } from "./problem";
|
|
8
10
|
import { ResolverOperation } from "./resolver-operation";
|
|
9
11
|
|
|
10
12
|
export class Resolver extends Rank {
|
|
@@ -22,7 +24,7 @@ export class Resolver extends Rank {
|
|
|
22
24
|
|
|
23
25
|
{
|
|
24
26
|
const ix = _.sortedIndex(
|
|
25
|
-
submissions.map(
|
|
27
|
+
submissions.map(s => s.timestamp),
|
|
26
28
|
contest.unFreezeDurationTimestamp,
|
|
27
29
|
);
|
|
28
30
|
|
|
@@ -65,7 +67,7 @@ export class Resolver extends Rank {
|
|
|
65
67
|
{
|
|
66
68
|
const teams_ = _.cloneDeep(this.teams);
|
|
67
69
|
|
|
68
|
-
for (let i = this.teams.length - 1; i >= 0;
|
|
70
|
+
for (let i = this.teams.length - 1; i >= 0;) {
|
|
69
71
|
const team = teams_[i];
|
|
70
72
|
const teamId = team.id;
|
|
71
73
|
|
package/src/submission.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { Submission as ISubmission, Submissions as ISubmissions
|
|
1
|
+
import type { Submission as ISubmission, Submissions as ISubmissions } from "@xcpcio/types";
|
|
2
|
+
import { SubmissionStatus } from "@xcpcio/types";
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
isAccepted,
|
|
5
|
-
isRejected,
|
|
6
|
-
isPending,
|
|
7
6
|
isNotCalculatedPenaltyStatus,
|
|
7
|
+
isPending,
|
|
8
|
+
isRejected,
|
|
8
9
|
stringToSubmissionStatus,
|
|
9
10
|
} from "./submission-status";
|
|
10
11
|
|
package/src/team.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { Team as ITeam, Teams as ITeams } from "@xcpcio/types";
|
|
1
|
+
import type { Team as ITeam, Teams as ITeams, Image } from "@xcpcio/types";
|
|
2
2
|
|
|
3
|
-
import { TeamProblemStatistics } from "./problem";
|
|
3
|
+
import type { TeamProblemStatistics } from "./problem";
|
|
4
|
+
import { calcDict } from "./utils";
|
|
4
5
|
|
|
5
6
|
export class Team {
|
|
6
7
|
id: string;
|
|
7
8
|
name: string;
|
|
9
|
+
|
|
8
10
|
organization: string;
|
|
11
|
+
badge?: Image;
|
|
9
12
|
|
|
10
13
|
group: Array<string>;
|
|
11
14
|
tag: Array<string>;
|
|
@@ -14,7 +17,11 @@ export class Team {
|
|
|
14
17
|
members?: string | Array<string>;
|
|
15
18
|
|
|
16
19
|
rank: number;
|
|
20
|
+
organizationRank: number;
|
|
21
|
+
|
|
17
22
|
solvedProblemNum: number;
|
|
23
|
+
attemptedProblemNum: number;
|
|
24
|
+
|
|
18
25
|
penalty: number;
|
|
19
26
|
|
|
20
27
|
problemStatistics: Array<TeamProblemStatistics>;
|
|
@@ -23,30 +30,45 @@ export class Team {
|
|
|
23
30
|
constructor() {
|
|
24
31
|
this.id = "";
|
|
25
32
|
this.name = "";
|
|
33
|
+
|
|
26
34
|
this.organization = "";
|
|
27
35
|
|
|
28
36
|
this.group = [];
|
|
29
37
|
this.tag = [];
|
|
30
38
|
|
|
31
39
|
this.rank = 0;
|
|
40
|
+
this.organizationRank = -1;
|
|
41
|
+
|
|
32
42
|
this.solvedProblemNum = 0;
|
|
43
|
+
this.attemptedProblemNum = 0;
|
|
44
|
+
|
|
33
45
|
this.penalty = 0;
|
|
34
46
|
|
|
35
47
|
this.problemStatistics = [];
|
|
36
48
|
this.problemStatisticsMap = new Map<string, TeamProblemStatistics>();
|
|
37
49
|
}
|
|
38
50
|
|
|
39
|
-
penaltyToMinute() {
|
|
51
|
+
get penaltyToMinute() {
|
|
40
52
|
return Math.floor(this.penalty / 60);
|
|
41
53
|
}
|
|
42
54
|
|
|
55
|
+
get dict() {
|
|
56
|
+
const attemptedNum = this.attemptedProblemNum;
|
|
57
|
+
const solvedNum = this.solvedProblemNum;
|
|
58
|
+
|
|
59
|
+
return calcDict(attemptedNum, solvedNum);
|
|
60
|
+
}
|
|
61
|
+
|
|
43
62
|
calcSolvedData() {
|
|
44
63
|
this.solvedProblemNum = 0;
|
|
45
64
|
this.penalty = 0;
|
|
65
|
+
this.attemptedProblemNum = 0;
|
|
46
66
|
|
|
47
67
|
for (const p of this.problemStatistics) {
|
|
48
68
|
if (p.isAccepted) {
|
|
49
69
|
this.solvedProblemNum++;
|
|
70
|
+
this.attemptedProblemNum += p.failedCount + 1;
|
|
71
|
+
|
|
50
72
|
this.penalty += p.penalty;
|
|
51
73
|
}
|
|
52
74
|
}
|
|
@@ -80,21 +102,23 @@ export function createTeam(teamJSON: ITeam): Team {
|
|
|
80
102
|
t.name = teamJSON.name ?? teamJSON.team_name ?? "";
|
|
81
103
|
|
|
82
104
|
t.organization = teamJSON.organization ?? "";
|
|
105
|
+
t.badge = teamJSON.badge;
|
|
106
|
+
|
|
83
107
|
t.group = teamJSON.group ?? [];
|
|
84
108
|
t.tag = teamJSON.group ?? [];
|
|
85
109
|
|
|
86
110
|
t.coach = teamJSON.coach;
|
|
87
111
|
t.members = teamJSON.members;
|
|
88
112
|
|
|
89
|
-
if (teamJSON.official === true) {
|
|
113
|
+
if (Boolean(teamJSON.official) === true) {
|
|
90
114
|
t.group.push("official");
|
|
91
115
|
}
|
|
92
116
|
|
|
93
|
-
if (teamJSON.unofficial === true) {
|
|
117
|
+
if (Boolean(teamJSON.unofficial) === true) {
|
|
94
118
|
t.group.push("unofficial");
|
|
95
119
|
}
|
|
96
120
|
|
|
97
|
-
if (teamJSON.girl === true) {
|
|
121
|
+
if (Boolean(teamJSON.girl) === true) {
|
|
98
122
|
t.group.push("girl");
|
|
99
123
|
}
|
|
100
124
|
|
|
@@ -106,7 +130,7 @@ export function createTeam(teamJSON: ITeam): Team {
|
|
|
106
130
|
|
|
107
131
|
export function createTeams(teamsJSON: ITeams): Teams {
|
|
108
132
|
if (Array.isArray(teamsJSON)) {
|
|
109
|
-
return teamsJSON.map(
|
|
133
|
+
return teamsJSON.map(t => createTeam(t));
|
|
110
134
|
} else {
|
|
111
135
|
const teams = Object.entries(teamsJSON).map(([teamId, team]) =>
|
|
112
136
|
createTeam({ ...team, team_id: team.team_id ?? teamId }),
|
package/src/utils/dayjs.ts
CHANGED
|
@@ -1,27 +1,22 @@
|
|
|
1
1
|
import dayjs from "dayjs";
|
|
2
2
|
|
|
3
3
|
import duration from "dayjs/plugin/duration";
|
|
4
|
-
dayjs.extend(duration);
|
|
5
|
-
|
|
6
4
|
import utc from "dayjs/plugin/utc";
|
|
7
|
-
dayjs.extend(utc);
|
|
8
|
-
|
|
9
5
|
import timezone from "dayjs/plugin/timezone";
|
|
10
|
-
dayjs.extend(timezone);
|
|
11
|
-
|
|
12
6
|
import advancedFormat from "dayjs/plugin/advancedFormat";
|
|
13
|
-
dayjs.extend(advancedFormat);
|
|
14
|
-
|
|
15
7
|
import isSameOrBefore from "dayjs/plugin/isSameOrBefore";
|
|
16
|
-
dayjs.extend(isSameOrBefore);
|
|
17
|
-
|
|
18
8
|
import isSameOrAfter from "dayjs/plugin/isSameOrAfter";
|
|
19
|
-
dayjs.extend(isSameOrAfter);
|
|
20
|
-
|
|
21
9
|
import minMax from "dayjs/plugin/minMax";
|
|
10
|
+
import relativeTime from "dayjs/plugin/relativeTime";
|
|
11
|
+
|
|
12
|
+
dayjs.extend(duration);
|
|
13
|
+
dayjs.extend(utc);
|
|
14
|
+
dayjs.extend(timezone);
|
|
15
|
+
dayjs.extend(advancedFormat);
|
|
16
|
+
dayjs.extend(isSameOrBefore);
|
|
17
|
+
dayjs.extend(isSameOrAfter);
|
|
22
18
|
dayjs.extend(minMax);
|
|
23
19
|
|
|
24
|
-
import relativeTime from "dayjs/plugin/relativeTime";
|
|
25
20
|
dayjs.extend(relativeTime);
|
|
26
21
|
|
|
27
22
|
export function createDayJS(time: Date | string | number | undefined = undefined): dayjs.Dayjs {
|
|
@@ -46,7 +41,10 @@ export function getTimestamp(time: number | dayjs.Dayjs): number {
|
|
|
46
41
|
|
|
47
42
|
export function getTimeDiff(seconds: number): string {
|
|
48
43
|
const two = (a: number) => {
|
|
49
|
-
if (a < 10)
|
|
44
|
+
if (a < 10) {
|
|
45
|
+
return `0${a}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
50
48
|
return String(a);
|
|
51
49
|
};
|
|
52
50
|
|
package/src/utils/index.ts
CHANGED