-
Notifications
You must be signed in to change notification settings - Fork 103
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: 숫자를 순 우리말 서수사로 변환하는 함수 추가 (#307)
* feat: 서수사 기능 구현 * feat: 서수사 테스트 추가 * refactor: 숫자 유효성 검증 로직 개선 Co-authored-by: 박찬혁 <[email protected]> * docs: 서수사 함수 주석 보완 * docs: 서수사 함수 문서 추가 * fix: update seosusa function can handle more than 100 numbers * update: seosusa test code * update: seosusa documents * Create modern-trains-change.md --------- Co-authored-by: 박찬혁 <[email protected]>
- Loading branch information
Showing
7 changed files
with
235 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"es-hangul": patch | ||
--- | ||
|
||
feat: 숫자를 순 우리말 서수사로 변환하는 함수 추가 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
--- | ||
title: seosusa | ||
--- | ||
|
||
import { Sandpack } from '@/components/Sandpack'; | ||
|
||
# susa | ||
|
||
Convert numbers to Korean ordinal words. | ||
Integers from 1 to 99 are converted to native Korean ordinal words. | ||
Integers greater than 100 are converted to Sino-Korean ordinal words. | ||
|
||
```typescript | ||
function seosusa( | ||
// Number to convert | ||
num: number | ||
): string; | ||
``` | ||
|
||
## Examples | ||
|
||
```typescript | ||
seosusa(1); // '첫째' | ||
seosusa(2); // '둘째' | ||
seosusa(3); // '셋째' | ||
seosusa(10); // '열째' | ||
seosusa(11); // '열한째' | ||
seosusa(12); // '열두째' | ||
seosusa(13); // '열셋째' | ||
seosusa(20); // '스무째' | ||
seosusa(21); // '스물한째' | ||
seosusa(99); // '아흔아홉째' | ||
seosusa(100); // '백째' | ||
``` | ||
|
||
## Demo | ||
|
||
<br /> | ||
|
||
<Sandpack> | ||
|
||
```ts index.ts | ||
import { seosusa } from 'es-hangul'; | ||
|
||
console.log(seosusa(1)); | ||
``` | ||
|
||
</Sandpack> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
--- | ||
title: seosusa | ||
--- | ||
|
||
import { Sandpack } from '@/components/Sandpack'; | ||
|
||
# seosusa | ||
|
||
숫자를 한글 서수사로 변환합니다. | ||
1부터 99까지의 정수는 순우리말 서수사 문자열로 변환합니다. | ||
100 이상의 정수는 한자어 서수사 문자열로 변환합니다. | ||
|
||
```typescript | ||
function seosusa( | ||
// 변환할 숫자 | ||
num: number | ||
): string; | ||
``` | ||
|
||
## Examples | ||
|
||
```typescript | ||
seosusa(1); // '첫째' | ||
seosusa(2); // '둘째' | ||
seosusa(3); // '셋째' | ||
seosusa(10); // '열째' | ||
seosusa(11); // '열한째' | ||
seosusa(12); // '열두째' | ||
seosusa(13); // '열셋째' | ||
seosusa(20); // '스무째' | ||
seosusa(21); // '스물한째' | ||
seosusa(99); // '아흔아홉째' | ||
seosusa(100); // '백째' | ||
``` | ||
|
||
## 사용해보기 | ||
|
||
<br /> | ||
|
||
<Sandpack> | ||
|
||
```ts index.ts | ||
import { seosusa } from 'es-hangul'; | ||
|
||
console.log(seosusa(1)); | ||
``` | ||
|
||
</Sandpack> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: '스무', | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './seosusa'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
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: '아흔아홉째' }, | ||
{ num: 100, word: '백째' }, | ||
{ num: 101, word: '백일째' }, | ||
]; | ||
|
||
const invalidNumbers = [0, -1, 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이상의 정수만 지원합니다.'); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { numberToHangul } from '@/numberToHangul'; | ||
import { hasProperty } from '../_internal'; | ||
import { SEOSUSA_MAP, SEOSUSA_SPECIAL_CASE_MAP } from './constants'; | ||
|
||
/** | ||
* 숫자를 한글 서수사로 변환합니다. | ||
* | ||
* @remarks | ||
* - **서수사**는 순서를 나타내는 단어입니다. | ||
* - 1부터 99까지의 정수는 순우리말 서수사 문자열로 변환합니다. | ||
* - 100 이상의 정수는 한자어 서수사 문자열로 변환합니다. | ||
* | ||
* @param num - 변환할 숫자 | ||
* @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); // '아흔아홉째' | ||
* seosusa(100); // '백째' | ||
* | ||
* @see https://es-hangul.slash.page/docs/api/seosusa | ||
*/ | ||
export function seosusa(num: number): string { | ||
if (num === 0 || !Number.isInteger(num)) { | ||
throw new Error('유효하지 않은 입력입니다. 1이상의 정수만 지원합니다.'); | ||
} | ||
|
||
if (num >= 1 && num <= 99) { | ||
return `${getOrdinalWord(num)}째`; | ||
} | ||
|
||
try { | ||
return `${numberToHangul(num)}째`; | ||
} catch (error) { | ||
throw new Error('유효하지 않은 입력입니다. 1이상의 정수만 지원합니다.'); | ||
} | ||
} | ||
|
||
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}`; | ||
} |