diff --git "a/76-Temporal/01-\354\240\225\352\267\234\355\221\234\355\230\204\354\213\235&ANT\355\221\234\355\230\204\354\213\235/ANT_Style_Pattern.md" "b/76-Temporal/01-\354\240\225\352\267\234\355\221\234\355\230\204\354\213\235&ANT\355\221\234\355\230\204\354\213\235/ANT_Style_Pattern.md" new file mode 100644 index 0000000..8eca5fe --- /dev/null +++ "b/76-Temporal/01-\354\240\225\352\267\234\355\221\234\355\230\204\354\213\235&ANT\355\221\234\355\230\204\354\213\235/ANT_Style_Pattern.md" @@ -0,0 +1,148 @@ +# Ant-style Pattern + +## ๐Ÿ’ก ํ•ต์‹ฌ ์š”์•ฝ ๐Ÿ’ก + +> - **ํ•œ ์ค„ ์ •์˜** : ํŒŒ์ผ ๊ฒฝ๋กœ(Path)๋‚˜ URL์„ ๋งค์นญํ•˜๊ธฐ ์œ„ํ•ด Apache Ant ๋นŒ๋“œ ๋„๊ตฌ์—์„œ ๊ณ ์•ˆ๋œ ์ง๊ด€์ ์ธ **์™€์ผ๋“œ์นด๋“œ ํŒจํ„ด** ์ž…๋‹ˆ๋‹ค. +> +> - **ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ** : `์™€์ผ๋“œ์นด๋“œ`, `๊ฒฝ๋กœ ๋งค์นญ(Path Matching)`, `Double Asterisk(**)`, `Glob Pattern` +> +> - **์™œ ์ค‘์š”ํ•œ๊ฐ€?** : ์ •๊ทœํ‘œํ˜„์‹๋ณด๋‹ค ๋ฌธ๋ฒ•์ด ํ›จ์”ฌ ๋‹จ์ˆœํ•˜์—ฌ ๋””๋ ‰ํ„ฐ๋ฆฌ ๊ตฌ์กฐ ํƒ์ƒ‰์— ์ตœ์ ํ™”๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ **Spring Framework** ์˜ URL ์„ค์ •, ํŒŒ์ผ ๊ฒ€์ƒ‰ ๋“ฑ์—์„œ ํ‘œ์ค€์œผ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. + +## 1. ๊ฐœ๋… + +**Ant-style Pattern** ์€ ํŒŒ์ผ ์‹œ์Šคํ…œ์˜ ๊ฒฝ๋กœ(Path)๋‚˜ URL์„ ์‰ฝ๊ณ  ๊ฐ„๋‹จํ•˜๊ฒŒ ํ‘œํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ๋งŒ๋“ค์–ด์ง„ ๊ทœ์น™์ž…๋‹ˆ๋‹ค. + +๋ณต์žกํ•œ ๋ฌธ์ž์—ด ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ '์ •๊ทœํ‘œํ˜„์‹'๊ณผ ๋‹ฌ๋ฆฌ, **'๋””๋ ‰ํ„ฐ๋ฆฌ(ํด๋”)์™€ ํŒŒ์ผ์˜ ๊ณ„์ธต ๊ตฌ์กฐ'**๋ฅผ ๋งค์นญํ•˜๋Š” ๋ฐ ํŠนํ™”๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์šฐ๋ฆฌ ์ˆ˜์—…์—์„œ๋Š” Spring Framework์—์„œ ๋ฆฌ์†Œ์Šค ์œ„์น˜๋‚˜ URL ํŒจํ„ด(`@RequestMapping`)์„ ์ •์˜ํ•  ๋•Œ ์ฃผ๋กœ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. + +----- + +## 2. ์™œ ํ•„์š”ํ•œ๊ฐ€? + +### ์ •๊ทœํ‘œํ˜„์‹์˜ ๋ณต์žก์„ฑ ํ•ด์†Œ + + - ํŒŒ์ผ ๊ฒฝ๋กœ๋‚˜ URL์€ `/` (์Šฌ๋ž˜์‹œ)๋กœ ๊ตฌ๋ถ„๋œ ๊ณ„์ธต ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค. + - ์ด๋ฅผ ์ •๊ทœํ‘œํ˜„์‹์œผ๋กœ ํ‘œํ˜„ํ•˜๋ ค๋ฉด ์ด์Šค์ผ€์ดํ”„ ์ฒ˜๋ฆฌ(`\/`)์™€ ๋ณต์žกํ•œ ์ˆ˜๋Ÿ‰์ž ์กฐํ•ฉ์ด ํ•„์š”ํ•ด ๊ฐ€๋…์„ฑ์ด ๋–จ์–ด์ง‘๋‹ˆ๋‹ค. + - Ant ํŒจํ„ด์€ `*`, `**`, `?` ์„ธ ๊ฐ€์ง€๋งŒ์œผ๋กœ ์ง๊ด€์ ์ธ ๊ฒฝ๋กœ ํ‘œํ˜„์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. + +### ๋””๋ ‰ํ„ฐ๋ฆฌ ์žฌ๊ท€ ํƒ์ƒ‰ (Recursive Search) + + - ๊ฐ€์žฅ ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ์ธ `**`๋ฅผ ํ†ตํ•ด ํ•˜์œ„ ๋””๋ ‰ํ„ฐ๋ฆฌ์˜ ๊นŠ์ด(Depth)์— ์ƒ๊ด€์—†์ด ๋ชจ๋“  ๊ฒฝ๋กœ๋ฅผ ํฌํ•จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + - ์ด๋Š” ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ "ํŠน์ • ๊ฒฝ๋กœ ํ•˜์œ„์˜ ๋ชจ๋“  ํŽ˜์ด์ง€์— ๋ณด์•ˆ ํ•„ํ„ฐ๋ฅผ ์ ์šฉ"ํ•˜๋Š” ๋“ฑ์˜ ์‹œ๋‚˜๋ฆฌ์˜ค์— ํ•„์ˆ˜์ ์ž…๋‹ˆ๋‹ค. + +----- + +## 3. ์ปดํ“จํ„ฐ ๊ณผํ•™ ๋‚ด์—์„œ Ant ํŒจํ„ด์˜ ์œ„์น˜ + +### Glob Pattern์˜ ์ผ์ข… + +Ant ํŒจํ„ด์€ ์ปดํ“จํ„ฐ ๊ณผํ•™์—์„œ **Glob Pattern**์˜ ํ™•์žฅ๋œ ํ˜•ํƒœ์ž…๋‹ˆ๋‹ค. + + - **Glob Pattern**: ์œ ๋‹‰์Šค ์…ธ(Shell)์—์„œ ํŒŒ์ผ ์ด๋ฆ„ ํ™•์žฅ์„ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š” ํŒจํ„ด (์˜ˆ: `ls *.txt`) + - **Ant Pattern**: Glob ํŒจํ„ด์— **๋””๋ ‰ํ„ฐ๋ฆฌ ์žฌ๊ท€ ํƒ์ƒ‰(`**`)** ๊ฐœ๋…์„ ์ถ”๊ฐ€ํ•˜์—ฌ ํ™•์žฅํ•œ ๊ฒƒ + +### ์ •๊ทœํ‘œํ˜„์‹๊ณผ์˜ ๊ด€๊ณ„ + +Ant ํŒจํ„ด์€ ์ •๊ทœํ‘œํ˜„์‹์˜ **๋ถ€๋ถ„์ง‘ํ•ฉ(Subset)**์ฒ˜๋Ÿผ ๋™์ž‘ํ•˜์ง€๋งŒ, ๋‚ด๋ถ€์ ์œผ๋กœ๋Š” ์ •๊ทœํ‘œํ˜„์‹์œผ๋กœ ๋ณ€ํ™˜๋˜์–ด ์ฒ˜๋ฆฌ๋˜๊ฑฐ๋‚˜ ๋ณ„๋„์˜ ํŒŒ์„œ(Parser)๋ฅผ ํ†ตํ•ด ํ•ด์„๋ฉ๋‹ˆ๋‹ค. + +| ํŠน์„ฑ | Ant Pattern | ์ •๊ทœํ‘œํ˜„์‹ (Regex) | +| :--- | :--- | :--- | +| **์ฃผ ๋ชฉ์ ** | ๊ฒฝ๋กœ(Path), ํŒŒ์ผ, URL ๋งค์นญ | ๋ฒ”์šฉ ๋ฌธ์ž์—ด ๊ฒ€์ƒ‰ ๋ฐ ์กฐ์ž‘ | +| **๋ณต์žก๋„** | ๋งค์šฐ ๋‚ฎ์Œ (๋‹จ์ˆœ) | ๋†’์Œ (๊ฐ•๋ ฅํ•จ) | +| **์ฃผ์š” ์‹ฌ๋ณผ** | `?`, `*`, `**` | `.`, `*`, `+`, `^`, `$`, `[]` ๋“ฑ ๋‹ค์ˆ˜ | +| **๊ฒฝ๊ณ„ ๊ธฐ์ค€** | `/` (๋””๋ ‰ํ„ฐ๋ฆฌ ๊ตฌ๋ถ„์ž) ๊ธฐ์ค€ | ๋ฌธ์ž ๋‹จ์œ„ ๊ธฐ์ค€ | + +----- + +## 4. Ant ํŒจํ„ด์˜ ์ฃผ์š” ๊ตฌ์„ฑ ์š”์†Œ (Syntax) + +Ant ํŒจํ„ด์€ ๋‹ค์Œ 3๊ฐ€์ง€ ํŠน์ˆ˜ ๋ฌธ์ž๋ฅผ ์กฐํ•ฉํ•˜์—ฌ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + +### 4.1 `?` (๋ฌผ์Œํ‘œ) + +**ํ•œ ๊ธ€์ž**์™€ ๋งค์นญ๋ฉ๋‹ˆ๋‹ค. (๋””๋ ‰ํ„ฐ๋ฆฌ ๊ตฌ๋ถ„์ž `/` ์ œ์™ธ) + +```java +// ์˜ˆ์‹œ: t?st.txt +"test.txt" (O) // ? = e +"tast.txt" (O) // ? = a +"tst.txt" (X) // ๋ฌธ์ž๊ฐ€ ์žˆ์–ด์•ผ ํ•จ +"teest.txt" (X) // ํ•œ ๊ธ€์ž๋งŒ ๊ฐ€๋Šฅ +``` + +### 4.2 `*` (๋ณ„ํ‘œ) + +**0๊ฐœ ์ด์ƒ์˜ ๋ฌธ์ž** ์™€ ๋งค์นญ๋ฉ๋‹ˆ๋‹ค. ๋‹จ, **๋””๋ ‰ํ„ฐ๋ฆฌ ๊ตฌ๋ถ„์ž(`/`)๋ฅผ ๋„˜์–ด๊ฐˆ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.** (ํ˜„์žฌ ๋””๋ ‰ํ„ฐ๋ฆฌ/ํŒŒ์ผ ์ด๋ฆ„ ๋‚ด์—์„œ๋งŒ ์œ ํšจ) + +```java +// ์˜ˆ์‹œ: *.txt +"file.txt" (O) +"a.txt" (O) +".txt" (O) // ์ด๋ฆ„ ์—†๋Š” ๊ฒฝ์šฐ๋„ ๋งค์นญ (๊ตฌํ˜„์ฒด์— ๋”ฐ๋ผ ๋‹ค๋ฆ„) +"dir/file.txt" (X) // / ๋ฅผ ๋„˜์–ด๊ฐˆ ์ˆ˜ ์—†์Œ +``` + +### 4.3 `**` (๋”๋ธ” ์• ์Šคํ„ฐ๋ฆฌ์Šคํฌ) + +**0๊ฐœ ์ด์ƒ์˜ ๋””๋ ‰ํ„ฐ๋ฆฌ(ํŒจ์Šค)** ์™€ ๋งค์นญ๋ฉ๋‹ˆ๋‹ค. + +```java +// ์˜ˆ์‹œ: /project/**/test +"/project/test" (O) // ์ค‘๊ฐ„ ๋””๋ ‰ํ„ฐ๋ฆฌ 0๊ฐœ +"/project/a/test" (O) // ์ค‘๊ฐ„ ๋””๋ ‰ํ„ฐ๋ฆฌ 1๊ฐœ +"/project/a/b/c/d/test" (O) // ์ค‘๊ฐ„ ๋””๋ ‰ํ„ฐ๋ฆฌ ๋‹ค์ˆ˜ + +// ์˜ˆ์‹œ: /static/** +"/static/css/style.css" (O) +"/static/images/logo.png" (O) +``` + +----- + +## 5. ์‹ค์ „ ๋น„๊ต: Ant Pattern vs Regex + +Spring Framework์˜ `AntPathMatcher`๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด ๊ฐ™์€ ์˜๋„๋ฅผ ๊ฐ€์ง„ ํŒจํ„ด์„ ๋น„๊ตํ•ด ๋ด…๋‹ˆ๋‹ค. + +### ์‹œ๋‚˜๋ฆฌ์˜ค 1: ๋ชจ๋“  .jsp ํŒŒ์ผ ์ฐพ๊ธฐ + + - **Ant Pattern**: `/**/*.jsp` + - **Regex**: `^/.*.jsp$` (๋˜๋Š” ๊ฒฝ๋กœ ๊ตฌ๋ถ„์ž๋ฅผ ๋ช…ํ™•ํžˆ ํ•˜๋ ค๋ฉด `^/.*[^/].jsp$`) + - *ํ•ด์„*: Ant ํŒจํ„ด์ด ํ›จ์”ฌ ์ง๊ด€์ ์ž…๋‹ˆ๋‹ค. + +### ์‹œ๋‚˜๋ฆฌ์˜ค 2: /admin ํ•˜์œ„์˜ ๋ชจ๋“  URL (๊นŠ์ด ๋ฌด๊ด€) + + - **Ant Pattern**: `/admin/**` + - **Regex**: `^/admin(/.*)?$` + - *ํ•ด์„*: Regex๋Š” ํ•˜์œ„ ๊ฒฝ๋กœ๊ฐ€ ์•„์˜ˆ ์—†๋Š” ๊ฒฝ์šฐ์™€ ์žˆ๋Š” ๊ฒฝ์šฐ๋ฅผ ๋ชจ๋‘ ๊ณ ๋ คํ•˜๋Š” ๊ทธ๋ฃนํ™”๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. + +----- + +## 6. Java(Spring)์—์„œ์˜ ํ™œ์šฉ ์˜ˆ์‹œ + +Java Spring ์ƒํƒœ๊ณ„์—์„œ๋Š” `AntPathMatcher` ์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค๋ฅผ ํ†ตํ•ด ์ด ํŒจํ„ด์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. + +```java +import org.springframework.util.AntPathMatcher; + +public class AntPatternExample { + public static void main(String[] args) { + AntPathMatcher matcher = new AntPathMatcher(); + + // 1. ? ๋งค์นญ + System.out.println(matcher.match("t?st.jsp", "test.jsp")); // true + + // 2. * ๋งค์นญ (๊ฐ™์€ ๋ ˆ๋ฒจ) + System.out.println(matcher.match("*.jsp", "hello.jsp")); // true + System.out.println(matcher.match("*.jsp", "a/hello.jsp")); // false (*์€ /๋ฅผ ๋ชป ๋„˜์Œ) + + // 3. ** ๋งค์นญ (ํ•˜์œ„ ๊ฒฝ๋กœ ํฌํ•จ) + System.out.println(matcher.match("/**/api", "/v1/api")); // true + System.out.println(matcher.match("/app/**/*.html", "/app/views/home.html")); // true + } +} +``` + +### ์ฃผ์š” ํ™œ์šฉ์ฒ˜ + +1. **URL ๋งคํ•‘**: `@RequestMapping("/api/**")`, `` +2. **Spring Security**: `antMatchers("/admin/**").hasRole("ADMIN")` +3. **ํŒŒ์ผ ๊ฒ€์ƒ‰**: `PathMatchingResourcePatternResolver`๋ฅผ ํ†ตํ•œ ํด๋ž˜์ŠคํŒจ์Šค ๋‚ด ํŒŒ์ผ ๋กœ๋“œ diff --git "a/76-Temporal/01-\354\240\225\352\267\234\355\221\234\355\230\204\354\213\235&ANT\355\221\234\355\230\204\354\213\235/README.md" "b/76-Temporal/01-\354\240\225\352\267\234\355\221\234\355\230\204\354\213\235&ANT\355\221\234\355\230\204\354\213\235/README.md" new file mode 100644 index 0000000..d455fdb --- /dev/null +++ "b/76-Temporal/01-\354\240\225\352\267\234\355\221\234\355\230\204\354\213\235&ANT\355\221\234\355\230\204\354\213\235/README.md" @@ -0,0 +1,4 @@ +# ์ •๊ทœ ํ‘œํ˜„์‹๊ณผ ANT ํ‘œํ˜„์‹ +1.1 (์ •๊ทœ ํ‘œํ˜„์‹(Regular Expression))[./Regular_Expression.md] +1.1.1 (์ •๊ทœ ํ‘œํ˜„์‹ ์˜ˆ์ œ)[./Regular_Expression_Example.md] +1.2 (ANT ํ‘œํ˜„์‹(ANT Expression))[./ANT_Style_Pattern.md] diff --git "a/76-Temporal/01-\354\240\225\352\267\234\355\221\234\355\230\204\354\213\235&ANT\355\221\234\355\230\204\354\213\235/Regular_Expression.md" "b/76-Temporal/01-\354\240\225\352\267\234\355\221\234\355\230\204\354\213\235&ANT\355\221\234\355\230\204\354\213\235/Regular_Expression.md" new file mode 100644 index 0000000..4216d64 --- /dev/null +++ "b/76-Temporal/01-\354\240\225\352\267\234\355\221\234\355\230\204\354\213\235&ANT\355\221\234\355\230\204\354\213\235/Regular_Expression.md" @@ -0,0 +1,512 @@ + +# ์ •๊ทœํ‘œํ˜„์‹(Regular Expression : Regex, Regexp) + +## ๐Ÿ’ก ํ•ต์‹ฌ ์š”์•ฝ๐Ÿ’ก +> - **ํ•œ ์ค„ ์ •์˜** : ๋ฌธ์ž์—ด์—์„œ ํŠน์ • ํŒจํ„ด์„ ์ฐพ๊ฑฐ๋‚˜ ์กฐ์ž‘ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š” ์ผ์ข…์˜ ํ˜•์‹ํ™”๋œ ๋ฌธ์ž์—ด์ž…๋‹ˆ๋‹ค. +> +> - **ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ** : `ํŒจํ„ด ๋งค์นญ`, `๋ฉ”ํƒ€๋ฌธ์ž`, `๋ฌธ์ž ํด๋ž˜์Šค`, `์ˆ˜๋Ÿ‰์ž`, `์•ต์ปค`, `๊ทธ๋ฃนํ™”` +> +> - **์™œ ์ค‘์š”ํ•œ๊ฐ€?** : ํ…์ŠคํŠธ ์ฒ˜๋ฆฌ์™€ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ์—์„œ ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ๋กœ ํ™œ์šฉ๋˜๋ฉฐ, ๋ณต์žกํ•œ ๋ฌธ์ž์—ด ๊ฒ€์ƒ‰, ์น˜ํ™˜, ์ถ”์ถœ ์ž‘์—…์„ ํšจ์œจ์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์–ด ๊ฐœ๋ฐœ์ž์™€ ๋ฐ์ดํ„ฐ ๊ณผํ•™์ž์—๊ฒŒ ํ•„์ˆ˜์ ์ธ ๊ธฐ์ˆ ์ž…๋‹ˆ๋‹ค. + +## 1. ๊ฐœ๋… + +ํŠน์ • ํŒจํ„ด์„ ๊ฐ€์ง„ ๋ฌธ์ž์—ด์„ ์ฐพ๊ฑฐ๋‚˜ ์กฐ์ž‘ํ•˜๊ธฐ ์œ„ํ•œ ํ˜•์‹ ์–ธ์–ด(Formal Language)์ž…๋‹ˆ๋‹ค. + +์ •๊ทœํ‘œํ˜„์‹์€ ๋‹ค์–‘ํ•œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด์™€ ๋„๊ตฌ์—์„œ ์ง€์›๋˜๋ฉฐ, ๋ณต์žกํ•œ ๋ฌธ์ž์—ด ๊ฒ€์ƒ‰, ์น˜ํ™˜, ์ถ”์ถœ ์ž‘์—…์„ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ์•„์‰ฝ๊ฒŒ๋„ **ํ•˜๋‚˜์˜ ํ‘œ์ค€์ด ์กด์žฌํ•˜์ง€ ์•Š์•„ ์–ธ์–ด๋‚˜ ๊ตฌํ˜„์ฒด๋งˆ๋‹ค ๋ฌธ๋ฒ•๊ณผ ๊ธฐ๋Šฅ์ด ๋‹ค๋ฆ…๋‹ˆ๋‹ค.** + +๋งŒ์•ฝ ์ž๋ฐ”์™€ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋กœ ๊ฐœ๋ฐœ์„ ํ•  ๋•Œ, ๊ฐ™์€ ๋ฌธ๋ฒ•์œผ๋กœ ์ •๊ทœ ํ‘œํ˜„์‹์„ ์ž‘์„ฑํ•˜๋ฉด ํ˜ธํ™˜์„ฑ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +--- + +## 2. ์™œ ํ•„์š”ํ•œ๊ฐ€? + +### ํ…์ŠคํŠธ ์ฒ˜๋ฆฌ์˜ ๋ณต์žก์„ฑ +- ๋Œ€๋Ÿ‰์˜ ํ…์ŠคํŠธ ๋ฐ์ดํ„ฐ์—์„œ ํŠน์ • ํŒจํ„ด์„ ์ฐพ๊ฑฐ๋‚˜ ๋ณ€ํ™˜ํ•˜๋Š” ์ž‘์—…์€ ๋งค์šฐ ๋ณต์žกํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +- ์˜ˆ๋ฅผ ๋“ค์–ด, ์ด๋ฉ”์ผ ์ฃผ์†Œ, ์ „ํ™”๋ฒˆํ˜ธ, ์šฐํŽธ๋ฒˆํ˜ธ ๋“ฑ ํŠน์ • ํ˜•์‹์„ ๊ฐ€์ง„ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”์ถœํ•˜๊ฑฐ๋‚˜ ๊ฒ€์ฆํ•˜๋Š” ์ž‘์—…์ด ํ•„์š”ํ•  ๋•Œ ์ •๊ทœํ‘œํ˜„์‹์ด ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. + +### ํšจ์œจ์ ์ธ ๋ฌธ์ž์—ด ์กฐ์ž‘ +- ์ •๊ทœํ‘œํ˜„์‹์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ณต์žกํ•œ ๋ฌธ์ž์—ด ์กฐ์ž‘ ์ž‘์—…์„ ๊ฐ„๊ฒฐํ•œ ์ฝ”๋“œ๋กœ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +- ๋ฐ˜๋ณต๋ฌธ๊ณผ ์กฐ๊ฑด๋ฌธ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ ๋„ ํŒจํ„ด ๋งค์นญ๊ณผ ์น˜ํ™˜์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์–ด ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์ด ํ–ฅ์ƒ๋ฉ๋‹ˆ๋‹ค. + +### ๋‹ค์–‘ํ•œ ์‘์šฉ ๋ถ„์•ผ +- ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ: ์‚ฌ์šฉ์ž ์ž…๋ ฅ์ด ํŠน์ • ํ˜•์‹์„ ๋”ฐ๋ฅด๋Š”์ง€ ํ™•์ธ +- ๋กœ๊ทธ ๋ถ„์„: ๋กœ๊ทธ ํŒŒ์ผ์—์„œ ํŠน์ • ์ด๋ฒคํŠธ๋‚˜ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€ ์ถ”์ถœ +- ์›น ์Šคํฌ๋ž˜ํ•‘: HTML ๋ฌธ์„œ์—์„œ ์›ํ•˜๋Š” ๋ฐ์ดํ„ฐ ์ถ”์ถœ + +--- + +## 3. ์ปดํ“จํ„ฐ ๊ณผํ•™ ๋‚ด์—์„œ ์ •๊ทœํ‘œํ˜„์‹์˜ ์œ„์น˜ +### ์ด˜์Šคํ‚ค ์œ„๊ณ„(Chomsky Hierarchy) + +๋…ธ์—„ ์ด˜์Šคํ‚ค(Avram Noam Chomsky)๊ฐ€ 1956๋…„์— ์ œ์‹œํ•œ ํ˜•์‹ ์–ธ์–ด ๋ถ„๋ฅ˜ ์ฒด๊ณ„๋กœ, ์–ธ์–ด์˜ ์ƒ์„ฑ ๊ทœ์น™์— ๋”ฐ๋ผ 4๊ฐ€์ง€ ์œ ํ˜•์œผ๋กœ ๋‚˜๋ˆ•๋‹ˆ๋‹ค. + +![์ด˜์Šคํ‚ค ์œ„๊ณ„](./img/chomsky-hierarchy.png) +- Type 0 : **๋ฌด์ œํ•œ ๋ฌธ๋ฒ•(Unrestricted Grammar : UG)** + + Recognized By Turing Machine + + ์ƒ์„ฑ ๊ทœ์น™ : ฮฑAฮฒ โ†’ ฮฒ (ฮฑ, ฮฒ๋Š” ์ž„์˜์˜ ๋ฌธ์ž์—ด) + +- Type 1 : **๋ฌธ๋งฅ ๋ฏผ๊ฐ ๋ฌธ๋ฒ•(Context-Sensitive Grammar : CSG)** + + Accepted By Linear Bounded Automaton + + ์ƒ์„ฑ ๊ทœ์น™ : ฮฑAฮฒ โ†’ ฮฑฮณฮฒ (ฮณ๋Š” ๊ณต๋ฐฑ์ด ์•„๋‹Œ ๋ฌธ์ž์—ด) + +- Type 2 : **๋ฌธ๋งฅ ์ž์œ  ๋ฌธ๋ฒ• (Context-Free Grammar : CFG)** + + Accepted By Pushdown Automaton + + ์ƒ์„ฑ ๊ทœ์น™ : A โ†’ ฮฑ (A๋Š” ๋‹จ์ผ ๋น„๋‹จ๋ง ๊ธฐํ˜ธ, ฮฑ๋Š” ์ž„์˜์˜ ๋ฌธ์ž์—ด) + +- Type 3 : **์ •๊ทœ ๋ฌธ๋ฒ• (Regular Grammar : RG)** <- ์ •๊ทœํ‘œํ˜„์‹์˜ ์œ„์น˜ + + Accepted By Finite Automaton + + ์ƒ์„ฑ ๊ทœ์น™ : A โ†’ aB ๋˜๋Š” A โ†’ a (A, B๋Š” ๋‹จ์ผ ๋น„๋‹จ๋ง ๊ธฐํ˜ธ, a๋Š” ๋‹จ์ผ ๋‹จ๋ง ๊ธฐํ˜ธ) + +### ์ •๊ทœ ํ‘œํ˜„์‹๊ณผ ์œ ํ•œ ์˜คํ† ๋งˆํƒ€, ์ •๊ทœ ์–ธ์–ด +- ์ •๊ทœ ์–ธ์–ด(Regular Language)๋Š” ์ •๊ทœ ๋ฌธ๋ฒ•์œผ๋กœ ์ƒ์„ฑ๋˜๋Š” ์–ธ์–ด์ž…๋‹ˆ๋‹ค. ๋‹ค๋ฅธ๋ง๋กœ ํ•˜๋ฉด ์ •๊ทœ ํ‘œํ˜„์‹์œผ๋กœ ํ‘œํ˜„ ๊ฐ€๋Šฅํ•œ ์–ธ์–ด๋ผ๊ณ ๋„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +- ์œ ํ•œ ์˜คํ† ๋งˆํƒ€(Finite Automaton)๋Š” ์ •๊ทœ ์–ธ์–ด๋ฅผ ์ธ์‹ํ•˜๋Š” ์ถ”์ƒ ๊ธฐ๊ณ„์ž…๋‹ˆ๋‹ค. ์ •๊ทœ ํ‘œํ˜„์‹๊ณผ ์œ ํ•œ ์˜คํ† ๋งˆํƒ€๋Š” ์„œ๋กœ ๋ณ€ํ™˜์ด ๊ฐ€๋Šฅํ•˜๋ฉฐ, ๋™์ผํ•œ ์–ธ์–ด๋ฅผ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +``` +์ •๊ทœ ํ‘œํ˜„์‹ <-> ์œ ํ•œ ์˜คํ† ๋งˆํƒ€ <-> ์ •๊ทœ ๋ฌธ๋ฒ• +``` + +### ์ •๊ทœ ํ‘œํ˜„์‹ ์—”์ง„์˜ ๊ธฐ๋ณธ ๊ฐœ๋… +- **NFA (Nondeterministic Finite Automaton)** : ๋น„๊ฒฐ์ •์  ์œ ํ•œ ์˜คํ† ๋งˆํƒ€ + - ์—ฌ๋Ÿฌ ๊ฒฝ๋กœ๋ฅผ ๋™์‹œ์— ํƒ์ƒ‰ (๋ฐฑํŠธ๋ž˜ํ‚น ์‚ฌ์šฉ) + - ๋А๋ฆฌ์ง€๋งŒ ๋ณต์žกํ•œ ํŒจํ„ด ์ฒ˜๋ฆฌ์— ์œ ๋ฆฌ + - ๋ฐฑํŠธ๋ž˜ํ‚น์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ตœ์•…์˜ ๊ฒฝ์šฐ O(2^n) ์‹œ๊ฐ„ ๋ณต์žก๋„๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Œ + - ์ด๋Ÿฐ ๋ถ€๋ถ„์„ ๊ณต๊ฒฉํ•˜๋ฉด ReDoS (Regular Expression Denial of Service) ๊ณต๊ฒฉ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Œ + - ์บก์ฒ˜ ๊ทธ๋ฃน, ์—ญ์ฐธ์กฐ ์ง€์› + - Java, JavaScript, Python ๋“ฑ์—์„œ ์‚ฌ์šฉ + +- **DFA (Deterministic Finite Automaton)** : ๊ฒฐ์ •์  ์œ ํ•œ ์˜คํ† ๋งˆํƒ€ + - ๋‹จ์ผ ๊ฒฝ๋กœ๋งŒ ํƒ์ƒ‰ + - ๋น ๋ฅด์ง€๋งŒ ๋ณต์žกํ•œ ํŒจํ„ด ์ฒ˜๋ฆฌ์— ๋ถˆ๋ฆฌ + - ์บก์ฒ˜ ๊ทธ๋ฃน, ์—ญ์ฐธ์กฐ ๋ฏธ์ง€์› + - grep, awk, sed ๋“ฑ์—์„œ ์‚ฌ์šฉ + +--- + +## 4. ์ •๊ทœ ํ‘œํ˜„์‹์˜ ์ฃผ์š” ๊ตฌ์„ฑ ์š”์†Œ + +์ •๊ทœ ํ‘œํ˜„์‹์€ ํฌ๊ฒŒ ๋ฆฌํ„ฐ๋Ÿด, ๋ฉ”ํƒ€ ๋ฌธ์ž, ๋ฌธ์ž ํด๋ž˜์Šค, ์ˆ˜๋Ÿ‰์ž, ์•ต์ปค, ๊ทธ๋ฃนํ™” ๋“ฑ์œผ๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค. ๊ฐ ๊ตฌ์„ฑ ์š”์†Œ๋Š” ํŒจํ„ด์„ ์ •์˜ํ•˜๋Š” ๋ฐ ๊ณ ์œ ํ•œ ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. +์œ„์—์„œ ์ด์•ผ๊ธฐ ํ–ˆ๋˜๋Œ€๋กœ ํ•˜๋‚˜์˜ ํ‘œ์ค€์ด ์กด์žฌํ•˜์ง€๋Š” ์•Š์ง€๋งŒ ์•Œ์•„๋‘๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™์•„์„œ ์ฃผ์š” ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ์ •๋ฆฌํ•ด๋ดค์Šต๋‹ˆ๋‹ค. + +### 4.1 ๋ฆฌํ„ฐ๋Ÿด (Literals) +๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ํ˜•ํƒœ์˜ ์ •๊ทœ ํ‘œํ˜„์‹์œผ๋กœ, ์žˆ๋Š” ๊ทธ๋Œ€๋กœ์˜ ๋ฌธ์ž๋ฅผ ๋งค์นญํ•ฉ๋‹ˆ๋‹ค. + +```java +// ์ผ๋ฐ˜ ๋ฌธ์ž์—ด ๋งค์นญ +String pattern = "hello"; +"hello world".matches(".*hello.*"); // true +"Hello world".matches(".*hello.*"); // false (๋Œ€์†Œ๋ฌธ์ž ๊ตฌ๋ถ„) + +// ๋Œ€์†Œ๋ฌธ์ž ๋ฌด์‹œ (Pattern.CASE_INSENSITIVE ํ”Œ๋ž˜๊ทธ ์‚ฌ์šฉ) +Pattern p = Pattern.compile("hello", Pattern.CASE_INSENSITIVE); +p.matcher("Hello world").find(); // true +``` + +### 4.2 ๋ฉ”ํƒ€ ๋ฌธ์ž (Meta-characters) +์ •๊ทœ ํ‘œํ˜„์‹์—์„œ ํŠน๋ณ„ํ•œ ์˜๋ฏธ๋ฅผ ๊ฐ€์ง€๋Š” ๋ฌธ์ž๋“ค์ž…๋‹ˆ๋‹ค. ์ด ๋ฌธ์ž๋“ค์„ ๋ฆฌํ„ฐ๋Ÿด๋กœ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋ฐฑ์Šฌ๋ž˜์‹œ(`\`)๋กœ ์ด์Šค์ผ€์ดํ”„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +์ฃผ์š” ๋ฉ”ํƒ€ ๋ฌธ์ž: `. ^ $ * + ? { } [ ] \ | ( )` + +### 4.2.1 `.` (Any character) +๊ฐœํ–‰ ๋ฌธ์ž(`\n`)๋ฅผ ์ œ์™ธํ•œ ๋ชจ๋“  ๋‹จ์ผ ๋ฌธ์ž์™€ ๋งค์นญ๋ฉ๋‹ˆ๋‹ค. + +```java +// . ์€ ๊ฐœํ–‰ ๋ฌธ์ž๋ฅผ ์ œ์™ธํ•œ ๋ชจ๋“  ๋ฌธ์ž ํ•˜๋‚˜์™€ ๋งค์นญ +"cat".matches("c.t"); // true +"cut".matches("c.t"); // true +"ct".matches("c.t"); // false (๋ฌธ์ž ํ•˜๋‚˜ ํ•„์š”) +"c\\nt".matches("c.t"); // false (๊ฐœํ–‰ ์ œ์™ธ) + +// DOTALL ๋ชจ๋“œ๋กœ ๊ฐœํ–‰๋„ ํฌํ•จ +Pattern.compile("c.t", Pattern.DOTALL).matcher("c\\nt").matches(); // true +``` + +### 4.2.2 `|` (Alternation / OR) +๋‘˜ ์ค‘ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•˜๋Š” OR ์—ฐ์‚ฐ์ž์ž…๋‹ˆ๋‹ค. + +```java +// OR ์—ฐ์‚ฐ +"cat".matches("cat|dog"); // true +"dog".matches("cat|dog"); // true +"bird".matches("cat|dog"); // false + +// ๊ทธ๋ฃน๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉ +"gmail.com".matches("(gmail|naver|daum)\\\\.com"); // true +``` + +### 4.2.3 `\` (Escape Character) +๋ฉ”ํƒ€ ๋ฌธ์ž๋ฅผ ์ผ๋ฐ˜ ๋ฌธ์ž๋กœ ์ทจ๊ธ‰ํ•˜๊ฒŒ ํ•˜๊ฑฐ๋‚˜, ํŠน์ˆ˜ํ•œ ๋ฌธ์ž ํด๋ž˜์Šค๋ฅผ ์ •์˜ํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. +Java ๋ฌธ์ž์—ด์—์„œ๋Š” ๋ฐฑ์Šฌ๋ž˜์‹œ ์ž์ฒด๋ฅผ ์ด์Šค์ผ€์ดํ”„ํ•ด์•ผ ํ•˜๋ฏ€๋กœ `\\`๋กœ ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +```java +// ๋ฉ”ํƒ€ ๋ฌธ์ž . ์„ ์ผ๋ฐ˜ ๋ฌธ์ž๋กœ ๋งค์นญ +"abc.def".matches("abc\\\\.def"); // true +"abc.def".matches("abc.def"); // true (.์ด ๋ชจ๋“  ๋ฌธ์ž์™€ ๋งค์นญ๋˜๋ฏ€๋กœ) +"abcXdef".matches("abc\\\\.def"); // false +``` + +### 4.3 ๋ฌธ์ž ํด๋ž˜์Šค (Character Classes) +๋Œ€๊ด„ํ˜ธ `[]` ์•ˆ์— ํฌํ•จ๋œ ๋ฌธ์ž ์ค‘ ํ•˜๋‚˜์™€ ๋งค์นญ๋ฉ๋‹ˆ๋‹ค. + +```java +// [abc] : a, b, c ์ค‘ ํ•˜๋‚˜ +"a".matches("[abc]"); // true +"d".matches("[abc]"); // false + +// [^abc] : a, b, c๋ฅผ ์ œ์™ธํ•œ ๋ชจ๋“  ๋ฌธ์ž (๋ถ€์ •) +"d".matches("[^abc]"); // true +"a".matches("[^abc]"); // false + +// [a-z] : ๋ฒ”์œ„ ์ง€์ • (Range) +"m".matches("[a-z]"); // true (์†Œ๋ฌธ์ž) +"M".matches("[A-Z]"); // true (๋Œ€๋ฌธ์ž) +"5".matches("[0-9]"); // true (์ˆซ์ž) + +// [a-zA-Z] : ์กฐํ•ฉ (Union) +"a".matches("[a-zA-Z]"); // true + +// [a-z&&[def]] : ๊ต์ง‘ํ•ฉ (Intersection) - Java ์ •๊ทœ์‹ ํŠน์ง• +"d".matches("[a-z&&[def]]"); // true (d, e, f ์ค‘ ํ•˜๋‚˜์ด๋ฉด์„œ ์†Œ๋ฌธ์ž) +"a".matches("[a-z&&[def]]"); // false +``` + +### 4.4 ์‚ฌ์ „ ์ •์˜๋œ ๋ฌธ์ž ํด๋ž˜์Šค (Predefined Character Classes / Shorthands) +์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ๋ฌธ์ž ํด๋ž˜์Šค๋ฅผ ์งง๊ฒŒ ํ‘œํ˜„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +```java +// \\d - Digit (์ˆซ์ž) = [0-9] +"5".matches("\\\\d"); // true +"a".matches("\\\\d"); // false + +// \\D - Non-Digit (์ˆซ์ž ์•„๋‹˜) = [^0-9] +"a".matches("\\\\D"); // true + +// \\w - Word character (๋‹จ์–ด ๋ฌธ์ž) = [a-zA-Z0-9_] +"a".matches("\\\\w"); // true +"_".matches("\\\\w"); // true +"@".matches("\\\\w"); // false (ํŠน์ˆ˜๋ฌธ์ž ์ œ์™ธ) + +// \\W - Non-Word character = [^a-zA-Z0-9_] +"@".matches("\\\\W"); // true + +// \\s - Whitespace (๊ณต๋ฐฑ) = [ \\t\\n\\r\\f] +" ".matches("\\\\s"); // true +"\\t".matches("\\\\s"); // true + +// \\S - Non-Whitespace = [^\\s] +"a".matches("\\\\S"); // true +``` + +### 4.5 ์ˆ˜๋Ÿ‰์ž (Quantifiers) +์•ž์˜ ์š”์†Œ๊ฐ€ ์–ผ๋งˆ๋‚˜ ๋ฐ˜๋ณต๋˜๋Š”์ง€๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. + +#### 4.5.1 ๊ธฐ๋ณธ ์ˆ˜๋Ÿ‰์ž +```java +// * : 0ํšŒ ์ด์ƒ ({0,}) +"".matches("a*"); // true +"aaa".matches("a*"); // true + +// + : 1ํšŒ ์ด์ƒ ({1,}) +"".matches("a+"); // false +"a".matches("a+"); // true + +// ? : 0ํšŒ ๋˜๋Š” 1ํšŒ ({0,1}) +"a".matches("a?"); // true +"aa".matches("a?"); // false + +// {n} : ์ •ํ™•ํžˆ nํšŒ +"aa".matches("a{2}"); // true + +// {n,m} : nํšŒ ์ด์ƒ mํšŒ ์ดํ•˜ +"aaa".matches("a{2,4}"); // true + +// {n,} : nํšŒ ์ด์ƒ +"aaa".matches("a{2,}"); // true +``` + +#### 4.5.2 ์ˆ˜๋Ÿ‰์ž์˜ ์ข…๋ฅ˜ (Greedy, Lazy, Possessive) +| ์ข…๋ฅ˜ | ๋ฌธ๋ฒ• | ์„ค๋ช… | ์˜ˆ์‹œ (`aab` ์—์„œ `a+` ๋งค์นญ) | +|---|---|---|---| +| **Greedy** (ํƒ์š•์ ) | `*`, `+`, `?`, `{n,m}` | ๊ฐ€๋Šฅํ•œ ํ•œ **๊ฐ€์žฅ ๋งŽ์ด** ๋งค์นญํ•˜๋ ค๊ณ  ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค. (๊ธฐ๋ณธ๊ฐ’) | `aa` (์ „์ฒด ๋งค์นญ) | +| **Reluctant** (Lazy, ๊ฒŒ์œผ๋ฅธ) | `*?`, `+?`, `??`, `{n,m}?` | ๊ฐ€๋Šฅํ•œ ํ•œ **๊ฐ€์žฅ ์ ๊ฒŒ** ๋งค์นญํ•˜๋ ค๊ณ  ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค. | `a` (์ฒซ ๋ฒˆ์งธ a๋งŒ ๋งค์นญ) | +| **Possessive** (์†Œ์œ ์ ) | `*+`, `++`, `?+`, `{n,m}+` | Greedy์ฒ˜๋Ÿผ ๋งŽ์ด ๋งค์นญํ•˜์ง€๋งŒ, **๋ฐฑํŠธ๋ž˜ํ‚น์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.** ์„ฑ๋Šฅ ์ตœ์ ํ™”์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. | `aa` (๋งค์นญ ํ›„ ๋’ค๋กœ ๋Œ์•„๊ฐ€์ง€ ์•Š์Œ) | + +```java +// Greedy vs Lazy +String text = "
Hello
"; +// Greedy:
Hello
์ „์ฒด ๋งค์นญ +text.matches("
.*
"); +// Lazy:
Hello
์—์„œ
,
๊ฐ๊ฐ ๋งค์นญ ์‹œ๋„ ๊ฐ€๋Šฅ (find() ์‚ฌ์šฉ ์‹œ) +``` + +### 4.6 ๊ฒฝ๊ณ„ (Anchors) +๋ฌธ์ž์—ด์˜ ํŠน์ • ์œ„์น˜๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. ๋ฌธ์ž๋ฅผ ์†Œ๋น„ํ•˜์ง€ ์•Š๊ณ  ์œ„์น˜๋งŒ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค (Zero-width assertion). + +```java +// ^ : ๋ฌธ์ž์—ด(๋˜๋Š” ๋ผ์ธ)์˜ ์‹œ์ž‘ +Pattern.compile("^hello").matcher("hello world").find(); // true + +// $ : ๋ฌธ์ž์—ด(๋˜๋Š” ๋ผ์ธ)์˜ ๋ +Pattern.compile("world$").matcher("hello world").find(); // true + +// \\b : ๋‹จ์–ด ๊ฒฝ๊ณ„ (Word Boundary) - ๋ฌธ์ž์™€ ๊ณต๋ฐฑ/ํŠน์ˆ˜๋ฌธ์ž ์‚ฌ์ด +"cat".matches("\\\\bcat\\\\b"); // true +"cathedral".matches("\\\\bcat\\\\b"); // false (cat์ด ๋‹จ์–ด์˜ ์ผ๋ถ€์ž„) + +// \\B : ๋‹จ์–ด ๊ฒฝ๊ณ„๊ฐ€ ์•„๋‹˜ +"cathedral".matches(".*\\\\Bcat\\\\B.*"); // true (์ค‘๊ฐ„์— ํฌํ•จ๋œ cat) + +// \\A : ์ž…๋ ฅ์˜ ์‹œ์ž‘ (๋ฌด์กฐ๊ฑด ๋ฌธ์ž์—ด ์ „์ฒด์˜ ์‹œ์ž‘) +// \\z : ์ž…๋ ฅ์˜ ๋ (๋ฌด์กฐ๊ฑด ๋ฌธ์ž์—ด ์ „์ฒด์˜ ๋) +// \\Z : ์ž…๋ ฅ์˜ ๋ (๋งˆ์ง€๋ง‰ ์ข…๊ฒฐ์ž(\\n)๊ฐ€ ์žˆ์œผ๋ฉด ๊ทธ ์•ž) +``` +### 4.7 ๊ทธ๋ฃนํ™” (Grouping) +์—ฌ๋Ÿฌ ๋ฌธ์ž๋ฅผ ํ•˜๋‚˜์˜ ๋‹จ์œ„๋กœ ๋ฌถ๊ฑฐ๋‚˜, ๋งค์นญ๋œ ๋ถ€๋ถ„์„ ์ถ”์ถœํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + +#### 4.7.1 ์บก์ฒ˜ ๊ทธ๋ฃน (Capturing Groups) - `(...)` +๋งค์นญ๋œ ๋ถ€๋ถ„์„ ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅํ•˜์—ฌ ๋‚˜์ค‘์— ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +```java +String log = "2025-11-18 23:43:21 ERROR User login failed"; + +Pattern p = Pattern.compile("(\\\\d{4}-\\\\d{2}-\\\\d{2}) (\\\\d{2}:\\\\d{2}:\\\\d{2}) (\\\\w+) (.+)"); +Matcher m = p.matcher(log); + +if (m.find()) { + System.out.println("๋‚ ์งœ: " + m.group(1)); // 2025-11-18 + System.out.println("์‹œ๊ฐ„: " + m.group(2)); // 23:43:21 + System.out.println("๋ ˆ๋ฒจ: " + m.group(3)); // ERROR + System.out.println("๋ฉ”์‹œ์ง€: " + m.group(4)); // User login failed +} +``` + +#### 4.7.2 ๋น„์บก์ฒ˜ ๊ทธ๋ฃน (Non-Capturing Groups) - `(?:...)` +๊ทธ๋ฃนํ™”๋Š” ํ•„์š”ํ•˜์ง€๋งŒ, ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅํ•  ํ•„์š”๊ฐ€ ์—†์„ ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์„ฑ๋Šฅ์ƒ ์ด์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. + +```java +// ๊ทธ๋ฃนํ™”๋Š” ํ•„์š”ํ•˜์ง€๋งŒ ์บก์ฒ˜๋Š” ๋ถˆํ•„์š”ํ•œ ๊ฒฝ์šฐ +String url = ""; + +// ๋น„์บก์ฒ˜ ๊ทธ๋ฃน ์‚ฌ์šฉ (์„ฑ๋Šฅ ํ–ฅ์ƒ) +Pattern p2 = Pattern.compile("(?:https|http)://(.+)"); +Matcher m2 = p2.matcher(url); +if (m2.find()) { + // m2.group(1)์€ ์ด์ œ www.example.com (ํ”„๋กœํ† ์ฝœ์€ ์บก์ฒ˜ ์•ˆ ๋จ) + System.out.println(m2.group(1)); // www.example.com +} +``` + +#### 4.7.3 ๋ช…๋ช…๋œ ๊ทธ๋ฃน (Named Groups) - `(?...)` +์ธ๋ฑ์Šค ๋Œ€์‹  ์ด๋ฆ„์œผ๋กœ ๊ทธ๋ฃน์„ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ์–ด ๊ฐ€๋…์„ฑ์ด ์ข‹์•„์ง‘๋‹ˆ๋‹ค. + +```java +String date = "2025-11-18"; +Pattern p = Pattern.compile("(?\\\\d{4})-(?\\\\d{2})-(?\\\\d{2})"); +Matcher m = p.matcher(date); + +if (m.find()) { + System.out.println("๋…„๋„: " + m.group("year")); // 2025 + System.out.println("์›”: " + m.group("month")); // 11 + System.out.println("์ผ: " + m.group("day")); // 18 +} +``` + +#### 4.7.4 ์—ญ์ฐธ์กฐ (Back-reference) - `\\1`, `\\k` +์•ž์„œ ๋งค์นญ๋œ ๊ทธ๋ฃน์„ ๋‹ค์‹œ ์ฐธ์กฐํ•ฉ๋‹ˆ๋‹ค. + +```java +// ์ค‘๋ณต ๋‹จ์–ด ์ฐพ๊ธฐ +String text = "the the cat sat on the the mat"; +Pattern p = Pattern.compile("\\\\b(\\\\w+)\\\\s+\\\\1\\\\b"); +Matcher m = p.matcher(text); + +while (m.find()) { + System.out.println("์ค‘๋ณต ๋‹จ์–ด: " + m.group(1)); +} + +// HTML ํƒœ๊ทธ ๋งค์นญ (์—ฌ๋Š” ํƒœ๊ทธ์™€ ๋‹ซ๋Š” ํƒœ๊ทธ๊ฐ€ ๊ฐ™์•„์•ผ ํ•จ) +String html = "

Title

Content

Subtitle

"; +Pattern tagPattern = Pattern.compile("<(\\\\w+)>.*?"); +Matcher tagMatcher = tagPattern.matcher(html); + +while (tagMatcher.find()) { + System.out.println(tagMatcher.group()); +} +``` + +### 4.8 ํ”Œ๋ž˜๊ทธ (Flags) +์ •๊ทœ ํ‘œํ˜„์‹์˜ ๋™์ž‘ ๋ฐฉ์‹์„ ๋ณ€๊ฒฝํ•˜๋Š” ์˜ต์…˜์ž…๋‹ˆ๋‹ค. `Pattern.compile()`์˜ ๋‘ ๋ฒˆ์งธ ์ธ์ž๋กœ ์ „๋‹ฌํ•˜๊ฑฐ๋‚˜, ํŒจํ„ด ๋‚ด์— `(?flags)` ํ˜•ํƒœ๋กœ ํฌํ•จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +- `Pattern.CASE_INSENSITIVE` (`(?i)`): ๋Œ€์†Œ๋ฌธ์ž๋ฅผ ๊ตฌ๋ถ„ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +- `Pattern.MULTILINE` (`(?m)`): `^`์™€ `$`๊ฐ€ ์ „์ฒด ๋ฌธ์ž์—ด์ด ์•„๋‹Œ ๊ฐ ๋ผ์ธ์˜ ์‹œ์ž‘๊ณผ ๋์— ๋งค์นญ๋ฉ๋‹ˆ๋‹ค. +- `Pattern.DOTALL` (`(?s)`): `.`์ด ๊ฐœํ–‰ ๋ฌธ์ž๋ฅผ ํฌํ•จํ•œ ๋ชจ๋“  ๋ฌธ์ž์™€ ๋งค์นญ๋ฉ๋‹ˆ๋‹ค. +- `Pattern.COMMENTS` (`(?x)`): ํŒจํ„ด ๋‚ด์˜ ๊ณต๋ฐฑ๊ณผ ์ฃผ์„์„ ๋ฌด์‹œํ•ฉ๋‹ˆ๋‹ค. (๊ฐ€๋…์„ฑ ํ–ฅ์ƒ) + +```java +// MULTILINE ์˜ˆ์ œ +String text = "First line\\nSecond line"; +Pattern p = Pattern.compile("^Second", Pattern.MULTILINE); // ๊ฐ ์ค„์˜ ์‹œ์ž‘์—์„œ ๋งค์นญ +Matcher m = p.matcher(text); +System.out.println(m.find()); // true +``` + +--- + +## 5. ์‹ฌํ™” ๊ฐœ๋… (Advanced Concepts) + +### 5.1 Greedy vs. Lazy ์ƒ์„ธ ์˜ˆ์ œ + +#### Greedy (ํƒ์š•์ ) - ๊ธฐ๋ณธ ๋™์ž‘ + +```java +String html = "
Hello
World
"; + +// Greedy: ์ตœ๋Œ€ํ•œ ๋งŽ์ด ๋งค์นญ +Pattern greedy = Pattern.compile("
.*
"); +Matcher m = greedy.matcher(html); +if (m.find()) { + System.out.println(m.group()); + // ์ถœ๋ ฅ:
Hello
World
+ // (์ „์ฒด๋ฅผ ํ•˜๋‚˜๋กœ ๋งค์นญ) +} +``` + +#### Lazy (๊ฒŒ์œผ๋ฅธ) - `?` ์ถ”๊ฐ€ + +```java +String html = "
Hello
World
"; + +// Lazy: ์ตœ์†Œํ•œ๋งŒ ๋งค์นญ +Pattern lazy = Pattern.compile("
.*?
"); +Matcher m = lazy.matcher(html); +while (m.find()) { + System.out.println(m.group()); +} +// ์ถœ๋ ฅ: +//
Hello
+//
World
+// (๊ฐ๊ฐ ๊ฐœ๋ณ„ ๋งค์นญ) +``` + +**์ˆ˜๋Ÿ‰์ž๋ณ„ Lazy ๋ฒ„์ „**: + +```java +// Greedy โ†’ Lazy +// * โ†’ *? +// + โ†’ +? +// ? โ†’ ?? +// {n,m} โ†’ {n,m}? +// {n,} โ†’ {n,}? + +// ์‹ค๋ฌด ์˜ˆ์ œ: HTML ํƒœ๊ทธ ์ถ”์ถœ +String html = "

First

Second

"; + +// Greedy +html.replaceAll("

.*

", "[CONTENT]"); +// ๊ฒฐ๊ณผ: [CONTENT] + +// Lazy +html.replaceAll("

.*?

", "[CONTENT]"); +// ๊ฒฐ๊ณผ: [CONTENT][CONTENT] +``` + +## ๐Ÿ“Š 6. ์„ฑ๋Šฅ ์ตœ์ ํ™” ํŒ + +### 6.1 Pattern ์žฌ์‚ฌ์šฉ + +```java +// โŒ ๋‚˜์œ ์˜ˆ: ๋งค๋ฒˆ ์ปดํŒŒ์ผ +public boolean validateEmail(String email) { + return email.matches("^[\\\\w.-]+@[\\\\w.-]+\\\\.[a-zA-Z]{2,}$"); + // matches()๋Š” ๋‚ด๋ถ€์—์„œ ๋งค๋ฒˆ Pattern.compile() ํ˜ธ์ถœ +} + +// โœ… ์ข‹์€ ์˜ˆ: Pattern ์žฌ์‚ฌ์šฉ +public class EmailValidator { + private static final Pattern EMAIL_PATTERN = + Pattern.compile("^[\\\\w.-]+@[\\\\w.-]+\\\\.[a-zA-Z]{2,}$"); + + public boolean validateEmail(String email) { + return EMAIL_PATTERN.matcher(email).matches(); + } +} + +// ์„ฑ๋Šฅ ์ฐจ์ด: ์•ฝ 3-5๋ฐฐ ๋น ๋ฆ„ + +``` + +### 6.2 ๋น„์บก์ฒ˜ ๊ทธ๋ฃน ์‚ฌ์šฉ + +```java +// โŒ ๋А๋ฆผ: ๋ถˆํ•„์š”ํ•œ ์บก์ฒ˜ +Pattern slow = Pattern.compile("(https|http)://([\\\\w.]+)"); + +// โœ… ๋น ๋ฆ„: ๋น„์บก์ฒ˜ ๊ทธ๋ฃน +Pattern fast = Pattern.compile("(?:https|http)://([\\\\w.]+)"); +// ๋„๋ฉ”์ธ๋งŒ ํ•„์š”ํ•œ ๊ฒฝ์šฐ ํ”„๋กœํ† ์ฝœ ์บก์ฒ˜ ๋ถˆํ•„์š” + +``` + +### 6.3 Catastrophic Backtracking ๋ฐฉ์ง€ + + + +์ด ์žฌ์•™์  ๋ฐฑํŠธ๋ž˜ํ‚น์€ ์ •๊ทœํ‘œํ˜„์‹์„ ์ด์šฉํ•œ ๊ณต๊ฒฉ์ธ ReDoS(Regular Expression Denial of Service)์˜ ์›์ธ์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์‹ค์ œ๋กœ 2019๋…„์—๋Š” ํด๋ผ์šฐ๋“œ ํ”Œ๋ ˆ์–ด์—์„œ ReDos ๊ณต๊ฒฉ์„ ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค. + +๊ฐ„๋‹จํ•˜๊ฒŒ ํ•ด๊ฒฐ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ ์ฃผ์˜ํ•ด์„œ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฉด ์‰ฝ๊ฒŒ ๊ณต๊ฒฉ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค. + +```java +// โŒ ์œ„ํ—˜: ์žฌ์•™์  ๋ฐฑํŠธ๋ž˜ํ‚น ๊ฐ€๋Šฅ +Pattern dangerous = Pattern.compile("(a+)+b"); +String input = "aaaaaaaaaaaaaaaaaaaaaaaaaaac"; // 'b'๊ฐ€ ์—†์Œ +// ์ด ๊ฒฝ์šฐ ์ง€์ˆ˜ ์‹œ๊ฐ„ ๋ณต์žก๋„๋กœ ์ธํ•ด ๋งค์šฐ ๋А๋ ค์ง (๋˜๋Š” ์Šคํƒ ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ) + +// โœ… ์•ˆ์ „: Possessive ์ˆ˜๋Ÿ‰์ž ์‚ฌ์šฉ +Pattern safe = Pattern.compile("(a++)b"); +// ๋˜๋Š” Atomic ๊ทธ๋ฃน ์‚ฌ์šฉ +Pattern safe2 = Pattern.compile("(?>a+)b"); + +// ์‹ค๋ฌด์—์„œ๋Š” ํƒ€์ž„์•„์›ƒ ์„ค์ • +public boolean matchesWithTimeout(String pattern, String input, long timeoutMs) { + ExecutorService executor = Executors.newSingleThreadExecutor(); + Future future = executor.submit(() -> { + return Pattern.compile(pattern).matcher(input).matches(); + }); + + try { + return future.get(timeoutMs, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + future.cancel(true); + return false; + } catch (Exception e) { + return false; + } finally { + executor.shutdownNow(); + } +} + +``` + +--- + +## ๐Ÿ“š 7. ์ฐธ๊ณ  ์ž๋ฃŒ ๋ฐ ํ•™์Šต ๋ฆฌ์†Œ์Šค + +### ์˜จ๋ผ์ธ ๋„๊ตฌ + +- **Regex101** (https://regex101.com/) - ์‹ค์‹œ๊ฐ„ ํŒจํ„ด ํ…Œ์ŠคํŠธ ๋ฐ ์„ค๋ช… +- **RegExr** (https://regexr.com/) - ์‹œ๊ฐ์  ๋””๋ฒ„๊น… +- **RegexPlanet** (https://www.regexplanet.com/) - ๋‹ค์–‘ํ•œ ์–ธ์–ด๋ณ„ ํ…Œ์ŠคํŠธ + +### Java ๊ณต์‹ ๋ฌธ์„œ + +- `java.util.regex.Pattern` JavaDoc +- `java.util.regex.Matcher` JavaDoc + diff --git "a/76-Temporal/01-\354\240\225\352\267\234\355\221\234\355\230\204\354\213\235&ANT\355\221\234\355\230\204\354\213\235/img/chomsky-hierarchy.png" "b/76-Temporal/01-\354\240\225\352\267\234\355\221\234\355\230\204\354\213\235&ANT\355\221\234\355\230\204\354\213\235/img/chomsky-hierarchy.png" new file mode 100644 index 0000000..cdedf0e Binary files /dev/null and "b/76-Temporal/01-\354\240\225\352\267\234\355\221\234\355\230\204\354\213\235&ANT\355\221\234\355\230\204\354\213\235/img/chomsky-hierarchy.png" differ diff --git "a/76-Temporal/01-\354\240\225\352\267\234\355\221\234\355\230\204\354\213\235&ANT\355\221\234\355\230\204\354\213\235/presentation/Regular_Expression_Example.md" "b/76-Temporal/01-\354\240\225\352\267\234\355\221\234\355\230\204\354\213\235&ANT\355\221\234\355\230\204\354\213\235/presentation/Regular_Expression_Example.md" new file mode 100644 index 0000000..0738cc4 --- /dev/null +++ "b/76-Temporal/01-\354\240\225\352\267\234\355\221\234\355\230\204\354\213\235&ANT\355\221\234\355\230\204\354\213\235/presentation/Regular_Expression_Example.md" @@ -0,0 +1,591 @@ +## ๐Ÿ’ผ 1. ์‹ค์šฉ ์˜ˆ์ œ + +### 1.1 ์œ ํšจ์„ฑ ๊ฒ€์ฆ (Validation) + +### ์ด๋ฉ”์ผ ๊ฒ€์ฆ + +```java +/** + * ์ด๋ฉ”์ผ ๊ฒ€์ฆ ํŒจํ„ด + * - ๋กœ์ปฌ ํŒŒํŠธ: ์˜๋ฌธ, ์ˆซ์ž, ํŠน์ˆ˜๋ฌธ์ž(.-_+) ํ—ˆ์šฉ + * - @ + * - ๋„๋ฉ”์ธ: ์˜๋ฌธ, ์ˆซ์ž, ํ•˜์ดํ”ˆ ํ—ˆ์šฉ + * - ์ตœ์ƒ์œ„ ๋„๋ฉ”์ธ: 2์ž ์ด์ƒ์˜ ์˜๋ฌธ + */ +public class EmailValidator { + + // ๊ฐ„๋‹จํ•œ ๋ฒ„์ „ + private static final String EMAIL_SIMPLE = + "^[\\\\w.-]+@[\\\\w.-]+\\\\.[a-zA-Z]{2,}$"; + + // RFC 5322 ์ค€์ˆ˜ (๋” ์—„๊ฒฉํ•œ) ๋ฒ„์ „ + private static final String EMAIL_STRICT = + "^[a-zA-Z0-9_+&*-]+(?:\\\\.[a-zA-Z0-9_+&*-]+)*@" + + "(?:[a-zA-Z0-9-]+\\\\.)+[a-zA-Z]{2,7}$"; + + public static boolean isValidEmail(String email) { + if (email == null || email.isEmpty()) { + return false; + } + return email.matches(EMAIL_SIMPLE); + } + + // ํ…Œ์ŠคํŠธ + public static void main(String[] args) { + String[] emails = { + "user@example.com", // true + "user.name@example.com", // true + "user+tag@example.co.kr", // true + "user@", // false + "@example.com", // false + "user@.com", // false + "user name@example.com" // false (๊ณต๋ฐฑ) + }; + + for (String email : emails) { + System.out.printf("%s: %b%n", email, isValidEmail(email)); + } + } +} + +``` + +### ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฒ€์ฆ + +```java +/** + * ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฐ•๋„ ๊ฒ€์ฆ + * ์š”๊ตฌ์‚ฌํ•ญ: + * - 8-20์ž + * - ์ตœ์†Œ 1๊ฐœ์˜ ๋Œ€๋ฌธ์ž + * - ์ตœ์†Œ 1๊ฐœ์˜ ์†Œ๋ฌธ์ž + * - ์ตœ์†Œ 1๊ฐœ์˜ ์ˆซ์ž + * - ์ตœ์†Œ 1๊ฐœ์˜ ํŠน์ˆ˜๋ฌธ์ž + */ +public class PasswordValidator { + + // Lookahead๋ฅผ ์‚ฌ์šฉํ•œ ๋ณต์žกํ•œ ๊ฒ€์ฆ + private static final String PASSWORD_PATTERN = + "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\\\d)(?=.*[@$!%*?&])[A-Za-z\\\\d@$!%*?&]{8,20}$"; + + public static ValidationResult validatePassword(String password) { + if (password == null || password.isEmpty()) { + return new ValidationResult(false, "๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."); + } + + // ๊ธธ์ด ์ฒดํฌ + if (password.length() < 8 || password.length() > 20) { + return new ValidationResult(false, "๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” 8-20์ž์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค."); + } + + // ์†Œ๋ฌธ์ž ํฌํ•จ ์ฒดํฌ + if (!password.matches(".*[a-z].*")) { + return new ValidationResult(false, "์†Œ๋ฌธ์ž๋ฅผ ์ตœ์†Œ 1๊ฐœ ํฌํ•จํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค."); + } + + // ๋Œ€๋ฌธ์ž ํฌํ•จ ์ฒดํฌ + if (!password.matches(".*[A-Z].*")) { + return new ValidationResult(false, "๋Œ€๋ฌธ์ž๋ฅผ ์ตœ์†Œ 1๊ฐœ ํฌํ•จํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค."); + } + + // ์ˆซ์ž ํฌํ•จ ์ฒดํฌ + if (!password.matches(".*\\\\d.*")) { + return new ValidationResult(false, "์ˆซ์ž๋ฅผ ์ตœ์†Œ 1๊ฐœ ํฌํ•จํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค."); + } + + // ํŠน์ˆ˜๋ฌธ์ž ํฌํ•จ ์ฒดํฌ + if (!password.matches(".*[@$!%*?&].*")) { + return new ValidationResult(false, "ํŠน์ˆ˜๋ฌธ์ž(@$!%*?&)๋ฅผ ์ตœ์†Œ 1๊ฐœ ํฌํ•จํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค."); + } + + return new ValidationResult(true, "์•ˆ์ „ํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ์ž…๋‹ˆ๋‹ค."); + } + + // ํ†ตํ•ฉ ํŒจํ„ด ์‚ฌ์šฉ (ํ•œ ๋ฒˆ์— ์ฒดํฌ) + public static boolean isValidPasswordQuick(String password) { + return password != null && password.matches(PASSWORD_PATTERN); + } + + static class ValidationResult { + boolean valid; + String message; + + ValidationResult(boolean valid, String message) { + this.valid = valid; + this.message = message; + } + } + + // ํ…Œ์ŠคํŠธ + public static void main(String[] args) { + String[] passwords = { + "Abc123!@", // true + "Password123!", // true + "weak", // false (๋„ˆ๋ฌด ์งง์Œ) + "alllowercase123!", // false (๋Œ€๋ฌธ์ž ์—†์Œ) + "ALLUPPERCASE123!", // false (์†Œ๋ฌธ์ž ์—†์Œ) + "NoSpecialChar123", // false (ํŠน์ˆ˜๋ฌธ์ž ์—†์Œ) + "NoNumber!@#", // false (์ˆซ์ž ์—†์Œ) + }; + + for (String pw : passwords) { + ValidationResult result = validatePassword(pw); + System.out.printf("%s: %b - %s%n", pw, result.valid, result.message); + } + } +} + +``` + +### ํ•œ๊ตญ ์ „ํ™”๋ฒˆํ˜ธ ๊ฒ€์ฆ + +```java +public class PhoneValidator { + + // ํœด๋Œ€ํฐ: 010-XXXX-XXXX + private static final String MOBILE_PATTERN = "^010-\\\\d{4}-\\\\d{4}$"; + + // ์ผ๋ฐ˜ ์ „ํ™”: 0XX-XXX(X)-XXXX + private static final String PHONE_PATTERN = "^0\\\\d{1,2}-\\\\d{3,4}-\\\\d{4}$"; + + // ํ†ตํ•ฉ ํŒจํ„ด + private static final String ALL_PHONE_PATTERN = + "^(010-\\\\d{4}-\\\\d{4}|0\\\\d{1,2}-\\\\d{3,4}-\\\\d{4})$"; + + public static boolean isValidMobile(String phone) { + return phone != null && phone.matches(MOBILE_PATTERN); + } + + public static boolean isValidPhone(String phone) { + return phone != null && phone.matches(ALL_PHONE_PATTERN); + } + + // ํ•˜์ดํ”ˆ ์ž๋™ ์ถ”๊ฐ€ + public static String formatPhone(String phone) { + if (phone == null) return null; + + // ์ˆซ์ž๋งŒ ์ถ”์ถœ + String numbers = phone.replaceAll("[^0-9]", ""); + + // ํœด๋Œ€ํฐ (11์ž๋ฆฌ) + if (numbers.matches("^010\\\\d{8}$")) { + return numbers.replaceAll("(\\\\d{3})(\\\\d{4})(\\\\d{4})", "$1-$2-$3"); + } + + // ์„œ์šธ (10์ž๋ฆฌ: 02-XXXX-XXXX) + if (numbers.matches("^02\\\\d{8}$")) { + return numbers.replaceAll("(\\\\d{2})(\\\\d{4})(\\\\d{4})", "$1-$2-$3"); + } + + // ๊ธฐํƒ€ ์ง€์—ญ (10์ž๋ฆฌ: 0XX-XXX-XXXX) + if (numbers.matches("^0\\\\d{9}$")) { + return numbers.replaceAll("(\\\\d{3})(\\\\d{3})(\\\\d{4})", "$1-$2-$3"); + } + + // ๊ธฐํƒ€ ์ง€์—ญ (11์ž๋ฆฌ: 0XX-XXXX-XXXX) + if (numbers.matches("^0\\\\d{10}$")) { + return numbers.replaceAll("(\\\\d{3})(\\\\d{4})(\\\\d{4})", "$1-$2-$3"); + } + + return phone; // ํฌ๋งทํ•  ์ˆ˜ ์—†์œผ๋ฉด ์›๋ณธ ๋ฐ˜ํ™˜ + } + + // ํ…Œ์ŠคํŠธ + public static void main(String[] args) { + System.out.println(formatPhone("01012345678")); // 010-1234-5678 + System.out.println(formatPhone("0212345678")); // 02-1234-5678 + System.out.println(formatPhone("0311234567")); // 031-123-4567 + System.out.println(formatPhone("03112345678")); // 031-1234-5678 + } +} + +``` + +### 1.2 ๋ฐ์ดํ„ฐ ํŒŒ์‹ฑ (Parsing) + +### ๋กœ๊ทธ ํŒŒ์ผ ํŒŒ์‹ฑ + +```java +/** + * ๋กœ๊ทธ ํŒŒ์ผ์—์„œ ์ •๋ณด ์ถ”์ถœ + * ๋กœ๊ทธ ํ˜•์‹: [2025-11-18 23:43:21] [ERROR] [UserService] User login failed - IP: 192.168.1.100 + */ +public class LogParser { + + private static final Pattern LOG_PATTERN = Pattern.compile( + "\\\\[(\\\\d{4}-\\\\d{2}-\\\\d{2} \\\\d{2}:\\\\d{2}:\\\\d{2})\\\\]\\\\s+" + // ํƒ€์ž„์Šคํƒฌํ”„ + "\\\\[(\\\\w+)\\\\]\\\\s+" + // ๋กœ๊ทธ ๋ ˆ๋ฒจ + "\\\\[([\\\\w]+)\\\\]\\\\s+" + // ์„œ๋น„์Šค๋ช… + "(.+?)(?:\\\\s+-\\\\s+IP:\\\\s+(\\\\d+\\\\.\\\\d+\\\\.\\\\d+\\\\.\\\\d+))?" // ๋ฉ”์‹œ์ง€ ๋ฐ IP (์˜ต์…˜) + ); + + public static class LogEntry { + String timestamp; + String level; + String service; + String message; + String ipAddress; + + @Override + public String toString() { + return String.format("LogEntry{time='%s', level='%s', service='%s', message='%s', ip='%s'}", + timestamp, level, service, message, ipAddress); + } + } + + public static LogEntry parse(String logLine) { + Matcher m = LOG_PATTERN.matcher(logLine); + if (!m.find()) { + return null; + } + + LogEntry entry = new LogEntry(); + entry.timestamp = m.group(1); + entry.level = m.group(2); + entry.service = m.group(3); + entry.message = m.group(4); + entry.ipAddress = m.group(5); // null์ผ ์ˆ˜ ์žˆ์Œ + + return entry; + } + + // ํŠน์ • IP ์ฃผ์†Œ ์ถ”์ถœ + public static List extractIPs(String text) { + List ips = new ArrayList<>(); + Pattern ipPattern = Pattern.compile("\\\\b(?:\\\\d{1,3}\\\\.){3}\\\\d{1,3}\\\\b"); + Matcher m = ipPattern.matcher(text); + + while (m.find()) { + ips.add(m.group()); + } + + return ips; + } + + // ์—๋Ÿฌ ๋กœ๊ทธ๋งŒ ํ•„ํ„ฐ๋ง + public static List filterErrorLogs(List logs) { + return logs.stream() + .filter(log -> log.matches(".*\\\\[ERROR\\\\].*")) + .collect(Collectors.toList()); + } + + // ํ…Œ์ŠคํŠธ + public static void main(String[] args) { + String log1 = "[2025-11-18 23:43:21] [ERROR] [UserService] User login failed - IP: 192.168.1.100"; + String log2 = "[2025-11-18 23:43:22] [INFO] [PaymentService] Payment processed successfully"; + + LogEntry entry1 = parse(log1); + LogEntry entry2 = parse(log2); + + System.out.println(entry1); + System.out.println(entry2); + + // IP ์ถ”์ถœ + String text = "Connections from 192.168.1.1, 10.0.0.1, and 172.16.0.1"; + List ips = extractIPs(text); + System.out.println("์ถ”์ถœ๋œ IP: " + ips); + } +} + +``` + +### URL ํŒŒ์‹ฑ + +```java +public class URLParser { + + private static final Pattern URL_PATTERN = Pattern.compile( + "^(?https?)://" + // ํ”„๋กœํ† ์ฝœ + "(?[^:/]+)" + // ๋„๋ฉ”์ธ + "(?::(?\\\\d+))?" + // ํฌํŠธ (์˜ต์…˜) + "(?/[^?#]*)?" + // ๊ฒฝ๋กœ (์˜ต์…˜) + "(?:\\\\?(?[^#]*))?" + // ์ฟผ๋ฆฌ ์ŠคํŠธ๋ง (์˜ต์…˜) + "(?:#(?.*))?$" // ํ”„๋ž˜๊ทธ๋จผํŠธ (์˜ต์…˜) + ); + + public static class URLInfo { + String protocol; + String domain; + String port; + String path; + String query; + String fragment; + Map queryParams; + + @Override + public String toString() { + return String.format("URL{protocol='%s', domain='%s', port='%s', path='%s', query='%s', fragment='%s', params=%s}", + protocol, domain, port, path, query, fragment, queryParams); + } + } + + public static URLInfo parse(String url) { + Matcher m = URL_PATTERN.matcher(url); + if (!m.matches()) { + return null; + } + + URLInfo info = new URLInfo(); + info.protocol = m.group("protocol"); + info.domain = m.group("domain"); + info.port = m.group("port"); + info.path = m.group("path"); + info.query = m.group("query"); + info.fragment = m.group("fragment"); + + // ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ ํŒŒ์‹ฑ + if (info.query != null && !info.query.isEmpty()) { + info.queryParams = parseQueryString(info.query); + } + + return info; + } + + private static Map parseQueryString(String query) { + Map params = new HashMap<>(); + Pattern paramPattern = Pattern.compile("([^&=]+)=([^&]*)"); + Matcher m = paramPattern.matcher(query); + + while (m.find()) { + params.put(m.group(1), m.group(2)); + } + + return params; + } + + // ํ…Œ์ŠคํŠธ + public static void main(String[] args) { + String url = ""; + URLInfo info = parse(url); + System.out.println(info); + + // ์ถœ๋ ฅ: + // URL{protocol='https', domain='example.com', port='8080', + // path='/api/users', query='id=123&name=john', + // fragment='section1', params={id=123, name=john}} + } +} + +``` + +### CSV/TSV ํŒŒ์‹ฑ + +```java +public class CSVParser { + + // ๊ธฐ๋ณธ CSV (์‰ผํ‘œ๋กœ ๊ตฌ๋ถ„, ๋”ฐ์˜ดํ‘œ ์ฒ˜๋ฆฌ) + private static final Pattern CSV_PATTERN = Pattern.compile( + ",(?=(?:[^\\"]*\\"[^\\"]*\\")*[^\\"]*$)" // ๋”ฐ์˜ดํ‘œ ์™ธ๋ถ€์˜ ์‰ผํ‘œ๋งŒ ๋งค์นญ + ); + + // RFC 4180 ์ค€์ˆ˜ CSV ํŒŒ์„œ + public static List parseCSVLine(String line) { + List fields = new ArrayList<>(); + + // ๋ณต์žกํ•œ ์ผ€์ด์Šค ์ฒ˜๋ฆฌ: "field1","field with, comma","field with ""quotes""" + Pattern pattern = Pattern.compile( + "\\"([^\\"]*(?:\\"\\"[^\\"]*)*)\\"|([^,]+)|(?<=,)(?=,)|^(?=,)|(?<=,)$" + ); + + Matcher m = pattern.matcher(line); + while (m.find()) { + String field; + if (m.group(1) != null) { + // ๋”ฐ์˜ดํ‘œ๋กœ ๊ฐ์‹ธ์ง„ ํ•„๋“œ + field = m.group(1).replace("\\"\\"", "\\""); + } else if (m.group(2) != null) { + // ์ผ๋ฐ˜ ํ•„๋“œ + field = m.group(2); + } else { + // ๋นˆ ํ•„๋“œ + field = ""; + } + fields.add(field); + } + + return fields; + } + + // ๊ฐ„๋‹จํ•œ ๋ฒ„์ „ (๋”ฐ์˜ดํ‘œ ์—†๋Š” ๊ฒฝ์šฐ) + public static List parseSimpleCSV(String line) { + return Arrays.asList(line.split(",")); + } + + // ํ…Œ์ŠคํŠธ + public static void main(String[] args) { + // ๋ณต์žกํ•œ ์ผ€์ด์Šค + String line1 = "John,Doe,\\"123 Main St, Apt 4\\",\\"He said \\"\\"Hello\\"\\"\\""; + List fields1 = parseCSVLine(line1); + System.out.println(fields1); + // [John, Doe, 123 Main St, Apt 4, He said "Hello"] + + // ๊ฐ„๋‹จํ•œ ์ผ€์ด์Šค + String line2 = "apple,banana,cherry"; + List fields2 = parseSimpleCSV(line2); + System.out.println(fields2); + // [apple, banana, cherry] + } +} + +``` + +--- + +## ๐Ÿ”ง 2. Spring Boot ์‹ค๋ฌด ํ†ตํ•ฉ ์˜ˆ์ œ + +### 2.1 Validation with Bean Validation + +```java +import jakarta.validation.Constraint; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import jakarta.validation.Payload; +import java.lang.annotation.*; + +// ์ปค์Šคํ…€ ์–ด๋…ธํ…Œ์ด์…˜ ์ •์˜ +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = PhoneNumberValidator.class) +@Documented +public @interface PhoneNumber { + String message() default "์œ ํšจํ•˜์ง€ ์•Š์€ ์ „ํ™”๋ฒˆํ˜ธ ํ˜•์‹์ž…๋‹ˆ๋‹ค"; + Class[] groups() default {}; + Class[] payload() default {}; +} + +// Validator ๊ตฌํ˜„ +public class PhoneNumberValidator implements ConstraintValidator { + + private static final String PHONE_PATTERN = "^(010-\\\\d{4}-\\\\d{4}|0\\\\d{1,2}-\\\\d{3,4}-\\\\d{4})$"; + + @Override + public boolean isValid(String value, ConstraintValidatorContext context) { + if (value == null || value.isEmpty()) { + return true; // @NotNull๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉ + } + return value.matches(PHONE_PATTERN); + } +} + +// DTO ์‚ฌ์šฉ +public class UserRegistrationDTO { + + @NotBlank(message = "์ด๋ฆ„์€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค") + @Size(min = 2, max = 50, message = "์ด๋ฆ„์€ 2-50์ž์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค") + private String name; + + @NotBlank(message = "์ด๋ฉ”์ผ์€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค") + @Email(message = "์œ ํšจํ•œ ์ด๋ฉ”์ผ ์ฃผ์†Œ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”") + private String email; + + @NotBlank(message = "์ „ํ™”๋ฒˆํ˜ธ๋Š” ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค") + @PhoneNumber // ์ปค์Šคํ…€ ๊ฒ€์ฆ + private String phone; + + @NotBlank(message = "๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค") + @Pattern( + regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\\\d)(?=.*[@$!%*?&])[A-Za-z\\\\d@$!%*?&]{8,20}$", + message = "๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” 8-20์ž์ด๋ฉฐ, ๋Œ€์†Œ๋ฌธ์ž, ์ˆซ์ž, ํŠน์ˆ˜๋ฌธ์ž๋ฅผ ๊ฐ๊ฐ 1๊ฐœ ์ด์ƒ ํฌํ•จํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค" + ) + private String password; + + // getters, setters... +} + +// Controller +@RestController +@RequestMapping("/api/users") +public class UserController { + + @PostMapping("/register") + public ResponseEntity register(@Valid @RequestBody UserRegistrationDTO dto) { + // @Valid๊ฐ€ ์ž๋™์œผ๋กœ ๊ฒ€์ฆ ์ˆ˜ํ–‰ + // ๊ฒ€์ฆ ์‹คํŒจ ์‹œ MethodArgumentNotValidException ๋ฐœ์ƒ + + // ๊ฒ€์ฆ ํ†ต๊ณผ ํ›„ ๋กœ์ง + return ResponseEntity.ok("๋“ฑ๋ก ์„ฑ๊ณต"); + } +} + +// Exception Handler +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity> handleValidationExceptions( + MethodArgumentNotValidException ex) { + + Map errors = new HashMap<>(); + ex.getBindingResult().getAllErrors().forEach((error) -> { + String fieldName = ((FieldError) error).getField(); + String errorMessage = error.getDefaultMessage(); + errors.put(fieldName, errorMessage); + }); + + return ResponseEntity.badRequest().body(errors); + } +} + +``` + +### 2.2 ๋กœ๊น… ๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง + +```java +@Service +public class LogAnalysisService { + + private static final Pattern ERROR_PATTERN = Pattern.compile( + "\\\\[(\\\\d{4}-\\\\d{2}-\\\\d{2} \\\\d{2}:\\\\d{2}:\\\\d{2})\\\\].*\\\\[ERROR\\\\].*" + ); + + private static final Pattern IP_PATTERN = Pattern.compile( + "\\\\b(?:\\\\d{1,3}\\\\.){3}\\\\d{1,3}\\\\b" + ); + + /** + * ๋กœ๊ทธ ํŒŒ์ผ์—์„œ ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ๊ฐ„๋Œ€ ๋ถ„์„ + */ + public Map analyzeErrorsByHour(List logs) { + Pattern hourPattern = Pattern.compile("\\\\d{4}-\\\\d{2}-\\\\d{2} (\\\\d{2}):"); + + return logs.stream() + .filter(log -> log.contains("[ERROR]")) + .map(log -> { + Matcher m = hourPattern.matcher(log); + return m.find() ? Integer.parseInt(m.group(1)) : -1; + }) + .filter(hour -> hour != -1) + .collect(Collectors.groupingBy( + hour -> hour, + Collectors.counting() + )); + } + + /** + * ์˜์‹ฌ์Šค๋Ÿฌ์šด IP ์ฃผ์†Œ ํƒ์ง€ (๋‹จ์‹œ๊ฐ„ ๋‚ด ๋งŽ์€ ์š”์ฒญ) + */ + public List detectSuspiciousIPs(List logs, int threshold) { + Map ipCounts = logs.stream() + .flatMap(log -> { + Matcher m = IP_PATTERN.matcher(log); + List ips = new ArrayList<>(); + while (m.find()) { + ips.add(m.group()); + } + return ips.stream(); + }) + .collect(Collectors.groupingBy( + ip -> ip, + Collectors.counting() + )); + + return ipCounts.entrySet().stream() + .filter(entry -> entry.getValue() > threshold) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + } +} + +``` + +--- diff --git a/76-Temporal/README.md b/76-Temporal/README.md new file mode 100644 index 0000000..9c52f28 --- /dev/null +++ b/76-Temporal/README.md @@ -0,0 +1,6 @@ +## Temporal + +### ์ฃผ์ œ +| ์ˆœ์„œ | ๋ฐœํ‘œ์ž | ์ฃผ์ œ | ์ผ์ž | +| :--- | :--- | :--- | :--- | +| 1 | ์กฐ์˜์žฌ | ์ •๊ทœ ํ‘œํ˜„์‹, ANT ํ‘œํ˜„์‹ | 2025-11-20 |