Skip to content

Commit a5b29c0

Browse files
authored
Merge pull request #1528 from AtCoder-NoviSteps/#1526
[WIP] :docs: Improve contest labels (#1526)
2 parents 6b847fe + 0fb8e0b commit a5b29c0

File tree

4 files changed

+154
-492
lines changed

4 files changed

+154
-492
lines changed

src/lib/utils/contest.ts

Lines changed: 102 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ const AGC_LIKE: ContestPrefix = {
108108
} as const;
109109
const agcLikePrefixes = getContestPrefixes(AGC_LIKE);
110110

111-
// HACK: As of early November 2024, only UTPC is included.
111+
// HACK: As of November 2024, UTPC, TTPC and TUPC are included.
112112
// More university contests may be added in the future.
113113
/**
114114
* Maps university contest ID prefixes to their display names.
@@ -190,7 +190,7 @@ export function getContestPrefixes(contestPrefixes: Record<string, string>) {
190190
}
191191

192192
/**
193-
* Contest type priorities (0 = Highest, 19 = Lowest)
193+
* Contest type priorities (0 = Highest, 20 = Lowest)
194194
*
195195
* Priority assignment rationale:
196196
* - Educational contests (0-10): ABS, ABC, APG4B, etc.
@@ -240,54 +240,122 @@ export function getContestPriority(contestId: string): number {
240240
}
241241
}
242242

243-
export const getContestNameLabel = (contest_id: string) => {
244-
if (contest_id === 'APG4b' || contest_id === 'APG4bPython') {
245-
return contest_id;
243+
/**
244+
* Regular expression to match contest codes.
245+
*
246+
* This regex matches strings that start with one of the following prefixes:
247+
* - "abc"
248+
* - "arc"
249+
* - "agc"
250+
*
251+
* followed by exactly three digits. The matching is case-insensitive.
252+
*
253+
* Example matches:
254+
* - "abc376"
255+
* - "ARC128"
256+
* - "agc045"
257+
*
258+
* Example non-matches:
259+
* - "xyz123"
260+
* - "abc12"
261+
* - "abc1234"
262+
*/
263+
const regexForAxc = /^(abc|arc|agc)(\d{3})/i;
264+
265+
/**
266+
* Regular expression to match AtCoder University contest identifiers.
267+
*
268+
* The pattern matches strings that:
269+
* - Start with either "ut", "tt", or "tu"
270+
* - Followed by "pc"
271+
* - End with exactly year (four digits)
272+
*
273+
* Example matches:
274+
* - "utpc2014"
275+
* - "ttpc2022"
276+
* - "tupc2023"
277+
*/
278+
const regexForAtCoderUniversity = /^(ut|tt|tu)(pc)(\d{4})/i;
279+
280+
export const getContestNameLabel = (contestId: string) => {
281+
// AtCoder
282+
if (regexForAxc.exec(contestId)) {
283+
return contestId.replace(
284+
regexForAxc,
285+
(_, contestType, contestNumber) => `${contestType.toUpperCase()} ${contestNumber}`,
286+
);
246287
}
247288

248-
if (contest_id === 'typical90') {
289+
if (contestId === 'APG4b' || contestId === 'APG4bPython') {
290+
return contestId;
291+
}
292+
293+
if (contestId === 'typical90') {
249294
return '競プロ典型 90 問';
250295
}
251296

252-
if (contest_id === 'dp') {
297+
if (contestId === 'dp') {
253298
return 'EDPC';
254299
}
255300

256-
if (contest_id === 'tdpc') {
301+
if (contestId === 'tdpc') {
257302
return 'TDPC';
258303
}
259304

260-
if (contest_id === 'practice2') {
305+
if (contestId === 'practice2') {
261306
return 'ACL Practice';
262307
}
263308

264-
if (contest_id === 'tessoku-book') {
309+
if (contestId === 'tessoku-book') {
265310
return '競技プログラミングの鉄則';
266311
}
267312

268-
if (contest_id === 'math-and-algorithm') {
313+
if (contestId === 'math-and-algorithm') {
269314
return 'アルゴリズムと数学';
270315
}
271316

272-
if (contest_id.startsWith('chokudai_S')) {
273-
return contest_id.replace('chokudai_S', 'Chokudai SpeedRun ');
317+
if (regexForAtCoderUniversity.exec(contestId)) {
318+
return getAtCoderUniversityContestLabel(contestId);
274319
}
275320

276-
if (aojCoursePrefixes.has(contest_id)) {
321+
if (contestId.startsWith('chokudai_S')) {
322+
return contestId.replace('chokudai_S', 'Chokudai SpeedRun ');
323+
}
324+
325+
// AIZU ONLINE JUDGE
326+
if (aojCoursePrefixes.has(contestId)) {
277327
return 'AOJ Courses';
278328
}
279329

280-
if (contest_id.startsWith('PCK')) {
281-
return getAojChallengeLabel(PCK_TRANSLATIONS, contest_id);
330+
if (contestId.startsWith('PCK')) {
331+
return getAojChallengeLabel(PCK_TRANSLATIONS, contestId);
282332
}
283333

284-
if (contest_id.startsWith('JAG')) {
285-
return getAojChallengeLabel(JAG_TRANSLATIONS, contest_id);
334+
if (contestId.startsWith('JAG')) {
335+
return getAojChallengeLabel(JAG_TRANSLATIONS, contestId);
286336
}
287337

288-
return contest_id.toUpperCase();
338+
return contestId.toUpperCase();
289339
};
290340

341+
/**
342+
* Generates a formatted contest label for AtCoder University contests.
343+
*
344+
* This function takes a contest ID string and replaces parts of it using a regular expression
345+
* to generate a formatted label. The label is constructed by converting the contest type and
346+
* common part to uppercase and appending the contest year.
347+
*
348+
* @param contestId - The ID of the contest to format (ex: utpc2023).
349+
* @returns The formatted contest label (ex: UTPC 2023).
350+
*/
351+
export function getAtCoderUniversityContestLabel(contestId: string): string {
352+
return contestId.replace(
353+
regexForAtCoderUniversity,
354+
(_, contestType, common, contestYear) =>
355+
`${(contestType + common).toUpperCase()} ${contestYear}`,
356+
);
357+
}
358+
291359
/**
292360
* Maps PCK contest type abbreviations to their Japanese translations.
293361
*
@@ -300,26 +368,24 @@ export const getContestNameLabel = (contest_id: string) => {
300368
*/
301369
const PCK_TRANSLATIONS = {
302370
PCK: 'パソコン甲子園',
303-
Prelim: '予選',
304-
Final: '本選',
371+
Prelim: ' 予選 ',
372+
Final: ' 本選 ',
305373
};
306374

307375
/**
308376
* Maps JAG contest type abbreviations to their Japanese translations.
309377
*
310378
* @example
311379
* {
312-
* Prelim: '模擬国内予選',
313-
* Regional: '模擬アジア地区予選'
380+
* Prelim: '模擬国内',
381+
* Regional: '模擬地区'
314382
* }
315383
*/
316384
const JAG_TRANSLATIONS = {
317-
Prelim: '模擬国内予選',
318-
Regional: '模擬アジア地区予選',
385+
Prelim: ' 模擬国内 ',
386+
Regional: ' 模擬地区 ',
319387
};
320388

321-
const aojBaseLabel = 'AOJ - ';
322-
323389
function getAojChallengeLabel(
324390
translations: Readonly<ContestLabelTranslations>,
325391
contestId: string,
@@ -330,11 +396,19 @@ function getAojChallengeLabel(
330396
label = label.replace(abbrEnglish, japanese);
331397
});
332398

333-
return aojBaseLabel + label;
399+
return '(' + label + ')';
334400
}
335401

336402
export const addContestNameToTaskIndex = (contestId: string, taskTableIndex: string): string => {
337403
const contestName = getContestNameLabel(contestId);
338404

405+
if (isAojContest(contestId)) {
406+
return `AOJ ${taskTableIndex}${contestName}`;
407+
}
408+
339409
return `${contestName} - ${taskTableIndex}`;
340410
};
411+
412+
function isAojContest(contestId: string): boolean {
413+
return contestId.startsWith('PCK') || contestId.startsWith('JAG');
414+
}

src/test/lib/utils/contest.test.ts

Lines changed: 0 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -350,30 +350,6 @@ describe('Contest', () => {
350350

351351
describe('get contest name label', () => {
352352
describe('AtCoder', () => {
353-
describe('when contest_id contains abc', () => {
354-
TestCasesForContestNameLabel.abc.forEach(({ name, value }) => {
355-
runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestNameLabel) => {
356-
expect(getContestNameLabel(contestId)).toEqual(expected);
357-
});
358-
});
359-
});
360-
361-
describe('when contest_id starts with APG4b', () => {
362-
TestCasesForContestNameLabel.apg4b.forEach(({ name, value }) => {
363-
runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestNameLabel) => {
364-
expect(getContestNameLabel(contestId)).toEqual(expected);
365-
});
366-
});
367-
});
368-
369-
describe('when contest_id is typical90', () => {
370-
TestCasesForContestNameLabel.typical90.forEach(({ name, value }) => {
371-
runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestNameLabel) => {
372-
expect(getContestNameLabel(contestId)).toEqual(expected);
373-
});
374-
});
375-
});
376-
377353
describe('when contest_id is dp (EDPC)', () => {
378354
TestCasesForContestNameLabel.edpc.forEach(({ name, value }) => {
379355
runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestNameLabel) => {
@@ -422,46 +398,6 @@ describe('Contest', () => {
422398
});
423399
});
424400

425-
describe('when contest_id is tessoku-book', () => {
426-
TestCasesForContestNameLabel.tessokuBook.forEach(({ name, value }) => {
427-
runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestNameLabel) => {
428-
expect(getContestNameLabel(contestId)).toEqual(expected);
429-
});
430-
});
431-
});
432-
433-
describe('when contest_id is math-and-algorithm', () => {
434-
TestCasesForContestNameLabel.mathAndAlgorithm.forEach(({ name, value }) => {
435-
runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestNameLabel) => {
436-
expect(getContestNameLabel(contestId)).toEqual(expected);
437-
});
438-
});
439-
});
440-
441-
describe('when contest_id contains arc', () => {
442-
TestCasesForContestNameLabel.arc.forEach(({ name, value }) => {
443-
runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestNameLabel) => {
444-
expect(getContestNameLabel(contestId)).toEqual(expected);
445-
});
446-
});
447-
});
448-
449-
describe('when contest_id contains agc', () => {
450-
TestCasesForContestNameLabel.agc.forEach(({ name, value }) => {
451-
runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestNameLabel) => {
452-
expect(getContestNameLabel(contestId)).toEqual(expected);
453-
});
454-
});
455-
});
456-
457-
describe('when contest_id matches contests held by university students', () => {
458-
TestCasesForContestNameLabel.universities.forEach(({ name, value }) => {
459-
runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestNameLabel) => {
460-
expect(getContestNameLabel(contestId)).toEqual(expected);
461-
});
462-
});
463-
});
464-
465401
describe('when contest_id contains chokudai_S', () => {
466402
TestCasesForContestNameLabel.atCoderOthers.forEach(({ name, value }) => {
467403
runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestNameLabel) => {
@@ -470,32 +406,6 @@ describe('Contest', () => {
470406
});
471407
});
472408
});
473-
474-
describe('AOJ', () => {
475-
describe('when contest_id means AOJ courses', () => {
476-
TestCasesForContestNameLabel.aojCourses.forEach(({ name, value }) => {
477-
runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestNameLabel) => {
478-
expect(getContestNameLabel(contestId)).toEqual(expected);
479-
});
480-
});
481-
});
482-
483-
describe('when contest_id means AOJ PCK (prelim and final)', () => {
484-
TestCasesForContestNameLabel.aojPck.forEach(({ name, value }) => {
485-
runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestNameLabel) => {
486-
expect(getContestNameLabel(contestId)).toEqual(expected);
487-
});
488-
});
489-
});
490-
491-
describe('when contest_id means AOJ JAG (prelim and regional)', () => {
492-
TestCasesForContestNameLabel.aojJag.forEach(({ name, value }) => {
493-
runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestNameLabel) => {
494-
expect(getContestNameLabel(contestId)).toEqual(expected);
495-
});
496-
});
497-
});
498-
});
499409
});
500410

501411
describe('add contest name to task index', () => {

0 commit comments

Comments
 (0)