@xcpcio/core 0.4.2 → 0.5.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 +193 -66
- package/dist/index.d.ts +29 -5
- package/dist/index.mjs +192 -67
- package/package.json +2 -2
- package/src/contest.ts +12 -4
- package/src/problem.ts +2 -0
- package/src/rank.ts +161 -70
- package/src/submission.ts +4 -0
- package/src/team.ts +82 -2
package/dist/index.cjs
CHANGED
|
@@ -86,6 +86,8 @@ class ProblemStatistics {
|
|
|
86
86
|
this.rejectedNum = 0;
|
|
87
87
|
this.pendingNum = 0;
|
|
88
88
|
this.submittedNum = 0;
|
|
89
|
+
this.attemptedNum = 0;
|
|
90
|
+
this.ignoreNum = 0;
|
|
89
91
|
this.firstSolveSubmissions = [];
|
|
90
92
|
this.lastSolveSubmissions = [];
|
|
91
93
|
}
|
|
@@ -217,19 +219,25 @@ class Contest {
|
|
|
217
219
|
}
|
|
218
220
|
return getTimeDiff(Math.floor(dayjs__default.duration(this.startTime.diff(baseTime)).asSeconds()));
|
|
219
221
|
}
|
|
220
|
-
|
|
222
|
+
getContestElapsedTime(nowTime) {
|
|
221
223
|
let baseTime = createDayJS(nowTime);
|
|
222
224
|
if (baseTime.isAfter(this.endTime)) {
|
|
223
225
|
baseTime = this.endTime;
|
|
224
226
|
}
|
|
225
|
-
|
|
227
|
+
if (baseTime.isBefore(this.startTime)) {
|
|
228
|
+
baseTime = this.startTime;
|
|
229
|
+
}
|
|
230
|
+
return getTimeDiff(Math.floor(dayjs__default.duration(baseTime.diff(this.startTime)).asSeconds()));
|
|
226
231
|
}
|
|
227
|
-
|
|
232
|
+
getContestRemainingTime(nowTime) {
|
|
228
233
|
let baseTime = createDayJS(nowTime);
|
|
229
234
|
if (baseTime.isAfter(this.endTime)) {
|
|
230
235
|
baseTime = this.endTime;
|
|
231
236
|
}
|
|
232
|
-
|
|
237
|
+
if (baseTime.isBefore(this.startTime)) {
|
|
238
|
+
baseTime = this.startTime;
|
|
239
|
+
}
|
|
240
|
+
return getTimeDiff(Math.floor(dayjs__default.duration(this.endTime.diff(baseTime)).asSeconds()));
|
|
233
241
|
}
|
|
234
242
|
getContestProgressRatio(nowTime) {
|
|
235
243
|
const baseTime = createDayJS(nowTime);
|
|
@@ -371,6 +379,13 @@ class RankStatistics {
|
|
|
371
379
|
}
|
|
372
380
|
}
|
|
373
381
|
|
|
382
|
+
class PlaceChartPointData {
|
|
383
|
+
constructor() {
|
|
384
|
+
this.timePoint = 0;
|
|
385
|
+
this.rank = 0;
|
|
386
|
+
this.lastSolvedProblem = null;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
374
389
|
class Team {
|
|
375
390
|
constructor() {
|
|
376
391
|
this.id = "";
|
|
@@ -382,9 +397,26 @@ class Team {
|
|
|
382
397
|
this.organizationRank = -1;
|
|
383
398
|
this.solvedProblemNum = 0;
|
|
384
399
|
this.attemptedProblemNum = 0;
|
|
400
|
+
this.lastSolvedProblem = null;
|
|
401
|
+
this.lastSolvedProblemTimestamp = 0;
|
|
385
402
|
this.penalty = 0;
|
|
386
403
|
this.problemStatistics = [];
|
|
387
404
|
this.problemStatisticsMap = /* @__PURE__ */ new Map();
|
|
405
|
+
this.submissions = [];
|
|
406
|
+
this.placeChartPoints = [];
|
|
407
|
+
}
|
|
408
|
+
reset() {
|
|
409
|
+
this.rank = 0;
|
|
410
|
+
this.organizationRank = -1;
|
|
411
|
+
this.solvedProblemNum = 0;
|
|
412
|
+
this.attemptedProblemNum = 0;
|
|
413
|
+
this.lastSolvedProblem = null;
|
|
414
|
+
this.lastSolvedProblemTimestamp = 0;
|
|
415
|
+
this.penalty = 0;
|
|
416
|
+
this.problemStatistics = [];
|
|
417
|
+
this.problemStatisticsMap = /* @__PURE__ */ new Map();
|
|
418
|
+
this.submissions = [];
|
|
419
|
+
this.placeChartPoints = [];
|
|
388
420
|
}
|
|
389
421
|
get penaltyToMinute() {
|
|
390
422
|
return Math.floor(this.penalty / 60);
|
|
@@ -396,8 +428,8 @@ class Team {
|
|
|
396
428
|
}
|
|
397
429
|
calcSolvedData() {
|
|
398
430
|
this.solvedProblemNum = 0;
|
|
399
|
-
this.penalty = 0;
|
|
400
431
|
this.attemptedProblemNum = 0;
|
|
432
|
+
this.penalty = 0;
|
|
401
433
|
for (const p of this.problemStatistics) {
|
|
402
434
|
if (p.isAccepted) {
|
|
403
435
|
this.solvedProblemNum++;
|
|
@@ -406,6 +438,27 @@ class Team {
|
|
|
406
438
|
}
|
|
407
439
|
}
|
|
408
440
|
}
|
|
441
|
+
isEqualRank(otherTeam) {
|
|
442
|
+
return this.solvedProblemNum === otherTeam.solvedProblemNum && this.penalty === otherTeam.penalty;
|
|
443
|
+
}
|
|
444
|
+
postProcessPlaceChartPoints() {
|
|
445
|
+
if (this.placeChartPoints.length === 0) {
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
const res = [];
|
|
449
|
+
res.push(this.placeChartPoints[0]);
|
|
450
|
+
for (let i = 1; i < this.placeChartPoints.length - 1; i++) {
|
|
451
|
+
const p = this.placeChartPoints[i];
|
|
452
|
+
const preP = res[res.length - 1];
|
|
453
|
+
if (p.rank !== preP.rank || p.lastSolvedProblem !== preP.lastSolvedProblem) {
|
|
454
|
+
res.push(p);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
if (this.placeChartPoints.length > 1) {
|
|
458
|
+
res.push(this.placeChartPoints[this.placeChartPoints.length - 1]);
|
|
459
|
+
}
|
|
460
|
+
this.placeChartPoints = res;
|
|
461
|
+
}
|
|
409
462
|
static compare(lhs, rhs) {
|
|
410
463
|
if (lhs.solvedProblemNum !== rhs.solvedProblemNum) {
|
|
411
464
|
return rhs.solvedProblemNum - lhs.solvedProblemNum;
|
|
@@ -413,6 +466,9 @@ class Team {
|
|
|
413
466
|
if (lhs.penalty !== rhs.penalty) {
|
|
414
467
|
return lhs.penalty - rhs.penalty;
|
|
415
468
|
}
|
|
469
|
+
if (lhs.lastSolvedProblemTimestamp !== rhs.lastSolvedProblemTimestamp) {
|
|
470
|
+
return lhs.lastSolvedProblemTimestamp - rhs.lastSolvedProblemTimestamp;
|
|
471
|
+
}
|
|
416
472
|
if (lhs.name < rhs.name) {
|
|
417
473
|
return -1;
|
|
418
474
|
} else if (lhs.name > rhs.name) {
|
|
@@ -593,6 +649,9 @@ class Submission {
|
|
|
593
649
|
isNotCalculatedPenaltyStatus() {
|
|
594
650
|
return isNotCalculatedPenaltyStatus(this.status);
|
|
595
651
|
}
|
|
652
|
+
get timestampToMinute() {
|
|
653
|
+
return Math.floor(this.timestamp / 60);
|
|
654
|
+
}
|
|
596
655
|
static compare(lhs, rhs) {
|
|
597
656
|
if (lhs.timestamp !== rhs.timestamp) {
|
|
598
657
|
return lhs.timestamp - rhs.timestamp;
|
|
@@ -629,6 +688,21 @@ function createSubmissions(submissionsJSON) {
|
|
|
629
688
|
}
|
|
630
689
|
}
|
|
631
690
|
|
|
691
|
+
class RankOptions {
|
|
692
|
+
constructor() {
|
|
693
|
+
this.enableFilterSubmissionsByTimestamp = false;
|
|
694
|
+
this.width = 0;
|
|
695
|
+
this.timestamp = 0;
|
|
696
|
+
}
|
|
697
|
+
setWidth(width, contest) {
|
|
698
|
+
this.width = width;
|
|
699
|
+
this.timestamp = Math.floor((contest.endTime.unix() - contest.startTime.unix()) * this.width * 1e-4);
|
|
700
|
+
this.enableFilterSubmissionsByTimestamp = true;
|
|
701
|
+
}
|
|
702
|
+
disableFilterSubmissionByTimestamp() {
|
|
703
|
+
this.enableFilterSubmissionsByTimestamp = false;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
632
706
|
class Rank {
|
|
633
707
|
constructor(contest, teams, submissions) {
|
|
634
708
|
this.contest = contest;
|
|
@@ -637,10 +711,12 @@ class Rank {
|
|
|
637
711
|
this.submissions = ___default.cloneDeep(submissions).sort(Submission.compare);
|
|
638
712
|
this.submissionsMap = new Map(this.submissions.map((s) => [s.id, s]));
|
|
639
713
|
this.rankStatistics = new RankStatistics();
|
|
714
|
+
this.options = new RankOptions();
|
|
640
715
|
}
|
|
641
|
-
buildRank(
|
|
716
|
+
buildRank() {
|
|
642
717
|
(() => {
|
|
643
718
|
for (const t of this.teams) {
|
|
719
|
+
t.reset();
|
|
644
720
|
t.problemStatistics = this.contest.problems.map((p) => {
|
|
645
721
|
const ps = new TeamProblemStatistics();
|
|
646
722
|
ps.problem = p;
|
|
@@ -652,76 +728,84 @@ class Rank {
|
|
|
652
728
|
this.contest.problems.forEach((p) => {
|
|
653
729
|
p.statistics.reset();
|
|
654
730
|
});
|
|
655
|
-
|
|
731
|
+
let preSubmissionTimestampToMinute = 0;
|
|
732
|
+
const allSubmissions = this.getSubmissions();
|
|
733
|
+
this.teams.forEach(
|
|
734
|
+
(t) => t.placeChartPoints.push({
|
|
735
|
+
timePoint: 0,
|
|
736
|
+
rank: 1,
|
|
737
|
+
lastSolvedProblem: null
|
|
738
|
+
})
|
|
739
|
+
);
|
|
740
|
+
for (let ix = 0; ix < allSubmissions.length; ix++) {
|
|
741
|
+
const s = allSubmissions[ix];
|
|
656
742
|
const teamId = s.teamId;
|
|
657
743
|
const problemId = s.problemId;
|
|
658
744
|
const team = this.teamsMap.get(teamId);
|
|
659
745
|
const problem = this.contest.problemsMap.get(problemId);
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
if (options?.timestamp !== void 0 && options?.timestamp !== null) {
|
|
664
|
-
if (s.timestamp > options.timestamp) {
|
|
665
|
-
break;
|
|
746
|
+
(() => {
|
|
747
|
+
if (team === void 0 || problem === void 0) {
|
|
748
|
+
return;
|
|
666
749
|
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
}
|
|
675
|
-
if (s.isIgnore || s.isNotCalculatedPenaltyStatus()) {
|
|
676
|
-
problem.statistics.ignoreNum++;
|
|
677
|
-
problemStatistics.ignoreCount++;
|
|
678
|
-
continue;
|
|
679
|
-
}
|
|
680
|
-
problemStatistics.isSubmitted = true;
|
|
681
|
-
problemStatistics.lastSubmitTimestamp = s.timestamp;
|
|
682
|
-
problemStatistics.totalCount++;
|
|
683
|
-
if (s.isAccepted()) {
|
|
684
|
-
problemStatistics.isSolved = true;
|
|
685
|
-
problemStatistics.solvedTimestamp = s.timestamp;
|
|
686
|
-
problem.statistics.acceptedNum++;
|
|
687
|
-
problem.statistics.attemptedNum += problemStatistics.failedCount + 1;
|
|
688
|
-
if (problem.statistics.firstSolveSubmissions.length === 0 || problem.statistics.firstSolveSubmissions[problem.statistics.firstSolveSubmissions.length - 1].timestamp === s.timestamp) {
|
|
689
|
-
problemStatistics.isFirstSolved = true;
|
|
690
|
-
problem.statistics.firstSolveSubmissions.push(s);
|
|
750
|
+
const problemStatistics = team.problemStatisticsMap.get(problemId);
|
|
751
|
+
const submissions = problemStatistics.submissions;
|
|
752
|
+
submissions.push(s);
|
|
753
|
+
team.submissions.push(s);
|
|
754
|
+
problem.statistics.submittedNum++;
|
|
755
|
+
if (problemStatistics.isSolved) {
|
|
756
|
+
return;
|
|
691
757
|
}
|
|
692
|
-
|
|
693
|
-
problem.statistics.
|
|
758
|
+
if (s.isIgnore || s.isNotCalculatedPenaltyStatus()) {
|
|
759
|
+
problem.statistics.ignoreNum++;
|
|
760
|
+
problemStatistics.ignoreCount++;
|
|
761
|
+
return;
|
|
694
762
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
for (const t of this.teams) {
|
|
718
|
-
if (!se.has(t.organization)) {
|
|
719
|
-
se.add(t.organization);
|
|
720
|
-
t.organizationRank = rank;
|
|
721
|
-
rank++;
|
|
763
|
+
problemStatistics.isSubmitted = true;
|
|
764
|
+
problemStatistics.lastSubmitTimestamp = s.timestamp;
|
|
765
|
+
problemStatistics.totalCount++;
|
|
766
|
+
if (s.isAccepted()) {
|
|
767
|
+
problemStatistics.isSolved = true;
|
|
768
|
+
problemStatistics.solvedTimestamp = s.timestamp;
|
|
769
|
+
problem.statistics.acceptedNum++;
|
|
770
|
+
problem.statistics.attemptedNum += problemStatistics.failedCount + 1;
|
|
771
|
+
if (problem.statistics.firstSolveSubmissions.length === 0 || problem.statistics.firstSolveSubmissions[problem.statistics.firstSolveSubmissions.length - 1].timestamp === s.timestamp) {
|
|
772
|
+
problemStatistics.isFirstSolved = true;
|
|
773
|
+
problem.statistics.firstSolveSubmissions.push(s);
|
|
774
|
+
}
|
|
775
|
+
while (problem.statistics.lastSolveSubmissions.length > 0) {
|
|
776
|
+
problem.statistics.lastSolveSubmissions.pop();
|
|
777
|
+
}
|
|
778
|
+
problem.statistics.lastSolveSubmissions.push(s);
|
|
779
|
+
team.lastSolvedProblem = problem;
|
|
780
|
+
team.lastSolvedProblemTimestamp = s.timestamp;
|
|
781
|
+
}
|
|
782
|
+
if (s.isRejected()) {
|
|
783
|
+
problemStatistics.failedCount++;
|
|
784
|
+
problem.statistics.rejectedNum++;
|
|
722
785
|
}
|
|
786
|
+
if (s.isPending()) {
|
|
787
|
+
problemStatistics.pendingCount++;
|
|
788
|
+
problem.statistics.pendingNum++;
|
|
789
|
+
}
|
|
790
|
+
})();
|
|
791
|
+
if (s.timestampToMinute > preSubmissionTimestampToMinute || ix === allSubmissions.length - 1) {
|
|
792
|
+
this.teams.forEach((t) => t.calcSolvedData());
|
|
793
|
+
this.teams.sort(Team.compare);
|
|
794
|
+
this.buildTeamRank();
|
|
795
|
+
this.teams.forEach(
|
|
796
|
+
(t) => t.placeChartPoints.push(
|
|
797
|
+
{
|
|
798
|
+
timePoint: s.timestampToMinute,
|
|
799
|
+
rank: t.rank,
|
|
800
|
+
lastSolvedProblem: t.lastSolvedProblem
|
|
801
|
+
}
|
|
802
|
+
)
|
|
803
|
+
);
|
|
723
804
|
}
|
|
805
|
+
preSubmissionTimestampToMinute = s.timestampToMinute;
|
|
724
806
|
}
|
|
807
|
+
this.teams.forEach((t) => t.postProcessPlaceChartPoints());
|
|
808
|
+
this.buildOrgRank();
|
|
725
809
|
})();
|
|
726
810
|
(() => {
|
|
727
811
|
this.rankStatistics.reset();
|
|
@@ -732,6 +816,47 @@ class Rank {
|
|
|
732
816
|
})();
|
|
733
817
|
return this;
|
|
734
818
|
}
|
|
819
|
+
buildTeamRank() {
|
|
820
|
+
let rank = 1;
|
|
821
|
+
let preTeam = null;
|
|
822
|
+
for (const t of this.teams) {
|
|
823
|
+
t.rank = rank++;
|
|
824
|
+
if (preTeam !== null) {
|
|
825
|
+
if (t.isEqualRank(preTeam)) {
|
|
826
|
+
t.rank = preTeam.rank;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
preTeam = t;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
buildOrgRank() {
|
|
833
|
+
if (!this.contest.organization) {
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
let rank = 1;
|
|
837
|
+
let preTeam = null;
|
|
838
|
+
const se = /* @__PURE__ */ new Set();
|
|
839
|
+
for (const t of this.teams) {
|
|
840
|
+
const org = t.organization;
|
|
841
|
+
if (se.has(org)) {
|
|
842
|
+
continue;
|
|
843
|
+
}
|
|
844
|
+
se.add(org);
|
|
845
|
+
t.organizationRank = rank++;
|
|
846
|
+
if (preTeam !== null) {
|
|
847
|
+
if (t.isEqualRank(preTeam)) {
|
|
848
|
+
t.organizationRank = preTeam.organizationRank;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
preTeam = t;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
getSubmissions() {
|
|
855
|
+
if (this.options.enableFilterSubmissionsByTimestamp === false) {
|
|
856
|
+
return this.submissions;
|
|
857
|
+
}
|
|
858
|
+
return this.submissions.filter((s) => s.timestamp <= this.options.timestamp).sort(Submission.compare);
|
|
859
|
+
}
|
|
735
860
|
}
|
|
736
861
|
|
|
737
862
|
class ResolverOperation {
|
|
@@ -826,9 +951,11 @@ exports.dayjs = dayjs__default;
|
|
|
826
951
|
exports.Contest = Contest;
|
|
827
952
|
exports.ContestIndex = ContestIndex;
|
|
828
953
|
exports.ContestIndexConfig = ContestIndexConfig;
|
|
954
|
+
exports.PlaceChartPointData = PlaceChartPointData;
|
|
829
955
|
exports.Problem = Problem;
|
|
830
956
|
exports.ProblemStatistics = ProblemStatistics;
|
|
831
957
|
exports.Rank = Rank;
|
|
958
|
+
exports.RankOptions = RankOptions;
|
|
832
959
|
exports.RankStatistics = RankStatistics;
|
|
833
960
|
exports.Resolver = Resolver;
|
|
834
961
|
exports.Submission = Submission;
|
package/dist/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ declare class Submission {
|
|
|
14
14
|
isRejected(): boolean;
|
|
15
15
|
isPending(): boolean;
|
|
16
16
|
isNotCalculatedPenaltyStatus(): boolean;
|
|
17
|
+
get timestampToMinute(): number;
|
|
17
18
|
static compare(lhs: Submission, rhs: Submission): number;
|
|
18
19
|
}
|
|
19
20
|
type Submissions = Array<Submission>;
|
|
@@ -101,8 +102,8 @@ declare class Contest {
|
|
|
101
102
|
getContestDuration(timeFormat?: string): string;
|
|
102
103
|
getContestState(nowTime?: Date): ContestState;
|
|
103
104
|
getContestPendingTime(nowTime?: Date): string;
|
|
104
|
-
getContestRemainingTime(nowTime?: Date): string;
|
|
105
105
|
getContestElapsedTime(nowTime?: Date): string;
|
|
106
|
+
getContestRemainingTime(nowTime?: Date): string;
|
|
106
107
|
getContestProgressRatio(nowTime?: Date): number;
|
|
107
108
|
}
|
|
108
109
|
declare function createContest(contestJSON: Contest$1): Contest;
|
|
@@ -135,6 +136,12 @@ declare class RankStatistics {
|
|
|
135
136
|
reset(): void;
|
|
136
137
|
}
|
|
137
138
|
|
|
139
|
+
declare class PlaceChartPointData {
|
|
140
|
+
timePoint: number;
|
|
141
|
+
rank: number;
|
|
142
|
+
lastSolvedProblem: Problem | null;
|
|
143
|
+
constructor();
|
|
144
|
+
}
|
|
138
145
|
declare class Team {
|
|
139
146
|
id: string;
|
|
140
147
|
name: string;
|
|
@@ -148,19 +155,34 @@ declare class Team {
|
|
|
148
155
|
organizationRank: number;
|
|
149
156
|
solvedProblemNum: number;
|
|
150
157
|
attemptedProblemNum: number;
|
|
158
|
+
lastSolvedProblem: Problem | null;
|
|
159
|
+
lastSolvedProblemTimestamp: number;
|
|
151
160
|
penalty: number;
|
|
152
161
|
problemStatistics: Array<TeamProblemStatistics>;
|
|
153
162
|
problemStatisticsMap: Map<string, TeamProblemStatistics>;
|
|
163
|
+
submissions: Submissions;
|
|
164
|
+
placeChartPoints: Array<PlaceChartPointData>;
|
|
154
165
|
constructor();
|
|
166
|
+
reset(): void;
|
|
155
167
|
get penaltyToMinute(): number;
|
|
156
168
|
get dict(): number;
|
|
157
169
|
calcSolvedData(): void;
|
|
170
|
+
isEqualRank(otherTeam: Team): boolean;
|
|
171
|
+
postProcessPlaceChartPoints(): void;
|
|
158
172
|
static compare(lhs: Team, rhs: Team): number;
|
|
159
173
|
}
|
|
160
174
|
type Teams = Array<Team>;
|
|
161
175
|
declare function createTeam(teamJSON: Team$1): Team;
|
|
162
176
|
declare function createTeams(teamsJSON: Teams$1): Teams;
|
|
163
177
|
|
|
178
|
+
declare class RankOptions {
|
|
179
|
+
enableFilterSubmissionsByTimestamp: boolean;
|
|
180
|
+
width: number;
|
|
181
|
+
timestamp: number;
|
|
182
|
+
constructor();
|
|
183
|
+
setWidth(width: number, contest: Contest): void;
|
|
184
|
+
disableFilterSubmissionByTimestamp(): void;
|
|
185
|
+
}
|
|
164
186
|
declare class Rank {
|
|
165
187
|
readonly contest: Contest;
|
|
166
188
|
teams: Teams;
|
|
@@ -168,10 +190,12 @@ declare class Rank {
|
|
|
168
190
|
submissions: Submissions;
|
|
169
191
|
submissionsMap: Map<string, Submission>;
|
|
170
192
|
rankStatistics: RankStatistics;
|
|
193
|
+
options: RankOptions;
|
|
171
194
|
constructor(contest: Contest, teams: Teams, submissions: Submissions);
|
|
172
|
-
buildRank(
|
|
173
|
-
|
|
174
|
-
|
|
195
|
+
buildRank(): this;
|
|
196
|
+
buildTeamRank(): void;
|
|
197
|
+
buildOrgRank(): void;
|
|
198
|
+
getSubmissions(): Submissions;
|
|
175
199
|
}
|
|
176
200
|
|
|
177
201
|
declare class ResolverOperation {
|
|
@@ -198,4 +222,4 @@ declare function isRejected(status: SubmissionStatus): boolean;
|
|
|
198
222
|
declare function isPending(status: SubmissionStatus): boolean;
|
|
199
223
|
declare function isNotCalculatedPenaltyStatus(status: SubmissionStatus): boolean;
|
|
200
224
|
|
|
201
|
-
export { Contest, ContestIndex, ContestIndexConfig, ContestIndexList, Problem, ProblemStatistics, Problems, Rank, RankStatistics, 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 };
|
|
225
|
+
export { Contest, ContestIndex, ContestIndexConfig, ContestIndexList, PlaceChartPointData, Problem, ProblemStatistics, Problems, Rank, RankOptions, RankStatistics, 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
|
@@ -70,6 +70,8 @@ class ProblemStatistics {
|
|
|
70
70
|
this.rejectedNum = 0;
|
|
71
71
|
this.pendingNum = 0;
|
|
72
72
|
this.submittedNum = 0;
|
|
73
|
+
this.attemptedNum = 0;
|
|
74
|
+
this.ignoreNum = 0;
|
|
73
75
|
this.firstSolveSubmissions = [];
|
|
74
76
|
this.lastSolveSubmissions = [];
|
|
75
77
|
}
|
|
@@ -201,19 +203,25 @@ class Contest {
|
|
|
201
203
|
}
|
|
202
204
|
return getTimeDiff(Math.floor(dayjs.duration(this.startTime.diff(baseTime)).asSeconds()));
|
|
203
205
|
}
|
|
204
|
-
|
|
206
|
+
getContestElapsedTime(nowTime) {
|
|
205
207
|
let baseTime = createDayJS(nowTime);
|
|
206
208
|
if (baseTime.isAfter(this.endTime)) {
|
|
207
209
|
baseTime = this.endTime;
|
|
208
210
|
}
|
|
209
|
-
|
|
211
|
+
if (baseTime.isBefore(this.startTime)) {
|
|
212
|
+
baseTime = this.startTime;
|
|
213
|
+
}
|
|
214
|
+
return getTimeDiff(Math.floor(dayjs.duration(baseTime.diff(this.startTime)).asSeconds()));
|
|
210
215
|
}
|
|
211
|
-
|
|
216
|
+
getContestRemainingTime(nowTime) {
|
|
212
217
|
let baseTime = createDayJS(nowTime);
|
|
213
218
|
if (baseTime.isAfter(this.endTime)) {
|
|
214
219
|
baseTime = this.endTime;
|
|
215
220
|
}
|
|
216
|
-
|
|
221
|
+
if (baseTime.isBefore(this.startTime)) {
|
|
222
|
+
baseTime = this.startTime;
|
|
223
|
+
}
|
|
224
|
+
return getTimeDiff(Math.floor(dayjs.duration(this.endTime.diff(baseTime)).asSeconds()));
|
|
217
225
|
}
|
|
218
226
|
getContestProgressRatio(nowTime) {
|
|
219
227
|
const baseTime = createDayJS(nowTime);
|
|
@@ -355,6 +363,13 @@ class RankStatistics {
|
|
|
355
363
|
}
|
|
356
364
|
}
|
|
357
365
|
|
|
366
|
+
class PlaceChartPointData {
|
|
367
|
+
constructor() {
|
|
368
|
+
this.timePoint = 0;
|
|
369
|
+
this.rank = 0;
|
|
370
|
+
this.lastSolvedProblem = null;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
358
373
|
class Team {
|
|
359
374
|
constructor() {
|
|
360
375
|
this.id = "";
|
|
@@ -366,9 +381,26 @@ class Team {
|
|
|
366
381
|
this.organizationRank = -1;
|
|
367
382
|
this.solvedProblemNum = 0;
|
|
368
383
|
this.attemptedProblemNum = 0;
|
|
384
|
+
this.lastSolvedProblem = null;
|
|
385
|
+
this.lastSolvedProblemTimestamp = 0;
|
|
369
386
|
this.penalty = 0;
|
|
370
387
|
this.problemStatistics = [];
|
|
371
388
|
this.problemStatisticsMap = /* @__PURE__ */ new Map();
|
|
389
|
+
this.submissions = [];
|
|
390
|
+
this.placeChartPoints = [];
|
|
391
|
+
}
|
|
392
|
+
reset() {
|
|
393
|
+
this.rank = 0;
|
|
394
|
+
this.organizationRank = -1;
|
|
395
|
+
this.solvedProblemNum = 0;
|
|
396
|
+
this.attemptedProblemNum = 0;
|
|
397
|
+
this.lastSolvedProblem = null;
|
|
398
|
+
this.lastSolvedProblemTimestamp = 0;
|
|
399
|
+
this.penalty = 0;
|
|
400
|
+
this.problemStatistics = [];
|
|
401
|
+
this.problemStatisticsMap = /* @__PURE__ */ new Map();
|
|
402
|
+
this.submissions = [];
|
|
403
|
+
this.placeChartPoints = [];
|
|
372
404
|
}
|
|
373
405
|
get penaltyToMinute() {
|
|
374
406
|
return Math.floor(this.penalty / 60);
|
|
@@ -380,8 +412,8 @@ class Team {
|
|
|
380
412
|
}
|
|
381
413
|
calcSolvedData() {
|
|
382
414
|
this.solvedProblemNum = 0;
|
|
383
|
-
this.penalty = 0;
|
|
384
415
|
this.attemptedProblemNum = 0;
|
|
416
|
+
this.penalty = 0;
|
|
385
417
|
for (const p of this.problemStatistics) {
|
|
386
418
|
if (p.isAccepted) {
|
|
387
419
|
this.solvedProblemNum++;
|
|
@@ -390,6 +422,27 @@ class Team {
|
|
|
390
422
|
}
|
|
391
423
|
}
|
|
392
424
|
}
|
|
425
|
+
isEqualRank(otherTeam) {
|
|
426
|
+
return this.solvedProblemNum === otherTeam.solvedProblemNum && this.penalty === otherTeam.penalty;
|
|
427
|
+
}
|
|
428
|
+
postProcessPlaceChartPoints() {
|
|
429
|
+
if (this.placeChartPoints.length === 0) {
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
const res = [];
|
|
433
|
+
res.push(this.placeChartPoints[0]);
|
|
434
|
+
for (let i = 1; i < this.placeChartPoints.length - 1; i++) {
|
|
435
|
+
const p = this.placeChartPoints[i];
|
|
436
|
+
const preP = res[res.length - 1];
|
|
437
|
+
if (p.rank !== preP.rank || p.lastSolvedProblem !== preP.lastSolvedProblem) {
|
|
438
|
+
res.push(p);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
if (this.placeChartPoints.length > 1) {
|
|
442
|
+
res.push(this.placeChartPoints[this.placeChartPoints.length - 1]);
|
|
443
|
+
}
|
|
444
|
+
this.placeChartPoints = res;
|
|
445
|
+
}
|
|
393
446
|
static compare(lhs, rhs) {
|
|
394
447
|
if (lhs.solvedProblemNum !== rhs.solvedProblemNum) {
|
|
395
448
|
return rhs.solvedProblemNum - lhs.solvedProblemNum;
|
|
@@ -397,6 +450,9 @@ class Team {
|
|
|
397
450
|
if (lhs.penalty !== rhs.penalty) {
|
|
398
451
|
return lhs.penalty - rhs.penalty;
|
|
399
452
|
}
|
|
453
|
+
if (lhs.lastSolvedProblemTimestamp !== rhs.lastSolvedProblemTimestamp) {
|
|
454
|
+
return lhs.lastSolvedProblemTimestamp - rhs.lastSolvedProblemTimestamp;
|
|
455
|
+
}
|
|
400
456
|
if (lhs.name < rhs.name) {
|
|
401
457
|
return -1;
|
|
402
458
|
} else if (lhs.name > rhs.name) {
|
|
@@ -577,6 +633,9 @@ class Submission {
|
|
|
577
633
|
isNotCalculatedPenaltyStatus() {
|
|
578
634
|
return isNotCalculatedPenaltyStatus(this.status);
|
|
579
635
|
}
|
|
636
|
+
get timestampToMinute() {
|
|
637
|
+
return Math.floor(this.timestamp / 60);
|
|
638
|
+
}
|
|
580
639
|
static compare(lhs, rhs) {
|
|
581
640
|
if (lhs.timestamp !== rhs.timestamp) {
|
|
582
641
|
return lhs.timestamp - rhs.timestamp;
|
|
@@ -613,6 +672,21 @@ function createSubmissions(submissionsJSON) {
|
|
|
613
672
|
}
|
|
614
673
|
}
|
|
615
674
|
|
|
675
|
+
class RankOptions {
|
|
676
|
+
constructor() {
|
|
677
|
+
this.enableFilterSubmissionsByTimestamp = false;
|
|
678
|
+
this.width = 0;
|
|
679
|
+
this.timestamp = 0;
|
|
680
|
+
}
|
|
681
|
+
setWidth(width, contest) {
|
|
682
|
+
this.width = width;
|
|
683
|
+
this.timestamp = Math.floor((contest.endTime.unix() - contest.startTime.unix()) * this.width * 1e-4);
|
|
684
|
+
this.enableFilterSubmissionsByTimestamp = true;
|
|
685
|
+
}
|
|
686
|
+
disableFilterSubmissionByTimestamp() {
|
|
687
|
+
this.enableFilterSubmissionsByTimestamp = false;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
616
690
|
class Rank {
|
|
617
691
|
constructor(contest, teams, submissions) {
|
|
618
692
|
this.contest = contest;
|
|
@@ -621,10 +695,12 @@ class Rank {
|
|
|
621
695
|
this.submissions = _.cloneDeep(submissions).sort(Submission.compare);
|
|
622
696
|
this.submissionsMap = new Map(this.submissions.map((s) => [s.id, s]));
|
|
623
697
|
this.rankStatistics = new RankStatistics();
|
|
698
|
+
this.options = new RankOptions();
|
|
624
699
|
}
|
|
625
|
-
buildRank(
|
|
700
|
+
buildRank() {
|
|
626
701
|
(() => {
|
|
627
702
|
for (const t of this.teams) {
|
|
703
|
+
t.reset();
|
|
628
704
|
t.problemStatistics = this.contest.problems.map((p) => {
|
|
629
705
|
const ps = new TeamProblemStatistics();
|
|
630
706
|
ps.problem = p;
|
|
@@ -636,76 +712,84 @@ class Rank {
|
|
|
636
712
|
this.contest.problems.forEach((p) => {
|
|
637
713
|
p.statistics.reset();
|
|
638
714
|
});
|
|
639
|
-
|
|
715
|
+
let preSubmissionTimestampToMinute = 0;
|
|
716
|
+
const allSubmissions = this.getSubmissions();
|
|
717
|
+
this.teams.forEach(
|
|
718
|
+
(t) => t.placeChartPoints.push({
|
|
719
|
+
timePoint: 0,
|
|
720
|
+
rank: 1,
|
|
721
|
+
lastSolvedProblem: null
|
|
722
|
+
})
|
|
723
|
+
);
|
|
724
|
+
for (let ix = 0; ix < allSubmissions.length; ix++) {
|
|
725
|
+
const s = allSubmissions[ix];
|
|
640
726
|
const teamId = s.teamId;
|
|
641
727
|
const problemId = s.problemId;
|
|
642
728
|
const team = this.teamsMap.get(teamId);
|
|
643
729
|
const problem = this.contest.problemsMap.get(problemId);
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
if (options?.timestamp !== void 0 && options?.timestamp !== null) {
|
|
648
|
-
if (s.timestamp > options.timestamp) {
|
|
649
|
-
break;
|
|
730
|
+
(() => {
|
|
731
|
+
if (team === void 0 || problem === void 0) {
|
|
732
|
+
return;
|
|
650
733
|
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
}
|
|
659
|
-
if (s.isIgnore || s.isNotCalculatedPenaltyStatus()) {
|
|
660
|
-
problem.statistics.ignoreNum++;
|
|
661
|
-
problemStatistics.ignoreCount++;
|
|
662
|
-
continue;
|
|
663
|
-
}
|
|
664
|
-
problemStatistics.isSubmitted = true;
|
|
665
|
-
problemStatistics.lastSubmitTimestamp = s.timestamp;
|
|
666
|
-
problemStatistics.totalCount++;
|
|
667
|
-
if (s.isAccepted()) {
|
|
668
|
-
problemStatistics.isSolved = true;
|
|
669
|
-
problemStatistics.solvedTimestamp = s.timestamp;
|
|
670
|
-
problem.statistics.acceptedNum++;
|
|
671
|
-
problem.statistics.attemptedNum += problemStatistics.failedCount + 1;
|
|
672
|
-
if (problem.statistics.firstSolveSubmissions.length === 0 || problem.statistics.firstSolveSubmissions[problem.statistics.firstSolveSubmissions.length - 1].timestamp === s.timestamp) {
|
|
673
|
-
problemStatistics.isFirstSolved = true;
|
|
674
|
-
problem.statistics.firstSolveSubmissions.push(s);
|
|
734
|
+
const problemStatistics = team.problemStatisticsMap.get(problemId);
|
|
735
|
+
const submissions = problemStatistics.submissions;
|
|
736
|
+
submissions.push(s);
|
|
737
|
+
team.submissions.push(s);
|
|
738
|
+
problem.statistics.submittedNum++;
|
|
739
|
+
if (problemStatistics.isSolved) {
|
|
740
|
+
return;
|
|
675
741
|
}
|
|
676
|
-
|
|
677
|
-
problem.statistics.
|
|
742
|
+
if (s.isIgnore || s.isNotCalculatedPenaltyStatus()) {
|
|
743
|
+
problem.statistics.ignoreNum++;
|
|
744
|
+
problemStatistics.ignoreCount++;
|
|
745
|
+
return;
|
|
678
746
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
for (const t of this.teams) {
|
|
702
|
-
if (!se.has(t.organization)) {
|
|
703
|
-
se.add(t.organization);
|
|
704
|
-
t.organizationRank = rank;
|
|
705
|
-
rank++;
|
|
747
|
+
problemStatistics.isSubmitted = true;
|
|
748
|
+
problemStatistics.lastSubmitTimestamp = s.timestamp;
|
|
749
|
+
problemStatistics.totalCount++;
|
|
750
|
+
if (s.isAccepted()) {
|
|
751
|
+
problemStatistics.isSolved = true;
|
|
752
|
+
problemStatistics.solvedTimestamp = s.timestamp;
|
|
753
|
+
problem.statistics.acceptedNum++;
|
|
754
|
+
problem.statistics.attemptedNum += problemStatistics.failedCount + 1;
|
|
755
|
+
if (problem.statistics.firstSolveSubmissions.length === 0 || problem.statistics.firstSolveSubmissions[problem.statistics.firstSolveSubmissions.length - 1].timestamp === s.timestamp) {
|
|
756
|
+
problemStatistics.isFirstSolved = true;
|
|
757
|
+
problem.statistics.firstSolveSubmissions.push(s);
|
|
758
|
+
}
|
|
759
|
+
while (problem.statistics.lastSolveSubmissions.length > 0) {
|
|
760
|
+
problem.statistics.lastSolveSubmissions.pop();
|
|
761
|
+
}
|
|
762
|
+
problem.statistics.lastSolveSubmissions.push(s);
|
|
763
|
+
team.lastSolvedProblem = problem;
|
|
764
|
+
team.lastSolvedProblemTimestamp = s.timestamp;
|
|
765
|
+
}
|
|
766
|
+
if (s.isRejected()) {
|
|
767
|
+
problemStatistics.failedCount++;
|
|
768
|
+
problem.statistics.rejectedNum++;
|
|
706
769
|
}
|
|
770
|
+
if (s.isPending()) {
|
|
771
|
+
problemStatistics.pendingCount++;
|
|
772
|
+
problem.statistics.pendingNum++;
|
|
773
|
+
}
|
|
774
|
+
})();
|
|
775
|
+
if (s.timestampToMinute > preSubmissionTimestampToMinute || ix === allSubmissions.length - 1) {
|
|
776
|
+
this.teams.forEach((t) => t.calcSolvedData());
|
|
777
|
+
this.teams.sort(Team.compare);
|
|
778
|
+
this.buildTeamRank();
|
|
779
|
+
this.teams.forEach(
|
|
780
|
+
(t) => t.placeChartPoints.push(
|
|
781
|
+
{
|
|
782
|
+
timePoint: s.timestampToMinute,
|
|
783
|
+
rank: t.rank,
|
|
784
|
+
lastSolvedProblem: t.lastSolvedProblem
|
|
785
|
+
}
|
|
786
|
+
)
|
|
787
|
+
);
|
|
707
788
|
}
|
|
789
|
+
preSubmissionTimestampToMinute = s.timestampToMinute;
|
|
708
790
|
}
|
|
791
|
+
this.teams.forEach((t) => t.postProcessPlaceChartPoints());
|
|
792
|
+
this.buildOrgRank();
|
|
709
793
|
})();
|
|
710
794
|
(() => {
|
|
711
795
|
this.rankStatistics.reset();
|
|
@@ -716,6 +800,47 @@ class Rank {
|
|
|
716
800
|
})();
|
|
717
801
|
return this;
|
|
718
802
|
}
|
|
803
|
+
buildTeamRank() {
|
|
804
|
+
let rank = 1;
|
|
805
|
+
let preTeam = null;
|
|
806
|
+
for (const t of this.teams) {
|
|
807
|
+
t.rank = rank++;
|
|
808
|
+
if (preTeam !== null) {
|
|
809
|
+
if (t.isEqualRank(preTeam)) {
|
|
810
|
+
t.rank = preTeam.rank;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
preTeam = t;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
buildOrgRank() {
|
|
817
|
+
if (!this.contest.organization) {
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
let rank = 1;
|
|
821
|
+
let preTeam = null;
|
|
822
|
+
const se = /* @__PURE__ */ new Set();
|
|
823
|
+
for (const t of this.teams) {
|
|
824
|
+
const org = t.organization;
|
|
825
|
+
if (se.has(org)) {
|
|
826
|
+
continue;
|
|
827
|
+
}
|
|
828
|
+
se.add(org);
|
|
829
|
+
t.organizationRank = rank++;
|
|
830
|
+
if (preTeam !== null) {
|
|
831
|
+
if (t.isEqualRank(preTeam)) {
|
|
832
|
+
t.organizationRank = preTeam.organizationRank;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
preTeam = t;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
getSubmissions() {
|
|
839
|
+
if (this.options.enableFilterSubmissionsByTimestamp === false) {
|
|
840
|
+
return this.submissions;
|
|
841
|
+
}
|
|
842
|
+
return this.submissions.filter((s) => s.timestamp <= this.options.timestamp).sort(Submission.compare);
|
|
843
|
+
}
|
|
719
844
|
}
|
|
720
845
|
|
|
721
846
|
class ResolverOperation {
|
|
@@ -806,4 +931,4 @@ class Resolver extends Rank {
|
|
|
806
931
|
}
|
|
807
932
|
}
|
|
808
933
|
|
|
809
|
-
export { Contest, ContestIndex, ContestIndexConfig, Problem, ProblemStatistics, Rank, RankStatistics, Resolver, Submission, Team, TeamProblemStatistics, calcDict, createContest, createContestIndex, createContestIndexList, createDayJS, createProblem, createProblems, createProblemsByProblemIds, createSubmission, createSubmissions, createTeam, createTeams, getImageSource, getTimeDiff, getTimestamp, isAccepted, isNotCalculatedPenaltyStatus, isPending, isRejected, stringToSubmissionStatus };
|
|
934
|
+
export { Contest, ContestIndex, ContestIndexConfig, PlaceChartPointData, Problem, ProblemStatistics, Rank, RankOptions, RankStatistics, 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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xcpcio/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "XCPCIO Core",
|
|
5
5
|
"author": "Dup4 <lyuzhi.pan@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"dayjs": "^1.11.8",
|
|
44
44
|
"lodash": "^4.17.21",
|
|
45
|
-
"@xcpcio/types": "0.
|
|
45
|
+
"@xcpcio/types": "0.5.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@babel/types": "^7.22.4",
|
package/src/contest.ts
CHANGED
|
@@ -89,22 +89,30 @@ export class Contest {
|
|
|
89
89
|
return getTimeDiff(Math.floor(dayjs.duration(this.startTime.diff(baseTime)).asSeconds()));
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
getContestElapsedTime(nowTime?: Date): string {
|
|
93
93
|
let baseTime = createDayJS(nowTime);
|
|
94
94
|
if (baseTime.isAfter(this.endTime)) {
|
|
95
95
|
baseTime = this.endTime;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
if (baseTime.isBefore(this.startTime)) {
|
|
99
|
+
baseTime = this.startTime;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return getTimeDiff(Math.floor(dayjs.duration(baseTime.diff(this.startTime)).asSeconds()));
|
|
99
103
|
}
|
|
100
104
|
|
|
101
|
-
|
|
105
|
+
getContestRemainingTime(nowTime?: Date): string {
|
|
102
106
|
let baseTime = createDayJS(nowTime);
|
|
103
107
|
if (baseTime.isAfter(this.endTime)) {
|
|
104
108
|
baseTime = this.endTime;
|
|
105
109
|
}
|
|
106
110
|
|
|
107
|
-
|
|
111
|
+
if (baseTime.isBefore(this.startTime)) {
|
|
112
|
+
baseTime = this.startTime;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return getTimeDiff(Math.floor(dayjs.duration(this.endTime.diff(baseTime)).asSeconds()));
|
|
108
116
|
}
|
|
109
117
|
|
|
110
118
|
getContestProgressRatio(nowTime?: Date): number {
|
package/src/problem.ts
CHANGED
package/src/rank.ts
CHANGED
|
@@ -8,6 +8,28 @@ import { Submission } from "./submission";
|
|
|
8
8
|
import { TeamProblemStatistics } from "./problem";
|
|
9
9
|
import { RankStatistics } from "./rank-statistics";
|
|
10
10
|
|
|
11
|
+
export class RankOptions {
|
|
12
|
+
enableFilterSubmissionsByTimestamp: boolean;
|
|
13
|
+
width: number;
|
|
14
|
+
timestamp: number;
|
|
15
|
+
|
|
16
|
+
constructor() {
|
|
17
|
+
this.enableFilterSubmissionsByTimestamp = false;
|
|
18
|
+
this.width = 0;
|
|
19
|
+
this.timestamp = 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
setWidth(width: number, contest: Contest) {
|
|
23
|
+
this.width = width;
|
|
24
|
+
this.timestamp = Math.floor((contest.endTime.unix() - contest.startTime.unix()) * this.width * 0.0001);
|
|
25
|
+
this.enableFilterSubmissionsByTimestamp = true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
disableFilterSubmissionByTimestamp() {
|
|
29
|
+
this.enableFilterSubmissionsByTimestamp = false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
11
33
|
export class Rank {
|
|
12
34
|
readonly contest: Contest;
|
|
13
35
|
|
|
@@ -19,6 +41,8 @@ export class Rank {
|
|
|
19
41
|
|
|
20
42
|
rankStatistics: RankStatistics;
|
|
21
43
|
|
|
44
|
+
options: RankOptions;
|
|
45
|
+
|
|
22
46
|
constructor(contest: Contest, teams: Teams, submissions: Submissions) {
|
|
23
47
|
this.contest = contest;
|
|
24
48
|
|
|
@@ -29,11 +53,15 @@ export class Rank {
|
|
|
29
53
|
this.submissionsMap = new Map(this.submissions.map(s => [s.id, s]));
|
|
30
54
|
|
|
31
55
|
this.rankStatistics = new RankStatistics();
|
|
56
|
+
|
|
57
|
+
this.options = new RankOptions();
|
|
32
58
|
}
|
|
33
59
|
|
|
34
|
-
buildRank(
|
|
60
|
+
buildRank() {
|
|
35
61
|
(() => {
|
|
36
62
|
for (const t of this.teams) {
|
|
63
|
+
t.reset();
|
|
64
|
+
|
|
37
65
|
t.problemStatistics = this.contest.problems.map((p) => {
|
|
38
66
|
const ps = new TeamProblemStatistics();
|
|
39
67
|
ps.problem = p;
|
|
@@ -49,104 +77,114 @@ export class Rank {
|
|
|
49
77
|
p.statistics.reset();
|
|
50
78
|
});
|
|
51
79
|
|
|
52
|
-
|
|
80
|
+
let preSubmissionTimestampToMinute = 0;
|
|
81
|
+
const allSubmissions = this.getSubmissions();
|
|
82
|
+
|
|
83
|
+
this.teams.forEach(t =>
|
|
84
|
+
t.placeChartPoints.push({
|
|
85
|
+
timePoint: 0,
|
|
86
|
+
rank: 1,
|
|
87
|
+
lastSolvedProblem: null,
|
|
88
|
+
}),
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
for (let ix = 0; ix < allSubmissions.length; ix++) {
|
|
92
|
+
const s = allSubmissions[ix];
|
|
93
|
+
|
|
53
94
|
const teamId = s.teamId;
|
|
54
95
|
const problemId = s.problemId;
|
|
55
96
|
const team = this.teamsMap.get(teamId);
|
|
56
97
|
const problem = this.contest.problemsMap.get(problemId);
|
|
57
98
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
99
|
+
(() => {
|
|
100
|
+
if (team === undefined || problem === undefined) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const problemStatistics = team.problemStatisticsMap.get(problemId) as TeamProblemStatistics;
|
|
105
|
+
const submissions = problemStatistics.submissions;
|
|
61
106
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
107
|
+
submissions.push(s);
|
|
108
|
+
team.submissions.push(s);
|
|
109
|
+
problem.statistics.submittedNum++;
|
|
110
|
+
|
|
111
|
+
if (problemStatistics.isSolved) {
|
|
112
|
+
return;
|
|
65
113
|
}
|
|
66
|
-
}
|
|
67
114
|
|
|
68
|
-
|
|
69
|
-
|
|
115
|
+
if (s.isIgnore || s.isNotCalculatedPenaltyStatus()) {
|
|
116
|
+
problem.statistics.ignoreNum++;
|
|
117
|
+
problemStatistics.ignoreCount++;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
70
120
|
|
|
71
|
-
|
|
72
|
-
|
|
121
|
+
problemStatistics.isSubmitted = true;
|
|
122
|
+
problemStatistics.lastSubmitTimestamp = s.timestamp;
|
|
123
|
+
problemStatistics.totalCount++;
|
|
73
124
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
125
|
+
if (s.isAccepted()) {
|
|
126
|
+
problemStatistics.isSolved = true;
|
|
127
|
+
problemStatistics.solvedTimestamp = s.timestamp;
|
|
77
128
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
problemStatistics.ignoreCount++;
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
129
|
+
problem.statistics.acceptedNum++;
|
|
130
|
+
problem.statistics.attemptedNum += problemStatistics.failedCount + 1;
|
|
83
131
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
132
|
+
if (
|
|
133
|
+
problem.statistics.firstSolveSubmissions.length === 0
|
|
134
|
+
|| problem.statistics.firstSolveSubmissions[problem.statistics.firstSolveSubmissions.length - 1].timestamp === s.timestamp
|
|
135
|
+
) {
|
|
136
|
+
problemStatistics.isFirstSolved = true;
|
|
137
|
+
problem.statistics.firstSolveSubmissions.push(s);
|
|
138
|
+
}
|
|
87
139
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
140
|
+
while (problem.statistics.lastSolveSubmissions.length > 0) {
|
|
141
|
+
problem.statistics.lastSolveSubmissions.pop();
|
|
142
|
+
}
|
|
91
143
|
|
|
92
|
-
|
|
93
|
-
problem.statistics.attemptedNum += problemStatistics.failedCount + 1;
|
|
144
|
+
problem.statistics.lastSolveSubmissions.push(s);
|
|
94
145
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|| problem.statistics.firstSolveSubmissions[problem.statistics.firstSolveSubmissions.length - 1].timestamp === s.timestamp
|
|
98
|
-
) {
|
|
99
|
-
problemStatistics.isFirstSolved = true;
|
|
100
|
-
problem.statistics.firstSolveSubmissions.push(s);
|
|
146
|
+
team.lastSolvedProblem = problem;
|
|
147
|
+
team.lastSolvedProblemTimestamp = s.timestamp;
|
|
101
148
|
}
|
|
102
149
|
|
|
103
|
-
|
|
104
|
-
|
|
150
|
+
if (s.isRejected()) {
|
|
151
|
+
problemStatistics.failedCount++;
|
|
152
|
+
problem.statistics.rejectedNum++;
|
|
105
153
|
}
|
|
106
154
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
155
|
+
if (s.isPending()) {
|
|
156
|
+
problemStatistics.pendingCount++;
|
|
157
|
+
problem.statistics.pendingNum++;
|
|
158
|
+
}
|
|
159
|
+
})();
|
|
160
|
+
|
|
161
|
+
if (s.timestampToMinute > preSubmissionTimestampToMinute || ix === allSubmissions.length - 1) {
|
|
162
|
+
this.teams.forEach(t => t.calcSolvedData());
|
|
163
|
+
this.teams.sort(Team.compare);
|
|
164
|
+
this.buildTeamRank();
|
|
165
|
+
|
|
166
|
+
this.teams.forEach(t =>
|
|
167
|
+
t.placeChartPoints.push(
|
|
168
|
+
{
|
|
169
|
+
timePoint: s.timestampToMinute,
|
|
170
|
+
rank: t.rank,
|
|
171
|
+
lastSolvedProblem: t.lastSolvedProblem,
|
|
172
|
+
},
|
|
173
|
+
),
|
|
174
|
+
);
|
|
118
175
|
}
|
|
119
|
-
}
|
|
120
176
|
|
|
121
|
-
|
|
122
|
-
this.teams.sort(Team.compare);
|
|
123
|
-
|
|
124
|
-
{
|
|
125
|
-
let rank = 1;
|
|
126
|
-
for (const t of this.teams) {
|
|
127
|
-
t.rank = rank++;
|
|
128
|
-
}
|
|
177
|
+
preSubmissionTimestampToMinute = s.timestampToMinute;
|
|
129
178
|
}
|
|
130
179
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const se = new Set<string>();
|
|
134
|
-
|
|
135
|
-
for (const t of this.teams) {
|
|
136
|
-
if (!se.has(t.organization)) {
|
|
137
|
-
se.add(t.organization);
|
|
138
|
-
t.organizationRank = rank;
|
|
139
|
-
rank++;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
180
|
+
this.teams.forEach(t => t.postProcessPlaceChartPoints());
|
|
181
|
+
this.buildOrgRank();
|
|
143
182
|
})();
|
|
144
183
|
|
|
145
184
|
(() => {
|
|
146
185
|
this.rankStatistics.reset();
|
|
147
186
|
|
|
148
187
|
this.rankStatistics.teamSolvedNum = Array(this.contest.problems.length + 1).fill(0);
|
|
149
|
-
|
|
150
188
|
for (const t of this.teams) {
|
|
151
189
|
this.rankStatistics.teamSolvedNum[t.solvedProblemNum]++;
|
|
152
190
|
}
|
|
@@ -154,4 +192,57 @@ export class Rank {
|
|
|
154
192
|
|
|
155
193
|
return this;
|
|
156
194
|
}
|
|
195
|
+
|
|
196
|
+
buildTeamRank() {
|
|
197
|
+
let rank = 1;
|
|
198
|
+
let preTeam = null;
|
|
199
|
+
for (const t of this.teams) {
|
|
200
|
+
t.rank = rank++;
|
|
201
|
+
|
|
202
|
+
if (preTeam !== null) {
|
|
203
|
+
if (t.isEqualRank(preTeam)) {
|
|
204
|
+
t.rank = preTeam.rank;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
preTeam = t;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
buildOrgRank() {
|
|
213
|
+
if (!this.contest.organization) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
let rank = 1;
|
|
218
|
+
let preTeam = null;
|
|
219
|
+
|
|
220
|
+
const se = new Set<string>();
|
|
221
|
+
|
|
222
|
+
for (const t of this.teams) {
|
|
223
|
+
const org = t.organization;
|
|
224
|
+
if (se.has(org)) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
se.add(org);
|
|
229
|
+
t.organizationRank = rank++;
|
|
230
|
+
|
|
231
|
+
if (preTeam !== null) {
|
|
232
|
+
if (t.isEqualRank(preTeam)) {
|
|
233
|
+
t.organizationRank = preTeam.organizationRank;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
preTeam = t;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
getSubmissions() {
|
|
242
|
+
if (this.options.enableFilterSubmissionsByTimestamp === false) {
|
|
243
|
+
return this.submissions;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return this.submissions.filter(s => s.timestamp <= this.options.timestamp).sort(Submission.compare);
|
|
247
|
+
}
|
|
157
248
|
}
|
package/src/submission.ts
CHANGED
|
@@ -41,6 +41,10 @@ export class Submission {
|
|
|
41
41
|
return isNotCalculatedPenaltyStatus(this.status);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
get timestampToMinute() {
|
|
45
|
+
return Math.floor(this.timestamp / 60);
|
|
46
|
+
}
|
|
47
|
+
|
|
44
48
|
static compare(lhs: Submission, rhs: Submission): number {
|
|
45
49
|
if (lhs.timestamp !== rhs.timestamp) {
|
|
46
50
|
return lhs.timestamp - rhs.timestamp;
|
package/src/team.ts
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
import type { Team as ITeam, Teams as ITeams, Image } from "@xcpcio/types";
|
|
2
2
|
|
|
3
|
-
import type { TeamProblemStatistics } from "./problem";
|
|
3
|
+
import type { Problem, TeamProblemStatistics } from "./problem";
|
|
4
4
|
import { calcDict } from "./utils";
|
|
5
|
+
import type { Submissions } from "./submission";
|
|
6
|
+
|
|
7
|
+
export class PlaceChartPointData {
|
|
8
|
+
timePoint: number;
|
|
9
|
+
rank: number;
|
|
10
|
+
lastSolvedProblem: Problem | null;
|
|
11
|
+
|
|
12
|
+
constructor() {
|
|
13
|
+
this.timePoint = 0;
|
|
14
|
+
this.rank = 0;
|
|
15
|
+
this.lastSolvedProblem = null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
5
18
|
|
|
6
19
|
export class Team {
|
|
7
20
|
id: string;
|
|
@@ -22,11 +35,18 @@ export class Team {
|
|
|
22
35
|
solvedProblemNum: number;
|
|
23
36
|
attemptedProblemNum: number;
|
|
24
37
|
|
|
38
|
+
lastSolvedProblem: Problem | null;
|
|
39
|
+
lastSolvedProblemTimestamp: number;
|
|
40
|
+
|
|
25
41
|
penalty: number;
|
|
26
42
|
|
|
27
43
|
problemStatistics: Array<TeamProblemStatistics>;
|
|
28
44
|
problemStatisticsMap: Map<string, TeamProblemStatistics>;
|
|
29
45
|
|
|
46
|
+
submissions: Submissions;
|
|
47
|
+
|
|
48
|
+
placeChartPoints: Array<PlaceChartPointData>;
|
|
49
|
+
|
|
30
50
|
constructor() {
|
|
31
51
|
this.id = "";
|
|
32
52
|
this.name = "";
|
|
@@ -42,10 +62,37 @@ export class Team {
|
|
|
42
62
|
this.solvedProblemNum = 0;
|
|
43
63
|
this.attemptedProblemNum = 0;
|
|
44
64
|
|
|
65
|
+
this.lastSolvedProblem = null;
|
|
66
|
+
this.lastSolvedProblemTimestamp = 0;
|
|
67
|
+
|
|
68
|
+
this.penalty = 0;
|
|
69
|
+
|
|
70
|
+
this.problemStatistics = [];
|
|
71
|
+
this.problemStatisticsMap = new Map<string, TeamProblemStatistics>();
|
|
72
|
+
|
|
73
|
+
this.submissions = [];
|
|
74
|
+
|
|
75
|
+
this.placeChartPoints = [];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
reset() {
|
|
79
|
+
this.rank = 0;
|
|
80
|
+
this.organizationRank = -1;
|
|
81
|
+
|
|
82
|
+
this.solvedProblemNum = 0;
|
|
83
|
+
this.attemptedProblemNum = 0;
|
|
84
|
+
|
|
85
|
+
this.lastSolvedProblem = null;
|
|
86
|
+
this.lastSolvedProblemTimestamp = 0;
|
|
87
|
+
|
|
45
88
|
this.penalty = 0;
|
|
46
89
|
|
|
47
90
|
this.problemStatistics = [];
|
|
48
91
|
this.problemStatisticsMap = new Map<string, TeamProblemStatistics>();
|
|
92
|
+
|
|
93
|
+
this.submissions = [];
|
|
94
|
+
|
|
95
|
+
this.placeChartPoints = [];
|
|
49
96
|
}
|
|
50
97
|
|
|
51
98
|
get penaltyToMinute() {
|
|
@@ -61,9 +108,10 @@ export class Team {
|
|
|
61
108
|
|
|
62
109
|
calcSolvedData() {
|
|
63
110
|
this.solvedProblemNum = 0;
|
|
64
|
-
this.penalty = 0;
|
|
65
111
|
this.attemptedProblemNum = 0;
|
|
66
112
|
|
|
113
|
+
this.penalty = 0;
|
|
114
|
+
|
|
67
115
|
for (const p of this.problemStatistics) {
|
|
68
116
|
if (p.isAccepted) {
|
|
69
117
|
this.solvedProblemNum++;
|
|
@@ -74,6 +122,34 @@ export class Team {
|
|
|
74
122
|
}
|
|
75
123
|
}
|
|
76
124
|
|
|
125
|
+
isEqualRank(otherTeam: Team) {
|
|
126
|
+
return this.solvedProblemNum === otherTeam.solvedProblemNum && this.penalty === otherTeam.penalty;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
postProcessPlaceChartPoints() {
|
|
130
|
+
if (this.placeChartPoints.length === 0) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const res = [];
|
|
135
|
+
res.push(this.placeChartPoints[0]);
|
|
136
|
+
|
|
137
|
+
for (let i = 1; i < this.placeChartPoints.length - 1; i++) {
|
|
138
|
+
const p = this.placeChartPoints[i];
|
|
139
|
+
const preP = res[res.length - 1];
|
|
140
|
+
|
|
141
|
+
if (p.rank !== preP.rank || p.lastSolvedProblem !== preP.lastSolvedProblem) {
|
|
142
|
+
res.push(p);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (this.placeChartPoints.length > 1) {
|
|
147
|
+
res.push(this.placeChartPoints[this.placeChartPoints.length - 1]);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
this.placeChartPoints = res;
|
|
151
|
+
}
|
|
152
|
+
|
|
77
153
|
static compare(lhs: Team, rhs: Team): number {
|
|
78
154
|
if (lhs.solvedProblemNum !== rhs.solvedProblemNum) {
|
|
79
155
|
return rhs.solvedProblemNum - lhs.solvedProblemNum;
|
|
@@ -83,6 +159,10 @@ export class Team {
|
|
|
83
159
|
return lhs.penalty - rhs.penalty;
|
|
84
160
|
}
|
|
85
161
|
|
|
162
|
+
if (lhs.lastSolvedProblemTimestamp !== rhs.lastSolvedProblemTimestamp) {
|
|
163
|
+
return lhs.lastSolvedProblemTimestamp - rhs.lastSolvedProblemTimestamp;
|
|
164
|
+
}
|
|
165
|
+
|
|
86
166
|
if (lhs.name < rhs.name) {
|
|
87
167
|
return -1;
|
|
88
168
|
} else if (lhs.name > rhs.name) {
|