์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿ ์ •๊ทœํ‘œํ˜„์‹๊ณผ ํ…์ŠคํŠธ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ๊ณ ๊ธ‰ ๋ฌธ์ž์—ด ์กฐ์ž‘ ์™„์ „ ์ •๋ณต

๐Ÿ ์ •๊ทœํ‘œํ˜„์‹๊ณผ ํ…์ŠคํŠธ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ๊ณ ๊ธ‰ ๋ฌธ์ž์—ด ์กฐ์ž‘ ์™„์ „ ์ •๋ณต

Python์œผ๋กœ ํ…์ŠคํŠธ๋ฅผ ๋งˆ๋ฒ•์ฒ˜๋Ÿผ ๋‹ค๋ฃจ๋Š” ์‹ค์ „ ๊ฐ€์ด๋“œ โ€” ํŒจํ„ด ๋งค์นญ๋ถ€ํ„ฐ ๊ณ ๊ธ‰ ์กฐ์ž‘๊นŒ์ง€

์•ผ, ์†”์งํžˆ ๋งํ•ด๋ด. ๐Ÿ˜… ์ฝ”๋”ฉํ•˜๋‹ค๊ฐ€ ํ…์ŠคํŠธ ์ฒ˜๋ฆฌํ•  ๋•Œ "์ด๊ฑฐ ์–ด๋–ป๊ฒŒ ๋ฝ‘์•„๋‚ด์ง€?" ํ•˜๋ฉด์„œ ๋ฉ ๋•Œ๋ฆฐ ์  ์žˆ์ง€ ์•Š์•„?
๋กœ๊ทธ ํŒŒ์ผ์—์„œ ํŠน์ • ํŒจํ„ด ์ฐพ๊ธฐ, ์ด๋ฉ”์ผ ์ฃผ์†Œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ, HTML ํƒœ๊ทธ ์ œ๊ฑฐ, ์ „ํ™”๋ฒˆํ˜ธ ํ˜•์‹ ํ†ต์ผโ€ฆ
์ด๋Ÿฐ ์ž‘์—…๋“ค์„ ์ผ์ผ์ด ๋ฐ˜๋ณต๋ฌธ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋‹ค ๋ณด๋ฉด ์ฝ”๋“œ๊ฐ€ ์ŠคํŒŒ๊ฒŒํ‹ฐ๊ฐ€ ๋˜๋Š” ๊ฑด ์‹œ๊ฐ„๋ฌธ์ œ์•ผ.

๊ทธ๋ž˜์„œ ์˜ค๋Š˜์€ Python์˜ ์ •๊ทœํ‘œํ˜„์‹(Regular Expression)๊ณผ ๊ณ ๊ธ‰ ๋ฌธ์ž์—ด ์กฐ์ž‘ ๊ธฐ๋ฒ•์„ ์™„์ „ํžˆ ํŒŒํ—ค์ณ๋ณผ ๊ฑฐ์•ผ.
์ฒ˜์Œ์—” ์ข€ ๋‚ฏ์„ค ์ˆ˜ ์žˆ๋Š”๋ฐ, ํ•œ ๋ฒˆ ์ตํžˆ๋ฉด "์ด๊ฑธ ์™œ ์ด์ œ์•ผ ์•Œ์•˜์ง€?" ํ•˜๋Š” ์ˆœ๊ฐ„์ด ๋ฐ˜๋“œ์‹œ ์˜จ๋‹ค๊ณ . ๐Ÿ”ฅ

์ž, ์ค€๋น„๋์–ด? ๊ฐ™์ด ๋‹ฌ๋ ค๋ณด์ž!


๐Ÿ“Œ 1. ์ •๊ทœํ‘œํ˜„์‹์ด ๋ญ”๋ฐ? โ€” ๊ธฐ์ดˆ ๊ฐœ๋… ์™„์ „ ์ •๋ฆฌ

์ •๊ทœํ‘œํ˜„์‹(Regular Expression, ์ค„์—ฌ์„œ regex ๋˜๋Š” regexp)์€ ๋ฌธ์ž์—ด์—์„œ ํŠน์ • ํŒจํ„ด์„ ์ฐพ๊ฑฐ๋‚˜, ๋Œ€์ฒดํ•˜๊ฑฐ๋‚˜, ์ถ”์ถœํ•˜๊ธฐ ์œ„ํ•œ ํŠน์ˆ˜ํ•œ ๋ฌธ์ž ์‹œํ€€์Šค์•ผ.
1950๋…„๋Œ€ ์ˆ˜ํ•™์ž ์Šคํ‹ฐ๋ธ ํด๋ ˆ์ด๋‹ˆ(Stephen Kleene)๊ฐ€ ์ •๊ทœ ์–ธ์–ด๋ฅผ ํ‘œํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ๊ฐœ๋ฐœํ•œ ๊ฐœ๋…์ธ๋ฐ, ์ง€๊ธˆ์€ ๊ฑฐ์˜ ๋ชจ๋“  ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด์—์„œ ์ง€์›ํ•ด.

Python์—์„œ๋Š” re ๋ชจ๋“ˆ์„ ํ†ตํ•ด ์ •๊ทœํ‘œํ˜„์‹์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด. ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ํฌํ•จ๋˜์–ด ์žˆ์œผ๋‹ˆ๊นŒ ๋ณ„๋„ ์„ค์น˜ ์—†์ด ๋ฐ”๋กœ ์“ธ ์ˆ˜ ์žˆ์ง€!

์ •๊ทœํ‘œํ˜„์‹์˜ ๋™์ž‘ ์›๋ฆฌ ๐Ÿ“„ ์›๋ณธ ํ…์ŠคํŠธ "user@email.com" "hello world 123" ๐Ÿ” ์ •๊ทœํ‘œํ˜„์‹ ํŒจํ„ด r'\w+@\w+\.\w+' ์ด๋ฉ”์ผ ํŒจํ„ด โœ… ๋งค์นญ ๊ฒฐ๊ณผ "user@email.com" ์ถ”์ถœ ์„ฑ๊ณต! ์ฃผ์š” ๋ฉ”ํƒ€๋ฌธ์ž ํ•œ๋ˆˆ์— ๋ณด๊ธฐ . ์ž„์˜์˜ ๋ฌธ์ž 1๊ฐœ ๋งค์นญ * 0ํšŒ ์ด์ƒ ๋ฐ˜๋ณต ํƒ์š•์  ๋งค์นญ + 1ํšŒ ์ด์ƒ ๋ฐ˜๋ณต ์ตœ์†Œ 1๊ฐœ ํ•„์š” ? 0ํšŒ ๋˜๋Š” 1ํšŒ ์„ ํƒ์  ๋งค์นญ [] ๋ฌธ์ž ํด๋ž˜์Šค ์ง‘ํ•ฉ ์ค‘ ํ•˜๋‚˜ ^$ ์‹œ์ž‘/๋ ์•ต์ปค ์œ„์น˜ ์ง€์ • ๐Ÿ’ก Python์—์„œ๋Š” raw string(r'ํŒจํ„ด')์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๊ถŒ์žฅ๋ผ! ๋ฐฑ์Šฌ๋ž˜์‹œ(\) ์ฒ˜๋ฆฌ ๋ฌธ์ œ๋ฅผ ๋ฐฉ์ง€ํ•˜๊ณ  ํŒจํ„ด์„ ๋” ๋ช…ํ™•ํ•˜๊ฒŒ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ์–ด. ์˜ˆ: r'\d+' vs '\\d+' โ†’ ๋‘˜ ๋‹ค ๊ฐ™์€ ์˜๋ฏธ์ง€๋งŒ raw string์ด ํ›จ์”ฌ ์ฝ๊ธฐ ์‰ฌ์›Œ!

๐Ÿ”ง re ๋ชจ๋“ˆ ์ž„ํฌํŠธ์™€ ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•

Python์—์„œ ์ •๊ทœํ‘œํ˜„์‹์„ ์“ฐ๋ ค๋ฉด ๋จผ์ € re ๋ชจ๋“ˆ์„ ์ž„ํฌํŠธํ•ด์•ผ ํ•ด. ์•„์ฃผ ๊ฐ„๋‹จํ•ด!

import re

# ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ์‚ฌ์šฉ๋ฒ•
text = "Python์€ ์ •๋ง ์žฌ๋ฏธ์žˆ๋Š” ์–ธ์–ด์•ผ! 2024๋…„์—๋„ ์ตœ๊ณ !"

# ์ˆซ์ž ์ฐพ๊ธฐ
result = re.search(r'\d+', text)
if result:
    print(result.group())  # ์ถœ๋ ฅ: 2024

# ๋ชจ๋“  ์ˆซ์ž ์ฐพ๊ธฐ
all_numbers = re.findall(r'\d+', text)
print(all_numbers)  # ์ถœ๋ ฅ: ['2024']
๐Ÿ’ก Tip! re.search()๋Š” ์ฒซ ๋ฒˆ์งธ ๋งค์นญ๋งŒ ์ฐพ๊ณ , re.findall()์€ ๋ชจ๋“  ๋งค์นญ์„ ๋ฆฌ์ŠคํŠธ๋กœ ๋ฐ˜ํ™˜ํ•ด. ์šฉ๋„์— ๋งž๊ฒŒ ๊ณจ๋ผ ์จ์•ผ ํ•ด!

๐ŸŽฏ 2. re ๋ชจ๋“ˆ์˜ ํ•ต์‹ฌ ํ•จ์ˆ˜๋“ค โ€” ์™„๋ฒฝ ํ•ด๋ถ€

re ๋ชจ๋“ˆ์—๋Š” ๋‹ค์–‘ํ•œ ํ•จ์ˆ˜๋“ค์ด ์žˆ๋Š”๋ฐ, ๊ฐ๊ฐ ์“ฐ์ž„์ƒˆ๊ฐ€ ๋‹ฌ๋ผ.
์ด๊ฑธ ์ œ๋Œ€๋กœ ์•Œ์•„์•ผ ์ƒํ™ฉ์— ๋งž๋Š” ํ•จ์ˆ˜๋ฅผ ๊ณจ๋ผ ์“ธ ์ˆ˜ ์žˆ๊ฑฐ๋“ . ํ•˜๋‚˜์”ฉ ํŒŒํ—ค์ณ๋ณด์ž! ๐Ÿ”ฌ

โ‘  re.match() โ€” ๋ฌธ์ž์—ด ์‹œ์ž‘๋ถ€ํ„ฐ ๋งค์นญ

re.match()๋Š” ๋ฌธ์ž์—ด์˜ ์ฒ˜์Œ๋ถ€ํ„ฐ ํŒจํ„ด์ด ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธํ•ด.
์ค‘๊ฐ„์— ์žˆ๋Š” ํŒจํ„ด์€ ์ฐพ์ง€ ๋ชปํ•œ๋‹ค๋Š” ์ ์ด ํ•ต์‹ฌ์ด์•ผ!

import re

text1 = "Python is awesome"
text2 = "I love Python"

# match๋Š” ๋ฌธ์ž์—ด ์‹œ์ž‘๋ถ€ํ„ฐ ํ™•์ธ
result1 = re.match(r'Python', text1)
result2 = re.match(r'Python', text2)

print(result1)  # <re.Match object; span=(0, 6), match='Python'>
print(result2)  # None  โ† ์‹œ์ž‘์ด 'Python'์ด ์•„๋‹ˆ๋‹ˆ๊นŒ!

# ๋งค์นญ ๊ฒฐ๊ณผ ํ™œ์šฉ
if result1:
    print(f"๋งค์นญ๋œ ๋ฌธ์ž์—ด: {result1.group()}")    # Python
    print(f"์‹œ์ž‘ ์œ„์น˜: {result1.start()}")         # 0
    print(f"๋ ์œ„์น˜: {result1.end()}")             # 6
    print(f"๋ฒ”์œ„: {result1.span()}")               # (0, 6)

โ‘ก re.search() โ€” ์ „์ฒด ๋ฌธ์ž์—ด์—์„œ ์ฒซ ๋ฒˆ์งธ ๋งค์นญ

re.search()๋Š” ๋ฌธ์ž์—ด ์ „์ฒด๋ฅผ ํƒ์ƒ‰ํ•ด์„œ ์ฒซ ๋ฒˆ์งธ๋กœ ์ผ์น˜ํ•˜๋Š” ํŒจํ„ด์„ ์ฐพ์•„.
re.match()์™€ ๋‹ฌ๋ฆฌ ๋ฌธ์ž์—ด ์ค‘๊ฐ„์— ์žˆ์–ด๋„ ์ฐพ์„ ์ˆ˜ ์žˆ์–ด!

import re

text = "์˜ค๋Š˜ ๋‚ ์”จ๋Š” ๋ง‘์Œ, ๊ธฐ์˜จ์€ 25๋„, ์Šต๋„๋Š” 60%"

# ์ฒซ ๋ฒˆ์งธ ์ˆซ์ž ์ฐพ๊ธฐ
result = re.search(r'\d+', text)
if result:
    print(f"์ฒซ ๋ฒˆ์งธ ์ˆซ์ž: {result.group()}")  # 25
    print(f"์œ„์น˜: {result.span()}")            # (12, 14)

# ์ด๋ฉ”์ผ ์ฐพ๊ธฐ
email_text = "์—ฐ๋ฝ์ฒ˜: contact@example.com ์œผ๋กœ ๋ฉ”์ผ ์ฃผ์„ธ์š”"
email_result = re.search(r'[\w.-]+@[\w.-]+\.\w+', email_text)
if email_result:
    print(f"์ด๋ฉ”์ผ: {email_result.group()}")  # contact@example.com

โ‘ข re.findall() โ€” ๋ชจ๋“  ๋งค์นญ ๊ฒฐ๊ณผ๋ฅผ ๋ฆฌ์ŠคํŠธ๋กœ

์‹ค๋ฌด์—์„œ ๊ฐ€์žฅ ๋งŽ์ด ์“ฐ๋Š” ํ•จ์ˆ˜ ์ค‘ ํ•˜๋‚˜์•ผ! ํŒจํ„ด๊ณผ ์ผ์น˜ํ•˜๋Š” ๋ชจ๋“  ๊ฒฐ๊ณผ๋ฅผ ๋ฆฌ์ŠคํŠธ๋กœ ๋ฐ˜ํ™˜ํ•ด์ค˜.

import re

log_text = """
2024-01-15 ERROR: ์„œ๋ฒ„ ์—ฐ๊ฒฐ ์‹คํŒจ
2024-01-15 INFO: ์žฌ์‹œ๋„ ์ค‘...
2024-01-16 WARNING: ๋ฉ”๋ชจ๋ฆฌ ๋ถ€์กฑ
2024-01-16 ERROR: ํƒ€์ž„์•„์›ƒ ๋ฐœ์ƒ
"""

# ๋ชจ๋“  ๋‚ ์งœ ์ถ”์ถœ
dates = re.findall(r'\d{4}-\d{2}-\d{2}', log_text)
print(dates)  # ['2024-01-15', '2024-01-15', '2024-01-16', '2024-01-16']

# ๋ชจ๋“  ๋กœ๊ทธ ๋ ˆ๋ฒจ ์ถ”์ถœ
levels = re.findall(r'ERROR|WARNING|INFO', log_text)
print(levels)  # ['ERROR', 'INFO', 'WARNING', 'ERROR']

# ๊ทธ๋ฃน์„ ์‚ฌ์šฉํ•œ ์ถ”์ถœ (ํŠœํ”Œ ๋ฆฌ์ŠคํŠธ ๋ฐ˜ํ™˜)
pattern = r'(\d{4}-\d{2}-\d{2})\s+(ERROR|WARNING|INFO)'
matches = re.findall(pattern, log_text)
print(matches)
# [('2024-01-15', 'ERROR'), ('2024-01-15', 'INFO'), 
#  ('2024-01-16', 'WARNING'), ('2024-01-16', 'ERROR')]
๐Ÿ’ก findall() ๊ทธ๋ฃน ๋ฐ˜ํ™˜ ๊ทœ์น™! ๊ทธ๋ฃน์ด ์—†์œผ๋ฉด โ†’ ๋ฌธ์ž์—ด ๋ฆฌ์ŠคํŠธ ๋ฐ˜ํ™˜
๊ทธ๋ฃน์ด 1๊ฐœ๋ฉด โ†’ ๊ทธ๋ฃน ๋‚ด์šฉ์˜ ๋ฌธ์ž์—ด ๋ฆฌ์ŠคํŠธ ๋ฐ˜ํ™˜
๊ทธ๋ฃน์ด 2๊ฐœ ์ด์ƒ์ด๋ฉด โ†’ ํŠœํ”Œ ๋ฆฌ์ŠคํŠธ ๋ฐ˜ํ™˜

โ‘ฃ re.finditer() โ€” ์ดํ„ฐ๋ ˆ์ดํ„ฐ๋กœ ๋งค์นญ ๊ฐ์ฒด ์ˆœํšŒ

re.findall()์ด ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค๋ฉด, re.finditer()๋Š” ์ดํ„ฐ๋ ˆ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•ด.
๋งค์นญ ๊ฐ์ฒด ์ž์ฒด๋ฅผ ์ˆœํšŒํ•  ์ˆ˜ ์žˆ์–ด์„œ ์œ„์น˜ ์ •๋ณด๊นŒ์ง€ ํ™œ์šฉํ•  ๋•Œ ์œ ์šฉํ•ด!

import re

text = "์‚ฌ๊ณผ 3๊ฐœ, ๋ฐ”๋‚˜๋‚˜ 5๊ฐœ, ํฌ๋„ 2๊ฐœ๋ฅผ ์ƒ€์–ด์š”"

# ์ˆซ์ž์™€ ๊ทธ ์œ„์น˜ ์ •๋ณด ํ•จ๊ป˜ ์ถ”์ถœ
for match in re.finditer(r'\d+', text):
    print(f"์ˆซ์ž: {match.group()}, ์œ„์น˜: {match.span()}")

# ์ถœ๋ ฅ:
# ์ˆซ์ž: 3, ์œ„์น˜: (3, 4)
# ์ˆซ์ž: 5, ์œ„์น˜: (10, 11)
# ์ˆซ์ž: 2, ์œ„์น˜: (16, 17)

โ‘ค re.sub() โ€” ํŒจํ„ด ์น˜ํ™˜์˜ ๋ํŒ์™•

re.sub()์€ ํŒจํ„ด์— ๋งค์นญ๋˜๋Š” ๋ถ€๋ถ„์„ ๋‹ค๋ฅธ ๋ฌธ์ž์—ด๋กœ ์น˜ํ™˜ํ•ด์ค˜.
๋‹จ์ˆœ ์น˜ํ™˜๋ถ€ํ„ฐ ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•œ ๋™์  ์น˜ํ™˜๊นŒ์ง€ ๊ฐ€๋Šฅํ•ด์„œ ์ •๋ง ๊ฐ•๋ ฅํ•ด!

import re

# ๊ธฐ๋ณธ ์น˜ํ™˜
text = "Hello   World   Python"
# ์—ฌ๋Ÿฌ ๊ณต๋ฐฑ์„ ํ•˜๋‚˜๋กœ
result = re.sub(r'\s+', ' ', text)
print(result)  # "Hello World Python"

# ์ „ํ™”๋ฒˆํ˜ธ ํ˜•์‹ ํ†ต์ผ
phone_text = "์—ฐ๋ฝ์ฒ˜: 010-1234-5678, 010.9876.5432, 01011112222"
# ๋ชจ๋“  ์ „ํ™”๋ฒˆํ˜ธ๋ฅผ XXX-XXXX-XXXX ํ˜•์‹์œผ๋กœ
normalized = re.sub(r'(\d{3})[-.]?(\d{4})[-.]?(\d{4})', r'\1-\2-\3', phone_text)
print(normalized)  # "์—ฐ๋ฝ์ฒ˜: 010-1234-5678, 010-9876-5432, 010-1111-2222"

# ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•œ ๋™์  ์น˜ํ™˜ (๊ฐ•๋ ฅ!)
def double_number(match):
    return str(int(match.group()) * 2)

text2 = "์‚ฌ๊ณผ 3๊ฐœ, ๋ฐ”๋‚˜๋‚˜ 5๊ฐœ, ํฌ๋„ 2๊ฐœ"
result2 = re.sub(r'\d+', double_number, text2)
print(result2)  # "์‚ฌ๊ณผ 6๊ฐœ, ๋ฐ”๋‚˜๋‚˜ 10๊ฐœ, ํฌ๋„ 4๊ฐœ"

# count ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์น˜ํ™˜ ํšŸ์ˆ˜ ์ œํ•œ
text3 = "aaa bbb aaa ccc aaa"
result3 = re.sub(r'aaa', 'XXX', text3, count=2)
print(result3)  # "XXX bbb XXX ccc aaa"

โ‘ฅ re.split() โ€” ํŒจํ„ด์œผ๋กœ ๋ฌธ์ž์—ด ๋ถ„๋ฆฌ

import re

# ๋‹ค์–‘ํ•œ ๊ตฌ๋ถ„์ž๋กœ ๋ถ„๋ฆฌ
text = "์‚ฌ๊ณผ,๋ฐ”๋‚˜๋‚˜;ํฌ๋„ ๋”ธ๊ธฐ|์ˆ˜๋ฐ•"
result = re.split(r'[,;\s|]+', text)
print(result)  # ['์‚ฌ๊ณผ', '๋ฐ”๋‚˜๋‚˜', 'ํฌ๋„', '๋”ธ๊ธฐ', '์ˆ˜๋ฐ•']

# ๊ตฌ๋ถ„์ž๋„ ๊ฒฐ๊ณผ์— ํฌํ•จ์‹œํ‚ค๊ธฐ (๊ทธ๋ฃน ์‚ฌ์šฉ)
text2 = "one1two2three3four"
result2 = re.split(r'(\d)', text2)
print(result2)  # ['one', '1', 'two', '2', 'three', '3', 'four']

# maxsplit์œผ๋กœ ๋ถ„๋ฆฌ ํšŸ์ˆ˜ ์ œํ•œ
result3 = re.split(r'\d', text2, maxsplit=2)
print(result3)  # ['one', 'two', 'three3four']
ํ•จ์ˆ˜๋ฐ˜ํ™˜๊ฐ’์ฃผ์š” ์šฉ๋„
re.match()Match ๊ฐ์ฒด / None๋ฌธ์ž์—ด ์‹œ์ž‘ ํŒจํ„ด ํ™•์ธ
re.search()Match ๊ฐ์ฒด / None์ฒซ ๋ฒˆ์งธ ํŒจํ„ด ํƒ์ƒ‰
re.findall()๋ฆฌ์ŠคํŠธ๋ชจ๋“  ๋งค์นญ ์ถ”์ถœ
re.finditer()์ดํ„ฐ๋ ˆ์ดํ„ฐ์œ„์น˜ ์ •๋ณด ํฌํ•จ ์ˆœํšŒ
re.sub()๋ฌธ์ž์—ดํŒจํ„ด ์น˜ํ™˜
re.split()๋ฆฌ์ŠคํŠธํŒจํ„ด์œผ๋กœ ๋ถ„๋ฆฌ
re.compile()Pattern ๊ฐ์ฒดํŒจํ„ด ์ปดํŒŒ์ผ (์žฌ์‚ฌ์šฉ)

๐Ÿงฉ 3. ์ •๊ทœํ‘œํ˜„์‹ ํŒจํ„ด ๋ฌธ๋ฒ• โ€” ์‹ค์ „ ๋ ˆ์‹œํ”ผ

์ด์ œ ์ง„์งœ ํ•ต์‹ฌ์ด์•ผ! ํŒจํ„ด ๋ฌธ๋ฒ•์„ ์ œ๋Œ€๋กœ ์•Œ์•„์•ผ ์ •๊ทœํ‘œํ˜„์‹์„ ์ž์œ ์ž์žฌ๋กœ ์“ธ ์ˆ˜ ์žˆ์–ด.
์™ธ์šธ ํ•„์š” ์—†์–ด. ์ดํ•ดํ•˜๋ฉด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์†์— ์ต๊ฑฐ๋“ . ๐Ÿ˜Ž

๐Ÿ”ค ๋ฌธ์ž ํด๋ž˜์Šค (Character Classes)

import re

text = "Hello World 123 !@#"

# \d : ์ˆซ์ž [0-9]
print(re.findall(r'\d', text))   # ['1', '2', '3']

# \D : ์ˆซ์ž๊ฐ€ ์•„๋‹Œ ๊ฒƒ
print(re.findall(r'\D+', text))  # ['Hello World ', ' !@#']

# \w : ๋‹จ์–ด ๋ฌธ์ž [a-zA-Z0-9_]
print(re.findall(r'\w+', text))  # ['Hello', 'World', '123']

# \W : ๋‹จ์–ด ๋ฌธ์ž๊ฐ€ ์•„๋‹Œ ๊ฒƒ
print(re.findall(r'\W+', text))  # [' ', ' ', ' !@#']

# \s : ๊ณต๋ฐฑ ๋ฌธ์ž (์ŠคํŽ˜์ด์Šค, ํƒญ, ์ค„๋ฐ”๊ฟˆ ๋“ฑ)
print(re.findall(r'\s+', text))  # [' ', ' ', ' ']

# \S : ๊ณต๋ฐฑ์ด ์•„๋‹Œ ๊ฒƒ
print(re.findall(r'\S+', text))  # ['Hello', 'World', '123', '!@#']

# . : ์ค„๋ฐ”๊ฟˆ ์ œ์™ธ ๋ชจ๋“  ๋ฌธ์ž
print(re.findall(r'.', "ab\nc"))  # ['a', 'b', 'c']  โ† \n ์ œ์™ธ!

๐Ÿ“ ์ˆ˜๋Ÿ‰์ž (Quantifiers)

import re

text = "color colour colouur"

# * : 0ํšŒ ์ด์ƒ
print(re.findall(r'colou*r', text))   # ['color', 'colour', 'colouur']

# + : 1ํšŒ ์ด์ƒ
print(re.findall(r'colou+r', text))   # ['colour', 'colouur']

# ? : 0ํšŒ ๋˜๋Š” 1ํšŒ
print(re.findall(r'colou?r', text))   # ['color', 'colour']

# {n} : ์ •ํ™•ํžˆ nํšŒ
print(re.findall(r'colou{2}r', text)) # ['colouur']

# {n,m} : nํšŒ ์ด์ƒ mํšŒ ์ดํ•˜
print(re.findall(r'colou{1,2}r', text)) # ['colour', 'colouur']

# {n,} : nํšŒ ์ด์ƒ
print(re.findall(r'colou{1,}r', text))  # ['colour', 'colouur']
โš ๏ธ ํƒ์š•์ (Greedy) vs ๋น„ํƒ์š•์ (Non-Greedy) ๋งค์นญ! ๊ธฐ๋ณธ์ ์œผ๋กœ ์ˆ˜๋Ÿ‰์ž๋Š” ํƒ์š•์ ์ด์•ผ โ€” ๊ฐ€๋Šฅํ•œ ํ•œ ๋งŽ์ด ๋งค์นญํ•˜๋ ค๊ณ  ํ•ด.
?๋ฅผ ์ˆ˜๋Ÿ‰์ž ๋’ค์— ๋ถ™์ด๋ฉด ๋น„ํƒ์š•์ ์ด ๋ผ โ€” ๊ฐ€๋Šฅํ•œ ํ•œ ์ ๊ฒŒ ๋งค์นญํ•ด.
import re

html = "<b>๊ตต๊ฒŒ</b> ๊ทธ๋ฆฌ๊ณ  <i>๊ธฐ์šธ์ž„</i>"

# ํƒ์š•์  ๋งค์นญ (greedy) - ๋„ˆ๋ฌด ๋งŽ์ด ์žก์•„๋ฒ„๋ฆผ
greedy = re.findall(r'<.+>', html)
print(greedy)  # ['<b>๊ตต๊ฒŒ</b> ๊ทธ๋ฆฌ๊ณ  <i>๊ธฐ์šธ์ž„</i>']  โ† ์ „์ฒด๋ฅผ ํ•˜๋‚˜๋กœ!

# ๋น„ํƒ์š•์  ๋งค์นญ (non-greedy) - ๋”ฑ ํ•„์š”ํ•œ ๋งŒํผ๋งŒ
non_greedy = re.findall(r'<.+?>', html)
print(non_greedy)  # ['<b>', '</b>', '<i>', '</i>']  โ† ๊ฐ ํƒœ๊ทธ๋ฅผ ๋”ฐ๋กœ!

๐ŸŽฏ ์•ต์ปค (Anchors) โ€” ์œ„์น˜๋ฅผ ์ง€์ •ํ•ด

import re

# ^ : ๋ฌธ์ž์—ด(๋˜๋Š” ์ค„)์˜ ์‹œ์ž‘
print(re.findall(r'^\d+', "123abc"))    # ['123']
print(re.findall(r'^\d+', "abc123"))    # []  โ† ์‹œ์ž‘์ด ์ˆซ์ž๊ฐ€ ์•„๋‹˜

# $ : ๋ฌธ์ž์—ด(๋˜๋Š” ์ค„)์˜ ๋
print(re.findall(r'\d+$', "abc123"))    # ['123']
print(re.findall(r'\d+$', "123abc"))    # []  โ† ๋์ด ์ˆซ์ž๊ฐ€ ์•„๋‹˜

# \b : ๋‹จ์–ด ๊ฒฝ๊ณ„
text = "cat concatenate cats"
print(re.findall(r'\bcat\b', text))     # ['cat']  โ† ์ •ํ™•ํžˆ 'cat'๋งŒ
print(re.findall(r'cat', text))         # ['cat', 'cat', 'cat']  โ† ๋ชจ๋‘ ํฌํ•จ

# \B : ๋‹จ์–ด ๊ฒฝ๊ณ„๊ฐ€ ์•„๋‹Œ ๊ณณ
print(re.findall(r'\Bcat\B', "concatenate"))  # ['cat']  โ† ๋‹จ์–ด ์ค‘๊ฐ„์˜ cat

๐Ÿ—‚๏ธ ๊ทธ๋ฃน (Groups) โ€” ํŒจํ„ด์„ ๋ฌถ์–ด์„œ ํ™œ์šฉ

๊ทธ๋ฃน์€ ์ •๊ทœํ‘œํ˜„์‹์˜ ๊ฝƒ์ด๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ์–ด! ํŒจํ„ด์„ ๋ฌถ์–ด์„œ ์ถ”์ถœํ•˜๊ฑฐ๋‚˜, ์—ญ์ฐธ์กฐํ•˜๊ฑฐ๋‚˜, ์ด๋ฆ„์„ ๋ถ™์ผ ์ˆ˜ ์žˆ์–ด.

import re

# ๊ธฐ๋ณธ ๊ทธ๋ฃน ()
text = "2024-01-15"
match = re.match(r'(\d{4})-(\d{2})-(\d{2})', text)
if match:
    print(match.group(0))   # '2024-01-15'  (์ „์ฒด)
    print(match.group(1))   # '2024'        (์ฒซ ๋ฒˆ์งธ ๊ทธ๋ฃน)
    print(match.group(2))   # '01'          (๋‘ ๋ฒˆ์งธ ๊ทธ๋ฃน)
    print(match.group(3))   # '15'          (์„ธ ๋ฒˆ์งธ ๊ทธ๋ฃน)
    print(match.groups())   # ('2024', '01', '15')

# ์ด๋ฆ„ ์žˆ๋Š” ๊ทธ๋ฃน (?P<name>...)
match2 = re.match(r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})', text)
if match2:
    print(match2.group('year'))   # '2024'
    print(match2.group('month'))  # '01'
    print(match2.group('day'))    # '15'
    print(match2.groupdict())     # {'year': '2024', 'month': '01', 'day': '15'}

# ๋น„์บก์ฒ˜ ๊ทธ๋ฃน (?:...) - ๊ทธ๋ฃนํ™”๋Š” ํ•˜๋˜ ์บก์ฒ˜๋Š” ์•ˆ ํ•จ
text3 = "Mr. Smith and Mrs. Jones"
result = re.findall(r'(?:Mr|Mrs)\. (\w+)', text3)
print(result)  # ['Smith', 'Jones']  โ† Mr/Mrs๋Š” ์บก์ฒ˜ ์•ˆ ๋จ

# ์—ญ์ฐธ์กฐ (Backreference) - ๊ฐ™์€ ํŒจํ„ด ๋ฐ˜๋ณต ์ฐพ๊ธฐ
text4 = "hello hello world world"
result4 = re.findall(r'\b(\w+) \1\b', text4)
print(result4)  # ['hello', 'world']  โ† ์—ฐ์†์œผ๋กœ ๋ฐ˜๋ณต๋œ ๋‹จ์–ด!
๊ทธ๋ฃน ์ข…๋ฅ˜ ํ•œ๋ˆˆ์— ๋น„๊ต ๐Ÿ“ฆ ์บก์ฒ˜ ๊ทธ๋ฃน ( ... ) ๋งค์นญ ๊ฒฐ๊ณผ๋ฅผ ์บก์ฒ˜ํ•ด์„œ group()์œผ๋กœ ์ ‘๊ทผ ๊ฐ€๋Šฅ r'(\d{4})-(\d{2})' โ†’ group(1), group(2) โœ… ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ๊ทธ๋ฃน ๐Ÿท๏ธ ์ด๋ฆ„ ์žˆ๋Š” ๊ทธ๋ฃน (?P<name> ... ) ์ด๋ฆ„์œผ๋กœ ๊ทธ๋ฃน์— ์ ‘๊ทผ ์ฝ”๋“œ ๊ฐ€๋…์„ฑ ํ–ฅ์ƒ r'(?P<year>\d{4})' โ†’ group('year') โœ… ๋ณต์žกํ•œ ํŒจํ„ด์— ์ถ”์ฒœ ๐Ÿšซ ๋น„์บก์ฒ˜ ๊ทธ๋ฃน (?: ... ) ๊ทธ๋ฃนํ™”๋Š” ํ•˜๋˜ ์บก์ฒ˜๋Š” ํ•˜์ง€ ์•Š์Œ r'(?:Mr|Mrs)\.(\w+)' โ†’ ์ด๋ฆ„๋งŒ ์บก์ฒ˜ โœ… ์„ฑ๋Šฅ ์ตœ์ ํ™”์— ์œ ๋ฆฌ ๐Ÿ‘€ ๋ฃฉ์–ดํ—ค๋“œ (Lookahead) ์•ž์— ํŠน์ • ํŒจํ„ด์ด ์˜ค๋Š”/์˜ค์ง€ ์•Š๋Š” ๊ฒฝ์šฐ๋งŒ ๋งค์นญ ๊ธ์ •: (?=ํŒจํ„ด) ๋ถ€์ •: (?!ํŒจํ„ด) r'\d+(?=์›)' โ†’ "100์›"์—์„œ "100"๋งŒ ์ถ”์ถœ r'\d+(?!์›)' โ†’ "100์› 200๊ฐœ"์—์„œ "200"๋งŒ ์ถ”์ถœ ๐Ÿ’ก ๋งค์นญ์— ํฌํ•จ๋˜์ง€ ์•Š๋Š” ์กฐ๊ฑด ๊ฒ€์‚ฌ! ๐Ÿ”™ ๋ฃฉ๋น„ํ•˜์ธ๋“œ (Lookbehind) ๋’ค์— ํŠน์ • ํŒจํ„ด์ด ์˜ค๋Š”/์˜ค์ง€ ์•Š๋Š” ๊ฒฝ์šฐ๋งŒ ๋งค์นญ ๊ธ์ •: (?<=ํŒจํ„ด) ๋ถ€์ •: (?<!ํŒจํ„ด) r'(?<=\$)\d+' โ†’ "$100"์—์„œ "100"๋งŒ ์ถ”์ถœ r'(?<!\$)\d+' โ†’ "$100 200"์—์„œ "200"๋งŒ ์ถ”์ถœ ๐Ÿ’ก ๊ณ ์ • ๊ธธ์ด ํŒจํ„ด๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ!

๐Ÿ‘€ ๋ฃฉ์–ดํ—ค๋“œ & ๋ฃฉ๋น„ํ•˜์ธ๋“œ โ€” ์ œ๋กœ ๋„ˆ๋น„ ์–ด์„œ์…˜

์ด๊ฑด ์ข€ ๊ณ ๊ธ‰ ๊ธฐ์ˆ ์ธ๋ฐ, ์•Œ๋ฉด ์ •๋ง ๊ฐ•๋ ฅํ•ด! ๋งค์นญ ๊ฒฐ๊ณผ์—๋Š” ํฌํ•จ๋˜์ง€ ์•Š์œผ๋ฉด์„œ ์กฐ๊ฑด๋งŒ ๊ฒ€์‚ฌํ•˜๋Š” ๊ธฐ๋ฒ•์ด์•ผ.

import re

# ๊ธ์ • ๋ฃฉ์–ดํ—ค๋“œ (?=...) - ๋’ค์— ํŠน์ • ํŒจํ„ด์ด ์˜ค๋Š” ๊ฒฝ์šฐ
prices = "์‚ฌ๊ณผ 1500์›, ๋ฐ”๋‚˜๋‚˜ 800์›, ํฌ๋„ 3000์›"
# '์›' ์•ž์˜ ์ˆซ์ž๋งŒ ์ถ”์ถœ
numbers = re.findall(r'\d+(?=์›)', prices)
print(numbers)  # ['1500', '800', '3000']

# ๋ถ€์ • ๋ฃฉ์–ดํ—ค๋“œ (?!...) - ๋’ค์— ํŠน์ • ํŒจํ„ด์ด ์˜ค์ง€ ์•Š๋Š” ๊ฒฝ์šฐ
text = "file.txt file.py file.jpg"
# .py๊ฐ€ ์•„๋‹Œ ํ™•์žฅ์ž ์ถ”์ถœ
exts = re.findall(r'\.\w+(?!py)\b', text)
print(exts)  # ['.txt', '.jpg']

# ๊ธ์ • ๋ฃฉ๋น„ํ•˜์ธ๋“œ (?<=...) - ์•ž์— ํŠน์ • ํŒจํ„ด์ด ์˜ค๋Š” ๊ฒฝ์šฐ
text2 = "๊ฐ€๊ฒฉ: $100, ํ• ์ธ: $30, ์ด์•ก: $70"
# $ ๋’ค์˜ ์ˆซ์ž๋งŒ ์ถ”์ถœ
amounts = re.findall(r'(?<=\$)\d+', text2)
print(amounts)  # ['100', '30', '70']

# ๋ถ€์ • ๋ฃฉ๋น„ํ•˜์ธ๋“œ (?<!...) - ์•ž์— ํŠน์ • ํŒจํ„ด์ด ์˜ค์ง€ ์•Š๋Š” ๊ฒฝ์šฐ
text3 = "un-happy, un-lucky, lucky, happy"
# 'un-' ๋’ค์— ์˜ค์ง€ ์•Š๋Š” ๋‹จ์–ด๋งŒ
words = re.findall(r'(?<!un-)\b\w+\b', text3)
print(words)  # ['un', 'un', 'lucky', 'happy']  โ† ์ฃผ์˜: 'un'๋„ ํฌํ•จ๋จ

โšก 4. re.compile() โ€” ์„ฑ๋Šฅ์„ ์œ„ํ•œ ํŒจํ„ด ์ปดํŒŒ์ผ

๊ฐ™์€ ํŒจํ„ด์„ ์—ฌ๋Ÿฌ ๋ฒˆ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด re.compile()๋กœ ๋ฏธ๋ฆฌ ์ปดํŒŒ์ผํ•ด๋‘๋Š” ๊ฒŒ ํ›จ์”ฌ ํšจ์œจ์ ์ด์•ผ!
ํŒจํ„ด์„ ๋งค๋ฒˆ ํŒŒ์‹ฑํ•˜๋Š” ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ๊ฑฐ๋“ . ์‹ค๋ฌด์—์„œ ๋Œ€์šฉ๋Ÿ‰ ํ…์ŠคํŠธ ์ฒ˜๋ฆฌํ•  ๋•Œ ํŠนํžˆ ์ค‘์š”ํ•ด.

import re
import time

# ์ปดํŒŒ์ผ ์—†์ด ์‚ฌ์šฉ
start = time.time()
text_list = ["์ด๋ฉ”์ผ: user@example.com"] * 100000
for text in text_list:
    re.findall(r'[\w.-]+@[\w.-]+\.\w+', text)
print(f"์ปดํŒŒ์ผ ์—†์ด: {time.time() - start:.3f}์ดˆ")

# ์ปดํŒŒ์ผ ํ›„ ์‚ฌ์šฉ
email_pattern = re.compile(r'[\w.-]+@[\w.-]+\.\w+')
start = time.time()
for text in text_list:
    email_pattern.findall(text)
print(f"์ปดํŒŒ์ผ ํ›„: {time.time() - start:.3f}์ดˆ")
# ์ปดํŒŒ์ผ ํ›„๊ฐ€ ํ›จ์”ฌ ๋น ๋ฆ„!

# ์ปดํŒŒ์ผ๋œ ํŒจํ„ด ๊ฐ์ฒด์˜ ๋ฉ”์„œ๋“œ๋“ค
pattern = re.compile(r'(\d{4})-(\d{2})-(\d{2})')

# ๋™์ผํ•œ ๋ฉ”์„œ๋“œ๋“ค์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด
result = pattern.match("2024-01-15 ์˜ค๋Š˜ ๋‚ ์งœ")
print(result.groups())  # ('2024', '01', '15')

result2 = pattern.findall("2024-01-15 ~ 2024-12-31")
print(result2)  # [('2024', '01', '15'), ('2024', '12', '31')]

๐Ÿšฉ ํ”Œ๋ž˜๊ทธ (Flags) โ€” ๋งค์นญ ๋™์ž‘ ์ œ์–ด

ํ”Œ๋ž˜๊ทธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ •๊ทœํ‘œํ˜„์‹์˜ ๋™์ž‘ ๋ฐฉ์‹์„ ์„ธ๋ฐ€ํ•˜๊ฒŒ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์–ด!

import re

# re.IGNORECASE (re.I) - ๋Œ€์†Œ๋ฌธ์ž ๋ฌด์‹œ
text = "Python python PYTHON"
result = re.findall(r'python', text, re.IGNORECASE)
print(result)  # ['Python', 'python', 'PYTHON']

# re.MULTILINE (re.M) - ์—ฌ๋Ÿฌ ์ค„ ๋ชจ๋“œ (^, $๊ฐ€ ๊ฐ ์ค„์— ์ ์šฉ)
multiline_text = """์ฒซ ๋ฒˆ์งธ ์ค„
๋‘ ๋ฒˆ์งธ ์ค„
์„ธ ๋ฒˆ์งธ ์ค„"""

# MULTILINE ์—†์ด - ์ „์ฒด ๋ฌธ์ž์—ด์˜ ์‹œ์ž‘๋งŒ
result1 = re.findall(r'^\w+', multiline_text)
print(result1)  # ['์ฒซ']

# MULTILINE ์žˆ์ด - ๊ฐ ์ค„์˜ ์‹œ์ž‘
result2 = re.findall(r'^\w+', multiline_text, re.MULTILINE)
print(result2)  # ['์ฒซ', '๋‘', '์„ธ']

# re.DOTALL (re.S) - . ์ด ์ค„๋ฐ”๊ฟˆ๋„ ํฌํ•จ
text2 = "์‹œ์ž‘\n์ค‘๊ฐ„\n๋"
print(re.findall(r'์‹œ์ž‘.+๋', text2))           # []  โ† . ์ด \n ๋ชป ๋„˜์Œ
print(re.findall(r'์‹œ์ž‘.+๋', text2, re.DOTALL)) # ['์‹œ์ž‘\n์ค‘๊ฐ„\n๋']

# re.VERBOSE (re.X) - ์ฃผ์„๊ณผ ๊ณต๋ฐฑ ํ—ˆ์šฉ (๋ณต์žกํ•œ ํŒจํ„ด ๊ฐ€๋…์„ฑ ํ–ฅ์ƒ!)
email_pattern = re.compile(r'''
    [\w.+-]+        # ๋กœ์ปฌ ํŒŒํŠธ (์‚ฌ์šฉ์ž๋ช…)
    @               # @ ๊ธฐํ˜ธ
    [\w-]+          # ๋„๋ฉ”์ธ ์ด๋ฆ„
    (?:\.[\w-]+)*   # ์„œ๋ธŒ๋„๋ฉ”์ธ (์„ ํƒ์ )
    \.\w{2,}        # ์ตœ์ƒ์œ„ ๋„๋ฉ”์ธ (.com, .kr ๋“ฑ)
''', re.VERBOSE)

emails = ["user@example.com", "test.email+tag@sub.domain.co.kr"]
for email in emails:
    if email_pattern.match(email):
        print(f"โœ… ์œ ํšจํ•œ ์ด๋ฉ”์ผ: {email}")

# ์—ฌ๋Ÿฌ ํ”Œ๋ž˜๊ทธ ์กฐํ•ฉ (| ์‚ฌ์šฉ)
pattern = re.compile(r'python', re.IGNORECASE | re.MULTILINE)
โœ… re.VERBOSE ๊ฐ•๋ ฅ ์ถ”์ฒœ! ๋ณต์žกํ•œ ์ •๊ทœํ‘œํ˜„์‹์„ ์ž‘์„ฑํ•  ๋•Œ re.VERBOSE ํ”Œ๋ž˜๊ทธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ฃผ์„์„ ๋‹ฌ ์ˆ˜ ์žˆ์–ด์„œ ๋‚˜์ค‘์— ์ฝ”๋“œ๋ฅผ ์ฝ์„ ๋•Œ ํ›จ์”ฌ ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์›Œ. ํŒ€ ํ”„๋กœ์ ํŠธ์—์„œ ํŠนํžˆ ์œ ์šฉํ•ด!

๐Ÿ› ๏ธ 5. ์‹ค์ „ ํŒจํ„ด ๋ ˆ์‹œํ”ผ โ€” ์ž์ฃผ ์“ฐ๋Š” ์ •๊ทœํ‘œํ˜„์‹ ๋ชจ์Œ

์ด๋ก ์€ ์ถฉ๋ถ„ํžˆ ๋ฐฐ์› ์œผ๋‹ˆ๊นŒ, ์ด์ œ ์‹ค์ œ๋กœ ์ž์ฃผ ์“ฐ์ด๋Š” ํŒจํ„ด๋“ค์„ ๋ชจ์•„๋ดค์–ด!
๋ณต๋ถ™ํ•ด์„œ ๋ฐ”๋กœ ์“ธ ์ˆ˜ ์žˆ๋Š” ์‹ค์šฉ์ ์ธ ๋ ˆ์‹œํ”ผ๋“ค์ด์•ผ. ๐Ÿณ

๐Ÿณ ์‹ค์ „ ์ •๊ทœํ‘œํ˜„์‹ ๋ ˆ์‹œํ”ผ๋ถ ๐Ÿ“ง ์ด๋ฉ”์ผ ์ฃผ์†Œ r'[\w.+-]+@[\w-]+(?:\.[\w-]+)*\.\w{2,}' ์˜ˆ: user@example.com, test+tag@sub.domain.co.kr โœ… ๋Œ€๋ถ€๋ถ„์˜ ์ด๋ฉ”์ผ ํ˜•์‹ ์ปค๋ฒ„ ๐Ÿ“ฑ ํ•œ๊ตญ ์ „ํ™”๋ฒˆํ˜ธ r'0\d{1,2}[-.]?\d{3,4}[-.]?\d{4}' ์˜ˆ: 010-1234-5678, 02.123.4567 โœ… ํœด๋Œ€ํฐ + ์ผ๋ฐ˜์ „ํ™” ๋ชจ๋‘ ์ปค๋ฒ„ ๐ŸŒ URL r'https?://[\w.-]+(?:/[\w./?=%&-]*)?' ์˜ˆ: https://www.example.com/path?q=1 โœ… http/https ๋ชจ๋‘ ์ง€์› ๐Ÿ–ฅ๏ธ IP ์ฃผ์†Œ (IPv4) r'\b(?:\d{1,3}\.){3}\d{1,3}\b' ์˜ˆ: 192.168.1.1, 10.0.0.255 โš ๏ธ 0-255 ๋ฒ”์œ„ ๊ฒ€์ฆ์€ ๋ณ„๋„ ํ•„์š” ๐Ÿ“… ๋‚ ์งœ (YYYY-MM-DD) r'\d{4}[-/]\d{2}[-/]\d{2}' ์˜ˆ: 2024-01-15, 2024/12/31 โœ… - ์™€ / ๊ตฌ๋ถ„์ž ๋ชจ๋‘ ์ง€์› ๐Ÿชช ์ฃผ๋ฏผ๋“ฑ๋ก๋ฒˆํ˜ธ (๋งˆ์Šคํ‚น์šฉ) r'\d{6}-[1-4]\d{6}' ์˜ˆ: 900101-1234567 โš ๏ธ ๊ฐœ์ธ์ •๋ณด ์ฒ˜๋ฆฌ ์‹œ ์ฃผ์˜! ๐Ÿท๏ธ HTML ํƒœ๊ทธ ์ œ๊ฑฐ r'<[^>]+>' ์˜ˆ: <b>ํ…์ŠคํŠธ</b> โ†’ ํ…์ŠคํŠธ โš ๏ธ ๋ณต์žกํ•œ HTML์€ ํŒŒ์„œ ์‚ฌ์šฉ ๊ถŒ์žฅ ๐Ÿ‡ฐ๐Ÿ‡ท ํ•œ๊ธ€ ๋ฌธ์ž r'[๊ฐ€-ํžฃ]+' ์˜ˆ: "Hello ์•ˆ๋…• World" โ†’ ['์•ˆ๋…•'] โœ… ์œ ๋‹ˆ์ฝ”๋“œ ๋ฒ”์œ„๋กœ ํ•œ๊ธ€ ์ถ”์ถœ
import re

# ===== ์‹ค์ „ ์˜ˆ์ œ ๋ชจ์Œ =====

# 1. ์ด๋ฉ”์ผ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
def validate_email(email):
    pattern = re.compile(r'^[\w.+-]+@[\w-]+(?:\.[\w-]+)*\.\w{2,}$')
    return bool(pattern.match(email))

print(validate_email("user@example.com"))     # True
print(validate_email("invalid.email"))         # False
print(validate_email("test@sub.domain.co.kr")) # True

# 2. ์ „ํ™”๋ฒˆํ˜ธ ์ •๊ทœํ™”
def normalize_phone(phone):
    # ์ˆซ์ž๋งŒ ์ถ”์ถœ
    digits = re.sub(r'\D', '', phone)
    # ํ˜•์‹ ์ ์šฉ
    if len(digits) == 11:  # 010-XXXX-XXXX
        return re.sub(r'(\d{3})(\d{4})(\d{4})', r'\1-\2-\3', digits)
    elif len(digits) == 10:  # 02-XXXX-XXXX ๋˜๋Š” 0XX-XXX-XXXX
        return re.sub(r'(\d{2,3})(\d{3,4})(\d{4})', r'\1-\2-\3', digits)
    return phone

print(normalize_phone("010.1234.5678"))  # 010-1234-5678
print(normalize_phone("01012345678"))    # 010-1234-5678
print(normalize_phone("02-123-4567"))    # 02-123-4567

# 3. HTML ํƒœ๊ทธ ์ œ๊ฑฐ
def strip_html(html_text):
    clean = re.sub(r'<[^>]+>', '', html_text)
    # ์—ฐ์† ๊ณต๋ฐฑ ์ •๋ฆฌ
    clean = re.sub(r'\s+', ' ', clean).strip()
    return clean

html = "<h1>์ œ๋ชฉ</h1><p>์ด๊ฒƒ์€ <b>๊ตต์€</b> ํ…์ŠคํŠธ์ž…๋‹ˆ๋‹ค.</p>"
print(strip_html(html))  # "์ œ๋ชฉ ์ด๊ฒƒ์€ ๊ตต์€ ํ…์ŠคํŠธ์ž…๋‹ˆ๋‹ค."

# 4. ๊ฐœ์ธ์ •๋ณด ๋งˆ์Šคํ‚น
def mask_personal_info(text):
    # ์ด๋ฉ”์ผ ๋งˆ์Šคํ‚น
    text = re.sub(r'([\w.+-]{2})[\w.+-]*(@[\w.-]+)', r'\1***\2', text)
    # ์ „ํ™”๋ฒˆํ˜ธ ๋งˆ์Šคํ‚น
    text = re.sub(r'(\d{3})-(\d{4})-(\d{4})', r'\1-****-\3', text)
    return text

personal_text = "์—ฐ๋ฝ์ฒ˜: 010-1234-5678, ์ด๋ฉ”์ผ: username@example.com"
print(mask_personal_info(personal_text))
# "์—ฐ๋ฝ์ฒ˜: 010-****-5678, ์ด๋ฉ”์ผ: us***@example.com"

# 5. ํ•œ๊ธ€๋งŒ ์ถ”์ถœ
def extract_korean(text):
    return re.findall(r'[๊ฐ€-ํžฃ]+', text)

mixed = "Hello ์•ˆ๋…•ํ•˜์„ธ์š” World ๋ฐ˜๊ฐ‘์Šต๋‹ˆ๋‹ค 123"
print(extract_korean(mixed))  # ['์•ˆ๋…•ํ•˜์„ธ์š”', '๋ฐ˜๊ฐ‘์Šต๋‹ˆ๋‹ค']

๐Ÿ”ฌ 6. ๊ณ ๊ธ‰ ๋ฌธ์ž์—ด ์กฐ์ž‘ โ€” Python ๋‚ด์žฅ ๊ธฐ๋Šฅ ์™„์ „ ํ™œ์šฉ

์ •๊ทœํ‘œํ˜„์‹๋งŒ์ด ๋‹ต์€ ์•„๋‹ˆ์•ผ! Python์˜ ๋‚ด์žฅ ๋ฌธ์ž์—ด ๋ฉ”์„œ๋“œ๋“ค๋„ ์—„์ฒญ ๊ฐ•๋ ฅํ•˜๊ฑฐ๋“ .
์ •๊ทœํ‘œํ˜„์‹๊ณผ ๋‚ด์žฅ ๋ฉ”์„œ๋“œ๋ฅผ ์ ์ ˆํžˆ ์กฐํ•ฉํ•˜๋ฉด ์ตœ๊ฐ•์˜ ํ…์ŠคํŠธ ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•ด. ๐Ÿ’ช

๐Ÿ”„ str.translate() โ€” ๋ฌธ์ž ๋‹จ์œ„ ๋ณ€ํ™˜์˜ ์ตœ๊ฐ•์ž

str.translate()๋Š” ๋ฌธ์ž ๋‹จ์œ„๋กœ ๋ณ€ํ™˜ํ•˜๊ฑฐ๋‚˜ ์‚ญ์ œํ•  ๋•Œ ์ •๊ทœํ‘œํ˜„์‹๋ณด๋‹ค ํ›จ์”ฌ ๋น ๋ฅธ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„!

# str.maketrans() + str.translate() ์กฐํ•ฉ
# ํŠน์ˆ˜๋ฌธ์ž ์ œ๊ฑฐ
text = "Hello, World! How are you? I'm fine."

# ์ œ๊ฑฐํ•  ๋ฌธ์ž๋“ค ์ง€์ •
remove_chars = ",.!?'"
table = str.maketrans('', '', remove_chars)
result = text.translate(table)
print(result)  # "Hello World How are you Im fine"

# ๋ฌธ์ž ์น˜ํ™˜
# ์˜์–ด ์†Œ๋ฌธ์ž๋ฅผ ๋Œ€๋ฌธ์ž๋กœ, ํŠน์ˆ˜๋ฌธ์ž ์ œ๊ฑฐ
table2 = str.maketrans(
    'abcdefghijklmnopqrstuvwxyz',  # ๋ณ€ํ™˜ ์ „
    'ABCDEFGHIJKLMNOPQRSTUVWXYZ',  # ๋ณ€ํ™˜ ํ›„
    ',.!?'                          # ์‚ญ์ œํ•  ๋ฌธ์ž
)
result2 = text.translate(table2)
print(result2)  # "HELLO WORLD HOW ARE YOU I'M FINE"

# ์œ ๋‹ˆ์ฝ”๋“œ ํ™œ์šฉ - ์ „๊ฐ ๋ฌธ์ž๋ฅผ ๋ฐ˜๊ฐ์œผ๋กœ
fullwidth = "๏ผก๏ผข๏ผฃ๏ผค๏ผฅ๏ผ‘๏ผ’๏ผ“๏ผ”๏ผ•"
table3 = str.maketrans(
    ''.join(chr(0xFF01 + i) for i in range(94)),  # ์ „๊ฐ ๋ฌธ์ž
    ''.join(chr(0x21 + i) for i in range(94))      # ๋ฐ˜๊ฐ ๋ฌธ์ž
)
print(fullwidth.translate(table3))  # "ABCDE12345"

โœ‚๏ธ ๊ณ ๊ธ‰ ์Šฌ๋ผ์ด์‹ฑ๊ณผ ๋ฌธ์ž์—ด ๋ถ„๋ฆฌ

# ๋‹ค์–‘ํ•œ ๋ฌธ์ž์—ด ๋ถ„๋ฆฌ ๊ธฐ๋ฒ•

# partition() - ๊ตฌ๋ถ„์ž ๊ธฐ์ค€์œผ๋กœ 3๋ถ„ํ• 
text = "์ด๋ฆ„: ํ™๊ธธ๋™"
before, sep, after = text.partition(': ')
print(before)  # "์ด๋ฆ„"
print(sep)     # ": "
print(after)   # "ํ™๊ธธ๋™"

# rpartition() - ์˜ค๋ฅธ์ชฝ์—์„œ ์ฒซ ๋ฒˆ์งธ ๊ตฌ๋ถ„์ž ๊ธฐ์ค€
path = "/home/user/documents/file.txt"
dir_path, sep, filename = path.rpartition('/')
print(dir_path)   # "/home/user/documents"
print(filename)   # "file.txt"

# splitlines() - ์ค„ ๋‹จ์œ„ ๋ถ„๋ฆฌ
multiline = "์ฒซ ๋ฒˆ์งธ ์ค„\n๋‘ ๋ฒˆ์งธ ์ค„\r\n์„ธ ๋ฒˆ์งธ ์ค„\r๋„ค ๋ฒˆ์งธ ์ค„"
lines = multiline.splitlines()
print(lines)
# ['์ฒซ ๋ฒˆ์งธ ์ค„', '๋‘ ๋ฒˆ์งธ ์ค„', '์„ธ ๋ฒˆ์งธ ์ค„', '๋„ค ๋ฒˆ์งธ ์ค„']

# keepends=True๋กœ ์ค„๋ฐ”๊ฟˆ ๋ฌธ์ž ์œ ์ง€
lines_with_ends = multiline.splitlines(keepends=True)
print(lines_with_ends)
# ['์ฒซ ๋ฒˆ์งธ ์ค„\n', '๋‘ ๋ฒˆ์งธ ์ค„\r\n', '์„ธ ๋ฒˆ์งธ ์ค„\r', '๋„ค ๋ฒˆ์งธ ์ค„']

# split() ๊ณ ๊ธ‰ ํ™œ์šฉ
csv_line = "ํ™๊ธธ๋™,25,์„œ์šธ,๊ฐœ๋ฐœ์ž"
fields = csv_line.split(',')
print(fields)  # ['ํ™๊ธธ๋™', '25', '์„œ์šธ', '๊ฐœ๋ฐœ์ž']

# maxsplit์œผ๋กœ ๋ถ„๋ฆฌ ํšŸ์ˆ˜ ์ œํ•œ
limited = csv_line.split(',', maxsplit=2)
print(limited)  # ['ํ™๊ธธ๋™', '25', '์„œ์šธ,๊ฐœ๋ฐœ์ž']

๐ŸŽจ ๋ฌธ์ž์—ด ํฌ๋งคํŒ… ๊ณ ๊ธ‰ ๊ธฐ๋ฒ•

# f-string ๊ณ ๊ธ‰ ํ™œ์šฉ (Python 3.6+)
name = "ํ™๊ธธ๋™"
score = 95.678
items = [1, 2, 3, 4, 5]

# ๊ธฐ๋ณธ ํฌ๋งคํŒ…
print(f"์ด๋ฆ„: {name}, ์ ์ˆ˜: {score:.2f}")  # "์ด๋ฆ„: ํ™๊ธธ๋™, ์ ์ˆ˜: 95.68"

# ์ •๋ ฌ๊ณผ ํŒจ๋”ฉ
print(f"{'์™ผ์ชฝ':<10}|{'๊ฐ€์šด๋ฐ':^10}|{'์˜ค๋ฅธ์ชฝ':>10}")
# "์™ผ์ชฝ      |  ๊ฐ€์šด๋ฐ  |    ์˜ค๋ฅธ์ชฝ"

# ์ˆซ์ž ํฌ๋งคํŒ…
big_num = 1234567890
print(f"{big_num:,}")        # "1,234,567,890"  (์ฒœ ๋‹จ์œ„ ๊ตฌ๋ถ„)
print(f"{big_num:010d}")     # "1234567890"  (0 ํŒจ๋”ฉ)
print(f"{0.1234:.2%}")       # "12.34%"  (ํผ์„ผํŠธ)
print(f"{255:#010x}")        # "0x000000ff"  (16์ง„์ˆ˜)
print(f"{255:#010b}")        # "0b011111111"  (2์ง„์ˆ˜)

# ํ‘œํ˜„์‹ ์‚ฌ์šฉ
print(f"ํ•ฉ๊ณ„: {sum(items)}, ํ‰๊ท : {sum(items)/len(items):.1f}")

# Python 3.8+ - = ๋””๋ฒ„๊น… ์ถœ๋ ฅ
x = 42
print(f"{x=}")  # "x=42"  โ† ๋ณ€์ˆ˜๋ช…๊ณผ ๊ฐ’์„ ํ•จ๊ป˜ ์ถœ๋ ฅ!

# format() ๋ฉ”์„œ๋“œ ๊ณ ๊ธ‰ ํ™œ์šฉ
template = "{name}๋‹˜์˜ {subject} ์ ์ˆ˜๋Š” {score:.1f}์ ์ž…๋‹ˆ๋‹ค."
print(template.format(name="๊น€์ฒ ์ˆ˜", subject="์ˆ˜ํ•™", score=87.5))

# ๋”•์…”๋„ˆ๋ฆฌ ์–ธํŒจํ‚น
data = {"name": "์ด์˜ํฌ", "subject": "์˜์–ด", "score": 92.3}
print(template.format(**data))

๐Ÿ” ๋ฌธ์ž์—ด ๊ฒ€์ƒ‰๊ณผ ๋ถ„์„

# ๊ณ ๊ธ‰ ๋ฌธ์ž์—ด ๊ฒ€์ƒ‰ ๊ธฐ๋ฒ•

text = "Python์€ ์ •๋ง ๊ฐ•๋ ฅํ•œ ์–ธ์–ด์•ผ. Python์„ ๋ฐฐ์šฐ๋ฉด ๋ญ๋“  ํ•  ์ˆ˜ ์žˆ์–ด!"

# count() - ์ถœํ˜„ ํšŸ์ˆ˜
print(text.count('Python'))    # 2
print(text.count('Python', 0, 20))  # 1  (๋ฒ”์œ„ ์ง€์ •)

# find() vs index()
print(text.find('๊ฐ•๋ ฅ'))       # 10  (๋ชป ์ฐพ์œผ๋ฉด -1 ๋ฐ˜ํ™˜)
print(text.find('์—†๋Š”๋‹จ์–ด'))   # -1
# print(text.index('์—†๋Š”๋‹จ์–ด')) # ValueError ๋ฐœ์ƒ!

# rfind() - ์˜ค๋ฅธ์ชฝ์—์„œ ๊ฒ€์ƒ‰
print(text.rfind('Python'))    # ๋งˆ์ง€๋ง‰ Python์˜ ์œ„์น˜

# startswith(), endswith() - ์ ‘๋‘์‚ฌ/์ ‘๋ฏธ์‚ฌ ํ™•์ธ
print(text.startswith('Python'))   # True
print(text.endswith('์žˆ์–ด!'))       # True

# ํŠœํ”Œ๋กœ ์—ฌ๋Ÿฌ ํŒจํ„ด ๋™์‹œ ํ™•์ธ
filenames = ["report.pdf", "data.csv", "image.png", "script.py"]
for f in filenames:
    if f.endswith(('.pdf', '.csv')):
        print(f"๋ฌธ์„œ ํŒŒ์ผ: {f}")
# ๋ฌธ์„œ ํŒŒ์ผ: report.pdf
# ๋ฌธ์„œ ํŒŒ์ผ: data.csv

# ๋ฌธ์ž์—ด ๋ถ„์„ ๋ฉ”์„œ๋“œ๋“ค
sample = "Hello World 123"
print(sample.isalpha())    # False (์ˆซ์ž ํฌํ•จ)
print(sample.isdigit())    # False (๋ฌธ์ž ํฌํ•จ)
print(sample.isalnum())    # False (๊ณต๋ฐฑ ํฌํ•จ)
print("Hello".isalpha())   # True
print("12345".isdigit())   # True
print("Hello123".isalnum()) # True
print("  ".isspace())      # True
print("HELLO".isupper())   # True
print("hello".islower())   # True
print("Hello World".istitle()) # True

๐Ÿš€ 7. ์‹ค์ „ ํ”„๋กœ์ ํŠธ โ€” ํ…์ŠคํŠธ ์ฒ˜๋ฆฌ ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์ถ•

์ด์ œ ๋ฐฐ์šด ๊ฒƒ๋“ค์„ ์ข…ํ•ฉํ•ด์„œ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ ์“ธ ์ˆ˜ ์žˆ๋Š” ํ…์ŠคํŠธ ์ฒ˜๋ฆฌ ํŒŒ์ดํ”„๋ผ์ธ์„ ๋งŒ๋“ค์–ด๋ณด์ž!
์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ ์‚ฌ์šฉ์ž ์ž…๋ ฅ ๋ฐ์ดํ„ฐ๋ฅผ ์ •์ œํ•˜๋Š” ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ์˜ˆ๋กœ ๋“ค์–ด๋ณผ๊ฒŒ. ๐Ÿ˜Š

๐Ÿ“Š ๋กœ๊ทธ ํŒŒ์ผ ๋ถ„์„๊ธฐ

import re
from collections import Counter, defaultdict
from datetime import datetime

class LogAnalyzer:
    """์„œ๋ฒ„ ๋กœ๊ทธ ํŒŒ์ผ ๋ถ„์„๊ธฐ"""
    
    # ํŒจํ„ด ๋ฏธ๋ฆฌ ์ปดํŒŒ์ผ (์„ฑ๋Šฅ ์ตœ์ ํ™”)
    LOG_PATTERN = re.compile(
        r'(?P<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\s+'
        r'(?P<level>DEBUG|INFO|WARNING|ERROR|CRITICAL)\s+'
        r'(?P<message>.+)',
        re.MULTILINE
    )
    IP_PATTERN = re.compile(r'\b(?:\d{1,3}\.){3}\d{1,3}\b')
    URL_PATTERN = re.compile(r'https?://[\w.-]+(?:/[\w./?=%&-]*)?')
    
    def __init__(self, log_text):
        self.log_text = log_text
        self.entries = self._parse()
    
    def _parse(self):
        """๋กœ๊ทธ ํŒŒ์‹ฑ"""
        entries = []
        for match in self.LOG_PATTERN.finditer(self.log_text):
            entries.append({
                'timestamp': match.group('timestamp'),
                'level': match.group('level'),
                'message': match.group('message').strip()
            })
        return entries
    
    def get_level_stats(self):
        """๋ ˆ๋ฒจ๋ณ„ ํ†ต๊ณ„"""
        return Counter(entry['level'] for entry in self.entries)
    
    def get_errors(self):
        """์—๋Ÿฌ ๋กœ๊ทธ๋งŒ ์ถ”์ถœ"""
        return [e for e in self.entries if e['level'] in ('ERROR', 'CRITICAL')]
    
    def extract_ips(self):
        """๋ชจ๋“  IP ์ฃผ์†Œ ์ถ”์ถœ"""
        ips = []
        for entry in self.entries:
            ips.extend(self.IP_PATTERN.findall(entry['message']))
        return Counter(ips)
    
    def get_hourly_stats(self):
        """์‹œ๊ฐ„๋Œ€๋ณ„ ๋กœ๊ทธ ํ†ต๊ณ„"""
        hourly = defaultdict(int)
        for entry in self.entries:
            hour = entry['timestamp'][11:13]  # HH ์ถ”์ถœ
            hourly[hour] += 1
        return dict(sorted(hourly.items()))
    
    def generate_report(self):
        """๋ถ„์„ ๋ฆฌํฌํŠธ ์ƒ์„ฑ"""
        level_stats = self.get_level_stats()
        errors = self.get_errors()
        top_ips = self.extract_ips().most_common(5)
        
        report = f"""
=== ๋กœ๊ทธ ๋ถ„์„ ๋ฆฌํฌํŠธ ===
์ด ๋กœ๊ทธ ์ˆ˜: {len(self.entries)}๊ฐœ

[๋ ˆ๋ฒจ๋ณ„ ํ†ต๊ณ„]
{chr(10).join(f'  {level}: {count}๊ฑด' for level, count in level_stats.items())}

[์—๋Ÿฌ ๋ชฉ๋ก] ({len(errors)}๊ฑด)
{chr(10).join(f'  [{e["timestamp"]}] {e["message"]}' for e in errors[:5])}

[์ƒ์œ„ IP ์ฃผ์†Œ]
{chr(10).join(f'  {ip}: {count}ํšŒ' for ip, count in top_ips)}
"""
        return report.strip()


# ์‚ฌ์šฉ ์˜ˆ์‹œ
sample_log = """
2024-01-15 09:00:01 INFO ์„œ๋ฒ„ ์‹œ์ž‘ ์™„๋ฃŒ
2024-01-15 09:00:15 INFO ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ: 192.168.1.100
2024-01-15 09:01:23 WARNING ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ 80% ์ดˆ๊ณผ
2024-01-15 09:02:45 ERROR ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์‹คํŒจ: 192.168.1.200
2024-01-15 09:03:12 INFO ์žฌ์—ฐ๊ฒฐ ์‹œ๋„ ์ค‘...
2024-01-15 09:03:45 ERROR ํƒ€์ž„์•„์›ƒ: 192.168.1.200
2024-01-15 09:05:00 INFO ์—ฐ๊ฒฐ ๋ณต๊ตฌ ์™„๋ฃŒ
2024-01-15 10:00:00 CRITICAL ๋””์Šคํฌ ์šฉ๋Ÿ‰ ๋ถ€์กฑ: 192.168.1.100
"""

analyzer = LogAnalyzer(sample_log)
print(analyzer.generate_report())

๐Ÿงน ํ…์ŠคํŠธ ์ •์ œ ํŒŒ์ดํ”„๋ผ์ธ

import re
import unicodedata

class TextCleaner:
    """ํ…์ŠคํŠธ ์ •์ œ ํŒŒ์ดํ”„๋ผ์ธ"""
    
    def __init__(self):
        # ํŒจํ„ด ์ปดํŒŒ์ผ
        self._html_tag = re.compile(r'<[^>]+>')
        self._url = re.compile(r'https?://\S+|www\.\S+')
        self._email = re.compile(r'[\w.+-]+@[\w-]+\.\w+')
        self._phone = re.compile(r'\d{2,3}[-.]?\d{3,4}[-.]?\d{4}')
        self._whitespace = re.compile(r'\s+')
        self._special_chars = re.compile(r'[^\w\s๊ฐ€-ํžฃ.,!?]')
        self._repeated_chars = re.compile(r'(.)\1{2,}')  # 3ํšŒ ์ด์ƒ ๋ฐ˜๋ณต
        self._emoticons = re.compile(
            r'[\U0001F600-\U0001F64F\U0001F300-\U0001F5FF'
            r'\U0001F680-\U0001F6FF\U0001F1E0-\U0001F1FF]+'
        )
    
    def remove_html(self, text):
        """HTML ํƒœ๊ทธ ์ œ๊ฑฐ"""
        return self._html_tag.sub(' ', text)
    
    def remove_urls(self, text):
        """URL ์ œ๊ฑฐ"""
        return self._url.sub('[URL]', text)
    
    def mask_personal_info(self, text):
        """๊ฐœ์ธ์ •๋ณด ๋งˆ์Šคํ‚น"""
        text = self._email.sub('[์ด๋ฉ”์ผ]', text)
        text = self._phone.sub('[์ „ํ™”๋ฒˆํ˜ธ]', text)
        return text
    
    def normalize_whitespace(self, text):
        """๊ณต๋ฐฑ ์ •๊ทœํ™”"""
        return self._whitespace.sub(' ', text).strip()
    
    def normalize_unicode(self, text):
        """์œ ๋‹ˆ์ฝ”๋“œ ์ •๊ทœํ™” (์ „๊ฐโ†’๋ฐ˜๊ฐ ๋“ฑ)"""
        return unicodedata.normalize('NFKC', text)
    
    def reduce_repeated_chars(self, text):
        """๋ฐ˜๋ณต ๋ฌธ์ž ์ถ•์†Œ (ใ…‹ใ…‹ใ…‹ใ…‹ โ†’ ใ…‹ใ…‹)"""
        return self._repeated_chars.sub(r'\1\1', text)
    
    def remove_emoticons(self, text):
        """์ด๋ชจํ‹ฐ์ฝ˜ ์ œ๊ฑฐ"""
        return self._emoticons.sub('', text)
    
    def clean(self, text, 
              remove_html=True,
              remove_urls=True, 
              mask_personal=True,
              normalize_ws=True,
              normalize_unicode=True,
              reduce_repeated=True):
        """์ „์ฒด ์ •์ œ ํŒŒ์ดํ”„๋ผ์ธ"""
        if normalize_unicode:
            text = self.normalize_unicode(text)
        if remove_html:
            text = self.remove_html(text)
        if remove_urls:
            text = self.remove_urls(text)
        if mask_personal:
            text = self.mask_personal_info(text)
        if reduce_repeated:
            text = self.reduce_repeated_chars(text)
        if normalize_ws:
            text = self.normalize_whitespace(text)
        return text


# ์‚ฌ์šฉ ์˜ˆ์‹œ
cleaner = TextCleaner()

dirty_text = """
<h1>์•ˆ๋…•ํ•˜์„ธ์š”!!!</h1>
<p>์ €๋Š” ํ™๊ธธ๋™์ž…๋‹ˆ๋‹คใ…‹ใ…‹ใ…‹ใ…‹ใ…‹</p>
์—ฐ๋ฝ์ฒ˜: 010-1234-5678
์ด๋ฉ”์ผ: hong@example.com
์ฐธ๊ณ : https://www.example.com/page
"""

clean_text = cleaner.clean(dirty_text)
print(clean_text)
# "์•ˆ๋…•ํ•˜์„ธ์š”!! ์ €๋Š” ํ™๊ธธ๋™์ž…๋‹ˆ๋‹คใ…‹ใ…‹ ์—ฐ๋ฝ์ฒ˜: [์ „ํ™”๋ฒˆํ˜ธ] ์ด๋ฉ”์ผ: [์ด๋ฉ”์ผ] ์ฐธ๊ณ : [URL]"
๐Ÿ’ก ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ์˜ ํ™œ์šฉ! ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ํ”„๋กœํ•„ ์ •๋ณด๋‚˜ ์žฌ๋Šฅ ์†Œ๊ฐœ๊ธ€์„ ์ฒ˜๋ฆฌํ•  ๋•Œ ์ด๋Ÿฐ ํ…์ŠคํŠธ ์ •์ œ ํŒŒ์ดํ”„๋ผ์ธ์ด ์ •๋ง ์œ ์šฉํ•ด.
๊ฐœ์ธ์ •๋ณด ๋งˆ์Šคํ‚น, HTML ์ธ์ ์…˜ ๋ฐฉ์ง€, ํ…์ŠคํŠธ ์ •๊ทœํ™” ๋“ฑ์„ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฑฐ๋“ !

โšก 8. ์„ฑ๋Šฅ ์ตœ์ ํ™” โ€” ๋น ๋ฅธ ํ…์ŠคํŠธ ์ฒ˜๋ฆฌ์˜ ๋น„๊ฒฐ

๋Œ€์šฉ๋Ÿ‰ ํ…์ŠคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•  ๋•Œ๋Š” ์„ฑ๋Šฅ์ด ์ •๋ง ์ค‘์š”ํ•ด!
๊ฐ™์€ ๊ฒฐ๊ณผ๋ฅผ ๋‚ด๋”๋ผ๋„ ๋ฐฉ๋ฒ•์— ๋”ฐ๋ผ ์†๋„ ์ฐจ์ด๊ฐ€ ์ˆ˜์‹ญ ๋ฐฐ๊นŒ์ง€ ๋‚  ์ˆ˜ ์žˆ๊ฑฐ๋“ . ๐ŸŽ๏ธ

โšก ํ…์ŠคํŠธ ์ฒ˜๋ฆฌ ์„ฑ๋Šฅ ๋น„๊ต ๋А๋ฆผ ๋น ๋ฆ„ ๋А๋ฆผ re.sub (๋ฐ˜๋ณต ํ˜ธ์ถœ) ๋ณดํ†ต compile + sub ๋น ๋ฆ„ str.replace (๋‹จ์ˆœ ์น˜ํ™˜) ๋งค์šฐ ๋น ๋ฆ„ str.translate (๋ฌธ์ž ๋ณ€ํ™˜) ์ตœ๊ณ  ๐Ÿ† ''.join() (๋ฌธ์ž์—ด ๊ฒฐํ•ฉ) * ๋‹จ์ˆœ ๋ฌธ์ž ์ฒ˜๋ฆฌ ๊ธฐ์ค€ ์ƒ๋Œ€์  ์„ฑ๋Šฅ ๋น„๊ต (์‹ค์ œ ์„ฑ๋Šฅ์€ ์ƒํ™ฉ์— ๋”ฐ๋ผ ๋‹ค๋ฆ„)
import re
import time

# ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ ์˜ˆ์‹œ

# ๋‚˜์œ ์˜ˆ: ๋ฃจํ”„ ์•ˆ์—์„œ ๋งค๋ฒˆ ํŒจํ„ด ์ปดํŒŒ์ผ
def bad_approach(texts):
    results = []
    for text in texts:
        result = re.sub(r'\s+', ' ', text)  # ๋งค๋ฒˆ ์ปดํŒŒ์ผ!
        results.append(result)
    return results

# ์ข‹์€ ์˜ˆ: ๋ฏธ๋ฆฌ ์ปดํŒŒ์ผ
whitespace_pattern = re.compile(r'\s+')
def good_approach(texts):
    return [whitespace_pattern.sub(' ', text) for text in texts]

# ๋” ์ข‹์€ ์˜ˆ: ๋‹จ์ˆœ ์น˜ํ™˜์€ str.replace()๊ฐ€ ๋” ๋น ๋ฅผ ์ˆ˜ ์žˆ์Œ
def best_approach_simple(texts):
    return [' '.join(text.split()) for text in texts]

# ๋ฌธ์ž์—ด ๊ฒฐํ•ฉ - ์ ˆ๋Œ€ + ์—ฐ์‚ฐ์ž๋กœ ๋ฃจํ”„ ๋Œ๋ฆฌ์ง€ ๋งˆ!
# ๋‚˜์œ ์˜ˆ
def bad_concat(words):
    result = ""
    for word in words:
        result += word + " "  # O(nยฒ) ๋ณต์žก๋„!
    return result.strip()

# ์ข‹์€ ์˜ˆ
def good_concat(words):
    return " ".join(words)  # O(n) ๋ณต์žก๋„!

# ์„ฑ๋Šฅ ์ฐจ์ด ํ™•์ธ
words = ["Python"] * 10000

start = time.time()
bad_concat(words)
print(f"+ ์—ฐ์‚ฐ์ž: {time.time() - start:.4f}์ดˆ")

start = time.time()
good_concat(words)
print(f"join(): {time.time() - start:.4f}์ดˆ")
# join()์ด ์ˆ˜์‹ญ ๋ฐฐ ๋น ๋ฆ„!

๐ŸŽฏ ์„ฑ๋Šฅ ์ตœ์ ํ™” ํ•ต์‹ฌ ์›์น™

1๏ธโƒฃ ํŒจํ„ด์€ ๋ฏธ๋ฆฌ ์ปดํŒŒ์ผํ•˜๋ผ ๊ฐ™์€ ํŒจํ„ด์„ ์—ฌ๋Ÿฌ ๋ฒˆ ์“ด๋‹ค๋ฉด re.compile()๋กœ ๋ฏธ๋ฆฌ ์ปดํŒŒ์ผํ•ด๋‘์ž.

2๏ธโƒฃ ๋‹จ์ˆœ ์ž‘์—…์—” ๋‚ด์žฅ ๋ฉ”์„œ๋“œ๋ฅผ ์จ๋ผ
๊ณ ์ • ๋ฌธ์ž์—ด ์น˜ํ™˜์€ str.replace()๊ฐ€ ์ •๊ทœํ‘œํ˜„์‹๋ณด๋‹ค ๋น ๋ฅผ ์ˆ˜ ์žˆ์–ด.
๋ฌธ์ž ๋‹จ์œ„ ๋ณ€ํ™˜์€ str.translate()๊ฐ€ ์ตœ๊ฐ•์ด์•ผ.

3๏ธโƒฃ ๋ฌธ์ž์—ด ๊ฒฐํ•ฉ์—” join()์„ ์จ๋ผ
+ ์—ฐ์‚ฐ์ž๋กœ ๋ฃจํ”„์—์„œ ๋ฌธ์ž์—ด์„ ์ด์–ด๋ถ™์ด๋ฉด O(nยฒ) ๋ณต์žก๋„๊ฐ€ ๋ผ. ํ•ญ์ƒ ''.join()์„ ์จ!

4๏ธโƒฃ ํƒ์š•์  ๋งค์นญ์„ ์กฐ์‹ฌํ•ด๋ผ
.* ๊ฐ™์€ ํƒ์š•์  ํŒจํ„ด์€ ๋ฐฑํŠธ๋ž˜ํ‚น์ด ๋งŽ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์–ด. ํ•„์š”ํ•˜๋ฉด ๋น„ํƒ์š•์ (.*?)์œผ๋กœ ๋ฐ”๊ฟ”๋ด.

5๏ธโƒฃ finditer()๋ฅผ ํ™œ์šฉํ•ด๋ผ
๋Œ€์šฉ๋Ÿ‰ ํ…์ŠคํŠธ์—์„œ ๋ชจ๋“  ๋งค์นญ์„ ์ฒ˜๋ฆฌํ•  ๋•Œ findall()๋ณด๋‹ค finditer()๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์ ์ด์•ผ.


๐ŸŒŸ 9. ๊ณ ๊ธ‰ ๊ธฐ๋ฒ• โ€” ์•Œ๋ฉด ๋ ˆ๋ฒจ์—… ๋˜๋Š” ๊ฒƒ๋“ค

๐Ÿ”„ ์กฐ๊ฑด๋ถ€ ํŒจํ„ด (Conditional Patterns)

import re

# (?(id)yes|no) - ๊ทธ๋ฃน id๊ฐ€ ๋งค์นญ๋์œผ๋ฉด yes ํŒจํ„ด, ์•„๋‹ˆ๋ฉด no ํŒจํ„ด
# ์˜ˆ: ๋”ฐ์˜ดํ‘œ๊ฐ€ ์žˆ์œผ๋ฉด ๋”ฐ์˜ดํ‘œ๋กœ ๋๋‚˜์•ผ ํ•˜๊ณ , ์—†์œผ๋ฉด ๊ทธ๋ƒฅ ๋‹จ์–ด

pattern = re.compile(r'("?)(\w+)(?(1)"|)')
# ๊ทธ๋ฃน 1("?)์ด ๋งค์นญ๋์œผ๋ฉด(๋”ฐ์˜ดํ‘œ ์žˆ์œผ๋ฉด) ๋์— "๊ฐ€ ์™€์•ผ ํ•จ

test_cases = ['"hello"', 'world', '"python']
for test in test_cases:
    match = pattern.fullmatch(test)
    print(f"'{test}': {'๋งค์นญ' if match else '๋ถˆ์ผ์น˜'}")
# '"hello"': ๋งค์นญ
# 'world': ๋งค์นญ
# '"python': ๋ถˆ์ผ์น˜  โ† ์‹œ์ž‘ ๋”ฐ์˜ดํ‘œ๋Š” ์žˆ๋Š”๋ฐ ๋ ๋”ฐ์˜ดํ‘œ๊ฐ€ ์—†์Œ!

๐Ÿ” ์›์ž์  ๊ทธ๋ฃน๊ณผ ์†Œ์œ ์  ์ˆ˜๋Ÿ‰์ž

import re

# Python์˜ re ๋ชจ๋“ˆ์€ ์›์ž์  ๊ทธ๋ฃน์„ ์ง์ ‘ ์ง€์›ํ•˜์ง€ ์•Š์ง€๋งŒ
# regex ๋ชจ๋“ˆ(pip install regex)์„ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ€๋Šฅํ•ด

# pip install regex
import regex  # ์„ค์น˜ ํ•„์š”

# ์†Œ์œ ์  ์ˆ˜๋Ÿ‰์ž (Possessive Quantifier) - ๋ฐฑํŠธ๋ž˜ํ‚น ์—†์Œ
# ์ผ๋ฐ˜: r'\d+\b'  vs  ์†Œ์œ ์ : r'\d++\b'
text = "12345abc"

# ์ผ๋ฐ˜ ์ˆ˜๋Ÿ‰์ž - ๋ฐฑํŠธ๋ž˜ํ‚น ๋ฐœ์ƒ
result1 = re.search(r'\d+\b', text)
print(result1)  # None (๋ฐฑํŠธ๋ž˜ํ‚น์œผ๋กœ ์‹œ๋„ํ•˜์ง€๋งŒ ์‹คํŒจ)

# ์›์ž์  ๊ทธ๋ฃน - ๋ฐฑํŠธ๋ž˜ํ‚น ์—†์Œ (regex ๋ชจ๋“ˆ)
result2 = regex.search(r'(?>\d+)\b', text)
print(result2)  # None (๋” ๋น ๋ฅด๊ฒŒ ์‹คํŒจ)

๐Ÿ“ ์ •๊ทœํ‘œํ˜„์‹ ๋””๋ฒ„๊น… โ€” regex101 ์Šคํƒ€์ผ

import re

def debug_regex(pattern, text, flags=0):
    """์ •๊ทœํ‘œํ˜„์‹ ๋””๋ฒ„๊น… ๋„์šฐ๋ฏธ"""
    try:
        compiled = re.compile(pattern, flags)
        matches = list(compiled.finditer(text))
        
        print(f"ํŒจํ„ด: {pattern}")
        print(f"ํ…์ŠคํŠธ: {text}")
        print(f"๋งค์นญ ์ˆ˜: {len(matches)}๊ฐœ")
        print("-" * 50)
        
        for i, match in enumerate(matches, 1):
            print(f"๋งค์นญ {i}:")
            print(f"  ์ „์ฒด: '{match.group()}'")
            print(f"  ์œ„์น˜: {match.span()}")
            
            if match.lastindex:
                for j in range(1, match.lastindex + 1):
                    print(f"  ๊ทธ๋ฃน {j}: '{match.group(j)}'")
            
            if match.groupdict():
                for name, value in match.groupdict().items():
                    print(f"  {name}: '{value}'")
        
        # ๋งค์นญ ์‹œ๊ฐํ™”
        print("\n์‹œ๊ฐํ™”:")
        result = list(text)
        for match in reversed(matches):
            start, end = match.span()
            result.insert(end, 'ใ€‘')
            result.insert(start, 'ใ€')
        print(''.join(result))
        
    except re.error as e:
        print(f"ํŒจํ„ด ์˜ค๋ฅ˜: {e}")

# ์‚ฌ์šฉ ์˜ˆ์‹œ
debug_regex(
    r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})',
    "์˜ค๋Š˜์€ 2024-01-15์ด๊ณ  ๋‚ด์ผ์€ 2024-01-16์ž…๋‹ˆ๋‹ค."
)

๐ŸŒ ์œ ๋‹ˆ์ฝ”๋“œ์™€ ๋‹ค๊ตญ์–ด ์ฒ˜๋ฆฌ

import re
import unicodedata

# ์œ ๋‹ˆ์ฝ”๋“œ ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ์ด์šฉํ•œ ๋ฌธ์ž ๋ถ„๋ฅ˜
def categorize_chars(text):
    categories = {}
    for char in text:
        cat = unicodedata.category(char)
        cat_name = {
            'Lu': '๋Œ€๋ฌธ์ž', 'Ll': '์†Œ๋ฌธ์ž', 'Nd': '์ˆซ์ž',
            'Zs': '๊ณต๋ฐฑ', 'Po': '๊ตฌ๋‘์ ', 'Lo': '๊ธฐํƒ€๋ฌธ์ž(ํ•œ๊ธ€๋“ฑ)'
        }.get(cat, cat)
        categories[cat_name] = categories.get(cat_name, 0) + 1
    return categories

text = "Hello ์•ˆ๋…• 123 !@#"
print(categorize_chars(text))

# ์œ ๋‹ˆ์ฝ”๋“œ ์†์„ฑ์„ ์ด์šฉํ•œ ํŒจํ„ด (regex ๋ชจ๋“ˆ)
# import regex
# ํ•œ๊ธ€, ์ผ๋ณธ์–ด, ์ค‘๊ตญ์–ด ๋“ฑ ๋ชจ๋“  ์•„์‹œ์•„ ๋ฌธ์ž
# pattern = regex.compile(r'\p{Han}+')  # ํ•œ์ž
# pattern = regex.compile(r'\p{Hangul}+')  # ํ•œ๊ธ€
# pattern = regex.compile(r'\p{Hiragana}+')  # ํžˆ๋ผ๊ฐ€๋‚˜

# ํ‘œ์ค€ re ๋ชจ๋“ˆ๋กœ ๋‹ค๊ตญ์–ด ์ฒ˜๋ฆฌ
# ํ•œ๊ธ€: [๊ฐ€-ํžฃ] ๋˜๋Š” [\uAC00-\uD7A3]
# ์ผ๋ณธ์–ด ํžˆ๋ผ๊ฐ€๋‚˜: [\u3040-\u309F]
# ์ผ๋ณธ์–ด ๊ฐ€ํƒ€์นด๋‚˜: [\u30A0-\u30FF]
# ํ•œ์ž: [\u4E00-\u9FFF]

multilingual = "Hello ์•ˆ๋…• ใ“ใ‚“ใซใกใฏ ไฝ ๅฅฝ ู…ุฑุญุจุง"

korean = re.findall(r'[๊ฐ€-ํžฃ]+', multilingual)
japanese = re.findall(r'[\u3040-\u30FF]+', multilingual)
chinese = re.findall(r'[\u4E00-\u9FFF]+', multilingual)

print(f"ํ•œ๊ตญ์–ด: {korean}")    # ['์•ˆ๋…•']
print(f"์ผ๋ณธ์–ด: {japanese}")  # ['ใ“ใ‚“ใซใกใฏ']
print(f"์ค‘๊ตญ์–ด: {chinese}")   # ['ไฝ ๅฅฝ']

# ์œ ๋‹ˆ์ฝ”๋“œ ์ •๊ทœํ™”
# NFC: ์กฐํ•ฉํ˜• (ํ•œ๊ธ€ ์™„์„ฑํ˜•)
# NFD: ๋ถ„ํ•ดํ˜• (ํ•œ๊ธ€ ์ž๋ชจ ๋ถ„๋ฆฌ)
# NFKC: ํ˜ธํ™˜์„ฑ ์กฐํ•ฉํ˜• (์ „๊ฐโ†’๋ฐ˜๊ฐ ๋“ฑ)
# NFKD: ํ˜ธํ™˜์„ฑ ๋ถ„ํ•ดํ˜•

text_with_fullwidth = "๏ผก๏ผข๏ผฃ๏ผค๏ผ‘๏ผ’๏ผ“๏ผ”"
normalized = unicodedata.normalize('NFKC', text_with_fullwidth)
print(normalized)  # "ABCD1234"  (์ „๊ฐโ†’๋ฐ˜๊ฐ ๋ณ€ํ™˜)

๐Ÿ“š 10. ์ž์ฃผ ํ•˜๋Š” ์‹ค์ˆ˜์™€ ์ฃผ์˜์‚ฌํ•ญ

์ •๊ทœํ‘œํ˜„์‹์„ ์“ฐ๋‹ค ๋ณด๋ฉด ๋ˆ„๊ตฌ๋‚˜ ํ•œ ๋ฒˆ์”ฉ ๋น ์ง€๋Š” ํ•จ์ •๋“ค์ด ์žˆ์–ด.
๋ฏธ๋ฆฌ ์•Œ์•„๋‘๋ฉด ์‚ฝ์งˆ์„ ์ค„์ผ ์ˆ˜ ์žˆ์–ด! ๐Ÿ˜…

โš ๏ธ ์‹ค์ˆ˜ 1: ํƒ์š•์  ๋งค์นญ์œผ๋กœ ์ธํ•œ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๊ฒฐ๊ณผ
import re

html = "<b>์ฒซ ๋ฒˆ์งธ</b> ํ…์ŠคํŠธ <b>๋‘ ๋ฒˆ์งธ</b>"

# ์ž˜๋ชป๋œ ๋ฐฉ๋ฒ• - ํƒ์š•์  ๋งค์นญ
wrong = re.findall(r'<b>.+</b>', html)
print(wrong)  # ['<b>์ฒซ ๋ฒˆ์งธ</b> ํ…์ŠคํŠธ <b>๋‘ ๋ฒˆ์งธ</b>']  โ† ์ „์ฒด๋ฅผ ํ•˜๋‚˜๋กœ!

# ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉ๋ฒ• - ๋น„ํƒ์š•์  ๋งค์นญ
correct = re.findall(r'<b>.+?</b>', html)
print(correct)  # ['<b>์ฒซ ๋ฒˆ์งธ</b>', '<b>๋‘ ๋ฒˆ์งธ</b>']  โ† ๊ฐ๊ฐ ๋”ฐ๋กœ!
โš ๏ธ ์‹ค์ˆ˜ 2: ๋ฐฑ์Šฌ๋ž˜์‹œ ์ฒ˜๋ฆฌ ๋ฌธ์ œ
import re

# ์ž˜๋ชป๋œ ๋ฐฉ๋ฒ• - raw string ๋ฏธ์‚ฌ์šฉ
# \d๋Š” ํŒŒ์ด์ฌ ๋ฌธ์ž์—ด์—์„œ ๊ทธ๋ƒฅ d๋กœ ์ฒ˜๋ฆฌ๋  ์ˆ˜ ์žˆ์Œ
try:
    result = re.findall('\d+', "abc123")  # ์ด๊ฑด ์šฐ์—ฐํžˆ ์ž‘๋™ํ•จ
    result2 = re.findall('\b\w+\b', "hello world")  # \b๊ฐ€ ๋ฐฑ์ŠคํŽ˜์ด์Šค๋กœ!
    print(result2)  # ์˜ˆ์ƒ๊ณผ ๋‹ค๋ฅธ ๊ฒฐ๊ณผ
except:
    pass

# ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉ๋ฒ• - raw string ์‚ฌ์šฉ
result = re.findall(r'\d+', "abc123")       # ['123']
result2 = re.findall(r'\b\w+\b', "hello world")  # ['hello', 'world']
print(result2)
โš ๏ธ ์‹ค์ˆ˜ 3: re.match()์™€ re.search() ํ˜ผ๋™
import re

text = "์˜ค๋Š˜์€ 2024๋…„ 1์›” 15์ผ์ž…๋‹ˆ๋‹ค"

# match๋Š” ์ฒ˜์Œ๋ถ€ํ„ฐ ๋งค์นญ - ์ˆซ์ž๋กœ ์‹œ์ž‘ํ•˜์ง€ ์•Š์œผ๋‹ˆ None!
result_match = re.match(r'\d+', text)
print(result_match)  # None

# search๋Š” ์ „์ฒด ํƒ์ƒ‰ - ์ค‘๊ฐ„์˜ ์ˆซ์ž๋„ ์ฐพ์Œ
result_search = re.search(r'\d+', text)
print(result_search.group())  # '2024'

# ์ „์ฒด ๋ฌธ์ž์—ด์ด ํŒจํ„ด๊ณผ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋ ค๋ฉด fullmatch() ์‚ฌ์šฉ
print(re.fullmatch(r'\d{4}', "2024"))   # ๋งค์นญ
print(re.fullmatch(r'\d{4}', "20245"))  # None (๊ธธ์ด ๋‹ค๋ฆ„)
โš ๏ธ ์‹ค์ˆ˜ 4: ์ด๋ฉ”์ผ/URL ๊ฒ€์ฆ์— ๋„ˆ๋ฌด ๋‹จ์ˆœํ•œ ํŒจํ„ด ์‚ฌ์šฉ
import re

# ๋„ˆ๋ฌด ๋‹จ์ˆœํ•œ ์ด๋ฉ”์ผ ํŒจํ„ด - ๋งŽ์€ ์œ ํšจํ•œ ์ด๋ฉ”์ผ์„ ๋†“์นจ
simple_pattern = r'\w+@\w+\.\w+'
# test+tag@example.com  โ† + ๊ธฐํ˜ธ ๋•Œ๋ฌธ์— ์‹คํŒจ!
# user@sub.domain.com   โ† ์„œ๋ธŒ๋„๋ฉ”์ธ ๋•Œ๋ฌธ์— ์‹คํŒจ!

# ๋” ๋‚˜์€ ํŒจํ„ด
better_pattern = r'[\w.+-]+@[\w-]+(?:\.[\w-]+)*\.\w{2,}'

test_emails = [
    "user@example.com",
    "test+tag@example.com",
    "user@sub.domain.co.kr",
    "user.name@example.org"
]

for email in test_emails:
    simple = bool(re.match(r'^\w+@\w+\.\w+$', email))
    better = bool(re.match(r'^[\w.+-]+@[\w-]+(?:\.[\w-]+)*\.\w{2,}$', email))
    print(f"{email}: ๋‹จ์ˆœ={simple}, ๊ฐœ์„ ={better}")
๐Ÿ’ก ์ด๋ฉ”์ผ ๊ฒ€์ฆ์˜ ์ง„์‹ค! ์™„๋ฒฝํ•œ ์ด๋ฉ”์ผ ์ •๊ทœํ‘œํ˜„์‹์€ RFC 5322 ํ‘œ์ค€์„ ๋”ฐ๋ฅด๋ฉด ์ˆ˜๋ฐฑ ์ค„์ด ๋  ์ˆ˜ ์žˆ์–ด.
์‹ค๋ฌด์—์„œ๋Š” ์ ๋‹นํžˆ ๊ด€๋Œ€ํ•œ ํŒจํ„ด์œผ๋กœ ๊ธฐ๋ณธ ํ˜•์‹๋งŒ ํ™•์ธํ•˜๊ณ , ์‹ค์ œ ์œ ํšจ์„ฑ์€ ์ด๋ฉ”์ผ ๋ฐœ์†ก ํ›„ ํ™•์ธํ•˜๋Š” ๋ฐฉ์‹์„ ๋งŽ์ด ์จ!
โš ๏ธ ์‹ค์ˆ˜ 5: ์ •๊ทœํ‘œํ˜„์‹ ๋‚จ์šฉ
# ์ •๊ทœํ‘œํ˜„์‹์ด ํ•ญ์ƒ ์ตœ์„ ์€ ์•„๋‹ˆ์•ผ!

# ๋‚˜์œ ์˜ˆ: ๋‹จ์ˆœ ํฌํ•จ ์—ฌ๋ถ€ ํ™•์ธ์— ์ •๊ทœํ‘œํ˜„์‹ ์‚ฌ์šฉ
import re
text = "Hello World"
if re.search(r'World', text):  # ์˜ค๋ฒ„ํ‚ฌ!
    print("์žˆ์Œ")

# ์ข‹์€ ์˜ˆ: ๋‹จ์ˆœ ํฌํ•จ ์—ฌ๋ถ€๋Š” in ์—ฐ์‚ฐ์ž
if 'World' in text:
    print("์žˆ์Œ")

# ๋‚˜์œ ์˜ˆ: ๋‹จ์ˆœ ์น˜ํ™˜์— ์ •๊ทœํ‘œํ˜„์‹ ์‚ฌ์šฉ
result = re.sub(r'Hello', 'Hi', text)  # ์˜ค๋ฒ„ํ‚ฌ!

# ์ข‹์€ ์˜ˆ: ๋‹จ์ˆœ ์น˜ํ™˜์€ str.replace()
result = text.replace('Hello', 'Hi')

# ์ •๊ทœํ‘œํ˜„์‹์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ:
# - ํŒจํ„ด์ด ๋ณต์žกํ•  ๋•Œ (์ด๋ฉ”์ผ, URL, ๋‚ ์งœ ํ˜•์‹ ๋“ฑ)
# - ์—ฌ๋Ÿฌ ํŒจํ„ด์„ ๋™์‹œ์— ์ฒ˜๋ฆฌํ•  ๋•Œ
# - ๊ทธ๋ฃน ์บก์ฒ˜๊ฐ€ ํ•„์š”ํ•  ๋•Œ
# - ๋™์  ํŒจํ„ด์ด ํ•„์š”ํ•  ๋•Œ

๐ŸŽ“ 11. ๋งˆ๋ฌด๋ฆฌ โ€” ์ •๊ทœํ‘œํ˜„์‹ ๋งˆ์Šคํ„ฐ๊ฐ€ ๋˜๋Š” ๊ธธ

์™€, ์—ฌ๊ธฐ๊นŒ์ง€ ์™”๋‹ค๋ฉด ์ •๋ง ๋Œ€๋‹จํ•ด! ๐Ÿ‘
์ •๊ทœํ‘œํ˜„์‹๊ณผ ๊ณ ๊ธ‰ ๋ฌธ์ž์—ด ์กฐ์ž‘์€ ์ฒ˜์Œ์—” ์–ด๋ ต๊ฒŒ ๋А๊ปด์ง€์ง€๋งŒ, ํ•œ ๋ฒˆ ์ตํžˆ๋ฉด ์ฝ”๋”ฉ ์ƒ์‚ฐ์„ฑ์ด ๋ช‡ ๋ฐฐ๋กœ ์˜ฌ๋ผ๊ฐ€๋Š” ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ์•ผ.

์˜ค๋Š˜ ๋ฐฐ์šด ๋‚ด์šฉ์„ ์ •๋ฆฌํ•ด๋ณด๋ฉด:

โœ… re ๋ชจ๋“ˆ์˜ ํ•ต์‹ฌ ํ•จ์ˆ˜๋“ค โ€” match, search, findall, finditer, sub, split
โœ… ํŒจํ„ด ๋ฌธ๋ฒ• โ€” ๋ฉ”ํƒ€๋ฌธ์ž, ์ˆ˜๋Ÿ‰์ž, ์•ต์ปค, ๊ทธ๋ฃน, ๋ฃฉ์–ดํ—ค๋“œ/๋ฃฉ๋น„ํ•˜์ธ๋“œ
โœ… ์„ฑ๋Šฅ ์ตœ์ ํ™” โ€” compile(), ๋‚ด์žฅ ๋ฉ”์„œ๋“œ ํ™œ์šฉ, join() ์‚ฌ์šฉ
โœ… ์‹ค์ „ ํŒจํ„ด โ€” ์ด๋ฉ”์ผ, ์ „ํ™”๋ฒˆํ˜ธ, URL, ๋‚ ์งœ, ํ•œ๊ธ€ ์ถ”์ถœ
โœ… ๊ณ ๊ธ‰ ๊ธฐ๋ฒ• โ€” ์œ ๋‹ˆ์ฝ”๋“œ ์ฒ˜๋ฆฌ, ํ…์ŠคํŠธ ์ •์ œ ํŒŒ์ดํ”„๋ผ์ธ
โœ… ์ž์ฃผ ํ•˜๋Š” ์‹ค์ˆ˜ โ€” ํƒ์š•์  ๋งค์นญ, ๋ฐฑ์Šฌ๋ž˜์‹œ, match vs search ํ˜ผ๋™

๐Ÿ›ค๏ธ ๋‹ค์Œ ๋‹จ๊ณ„๋กœ ๋‚˜์•„๊ฐ€๋ ค๋ฉด

1

regex ๋ชจ๋“ˆ ํƒ๊ตฌ โ€” ํ‘œ์ค€ re ๋ชจ๋“ˆ๋ณด๋‹ค ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ๋“ค (์›์ž์  ๊ทธ๋ฃน, ์†Œ์œ ์  ์ˆ˜๋Ÿ‰์ž, ์œ ๋‹ˆ์ฝ”๋“œ ์†์„ฑ ๋“ฑ)

2

ํŒŒ์„œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ•™์Šต โ€” HTML/XML์€ ์ •๊ทœํ‘œํ˜„์‹๋ณด๋‹ค BeautifulSoup, lxml ๊ฐ™์€ ํŒŒ์„œ๊ฐ€ ๋” ์ ํ•ฉํ•ด!

3

NLP ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํƒ๊ตฌ โ€” ํ…์ŠคํŠธ ์ฒ˜๋ฆฌ๋ฅผ ๋” ๊นŠ์ด ํŒŒ๊ณ  ์‹ถ๋‹ค๋ฉด NLTK, spaCy, KoNLPy(ํ•œ๊ตญ์–ด) ๋ฅผ ๊ณต๋ถ€ํ•ด๋ด!

4

์‹ค์ „ ํ”„๋กœ์ ํŠธ โ€” ๋กœ๊ทธ ๋ถ„์„๊ธฐ, ๋ฐ์ดํ„ฐ ์ •์ œ ํŒŒ์ดํ”„๋ผ์ธ, ํฌ๋กค๋Ÿฌ ๋“ฑ์„ ์ง์ ‘ ๋งŒ๋“ค์–ด๋ด. ์‹ค์ „์ด ์ตœ๊ณ ์˜ ์„ ์ƒ๋‹˜์ด์•ผ!

5

regex101.com ํ™œ์šฉ โ€” ์ •๊ทœํ‘œํ˜„์‹์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ…Œ์ŠคํŠธํ•˜๊ณ  ๋””๋ฒ„๊น…ํ•  ์ˆ˜ ์žˆ๋Š” ์ตœ๊ณ ์˜ ์˜จ๋ผ์ธ ๋„๊ตฌ์•ผ!

Python ๊ฐœ๋ฐœ ์‹ค๋ ฅ์„ ํ‚ค์šฐ๊ณ  ์‹ถ๋‹ค๋ฉด ์žฌ๋Šฅ๋„ท์—์„œ Python ์ „๋ฌธ๊ฐ€๋“ค์˜ ๊ฐ•์˜๋‚˜ ์ฝ”๋“œ ๋ฆฌ๋ทฐ ์„œ๋น„์Šค๋ฅผ ์ฐพ์•„๋ณด๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์•ผ.
๋‹ค์–‘ํ•œ ๋ถ„์•ผ์˜ ์ „๋ฌธ๊ฐ€๋“ค์ด ๋ชจ์—ฌ ์žˆ์–ด์„œ ์‹ค๋ฌด ๋…ธํ•˜์šฐ๋ฅผ ๋น ๋ฅด๊ฒŒ ์Šต๋“ํ•  ์ˆ˜ ์žˆ๊ฑฐ๋“ ! ๐Ÿš€

์ •๊ทœํ‘œํ˜„์‹์€ ์ฒ˜์Œ์—” ์™ธ๊ณ„์–ด์ฒ˜๋Ÿผ ๋ณด์ด์ง€๋งŒ, ๊พธ์ค€ํžˆ ์“ฐ๋‹ค ๋ณด๋ฉด ์–ด๋А ์ˆœ๊ฐ„ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ฝํžˆ๋Š” ๋‚ ์ด ์™€.
๊ทธ ๋‚ ์ด ์˜ค๋ฉด ๋„ˆ๋„ ์ง„์งœ ํ…์ŠคํŠธ ์ฒ˜๋ฆฌ ๋งˆ์Šคํ„ฐ๊ฐ€ ๋œ ๊ฑฐ์•ผ! ๐Ÿ’ช

๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.