@algoux/standard-ranklist-utils 0.1.1 → 0.2.1

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