from fractions import Fraction
import re
import unittest
def parse_krw(text: str) -> int | None:
"""한국어 금액 문자열을 원 단위 정수로 변환합니다."""
if not isinstance(text, str):
return None
value = "".join(text.split())
if not value:
return None
negative = False
if value.startswith("-"):
negative = True
value = value[1:]
elif value.startswith("마이너스"):
negative = True
value = value[len("마이너스"):]
if not value:
return None
if value.endswith("원"):
value = value[:-1]
if not value:
return None
# 올바른 쉼표 묶음이 있는 숫자 또는 쉼표 없는 숫자를 찾습니다.
number_pattern = re.compile(
r"(?:[0-9]{1,3}(?:,[0-9]{3})+|[0-9]+)(?:\.[0-9]+)?"
)
# 숫자와 한글 단위를 순서대로 찾습니다.
token_pattern = re.compile(
r"(?:[0-9]{1,3}(?:,[0-9]{3})+|[0-9]+)(?:\.[0-9]+)?|천|백|십|만|억|조"
)
tokens = []
position = 0
while position < len(value):
match = token_pattern.match(value, position)
if match is None:
return None
tokens.append(match.group())
position = match.end()
total = Fraction(0)
section = Fraction(0)
pending = None
pending_decimal = False
has_unit = False
last_small_rank = 4
last_large_rank = 4
small_units = {"천": (1000, 3), "백": (100, 2), "십": (10, 1)}
large_units = {"만": (10**4, 3), "억": (10**8, 2), "조": (10**12, 1)}
for token in tokens:
if number_pattern.fullmatch(token):
if pending is not None:
return None
normalized = token.replace(",", "")
pending_decimal = "." in normalized
try:
pending = Fraction(normalized)
except (ValueError, ZeroDivisionError):
return None
continue
has_unit = True
if token in small_units:
if pending_decimal:
return None
multiplier, rank = small_units[token]
if rank >= last_small_rank:
return None
coefficient = pending if pending is not None else Fraction(1)
section += coefficient * multiplier
pending = None
pending_decimal = False
last_small_rank = rank
continue
multiplier, rank = large_units[token]
if rank >= last_large_rank:
return None
if pending is not None and section != 0:
return None
coefficient = pending if pending is not None else section
if coefficient == 0:
coefficient = Fraction(1)
total += coefficient * multiplier
section = Fraction(0)
pending = None
pending_decimal = False
last_small_rank = 4
last_large_rank = rank
if has_unit:
if pending is not None:
return None
result = total + section
else:
if pending is None or pending.denominator != 1:
return None
result = pending
if result.denominator != 1:
return None
integer = int(result)
return -integer if negative else integer
class ParseKrwTests(unittest.TestCase):
def test_examples(self):
examples = {
"1,234원": 1234,
"1234": 1234,
"3억 5천만원": 350000000,
"1조 2억": 1000200000000,
"5천만": 50000000,
"2백만": 2000000,
"3십만": 300000,
"천원": 1000,
"1.5만": 15000,
"2.35억": 235000000,
" 3 억 5천 만 원 ": 350000000,
"-1,000원": -1000,
"마이너스 2만": -20000,
}
for text, expected in examples.items():
with self.subTest(text=text):
self.assertEqual(parse_krw(text), expected)
def test_invalid_inputs(self):
invalid = [
"",
"만만",
"억3",
"1,23원",
"abc",
"1.23456만",
"3억 2억",
"5만 3억",
"1.5천",
"1.2",
]
for text in invalid:
with self.subTest(text=text):
self.assertIsNone(parse_krw(text))
if __name__ == "__main__":
unittest.main()
파일로 저장한 뒤 python 파일명.py를 실행하시면 테스트를 확인하실 수 있습니다.