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
130 changes: 102 additions & 28 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,122 @@ 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})/i;

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 (regexForAtCoderUniversity.exec(contestId)) {
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}`,
);
}
KATO-Hiro marked this conversation as resolved.
Show resolved Hide resolved

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

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

const aojBaseLabel = 'AOJ - ';

function getAojChallengeLabel(
translations: Readonly<ContestLabelTranslations>,
contestId: string,
Expand All @@ -330,11 +396,19 @@ function getAojChallengeLabel(
label = label.replace(abbrEnglish, japanese);
});

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

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

if (isAojContest(contestId)) {
return `AOJ ${taskTableIndex}${contestName}`;
}

return `${contestName} - ${taskTableIndex}`;
};

function isAojContest(contestId: string): boolean {
return contestId.startsWith('PCK') || contestId.startsWith('JAG');
}
90 changes: 0 additions & 90 deletions src/test/lib/utils/contest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,30 +350,6 @@ describe('Contest', () => {

describe('get contest name label', () => {
describe('AtCoder', () => {
describe('when contest_id contains abc', () => {
TestCasesForContestNameLabel.abc.forEach(({ name, value }) => {
runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestNameLabel) => {
expect(getContestNameLabel(contestId)).toEqual(expected);
});
});
});

describe('when contest_id starts with APG4b', () => {
TestCasesForContestNameLabel.apg4b.forEach(({ name, value }) => {
runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestNameLabel) => {
expect(getContestNameLabel(contestId)).toEqual(expected);
});
});
});

describe('when contest_id is typical90', () => {
TestCasesForContestNameLabel.typical90.forEach(({ name, value }) => {
runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestNameLabel) => {
expect(getContestNameLabel(contestId)).toEqual(expected);
});
});
});

describe('when contest_id is dp (EDPC)', () => {
TestCasesForContestNameLabel.edpc.forEach(({ name, value }) => {
runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestNameLabel) => {
Expand Down Expand Up @@ -422,46 +398,6 @@ describe('Contest', () => {
});
});

describe('when contest_id is tessoku-book', () => {
TestCasesForContestNameLabel.tessokuBook.forEach(({ name, value }) => {
runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestNameLabel) => {
expect(getContestNameLabel(contestId)).toEqual(expected);
});
});
});

describe('when contest_id is math-and-algorithm', () => {
TestCasesForContestNameLabel.mathAndAlgorithm.forEach(({ name, value }) => {
runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestNameLabel) => {
expect(getContestNameLabel(contestId)).toEqual(expected);
});
});
});

describe('when contest_id contains arc', () => {
TestCasesForContestNameLabel.arc.forEach(({ name, value }) => {
runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestNameLabel) => {
expect(getContestNameLabel(contestId)).toEqual(expected);
});
});
});

describe('when contest_id contains agc', () => {
TestCasesForContestNameLabel.agc.forEach(({ name, value }) => {
runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestNameLabel) => {
expect(getContestNameLabel(contestId)).toEqual(expected);
});
});
});

describe('when contest_id matches contests held by university students', () => {
TestCasesForContestNameLabel.universities.forEach(({ name, value }) => {
runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestNameLabel) => {
expect(getContestNameLabel(contestId)).toEqual(expected);
});
});
});

describe('when contest_id contains chokudai_S', () => {
TestCasesForContestNameLabel.atCoderOthers.forEach(({ name, value }) => {
runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestNameLabel) => {
Expand All @@ -470,32 +406,6 @@ describe('Contest', () => {
});
});
});

describe('AOJ', () => {
describe('when contest_id means AOJ courses', () => {
TestCasesForContestNameLabel.aojCourses.forEach(({ name, value }) => {
runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestNameLabel) => {
expect(getContestNameLabel(contestId)).toEqual(expected);
});
});
});

describe('when contest_id means AOJ PCK (prelim and final)', () => {
TestCasesForContestNameLabel.aojPck.forEach(({ name, value }) => {
runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestNameLabel) => {
expect(getContestNameLabel(contestId)).toEqual(expected);
});
});
});

describe('when contest_id means AOJ JAG (prelim and regional)', () => {
TestCasesForContestNameLabel.aojJag.forEach(({ name, value }) => {
runTests(`${name}`, [value], ({ contestId, expected }: TestCaseForContestNameLabel) => {
expect(getContestNameLabel(contestId)).toEqual(expected);
});
});
});
});
});

describe('add contest name to task index', () => {
Expand Down
Loading
Loading