@algoux/standard-ranklist-utils 0.1.1 → 0.2.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/README.md +8 -0
- package/dist/index.cjs +689 -0
- package/dist/index.d.cts +100 -0
- package/dist/index.d.mts +100 -0
- package/dist/index.mjs +669 -0
- package/package.json +26 -10
- package/dist/constants.d.ts +0 -1
- package/dist/constants.js +0 -1
- package/dist/enums.d.ts +0 -4
- package/dist/enums.js +0 -5
- package/dist/formatters.d.ts +0 -35
- package/dist/formatters.js +0 -124
- package/dist/index.d.ts +0 -6
- package/dist/index.js +0 -6
- package/dist/ranklist.d.ts +0 -9
- package/dist/ranklist.js +0 -492
- package/dist/resolvers.d.ts +0 -25
- package/dist/resolvers.js +0 -98
- package/dist/types.d.ts +0 -26
- package/dist/types.js +0 -1
package/README.md
CHANGED
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,689 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var TEXTColor = require('textcolor');
|
|
4
|
+
var bcp47Match = require('bcp-47-match');
|
|
5
|
+
var semver = require('semver');
|
|
6
|
+
var BigNumber = require('bignumber.js');
|
|
7
|
+
|
|
8
|
+
const MIN_REGEN_SUPPORTED_VERSION = "0.3.0";
|
|
9
|
+
|
|
10
|
+
var EnumTheme = /* @__PURE__ */ ((EnumTheme2) => {
|
|
11
|
+
EnumTheme2["light"] = "light";
|
|
12
|
+
EnumTheme2["dark"] = "dark";
|
|
13
|
+
return EnumTheme2;
|
|
14
|
+
})(EnumTheme || {});
|
|
15
|
+
|
|
16
|
+
function formatTimeDuration(time, targetUnit = "ms", fmt = (num) => num) {
|
|
17
|
+
let ms = -1;
|
|
18
|
+
switch (time[1]) {
|
|
19
|
+
case "ms":
|
|
20
|
+
ms = time[0];
|
|
21
|
+
break;
|
|
22
|
+
case "s":
|
|
23
|
+
ms = time[0] * 1e3;
|
|
24
|
+
break;
|
|
25
|
+
case "min":
|
|
26
|
+
ms = time[0] * 1e3 * 60;
|
|
27
|
+
break;
|
|
28
|
+
case "h":
|
|
29
|
+
ms = time[0] * 1e3 * 60 * 60;
|
|
30
|
+
break;
|
|
31
|
+
case "d":
|
|
32
|
+
ms = time[0] * 1e3 * 60 * 60 * 24;
|
|
33
|
+
break;
|
|
34
|
+
default:
|
|
35
|
+
throw new Error(`Invalid source time unit ${time[1]}`);
|
|
36
|
+
}
|
|
37
|
+
switch (targetUnit) {
|
|
38
|
+
case "ms":
|
|
39
|
+
return ms;
|
|
40
|
+
case "s":
|
|
41
|
+
return fmt(ms / 1e3);
|
|
42
|
+
case "min":
|
|
43
|
+
return fmt(ms / 1e3 / 60);
|
|
44
|
+
case "h":
|
|
45
|
+
return fmt(ms / 1e3 / 60 / 60);
|
|
46
|
+
case "d":
|
|
47
|
+
return fmt(ms / 1e3 / 60 / 60 / 24);
|
|
48
|
+
default:
|
|
49
|
+
throw new Error(`Invalid target time unit ${targetUnit}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function preZeroFill(num, size) {
|
|
53
|
+
if (num >= Math.pow(10, size)) {
|
|
54
|
+
return num.toString();
|
|
55
|
+
} else {
|
|
56
|
+
let str = Array(size + 1).join("0") + num;
|
|
57
|
+
return str.slice(str.length - size);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function secToTimeStr(second, options = {}) {
|
|
61
|
+
let sec = second;
|
|
62
|
+
let d = 0;
|
|
63
|
+
const { fillHour = false, showDay = false } = options;
|
|
64
|
+
if (showDay) {
|
|
65
|
+
d = Math.floor(sec / 86400);
|
|
66
|
+
sec %= 86400;
|
|
67
|
+
}
|
|
68
|
+
let h = Math.floor(sec / 3600);
|
|
69
|
+
sec %= 3600;
|
|
70
|
+
let m = Math.floor(sec / 60);
|
|
71
|
+
sec %= 60;
|
|
72
|
+
let s = Math.floor(sec);
|
|
73
|
+
let dayStr = "";
|
|
74
|
+
if (showDay && d >= 1) {
|
|
75
|
+
dayStr = d + "D ";
|
|
76
|
+
}
|
|
77
|
+
if (sec < 0) {
|
|
78
|
+
return "--";
|
|
79
|
+
}
|
|
80
|
+
return dayStr + (fillHour ? preZeroFill(h, 2) : `${h}`) + ":" + preZeroFill(m, 2) + ":" + preZeroFill(s, 2);
|
|
81
|
+
}
|
|
82
|
+
function numberToAlphabet(number) {
|
|
83
|
+
let n = ~~number;
|
|
84
|
+
const radix = 26;
|
|
85
|
+
let cnt = 1;
|
|
86
|
+
let p = radix;
|
|
87
|
+
while (n >= p) {
|
|
88
|
+
n -= p;
|
|
89
|
+
cnt++;
|
|
90
|
+
p *= radix;
|
|
91
|
+
}
|
|
92
|
+
let res = [];
|
|
93
|
+
for (; cnt > 0; cnt--) {
|
|
94
|
+
res.push(String.fromCharCode(n % radix + 65));
|
|
95
|
+
n = Math.trunc(n / radix);
|
|
96
|
+
}
|
|
97
|
+
return res.reverse().join("");
|
|
98
|
+
}
|
|
99
|
+
function alphabetToNumber(alphabet) {
|
|
100
|
+
if (typeof alphabet !== "string" || !alphabet.length) {
|
|
101
|
+
return -1;
|
|
102
|
+
}
|
|
103
|
+
const chars = `${alphabet}`.toUpperCase().split("").reverse();
|
|
104
|
+
const radix = 26;
|
|
105
|
+
let p = 1;
|
|
106
|
+
let res = -1;
|
|
107
|
+
chars.forEach((ch) => {
|
|
108
|
+
res += (ch.charCodeAt(0) - 65) * p + p;
|
|
109
|
+
p *= radix;
|
|
110
|
+
});
|
|
111
|
+
return res;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function resolveText(text) {
|
|
115
|
+
var _a;
|
|
116
|
+
if (text === void 0) {
|
|
117
|
+
return "";
|
|
118
|
+
}
|
|
119
|
+
if (typeof text === "string") {
|
|
120
|
+
return text;
|
|
121
|
+
} else {
|
|
122
|
+
const langs = Object.keys(text).filter((k) => k && k !== "fallback").sort().reverse();
|
|
123
|
+
const userLangs = typeof navigator !== "undefined" && [...navigator.languages] || [];
|
|
124
|
+
const usingLang = bcp47Match.lookup(userLangs, langs) || "fallback";
|
|
125
|
+
return (_a = text[usingLang]) != null ? _a : "";
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function resolveContributor(contributor) {
|
|
129
|
+
if (!contributor) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
let name = "";
|
|
133
|
+
let email;
|
|
134
|
+
let url;
|
|
135
|
+
const words = contributor.split(" ").map((s) => s.trim());
|
|
136
|
+
let index = words.length - 1;
|
|
137
|
+
while (index > 0) {
|
|
138
|
+
const word = words[index];
|
|
139
|
+
if (word.startsWith("<") && word.endsWith(">")) {
|
|
140
|
+
email = word.slice(1, -1);
|
|
141
|
+
index--;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (word.startsWith("(") && word.endsWith(")")) {
|
|
145
|
+
url = word.slice(1, -1);
|
|
146
|
+
index--;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
name = words.slice(0, index + 1).join(" ");
|
|
152
|
+
return { name, email, url };
|
|
153
|
+
}
|
|
154
|
+
function resolveColor(color) {
|
|
155
|
+
if (Array.isArray(color)) {
|
|
156
|
+
return `rgba(${color[0]},${color[1]},${color[2]},${color[3]})`;
|
|
157
|
+
} else if (color) {
|
|
158
|
+
return color;
|
|
159
|
+
}
|
|
160
|
+
return void 0;
|
|
161
|
+
}
|
|
162
|
+
function resolveThemeColor(themeColor) {
|
|
163
|
+
let light = resolveColor(typeof themeColor === "string" ? themeColor : themeColor.light);
|
|
164
|
+
let dark = resolveColor(typeof themeColor === "string" ? themeColor : themeColor.dark);
|
|
165
|
+
return {
|
|
166
|
+
[EnumTheme.light]: light,
|
|
167
|
+
[EnumTheme.dark]: dark
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function resolveStyle(style) {
|
|
171
|
+
const { textColor, backgroundColor } = style;
|
|
172
|
+
let usingTextColor = textColor;
|
|
173
|
+
if (backgroundColor && !textColor) {
|
|
174
|
+
if (typeof backgroundColor === "string") {
|
|
175
|
+
usingTextColor = TEXTColor.findTextColor(backgroundColor);
|
|
176
|
+
} else {
|
|
177
|
+
const { light, dark } = backgroundColor;
|
|
178
|
+
usingTextColor = {
|
|
179
|
+
light: light && TEXTColor.findTextColor(light),
|
|
180
|
+
dark: dark && TEXTColor.findTextColor(dark)
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const textThemeColor = resolveThemeColor(usingTextColor || "");
|
|
185
|
+
const backgroundThemeColor = resolveThemeColor(backgroundColor || "");
|
|
186
|
+
return {
|
|
187
|
+
textColor: textThemeColor,
|
|
188
|
+
backgroundColor: backgroundThemeColor
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function canRegenerateRanklist(ranklist) {
|
|
193
|
+
var _a;
|
|
194
|
+
try {
|
|
195
|
+
if (!semver.gte(ranklist.version, MIN_REGEN_SUPPORTED_VERSION)) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
if (((_a = ranklist.sorter) == null ? void 0 : _a.algorithm) !== "ICPC") {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
} catch (e) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
function getSortedCalculatedRawSolutions(rows) {
|
|
207
|
+
const solutions = [];
|
|
208
|
+
for (const row of rows) {
|
|
209
|
+
const { user, statuses } = row;
|
|
210
|
+
const userId = user.id && `${user.id}` || `${typeof user.name === "string" ? user.name : JSON.stringify(user.name)}`;
|
|
211
|
+
statuses.forEach((status, index) => {
|
|
212
|
+
var _a;
|
|
213
|
+
if (Array.isArray(status.solutions) && status.solutions.length) {
|
|
214
|
+
solutions.push(
|
|
215
|
+
...status.solutions.map(
|
|
216
|
+
(solution) => [userId, index, solution.result, solution.time]
|
|
217
|
+
)
|
|
218
|
+
);
|
|
219
|
+
} else if (status.result && ((_a = status.time) == null ? void 0 : _a[0])) {
|
|
220
|
+
if (status.result === "AC" || status.result === "FB") {
|
|
221
|
+
for (let i = 1; i < (status.tries || 0); i++) {
|
|
222
|
+
solutions.push([userId, index, "RJ", status.time]);
|
|
223
|
+
}
|
|
224
|
+
solutions.push([userId, index, status.result, status.time]);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
return solutions.sort((a, b) => {
|
|
230
|
+
const ta = a[3];
|
|
231
|
+
const tb = b[3];
|
|
232
|
+
const timeComp = ta[1] === tb[1] ? ta[0] - tb[0] : formatTimeDuration(ta) - formatTimeDuration(tb);
|
|
233
|
+
if (timeComp !== 0) {
|
|
234
|
+
return timeComp;
|
|
235
|
+
}
|
|
236
|
+
const resultValue = {
|
|
237
|
+
FB: 998,
|
|
238
|
+
AC: 999,
|
|
239
|
+
"?": 1e3
|
|
240
|
+
};
|
|
241
|
+
const resultA = resultValue[a[2]] || 0;
|
|
242
|
+
const resultB = resultValue[b[2]] || 0;
|
|
243
|
+
return resultA - resultB;
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
function filterSolutionsUntil(solutions, time) {
|
|
247
|
+
const timeValue = formatTimeDuration(time);
|
|
248
|
+
const check = (tetrad) => formatTimeDuration(tetrad[3]) <= timeValue;
|
|
249
|
+
let lastIndex = -1;
|
|
250
|
+
let low = 0;
|
|
251
|
+
let high = solutions.length - 1;
|
|
252
|
+
while (low <= high) {
|
|
253
|
+
const mid = low + high >> 1;
|
|
254
|
+
if (check(solutions[mid])) {
|
|
255
|
+
lastIndex = mid;
|
|
256
|
+
low = mid + 1;
|
|
257
|
+
} else {
|
|
258
|
+
high = mid - 1;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return solutions.slice(0, lastIndex + 1);
|
|
262
|
+
}
|
|
263
|
+
function sortRows(rows) {
|
|
264
|
+
rows.sort((a, b) => {
|
|
265
|
+
if (a.score.value !== b.score.value) {
|
|
266
|
+
return b.score.value - a.score.value;
|
|
267
|
+
}
|
|
268
|
+
return formatTimeDuration(a.score.time) - formatTimeDuration(b.score.time);
|
|
269
|
+
});
|
|
270
|
+
return rows;
|
|
271
|
+
}
|
|
272
|
+
function regenerateRanklistBySolutions(originalRanklist, solutions) {
|
|
273
|
+
var _a;
|
|
274
|
+
if (!canRegenerateRanklist(originalRanklist)) {
|
|
275
|
+
throw new Error("The ranklist is not supported to regenerate");
|
|
276
|
+
}
|
|
277
|
+
const sorterConfig = {
|
|
278
|
+
penalty: [20, "min"],
|
|
279
|
+
noPenaltyResults: ["FB", "AC", "?", "CE", "UKE", null],
|
|
280
|
+
timeRounding: "floor",
|
|
281
|
+
...JSON.parse(JSON.stringify(((_a = originalRanklist.sorter) == null ? void 0 : _a.config) || {}))
|
|
282
|
+
};
|
|
283
|
+
const ranklist = {
|
|
284
|
+
...originalRanklist,
|
|
285
|
+
rows: []
|
|
286
|
+
};
|
|
287
|
+
const rows = [];
|
|
288
|
+
const userRowMap = /* @__PURE__ */ new Map();
|
|
289
|
+
const problemCount = originalRanklist.problems.length;
|
|
290
|
+
originalRanklist.rows.forEach((row) => {
|
|
291
|
+
const userId = row.user.id && `${row.user.id}` || `${typeof row.user.name === "string" ? row.user.name : JSON.stringify(row.user.name)}`;
|
|
292
|
+
userRowMap.set(userId, {
|
|
293
|
+
user: row.user,
|
|
294
|
+
score: {
|
|
295
|
+
value: 0
|
|
296
|
+
},
|
|
297
|
+
statuses: new Array(problemCount).fill(null).map(() => ({ result: null, solutions: [] }))
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
for (const tetrad of solutions) {
|
|
301
|
+
const [userId, problemIndex, result, time] = tetrad;
|
|
302
|
+
let row = userRowMap.get(userId);
|
|
303
|
+
if (!row) {
|
|
304
|
+
console.error(`Invalid user id ${userId} found when regenerating ranklist`);
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
const status = row.statuses[problemIndex];
|
|
308
|
+
status.solutions.push({ result, time });
|
|
309
|
+
}
|
|
310
|
+
const problemAcceptedCount = new Array(problemCount).fill(0);
|
|
311
|
+
const problemSubmittedCount = new Array(problemCount).fill(0);
|
|
312
|
+
for (const row of userRowMap.values()) {
|
|
313
|
+
const { statuses } = row;
|
|
314
|
+
let scoreValue = 0;
|
|
315
|
+
let totalTimeMs = 0;
|
|
316
|
+
for (let i = 0; i < statuses.length; ++i) {
|
|
317
|
+
const status = statuses[i];
|
|
318
|
+
const solutions2 = status.solutions;
|
|
319
|
+
for (const solution of solutions2) {
|
|
320
|
+
if (!solution.result) {
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
if (solution.result === "?") {
|
|
324
|
+
status.result = solution.result;
|
|
325
|
+
status.tries = (status.tries || 0) + 1;
|
|
326
|
+
problemSubmittedCount[i] += 1;
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
if (solution.result === "AC" || solution.result === "FB") {
|
|
330
|
+
status.result = solution.result;
|
|
331
|
+
status.time = solution.time;
|
|
332
|
+
status.tries = (status.tries || 0) + 1;
|
|
333
|
+
problemAcceptedCount[i] += 1;
|
|
334
|
+
problemSubmittedCount[i] += 1;
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
if ((sorterConfig.noPenaltyResults || []).includes(solution.result)) {
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
status.result = "RJ";
|
|
341
|
+
status.tries = (status.tries || 0) + 1;
|
|
342
|
+
problemSubmittedCount[i] += 1;
|
|
343
|
+
}
|
|
344
|
+
if (status.result === "AC" || status.result === "FB") {
|
|
345
|
+
const targetTime = [
|
|
346
|
+
formatTimeDuration(
|
|
347
|
+
status.time,
|
|
348
|
+
sorterConfig.timePrecision || "ms",
|
|
349
|
+
sorterConfig.timeRounding === "ceil" ? Math.ceil : sorterConfig.timeRounding === "round" ? Math.round : Math.floor
|
|
350
|
+
),
|
|
351
|
+
sorterConfig.timePrecision || "ms"
|
|
352
|
+
];
|
|
353
|
+
scoreValue += 1;
|
|
354
|
+
totalTimeMs += formatTimeDuration(targetTime, "ms") + (status.tries - 1) * formatTimeDuration(sorterConfig.penalty, "ms");
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
row.score = {
|
|
358
|
+
value: scoreValue,
|
|
359
|
+
time: [totalTimeMs, "ms"]
|
|
360
|
+
};
|
|
361
|
+
rows.push(row);
|
|
362
|
+
}
|
|
363
|
+
ranklist.rows = sortRows(rows);
|
|
364
|
+
ranklist.problems.forEach((problem, index) => {
|
|
365
|
+
if (!problem.statistics) {
|
|
366
|
+
problem.statistics = {
|
|
367
|
+
accepted: 0,
|
|
368
|
+
submitted: 0
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
problem.statistics.accepted = problemAcceptedCount[index];
|
|
372
|
+
problem.statistics.submitted = problemSubmittedCount[index];
|
|
373
|
+
});
|
|
374
|
+
return ranklist;
|
|
375
|
+
}
|
|
376
|
+
function regenerateRowsByIncrementalSolutions(originalRanklist, solutions) {
|
|
377
|
+
var _a;
|
|
378
|
+
if (!canRegenerateRanklist(originalRanklist)) {
|
|
379
|
+
throw new Error("The ranklist is not supported to regenerate");
|
|
380
|
+
}
|
|
381
|
+
const sorterConfig = {
|
|
382
|
+
penalty: [20, "min"],
|
|
383
|
+
noPenaltyResults: ["FB", "AC", "?", "CE", "UKE", null],
|
|
384
|
+
timeRounding: "floor",
|
|
385
|
+
...JSON.parse(JSON.stringify(((_a = originalRanklist.sorter) == null ? void 0 : _a.config) || {}))
|
|
386
|
+
};
|
|
387
|
+
const userRowIndexMap = /* @__PURE__ */ new Map();
|
|
388
|
+
const rows = [...originalRanklist.rows];
|
|
389
|
+
rows.forEach((row, index) => {
|
|
390
|
+
const userId = row.user.id && `${row.user.id}` || `${typeof row.user.name === "string" ? row.user.name : JSON.stringify(row.user.name)}`;
|
|
391
|
+
userRowIndexMap.set(userId, index);
|
|
392
|
+
});
|
|
393
|
+
const clonedRowMap = /* @__PURE__ */ new Set();
|
|
394
|
+
const clonedRowStatusMap = /* @__PURE__ */ new Set();
|
|
395
|
+
for (const tetrad of solutions) {
|
|
396
|
+
const [userId, problemIndex, result, time] = tetrad;
|
|
397
|
+
let rowIndex = userRowIndexMap.get(userId);
|
|
398
|
+
if (rowIndex === void 0) {
|
|
399
|
+
console.error(`Invalid user id ${userId} found when regenerating ranklist`);
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
402
|
+
let row = rows[rowIndex];
|
|
403
|
+
if (!clonedRowMap.has(userId)) {
|
|
404
|
+
row = { ...row };
|
|
405
|
+
row.score = { ...row.score };
|
|
406
|
+
row.statuses = [...row.statuses];
|
|
407
|
+
rows[rowIndex] = row;
|
|
408
|
+
clonedRowMap.add(userId);
|
|
409
|
+
}
|
|
410
|
+
if (!clonedRowStatusMap.has(`${userId}_${problemIndex}`)) {
|
|
411
|
+
row.statuses[problemIndex] = { ...row.statuses[problemIndex] };
|
|
412
|
+
row.statuses[problemIndex].solutions = [...row.statuses[problemIndex].solutions];
|
|
413
|
+
clonedRowStatusMap.add(`${userId}_${problemIndex}`);
|
|
414
|
+
}
|
|
415
|
+
const status = row.statuses[problemIndex];
|
|
416
|
+
status.solutions.push({ result, time });
|
|
417
|
+
if (status.result === "AC" || status.result === "FB") {
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
if (result === "?") {
|
|
421
|
+
status.result = result;
|
|
422
|
+
status.tries = (status.tries || 0) + 1;
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
if (result === "AC" || result === "FB") {
|
|
426
|
+
status.result = result;
|
|
427
|
+
status.time = time;
|
|
428
|
+
status.tries = (status.tries || 0) + 1;
|
|
429
|
+
row.score.value += 1;
|
|
430
|
+
const targetTime = [
|
|
431
|
+
formatTimeDuration(
|
|
432
|
+
status.time,
|
|
433
|
+
sorterConfig.timePrecision || "ms",
|
|
434
|
+
sorterConfig.timeRounding === "ceil" ? Math.ceil : sorterConfig.timeRounding === "round" ? Math.round : Math.floor
|
|
435
|
+
),
|
|
436
|
+
sorterConfig.timePrecision || "ms"
|
|
437
|
+
];
|
|
438
|
+
const totalTime = formatTimeDuration(row.score.time, "ms") || 0;
|
|
439
|
+
row.score.time = [
|
|
440
|
+
totalTime + formatTimeDuration(targetTime, "ms") + (status.tries - 1) * formatTimeDuration(sorterConfig.penalty, "ms"),
|
|
441
|
+
"ms"
|
|
442
|
+
];
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
if ((sorterConfig.noPenaltyResults || []).includes(result)) {
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
status.result = "RJ";
|
|
449
|
+
status.tries = (status.tries || 0) + 1;
|
|
450
|
+
}
|
|
451
|
+
return sortRows(rows);
|
|
452
|
+
}
|
|
453
|
+
function genSeriesCalcFns(series, rows, ranks, officialRanks) {
|
|
454
|
+
const fallbackSeriesCalcFn = () => ({
|
|
455
|
+
rank: null,
|
|
456
|
+
segmentIndex: null
|
|
457
|
+
});
|
|
458
|
+
const fns = series.map((seriesConfig) => {
|
|
459
|
+
const { rule } = seriesConfig;
|
|
460
|
+
if (!rule) {
|
|
461
|
+
return fallbackSeriesCalcFn;
|
|
462
|
+
}
|
|
463
|
+
const { preset } = rule;
|
|
464
|
+
switch (preset) {
|
|
465
|
+
case "Normal": {
|
|
466
|
+
const options = rule.options;
|
|
467
|
+
return (row, index) => {
|
|
468
|
+
if ((options == null ? void 0 : options.includeOfficialOnly) && row.user.official === false) {
|
|
469
|
+
return {
|
|
470
|
+
rank: null,
|
|
471
|
+
segmentIndex: null
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
return {
|
|
475
|
+
rank: (options == null ? void 0 : options.includeOfficialOnly) ? officialRanks[index] : ranks[index],
|
|
476
|
+
segmentIndex: null
|
|
477
|
+
};
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
case "UniqByUserField": {
|
|
481
|
+
const options = rule.options;
|
|
482
|
+
const field = options == null ? void 0 : options.field;
|
|
483
|
+
const assignedRanksMap = /* @__PURE__ */ new Map();
|
|
484
|
+
const valueSet = /* @__PURE__ */ new Set();
|
|
485
|
+
const stringify = (v) => typeof v === "object" ? JSON.stringify(v) : `${v}`;
|
|
486
|
+
let lastOuterRank = 0;
|
|
487
|
+
let lastRank = 0;
|
|
488
|
+
rows.forEach((row, index) => {
|
|
489
|
+
if (options.includeOfficialOnly && row.user.official === false) {
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
const value = stringify(row.user[field]);
|
|
493
|
+
if (value && !valueSet.has(value)) {
|
|
494
|
+
const outerRank = options.includeOfficialOnly ? officialRanks[index] : ranks[index];
|
|
495
|
+
valueSet.add(value);
|
|
496
|
+
if (outerRank !== lastOuterRank) {
|
|
497
|
+
lastOuterRank = outerRank;
|
|
498
|
+
lastRank = assignedRanksMap.size + 1;
|
|
499
|
+
assignedRanksMap.set(index, lastRank);
|
|
500
|
+
}
|
|
501
|
+
assignedRanksMap.set(index, lastRank);
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
return (row, index) => {
|
|
505
|
+
var _a;
|
|
506
|
+
return {
|
|
507
|
+
rank: (_a = assignedRanksMap.get(index)) != null ? _a : null,
|
|
508
|
+
segmentIndex: null
|
|
509
|
+
};
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
case "ICPC": {
|
|
513
|
+
const options = rule.options;
|
|
514
|
+
let filteredRows = rows.filter((row) => row.user.official === void 0 || row.user.official === true);
|
|
515
|
+
let filteredOfficialRanks = [...officialRanks];
|
|
516
|
+
if (options.filter) {
|
|
517
|
+
if (Array.isArray(options.filter.byUserFields) && options.filter.byUserFields.length) {
|
|
518
|
+
const currentFilteredRows = [];
|
|
519
|
+
filteredOfficialRanks = filteredOfficialRanks.map(() => null);
|
|
520
|
+
let currentOfficialRank2 = 0;
|
|
521
|
+
let currentOfficialRankOld = 0;
|
|
522
|
+
rows.forEach((row, index) => {
|
|
523
|
+
const shouldInclude = options.filter.byUserFields.every((filter) => {
|
|
524
|
+
const { field, rule: rule2 } = filter;
|
|
525
|
+
const value = row.user[field];
|
|
526
|
+
if (value === void 0) {
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
return new RegExp(rule2).test(`${value}`);
|
|
530
|
+
});
|
|
531
|
+
if (shouldInclude) {
|
|
532
|
+
currentFilteredRows.push(row);
|
|
533
|
+
const oldRank = officialRanks[index];
|
|
534
|
+
if (oldRank !== null) {
|
|
535
|
+
if (currentOfficialRankOld !== oldRank) {
|
|
536
|
+
currentOfficialRank2++;
|
|
537
|
+
currentOfficialRankOld = oldRank;
|
|
538
|
+
}
|
|
539
|
+
filteredOfficialRanks[index] = currentOfficialRank2;
|
|
540
|
+
} else {
|
|
541
|
+
filteredOfficialRanks[index] = null;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
filteredRows = currentFilteredRows;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
const usingEndpointRules = [];
|
|
549
|
+
let noTied = false;
|
|
550
|
+
if (options.ratio) {
|
|
551
|
+
const { value, rounding = "ceil", denominator = "all" } = options.ratio;
|
|
552
|
+
const officialRows = filteredRows;
|
|
553
|
+
let total = denominator === "submitted" ? officialRows.filter((row) => !row.statuses.every((s) => s.result === null)).length : officialRows.length;
|
|
554
|
+
const accValues = [];
|
|
555
|
+
for (let i = 0; i < value.length; i++) {
|
|
556
|
+
if (i === 0) {
|
|
557
|
+
accValues[i] = new BigNumber(value[i]);
|
|
558
|
+
} else {
|
|
559
|
+
accValues[i] = accValues[i - 1].plus(new BigNumber(value[i]));
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
const segmentRawEndpoints = accValues.map((v) => v.times(total).toNumber());
|
|
563
|
+
usingEndpointRules.push(
|
|
564
|
+
segmentRawEndpoints.map((v) => {
|
|
565
|
+
return rounding === "floor" ? Math.floor(v) : rounding === "round" ? Math.round(v) : Math.ceil(v);
|
|
566
|
+
})
|
|
567
|
+
);
|
|
568
|
+
if (options.ratio.noTied) {
|
|
569
|
+
noTied = true;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
if (options.count) {
|
|
573
|
+
const { value } = options.count;
|
|
574
|
+
const accValues = [];
|
|
575
|
+
for (let i = 0; i < value.length; i++) {
|
|
576
|
+
accValues[i] = (i > 0 ? accValues[i - 1] : 0) + value[i];
|
|
577
|
+
}
|
|
578
|
+
usingEndpointRules.push(accValues);
|
|
579
|
+
if (options.count.noTied) {
|
|
580
|
+
noTied = true;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
const officialRanksNoTied = [];
|
|
584
|
+
let currentOfficialRank = 0;
|
|
585
|
+
for (let i = 0; i < filteredOfficialRanks.length; i++) {
|
|
586
|
+
officialRanksNoTied.push(filteredOfficialRanks[i] === null ? null : ++currentOfficialRank);
|
|
587
|
+
}
|
|
588
|
+
return (row, index) => {
|
|
589
|
+
if (row.user.official === false || !filteredRows.find((r) => r.user.id === row.user.id)) {
|
|
590
|
+
return {
|
|
591
|
+
rank: null,
|
|
592
|
+
segmentIndex: null
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
const usingSegmentIndex = (seriesConfig.segments || []).findIndex((_, segIndex) => {
|
|
596
|
+
return usingEndpointRules.map((e) => e[segIndex]).every((endpoints) => (noTied ? officialRanksNoTied : filteredOfficialRanks)[index] <= endpoints);
|
|
597
|
+
});
|
|
598
|
+
return {
|
|
599
|
+
rank: filteredOfficialRanks[index],
|
|
600
|
+
segmentIndex: usingSegmentIndex > -1 ? usingSegmentIndex : null
|
|
601
|
+
};
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
default:
|
|
605
|
+
console.warn("Unknown series rule preset\uFF1A", preset);
|
|
606
|
+
return fallbackSeriesCalcFn;
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
return fns;
|
|
610
|
+
}
|
|
611
|
+
function genRowRanks(rows) {
|
|
612
|
+
const compareScoreEqual = (a, b) => {
|
|
613
|
+
if (a.value !== b.value) {
|
|
614
|
+
return false;
|
|
615
|
+
}
|
|
616
|
+
const da = a.time ? formatTimeDuration(a.time) : 0;
|
|
617
|
+
const db = b.time ? formatTimeDuration(b.time) : 0;
|
|
618
|
+
return da === db;
|
|
619
|
+
};
|
|
620
|
+
const genRanks = (rows2) => {
|
|
621
|
+
let ranks2 = new Array(rows2.length).fill(null);
|
|
622
|
+
for (let i = 0; i < rows2.length; ++i) {
|
|
623
|
+
if (i === 0) {
|
|
624
|
+
ranks2[i] = 1;
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
if (compareScoreEqual(rows2[i].score, rows2[i - 1].score)) {
|
|
628
|
+
ranks2[i] = ranks2[i - 1];
|
|
629
|
+
} else {
|
|
630
|
+
ranks2[i] = i + 1;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
return ranks2;
|
|
634
|
+
};
|
|
635
|
+
const ranks = genRanks(rows);
|
|
636
|
+
const officialPartialRows = [];
|
|
637
|
+
const officialIndexBackMap = /* @__PURE__ */ new Map();
|
|
638
|
+
rows.forEach((row, index) => {
|
|
639
|
+
if (row.user.official !== false) {
|
|
640
|
+
officialIndexBackMap.set(index, officialPartialRows.length);
|
|
641
|
+
officialPartialRows.push(row);
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
const officialPartialRanks = genRanks(officialPartialRows);
|
|
645
|
+
const officialRanks = new Array(rows.length).fill(null).map(
|
|
646
|
+
(_, index) => officialIndexBackMap.get(index) === void 0 ? null : officialPartialRanks[officialIndexBackMap.get(index)]
|
|
647
|
+
);
|
|
648
|
+
return {
|
|
649
|
+
ranks,
|
|
650
|
+
officialRanks
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
function convertToStaticRanklist(ranklist) {
|
|
654
|
+
if (!ranklist) {
|
|
655
|
+
return ranklist;
|
|
656
|
+
}
|
|
657
|
+
const { series, rows } = ranklist;
|
|
658
|
+
const rowRanks = genRowRanks(rows);
|
|
659
|
+
const seriesCalcFns = genSeriesCalcFns(series, rows, rowRanks.ranks, rowRanks.officialRanks);
|
|
660
|
+
return {
|
|
661
|
+
...ranklist,
|
|
662
|
+
rows: rows.map((row, index) => {
|
|
663
|
+
return {
|
|
664
|
+
...row,
|
|
665
|
+
rankValues: seriesCalcFns.map((fn) => fn(row, index))
|
|
666
|
+
};
|
|
667
|
+
})
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
exports.EnumTheme = EnumTheme;
|
|
672
|
+
exports.MIN_REGEN_SUPPORTED_VERSION = MIN_REGEN_SUPPORTED_VERSION;
|
|
673
|
+
exports.alphabetToNumber = alphabetToNumber;
|
|
674
|
+
exports.canRegenerateRanklist = canRegenerateRanklist;
|
|
675
|
+
exports.convertToStaticRanklist = convertToStaticRanklist;
|
|
676
|
+
exports.filterSolutionsUntil = filterSolutionsUntil;
|
|
677
|
+
exports.formatTimeDuration = formatTimeDuration;
|
|
678
|
+
exports.getSortedCalculatedRawSolutions = getSortedCalculatedRawSolutions;
|
|
679
|
+
exports.numberToAlphabet = numberToAlphabet;
|
|
680
|
+
exports.preZeroFill = preZeroFill;
|
|
681
|
+
exports.regenerateRanklistBySolutions = regenerateRanklistBySolutions;
|
|
682
|
+
exports.regenerateRowsByIncrementalSolutions = regenerateRowsByIncrementalSolutions;
|
|
683
|
+
exports.resolveColor = resolveColor;
|
|
684
|
+
exports.resolveContributor = resolveContributor;
|
|
685
|
+
exports.resolveStyle = resolveStyle;
|
|
686
|
+
exports.resolveText = resolveText;
|
|
687
|
+
exports.resolveThemeColor = resolveThemeColor;
|
|
688
|
+
exports.secToTimeStr = secToTimeStr;
|
|
689
|
+
exports.sortRows = sortRows;
|