Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

:docs: Improve contest labels (#1526) #1528

Merged
merged 8 commits into from
Nov 25, 2024
122 changes: 96 additions & 26 deletions src/lib/utils/contest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ const AGC_LIKE: ContestPrefix = {
} as const;
const agcLikePrefixes = getContestPrefixes(AGC_LIKE);

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

/**
* Contest type priorities (0 = Highest, 19 = Lowest)
* Contest type priorities (0 = Highest, 20 = Lowest)
*
* Priority assignment rationale:
* - Educational contests (0-10): ABS, ABC, APG4B, etc.
Expand Down Expand Up @@ -240,54 +240,124 @@ export function getContestPriority(contestId: string): number {
}
}

export const getContestNameLabel = (contest_id: string) => {
if (contest_id === 'APG4b' || contest_id === 'APG4bPython') {
return contest_id;
/**
* Regular expression to match contest codes.
*
* This regex matches strings that start with one of the following prefixes:
* - "abc"
* - "arc"
* - "agc"
*
* followed by exactly three digits. The matching is case-insensitive.
*
* Example matches:
* - "abc376"
* - "ARC128"
* - "agc045"
*
* Example non-matches:
* - "xyz123"
* - "abc12"
* - "abc1234"
*/
const regexForAxc = /^(abc|arc|agc)(\d{3})/i;

/**
* Regular expression to match AtCoder University contest identifiers.
*
* The pattern matches strings that:
* - Start with either "ut", "tt", or "tu"
* - Followed by "pc"
* - End with exactly year (four digits)
*
* Example matches:
* - "utpc2014"
* - "ttpc2022"
* - "tupc2023"
*/
const regexForAtCoderUniversity = /^(ut|tt|tu)(pc)(\d{4})/;

export const getContestNameLabel = (contestId: string) => {
// AtCoder
if (regexForAxc.exec(contestId)) {
return contestId.replace(
regexForAxc,
(_, contestType, contestNumber) => `${contestType.toUpperCase()} ${contestNumber}`,
);
}

if (contest_id === 'typical90') {
if (contestId === 'APG4b' || contestId === 'APG4bPython') {
return contestId;
}

if (contestId === 'typical90') {
return '競プロ典型 90 問';
}

if (contest_id === 'dp') {
if (contestId === 'dp') {
return 'EDPC';
}

if (contest_id === 'tdpc') {
if (contestId === 'tdpc') {
return 'TDPC';
}

if (contest_id === 'practice2') {
if (contestId === 'practice2') {
return 'ACL Practice';
}

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

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

if (contest_id.startsWith('chokudai_S')) {
return contest_id.replace('chokudai_S', 'Chokudai SpeedRun ');
if (atCoderUniversityPrefixes.some((prefix) => contestId.startsWith(prefix))) {
return getAtCoderUniversityContestLabel(contestId);
}

if (aojCoursePrefixes.has(contest_id)) {
if (contestId.startsWith('chokudai_S')) {
return contestId.replace('chokudai_S', 'Chokudai SpeedRun ');
}

// AIZU ONLINE JUDGE
if (aojCoursePrefixes.has(contestId)) {
return 'AOJ Courses';
}

if (contest_id.startsWith('PCK')) {
return getAojChallengeLabel(PCK_TRANSLATIONS, contest_id);
if (contestId.startsWith('PCK')) {
return getAojChallengeLabel(PCK_TRANSLATIONS, contestId);
}

if (contest_id.startsWith('JAG')) {
return getAojChallengeLabel(JAG_TRANSLATIONS, contest_id);
if (contestId.startsWith('JAG')) {
return getAojChallengeLabel(JAG_TRANSLATIONS, contestId);
}

return contest_id.toUpperCase();
return contestId.toUpperCase();
};

/**
* Generates a formatted contest label for AtCoder University contests.
*
* This function takes a contest ID string and replaces parts of it using a regular expression
* to generate a formatted label. The label is constructed by converting the contest type and
* common part to uppercase and appending the contest year.
*
* @param contestId - The ID of the contest to format (ex: utpc2023).
* @returns The formatted contest label (ex: UTPC 2023).
*/
export function getAtCoderUniversityContestLabel(contestId: string): string {
return contestId.replace(
regexForAtCoderUniversity,
(_, contestType, common, contestYear) =>
`${(contestType + common).toUpperCase()} ${contestYear}`,
);
}

const SPACE = ' ';

/**
* Maps PCK contest type abbreviations to their Japanese translations.
*
Expand All @@ -300,22 +370,22 @@ export const getContestNameLabel = (contest_id: string) => {
*/
const PCK_TRANSLATIONS = {
PCK: 'パソコン甲子園',
Prelim: '予選',
Final: '本選',
Prelim: SPACE + '予選' + SPACE,
Final: SPACE + '本選' + SPACE,
};

/**
* Maps JAG contest type abbreviations to their Japanese translations.
*
* @example
* {
* Prelim: '模擬国内予選',
* Regional: '模擬アジア地区予選'
* Prelim: '模擬国内',
* Regional: '模擬地区'
* }
*/
const JAG_TRANSLATIONS = {
Prelim: '模擬国内予選',
Regional: '模擬アジア地区予選',
Prelim: SPACE + '模擬国内' + SPACE,
Regional: SPACE + '模擬地区' + SPACE,
};

const aojBaseLabel = 'AOJ - ';
Expand All @@ -330,7 +400,7 @@ function getAojChallengeLabel(
label = label.replace(abbrEnglish, japanese);
});

return aojBaseLabel + label;
return aojBaseLabel + '(' + label + ')';
}

export const addContestNameToTaskIndex = (contestId: string, taskTableIndex: string): string => {
Expand Down
51 changes: 26 additions & 25 deletions src/test/lib/utils/test_cases/contest_name_and_task_index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createTestCase, zip } from '../../common/test_helpers';
import { getAtCoderUniversityContestLabel } from '$lib/utils/contest';

export type TestCaseForContestNameAndTaskIndex = {
contestId: string;
Expand All @@ -13,13 +14,13 @@ const generateAbcTestCases = (
taskIndices: string[],
): { name: string; value: TestCaseForContestNameAndTaskIndex }[] => {
return zip(contestIds, taskIndices).map(([contestId, taskIndex]) => {
const testCase = createTestCaseForContestNameAndTaskIndex(`ABC${contestId}, task ${taskIndex}`)(
{
contestId: `abc${contestId}`,
taskTableIndex: taskIndex,
expected: `ABC${contestId} - ${taskIndex}`,
},
);
const testCase = createTestCaseForContestNameAndTaskIndex(
`ABC ${contestId}, task ${taskIndex}`,
)({
contestId: `abc${contestId}`,
taskTableIndex: taskIndex,
expected: `ABC ${contestId} - ${taskIndex}`,
});

return testCase;
});
Expand Down Expand Up @@ -162,13 +163,13 @@ const generateArcTestCases = (
taskIndices: string[],
): { name: string; value: TestCaseForContestNameAndTaskIndex }[] => {
return zip(contestIds, taskIndices).map(([contestId, taskIndex]) => {
const testCase = createTestCaseForContestNameAndTaskIndex(`ARC${contestId}, task ${taskIndex}`)(
{
contestId: `arc${contestId}`,
taskTableIndex: taskIndex,
expected: `ARC${contestId} - ${taskIndex}`,
},
);
const testCase = createTestCaseForContestNameAndTaskIndex(
`ARC ${contestId}, task ${taskIndex}`,
)({
contestId: `arc${contestId}`,
taskTableIndex: taskIndex,
expected: `ARC ${contestId} - ${taskIndex}`,
});

return testCase;
});
Expand All @@ -184,13 +185,13 @@ const generateAgcTestCases = (
taskIndices: string[],
): { name: string; value: TestCaseForContestNameAndTaskIndex }[] => {
return zip(contestIds, taskIndices).map(([contestId, taskIndex]) => {
const testCase = createTestCaseForContestNameAndTaskIndex(`AGC${contestId}, task ${taskIndex}`)(
{
contestId: `agc${contestId}`,
taskTableIndex: taskIndex,
expected: `AGC${contestId} - ${taskIndex}`,
},
);
const testCase = createTestCaseForContestNameAndTaskIndex(
`AGC ${contestId}, task ${taskIndex}`,
)({
contestId: `agc${contestId}`,
taskTableIndex: taskIndex,
expected: `AGC ${contestId} - ${taskIndex}`,
});

return testCase;
});
Expand Down Expand Up @@ -300,11 +301,11 @@ const generateUniversityTestCases = (
): { name: string; value: TestCaseForContestNameAndTaskIndex }[] => {
return zip(contestIds, taskIndices).map(([contestId, taskIndex]) => {
const testCase = createTestCaseForContestNameAndTaskIndex(
`${contestId.toUpperCase()} ${taskIndex}`,
`${getAtCoderUniversityContestLabel(contestId)} ${taskIndex}`,
)({
contestId: `${contestId}`,
taskTableIndex: taskIndex,
expected: `${contestId.toUpperCase()} - ${taskIndex}`,
expected: `${getAtCoderUniversityContestLabel(contestId)} - ${taskIndex}`,
});

return testCase;
Expand Down Expand Up @@ -431,7 +432,7 @@ const generateAojPckTestCases = (
)({
contestId: `PCK${contestId}`,
taskTableIndex: taskIndex,
expected: `AOJ - パソコン甲子園${contestId.replace('Prelim', '予選').replace('Final', '本選')} - ${taskIndex}`,
expected: `AOJ - パソコン甲子園${contestId.replace('Prelim', ' 予選 ').replace('Final', ' 本選 ')} - ${taskIndex}`,
});

return testCase;
Expand Down Expand Up @@ -533,7 +534,7 @@ const generateAojJagTestCases = (contestIds: JagContestIds, taskIndices: string[
taskIndices,
(contestId, taskIndex) => `AOJ, JAG${contestId} - ${taskIndex}`,
(contestId, taskIndex) =>
`AOJ - JAG${contestId.replace('Prelim', '模擬国内予選').replace('Regional', '模擬アジア地区予選')} - ${taskIndex}`,
`AOJ - JAG${contestId.replace('Prelim', ' 模擬国内 ').replace('Regional', ' 模擬地区 ')} - ${taskIndex}`,
);

export const aojJag = Object.entries(AOJ_JAG_TEST_DATA).flatMap(([contestId, tasks]) =>
Expand Down
Loading
Loading