Skip to content

Commit

Permalink
Merge pull request #1528 from AtCoder-NoviSteps/#1526
Browse files Browse the repository at this point in the history
[WIP] :docs: Improve contest labels (#1526)
  • Loading branch information
KATO-Hiro authored Nov 25, 2024
2 parents 6b847fe + 0fb8e0b commit a5b29c0
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 492 deletions.
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}`,
);
}

/**
* 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

0 comments on commit a5b29c0

Please sign in to comment.