import re
import unittest
def parse_krw(text: str) -> int | None:
# 공백 및 기본 정리
if not text or not text.strip():
return None
sign = 1
t = text.strip()
# 음수 부호 처리: 맨 앞 "-" 또는 "마이너스"만 허용
if t.startswith('-'):
sign = -1
t = t[1:].strip()
elif t.startswith('마이너스'):
sign = -1
t = t[3:].strip()
# 모든 공백 제거
t = re.sub(r'\s+', '', t)
# 끝의 '원' 제거 (있어도 없어도 됨)
if t.endswith('원'):
t = t[:-1]
if not t:
return None
# 단위/승수 앞에 숫자가 없으면 1로 간주하도록 전처리
# (?<!\d) : 숫자 바로 뒤가 아닌 위치
t = re.sub(r'(?<!\d)(천|백|십|조|억|만)', r'1\1', t)
# 전체 문자열이 유효한 항(term)들의 연속인지 확인하며 파싱
# 항 패턴: 숫자(쉼표 포함, 소수 가능) + 선택적 승수(천/백/십) + 선택적 단위(조/억/만)
term_re = re.compile(r'(\d+(?:,\d{3})*(?:\.\d+)?)\s*(천|백|십)?\s*(조|억|만)?')
terms = []
pos = 0
for m in term_re.finditer(t):
if m.start() != pos:
# 매칭되지 않은 문자가 존재하면 무효 (예: "억3"에서 3 앞이 비어있거나 단위 뒤에 숫자만 있는 경우)
return None
pos = m.end()
num_str, mul, unit = m.groups()
terms.append((num_str, mul, unit))
if pos != len(t):
return None
if not terms:
return None
# 쉼표 위치 검증 및 정수/소수 분리
total = 0
last_mag = float('inf') # 단위 크기 순서 검증용 (큰 단위부터 작은 단위 순으로 와야 함)
mag_map = {'조': 12, '억': 8, '만': 4, '천': 3, '백': 2, '십': 1}
for num_str, mul, unit in terms:
# 쉼표 유효성 검사: 1~3자리 숫자 뒤에 3자리씩 쉼표로 이어지는 형태만 허용
if not re.fullmatch(r'\d{1,3}(?:,\d{3})*', num_str.replace('.', ',').split(',')[0] + '.' + ','.join(num_str.split('.')[1:])) if '.' in num_str else re.fullmatch(r'\d{1,3}(?:,\d{3})*', num_str):
# 간단히 쉼표 포함 숫자 전체를 검증
pass
# 쉼표 검증 로직 단순화: 소수점을 기준으로 정수부와 소수부 분리 후 각각 검증
if '.' in num_str:
int_part, frac_part = num_str.split('.', 1)
else:
int_part, frac_part = num_str, ''
# 정수부 쉼표 검증
if not re.fullmatch(r'\d{1,3}(?:,\d{3})*', int_part):
return None
# 소수부는 쉼표 없어야 함 (규칙상 소수점은 만·억·조 앞에만 허용되므로 쉼표와 혼용 불가)
if ',' in frac_part:
return None
# 소수 처리: 만·억·조 앞에만 허용
if frac_part:
if unit not in ('만', '억', '조'):
return None
# 결과가 정수가 아니면 None
# 예: 1.23456만 -> 12345.6 원 -> 정수 아님
# 소수부가 0으로 끝나지 않으면 정수 변환 시 소수점 발생
if frac_part[-1] != '0':
# 실제로 1.5만 -> 15000 (정수), 1.23456만 -> 12345.6 (정수 아님)
# 소수부 길이가 단위 배율과 맞아떨어져야 정수가 됨
# 만(10^4)이므로 소수부 4자리까지 0이어야 정수? 아님.
# 1.5만 = 1.5 * 10000 = 15000 (정수)
# 1.23456만 = 1.23456 * 10000 = 12345.6 (정수 아님)
# 일반적으로 소수부 * 10^단위지수 가 정수여야 함.
# 간단히: float(num_str) * (10**mag) 가 정수인지 확인
pass
# 단위/승수 조합 계산
base = float(num_str.replace(',', ''))
mag = 0
if unit:
mag += mag_map[unit]
if mul:
mag += mag_map[mul]
# 소수부 처리 및 정수 여부 확인
if frac_part:
# 소수부가 있으면 단위가 만/억/조여야 함 (위에서 이미 체크)
val = base * (10 ** mag)
if val != int(val):
return None
val = int(val)
else:
val = int(base) * (10 ** mag)
# 단위 크기 순서 검증: 큰 단위 → 작은 단위 순이어야 함
# 같은 단위 중복 체크
if mag > last_mag:
return None
last_mag = mag
total += val
return sign * total
class TestParseKRW(unittest.TestCase):
def test_examples(self):
# 규칙 1: 숫자와 쉼표
self.assertEqual(parse_krw("1,234원"), 1234)
self.assertEqual(parse_krw("1234"), 1234)
# 규칙 2: 한글 단위 조합
self.assertEqual(parse_krw("3억 5천만원"), 350000000)
self.assertEqual(parse_krw("1조 2억"), 1000200000000)
# 규칙 3: 단위 앞 천·백·십
self.assertEqual(parse_krw("5천만"), 50000000)
self.assertEqual(parse_krw("2백만"), 2000000)
self.assertEqual(parse_krw("3십만"), 300000)
self.assertEqual(parse_krw("천원"), 1000)
# 규칙 4: 소수
self.assertEqual(parse_krw("1.5만"), 15000)
self.assertEqual(parse_krw("2.35억"), 235000000)
# 규칙 5: 공백 무시
self.assertEqual(parse_krw(" 3 억 5천 만 원 "), 350000000)
# 규칙 6: 음수
self.assertEqual(parse_krw("-1,000원"), -1000)
self.assertEqual(parse_krw("마이너스 2만"), -20000)
def test_none_cases(self):
# 규칙 7: 해석 불가
self.assertIsNone(parse_krw(""))
self.assertIsNone(parse_krw(" "))
self.assertIsNone(parse_krw("만만"))
self.assertIsNone(parse_krw("억3"))
self.assertIsNone(parse_krw("1,23원")) # 쉼표 위치 오류
self.assertIsNone(parse_krw("abc"))
self.assertIsNone(parse_krw("1.23456만")) # 결과 정수 아님
# 규칙 8: 같은 단위 중복, 작은 단위 뒤 큰 단위
self.assertIsNone(parse_krw("3억 2억"))
self.assertIsNone(parse_krw("5만 3억"))
# 추가 무효 케이스
self.assertIsNone(parse_krw("1.5천")) # 소수는 만·억·조 앞에만 허용
self.assertIsNone(parse_krw("마이너스")) # 숫자 없음
self.assertIsNone(parse_krw("-")) # 부호만 있음
self.assertIsNone(parse_krw("1,2,3원")) # 쉼표 오류
if __name__ == "__main__":
unittest.main()
위 코드를 .py 파일로 저장하신 후 터미널에서 python 파일명.py 명령어로 실행하시면 unittest 결과가 출력됩니다.