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

feat: 숫자를 순 우리말 서수사로 변환하는 함수 추가 #307

Merged
merged 9 commits into from
Jan 6, 2025
27 changes: 27 additions & 0 deletions src/seosusa/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export const SEOSUSA_MAP = {
1: '한',
2: '두',
3: '셋',
4: '넷',
5: '다섯',
6: '여섯',
7: '일곱',
8: '여덟',
9: '아홉',
10: '열',
20: '스물',
30: '서른',
40: '마흔',
50: '쉰',
60: '예순',
70: '일흔',
80: '여든',
90: '아흔',
100: '백',
};

export const SEOSUSA_SPECIAL_CASE_MAP = {
1: '첫',
2: '둘',
20: '스무',
};
1 change: 1 addition & 0 deletions src/seosusa/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './seosusa';
42 changes: 42 additions & 0 deletions src/seosusa/seosusa.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { seosusa } from './seosusa';

describe('seosusa', () => {
const validNumbers = [
{ num: 1, word: '첫째' },
{ num: 2, word: '둘째' },
{ num: 3, word: '셋째' }, // '셋째'가 표준어이고 '세째'는 비표준어이다.(표준어 사정 원칙 제6항)
{ num: 4, word: '넷째' }, // '넷째'가 표준어이고 '네째'는 비표준어이다.(표준어 사정 원칙 제6항)
{ num: 5, word: '다섯째' },
{ num: 6, word: '여섯째' },
{ num: 7, word: '일곱째' },
{ num: 8, word: '여덟째' },
{ num: 9, word: '아홉째' },
{ num: 10, word: '열째' },
{ num: 11, word: '열한째' },
{ num: 12, word: '열두째' }, // '둘째'는 십 단위 이상의 서수사에 쓰일 때에 '두째'로 한다.(표준어 사정 원칙 제6항)
{ num: 13, word: '열셋째' },
{ num: 14, word: '열넷째' },
{ num: 15, word: '열다섯째' },
{ num: 20, word: '스무째' },
{ num: 21, word: '스물한째' },
{ num: 22, word: '스물두째' },
{ num: 30, word: '서른째' },
{ num: 40, word: '마흔째' },
{ num: 90, word: '아흔째' },
{ num: 99, word: '아흔아홉째' },
];

const invalidNumbers = [0, -1, 100, 101, 1.1, -1.1, Infinity, -Infinity, NaN];

validNumbers.forEach(({ num, word }) => {
it(`${num} - 순 우리말 서수사로 변환한다.`, () => {
expect(seosusa(num)).toBe(word);
});
});

invalidNumbers.forEach(num => {
it(`${num} - 유효하지 않은 숫자에 대해 오류를 발생시켜야 한다.`, () => {
expect(() => seosusa(num)).toThrow(`유효하지 않은 입력입니다. 1부터 99까지의 정수만 지원합니다.`);
});
});
});
54 changes: 54 additions & 0 deletions src/seosusa/seosusa.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { hasProperty } from '../_internal';
import { SEOSUSA_MAP, SEOSUSA_SPECIAL_CASE_MAP } from './constants';

/**
* 숫자를 순 우리말 서수사로 변환합니다.
*
* @remarks
* - **서수사**는 순서를 나타내는 우리말 단어입니다. 이 함수에서는 첫째, 둘째, 셋째 따위의 고유어 계통 단어를 다룹니다.
* - 1부터 99까지의 정수를 서수사 문자열로 변환합니다.
*
* @param num - 변환할 숫자 (1-99)
* @return 변환된 서수사 문자열
* @throws {Error} 지원하지 않는 숫자인 경우
*
* @example
* seosusa(1); // '첫째'
* seosusa(2); // '둘째'
* seosusa(3); // '셋째'
* seosusa(10); // '열째'
* seosusa(11); // '열한째'
* seosusa(12); // '열두째'
* seosusa(13); // '열셋째'
* seosusa(20); // '스무째'
* seosusa(21); // '스물한째'
* seosusa(30); // '서른째'
* seosusa(40); // '마흔째'
* seosusa(99); // '아흔아홉째'
*
* @see seosusa를_설명하는_링크
*/
export function seosusa(num: number): string {
validateNumber(num);
return `${getOrdinalWord(num)}째`;
}

function validateNumber(num: number): void {
if (Number.isNaN(num) || !Number.isFinite(num) || !Number.isInteger(num) || num <= 0 || num >= 100) {
throw new Error('유효하지 않은 입력입니다. 1부터 99까지의 정수만 지원합니다.');
}
}

wet6123 marked this conversation as resolved.
Show resolved Hide resolved
function getOrdinalWord(num: number): string {
if (hasProperty(SEOSUSA_SPECIAL_CASE_MAP, num)) {
return SEOSUSA_SPECIAL_CASE_MAP[num];
}

const tens = Math.floor(num / 10) * 10;
const ones = num % 10;

const tensWord = hasProperty(SEOSUSA_MAP, tens) ? SEOSUSA_MAP[tens] : '';
const onesWord = hasProperty(SEOSUSA_MAP, ones) ? SEOSUSA_MAP[ones] : '';

return `${tensWord}${onesWord}`;
}