-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
727 lines (694 loc) · 66.3 KB
/
Copy pathindex.html
File metadata and controls
727 lines (694 loc) · 66.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Codyssey 미션 1-3을 완주하기 위한 최소한의 파이썬 학습 교재">
<title>미션 파이썬</title>
<style>
:root{
--bg:#F6F7F9; --surface:#FFFFFF; --ink:#1B2430; --muted:#5B6673;
--line:#DFE4EA; --accent:#2E5E9E; --accent-soft:#E8EFF8;
--gold:#B07E10; --gold-bg:#FBF4DE; --warn:#A33B2E; --warn-bg:#FAEAE6;
--ex-bg:#EAF3EC; --ex:#2E6B45;
--code-bg:#131A22; --code-ink:#D8E0E8; --code-line:#22303E;
--k:#7FB5FF; --s:#E8C56B; --c:#6F8291; --fn:#8FD0A8; --num:#D8A0DF; --out:#8A99A8;
}
:root:not([data-theme="light"]){}
@media (prefers-color-scheme: dark){
:root:not([data-theme="light"]){
--bg:#0F141A; --surface:#161D25; --ink:#E4EAF1; --muted:#95A2B0;
--line:#2A3441; --accent:#82ACDE; --accent-soft:#1C2A3B;
--gold:#D9A83C; --gold-bg:#2A2310; --warn:#E08573; --warn-bg:#32201B;
--ex-bg:#1A2B20; --ex:#8FC9A5;
}
}
:root[data-theme="dark"]{
--bg:#0F141A; --surface:#161D25; --ink:#E4EAF1; --muted:#95A2B0;
--line:#2A3441; --accent:#82ACDE; --accent-soft:#1C2A3B;
--gold:#D9A83C; --gold-bg:#2A2310; --warn:#E08573; --warn-bg:#32201B;
--ex-bg:#1A2B20; --ex:#8FC9A5;
}
*{box-sizing:border-box}
body{
margin:0; background:var(--bg); color:var(--ink);
font-family:-apple-system,BlinkMacSystemFont,"Apple SD Gothic Neo","Noto Sans KR","Segoe UI",sans-serif;
font-size:16px; line-height:1.75; -webkit-font-smoothing:antialiased;
}
a{color:var(--accent); text-decoration:none}
a:hover,a:focus-visible{text-decoration:underline}
a:focus-visible{outline:2px solid var(--accent); outline-offset:2px; border-radius:2px}
.shell{display:grid; grid-template-columns:264px minmax(0,1fr); max-width:1160px; margin:0 auto; gap:0 48px; padding:0 24px}
nav.toc{position:sticky; top:0; align-self:start; height:100vh; overflow-y:auto; padding:40px 0 40px; border-right:1px solid var(--line); font-size:13.5px}
nav.toc .booktag{font-size:11px; letter-spacing:.14em; text-transform:uppercase; color:var(--gold); font-weight:700; margin-bottom:6px}
nav.toc .bookname{font-weight:800; font-size:17px; margin-bottom:22px; letter-spacing:-.01em}
nav.toc ol{list-style:none; margin:0; padding:0}
nav.toc li{margin:0}
nav.toc a{display:flex; gap:10px; padding:5px 10px 5px 0; color:var(--muted); border-left:2px solid transparent; padding-left:12px; margin-left:-14px; line-height:1.45}
nav.toc a .n{font-variant-numeric:tabular-nums; color:var(--gold); font-weight:700; min-width:1.4em}
nav.toc a:hover{color:var(--ink); text-decoration:none}
nav.toc .part{margin:18px 0 6px; font-size:11px; letter-spacing:.12em; text-transform:uppercase; color:var(--muted); font-weight:700}
main{padding:56px 0 120px; min-width:0}
.prose{max-width:68ch}
header.cover{margin-bottom:64px}
header.cover .eyebrow{font-size:12px; letter-spacing:.18em; text-transform:uppercase; color:var(--gold); font-weight:700}
header.cover h1{font-size:clamp(34px,5vw,52px); line-height:1.15; letter-spacing:-.03em; margin:10px 0 14px; font-weight:800; text-wrap:balance}
header.cover p.sub{font-size:18px; color:var(--muted); max-width:60ch; margin:0 0 8px}
section.ch{margin-top:88px; scroll-margin-top:24px}
section.ch:first-of-type{margin-top:0}
.ch-head{border-top:3px solid var(--ink); padding-top:18px; margin-bottom:24px}
.ch-head .chno{font-size:12px; letter-spacing:.16em; text-transform:uppercase; color:var(--gold); font-weight:800}
.ch-head h2{font-size:30px; letter-spacing:-.02em; margin:6px 0 10px; font-weight:800; text-wrap:balance}
h3{font-size:19px; margin:40px 0 10px; letter-spacing:-.01em; font-weight:750}
h4{font-size:16px; margin:28px 0 8px; font-weight:700}
p{margin:0 0 14px}
ul,ol{padding-left:22px; margin:0 0 16px}
li{margin-bottom:5px}
strong{font-weight:700}
.chips{display:flex; flex-wrap:wrap; gap:6px; margin:4px 0 0}
.chip{font-size:11.5px; font-weight:700; padding:3px 10px; border-radius:99px; border:1px solid var(--line); color:var(--muted); background:var(--surface)}
.chip.m1{border-color:color-mix(in srgb,var(--muted) 40%,transparent)}
.chip.m2{color:var(--accent); border-color:color-mix(in srgb,var(--accent) 45%,transparent); background:var(--accent-soft)}
.chip.m3{color:var(--gold); border-color:color-mix(in srgb,var(--gold) 45%,transparent); background:var(--gold-bg)}
code{font-family:ui-monospace,"SF Mono","JetBrains Mono",Menlo,Consolas,monospace; font-size:.88em; background:var(--surface); border:1px solid var(--line); border-radius:5px; padding:1px 6px; white-space:nowrap}
pre{background:var(--code-bg); color:var(--code-ink); border-radius:10px; padding:18px 20px; overflow-x:auto; line-height:1.6; margin:0 0 18px; font-size:13.5px; border:1px solid var(--code-line)}
pre code{background:none; border:0; padding:0; font-size:inherit; white-space:pre}
pre .k{color:var(--k)} pre .s{color:var(--s)} pre .c{color:var(--c); font-style:italic}
pre .fn{color:var(--fn)} pre .num{color:var(--num)} pre .o{color:var(--out)}
.codecap{font-size:12.5px; color:var(--muted); margin:-12px 0 20px; padding-left:2px}
.callout{border-radius:10px; padding:14px 18px; margin:0 0 18px; font-size:15px}
.callout .t{font-size:11.5px; font-weight:800; letter-spacing:.12em; text-transform:uppercase; display:block; margin-bottom:6px}
.callout p:last-child{margin-bottom:0}
.callout.link{background:var(--gold-bg)} .callout.link .t{color:var(--gold)}
.callout.warn{background:var(--warn-bg)} .callout.warn .t{color:var(--warn)}
.callout.ex{background:var(--ex-bg)} .callout.ex .t{color:var(--ex)}
table{border-collapse:collapse; width:100%; font-size:14.5px; margin:0 0 20px}
.tablewrap{overflow-x:auto; margin:0 0 20px}
.tablewrap table{margin:0}
th{text-align:left; font-size:12px; letter-spacing:.08em; text-transform:uppercase; color:var(--muted); border-bottom:2px solid var(--ink); padding:8px 14px 8px 0}
td{border-bottom:1px solid var(--line); padding:9px 14px 9px 0; vertical-align:top}
td.num,th.num{text-align:right; font-variant-numeric:tabular-nums}
footer.book{margin-top:96px; border-top:1px solid var(--line); padding-top:20px; color:var(--muted); font-size:13.5px}
details.mobiletoc{display:none}
@media (max-width:920px){
.shell{display:block; padding:0 18px}
nav.toc{display:none}
details.mobiletoc{display:block; margin:20px 0 8px; border:1px solid var(--line); border-radius:10px; background:var(--surface); padding:12px 16px}
details.mobiletoc summary{font-weight:700; cursor:pointer}
details.mobiletoc ol{list-style:none; padding:10px 0 0; margin:0}
details.mobiletoc a{display:block; padding:4px 0; color:var(--muted)}
main{padding-top:20px}
}
@media (prefers-reduced-motion: no-preference){
html{scroll-behavior:smooth}
}
</style>
</head>
<body>
<div class="shell">
<nav class="toc" aria-label="목차">
<div class="booktag">Codyssey</div>
<div class="bookname">미션 파이썬</div>
<ol>
<li><a href="#intro"><span class="n">0</span>왜 이만큼만 배우는가</a></li>
<li class="part">기초 문법 — 미션 2·3 공통</li>
<li><a href="#ch1"><span class="n">1</span>실행하고 출력하기</a></li>
<li><a href="#ch2"><span class="n">2</span>변수·자료형·입력</a></li>
<li><a href="#ch3"><span class="n">3</span>리스트와 딕셔너리</a></li>
<li><a href="#ch4"><span class="n">4</span>조건문과 값 비교</a></li>
<li><a href="#ch5"><span class="n">5</span>반복문과 2차원 순회</a></li>
<li><a href="#ch6"><span class="n">6</span>함수</a></li>
<li class="part">프로그램 완성 재료</li>
<li><a href="#ch7"><span class="n">7</span>예외 처리와 안전한 입력</a></li>
<li><a href="#ch8"><span class="n">8</span>클래스</a></li>
<li><a href="#ch9"><span class="n">9</span>파일과 JSON</a></li>
<li><a href="#ch10"><span class="n">10</span>시간 측정과 표준 라이브러리</a></li>
<li class="part">실전</li>
<li><a href="#ch11"><span class="n">11</span>미션 조립 가이드</a></li>
</ol>
</nav>
<main>
<details class="mobiletoc"><summary>목차</summary>
<ol>
<li><a href="#intro">0. 왜 이만큼만 배우는가</a></li>
<li><a href="#ch1">1. 실행하고 출력하기</a></li>
<li><a href="#ch2">2. 변수·자료형·입력</a></li>
<li><a href="#ch3">3. 리스트와 딕셔너리</a></li>
<li><a href="#ch4">4. 조건문과 값 비교</a></li>
<li><a href="#ch5">5. 반복문과 2차원 순회</a></li>
<li><a href="#ch6">6. 함수</a></li>
<li><a href="#ch7">7. 예외 처리와 안전한 입력</a></li>
<li><a href="#ch8">8. 클래스</a></li>
<li><a href="#ch9">9. 파일과 JSON</a></li>
<li><a href="#ch10">10. 시간 측정과 표준 라이브러리</a></li>
<li><a href="#ch11">11. 미션 조립 가이드</a></li>
</ol>
</details>
<header class="cover prose">
<div class="eyebrow">Codyssey Mission 1–3</div>
<h1>미션 파이썬</h1>
<p class="sub">퀴즈 게임(미션 2)과 Mini NPU 시뮬레이터(미션 3)를 완주하는 데 필요한 <strong>최소한의 파이썬</strong>만 담은 교재입니다. 문법 백과가 아니라, 각 장을 읽고 나면 미션 요구사항 하나를 직접 구현할 수 있도록 구성했습니다.</p>
</header>
<section class="ch prose" id="intro">
<div class="ch-head">
<div class="chno">Chapter 0</div>
<h2>왜 이만큼만 배우는가</h2>
</div>
<p>세 미션의 요구사항을 분석하면, 파이썬이 실제로 필요한 곳은 명확합니다.</p>
<ul>
<li><strong>미션 1</strong> (개발 워크스테이션): 터미널·Docker·Git 실습이 중심입니다. 웹 서버도 정적 HTML + NGINX로 충분해서 <strong>파이썬 문법이 거의 필요 없습니다.</strong> 이 책에서는 터미널에서 파이썬을 실행·확인하는 방법만 다룹니다.</li>
<li><strong>미션 2</strong> (퀴즈 게임): 변수, 자료형, 조건문, 반복문, 함수, <strong>클래스 2개</strong>, <strong>JSON 파일 저장(<code>state.json</code>)</strong>, <strong>try/except 예외 처리</strong>가 전부 요구사항에 명시되어 있습니다.</li>
<li><strong>미션 3</strong> (Mini NPU): <strong>2차원 리스트</strong>, <strong>중첩 반복문으로 MAC 연산</strong>, <strong>부동소수점 epsilon 비교</strong>, <strong>data.json 로드와 키 파싱</strong>, <strong>time으로 성능 측정</strong>이 핵심입니다. 외부 라이브러리는 금지이므로 표준 문법만으로 해결해야 합니다.</li>
</ul>
<p>따라서 이 책은 아래 매핑을 커리큘럼으로 삼습니다. 어떤 장을 왜 배우는지 항상 미션 요구사항으로 되짚을 수 있습니다.</p>
<div class="tablewrap">
<table>
<thead><tr><th>장</th><th>배우는 것</th><th>직접 구현하게 되는 미션 요구사항</th></tr></thead>
<tbody>
<tr><td>1</td><td>실행, print, 주석</td><td>M1 파이썬 설치 확인 · M2/M3 <code>python main.py</code> 실행</td></tr>
<tr><td>2</td><td>변수, int/float/str/bool, input, 형변환</td><td>M2 §3 입력 처리(공백 제거·숫자 변환) · M3 모드 1 숫자 파싱</td></tr>
<tr><td>3</td><td>list, dict, 중첩 구조</td><td>M2 퀴즈 목록/선택지 · M3 n×n 패턴·필터 저장</td></tr>
<tr><td>4</td><td>if/elif/else, 범위 검사, epsilon 비교</td><td>M2 메뉴 분기·정답 판정 · M3 점수 비교 정책(1e-9)</td></tr>
<tr><td>5</td><td>for, while, 중첩 루프</td><td>M2 메뉴 루프·퀴즈 출제 · M3 MAC 연산(반복문 직접 구현)</td></tr>
<tr><td>6</td><td>함수, 매개변수, 반환값</td><td>M2 기능별 함수 분리 · M3 연산/판정 함수</td></tr>
<tr><td>7</td><td>try/except, KeyboardInterrupt, EOFError</td><td>M2 §3 공통 예외 기준 · M3 케이스 단위 FAIL 처리</td></tr>
<tr><td>8</td><td>class, __init__, self, 메서드</td><td>M2 Quiz·QuizGame 클래스(필수 2개)</td></tr>
<tr><td>9</td><td>open, with, json.load/dump</td><td>M2 state.json 저장/복구 · M3 data.json 로드·스키마 검증</td></tr>
<tr><td>10</td><td>time.perf_counter, random, 문자열 메서드</td><td>M3 10회 반복 평균 측정·O(N²) 표 · M2 보너스 랜덤 출제</td></tr>
<tr><td>11</td><td>전체 조립</td><td>M2·M3 프로그램 뼈대와 제출 체크리스트</td></tr>
</tbody>
</table>
</div>
<div class="callout link"><span class="t">학습 순서 제안</span>
<p>미션 2를 먼저 진행한다면 1→8장을 순서대로 읽고 바로 구현을 시작하세요. 미션 3만 남았다면 3·4·5·6·9·10장이 핵심입니다. 각 장 끝의 <strong>연습</strong>은 미션 코드의 축소판이므로 건너뛰지 않는 것을 권합니다.</p></div>
</section>
<section class="ch prose" id="ch1">
<div class="ch-head">
<div class="chno">Chapter 1</div>
<h2>실행하고 출력하기</h2>
<div class="chips"><span class="chip m1">미션 1 · 환경 확인</span><span class="chip m2">미션 2 · 실행 방법</span><span class="chip m3">미션 3 · 실행 흐름</span></div>
</div>
<p>미션 2는 Python 3.10 이상, 미션 3은 3.8 이상을 요구합니다. 터미널에서 버전부터 확인합니다.</p>
<pre><code><span class="o">$</span> python3 --version
<span class="o">Python 3.12.4</span>
<span class="o">$</span> python3 main.py <span class="c"># 프로젝트 폴더에서 프로그램 실행</span></code></pre>
<p>파이썬 파일(<code>.py</code>)은 위에서 아래로 한 줄씩 실행됩니다. 화면 출력은 <code>print()</code>가 담당합니다.</p>
<pre><code><span class="c"># main.py — # 뒤는 주석: 실행되지 않는 설명</span>
<span class="fn">print</span>(<span class="s">"="</span> * <span class="num">40</span>) <span class="c"># 문자열 × 정수 = 반복 → 구분선을 손으로 치지 않는다</span>
<span class="fn">print</span>(<span class="s">"=== Mini NPU Simulator ==="</span>)
<span class="fn">print</span>(<span class="s">"1. 사용자 입력 (3x3)"</span>)
<span class="fn">print</span>(<span class="s">"2. data.json 분석"</span>)
<span class="fn">print</span>(<span class="s">"-"</span> * <span class="num">40</span>)</code></pre>
<p><code>print()</code>는 여러 값을 쉼표로 이어 출력할 수 있고, 자동으로 줄바꿈합니다. 미션 2의 메뉴 화면, 미션 3의 결과 표는 결국 <code>print()</code>의 나열입니다.</p>
<div class="callout warn"><span class="t">흔한 실수</span>
<p>맥에서는 <code>python</code>이 아니라 <code>python3</code>일 수 있습니다. 또 <code>main.py</code>가 있는 폴더로 <code>cd</code>한 뒤 실행해야 미션 3의 <code>data.json</code> 같은 상대 경로 파일을 제대로 찾습니다.</p></div>
<div class="callout ex"><span class="t">연습 1</span>
<p>미션 2 예시의 메뉴 화면(제목 줄, 1~5번 항목, 구분선)을 <code>print()</code>만으로 그대로 출력하는 <code>menu.py</code>를 만들어 실행해 보세요.</p></div>
</section>
<section class="ch prose" id="ch2">
<div class="ch-head">
<div class="chno">Chapter 2</div>
<h2>변수·자료형·입력</h2>
<div class="chips"><span class="chip m2">미션 2 §3 · 공통 입력 기준</span><span class="chip m3">미션 3 · 모드 1 입력 검증</span></div>
</div>
<h3>변수: 값에 이름 붙이기</h3>
<p>변수는 값을 담아두는 이름표입니다. 프로그램이 기억해야 하는 것 — 최고 점수, 현재 문제 번호, 필터 크기 — 은 전부 변수에 저장됩니다.</p>
<pre><code>best_score = <span class="num">0</span> <span class="c"># int : 정수 (점수, 정답 번호, 크기 N)</span>
score_a = <span class="num">0.9</span> <span class="c"># float : 실수 (MAC 점수는 float 가능)</span>
question = <span class="s">"Python의 창시자는?"</span> <span class="c"># str : 문자열</span>
is_correct = <span class="k">True</span> <span class="c"># bool : True / False 둘 중 하나</span></code></pre>
<p>네 가지 타입의 차이를 설명할 수 있어야 합니다(미션 2 과제 목표에 명시).</p>
<ul>
<li><code>int</code> — 셀 수 있는 수. 메뉴 번호, 정답 번호(1~4), 최고 점수.</li>
<li><code>float</code> — 소수점이 있는 수. 미션 3의 점수·시간(ms). <strong>오차가 있을 수 있음</strong>(4장에서 다룸).</li>
<li><code>str</code> — 글자들. 문제 텍스트, 파일에서 읽은 내용, <code>input()</code>의 결과는 항상 str.</li>
<li><code>bool</code> — 참/거짓. 판정 결과, 반복 계속 여부.</li>
</ul>
<h3>f-string: 문자열 안에 값 끼워 넣기</h3>
<pre><code>total, correct = <span class="num">5</span>, <span class="num">4</span>
<span class="fn">print</span>(<span class="s">f"🏆 결과: {total}문제 중 {correct}문제 정답! ({correct * 20}점)"</span>)
<span class="fn">print</span>(<span class="s">f"평균 시간: {0.0123456:.3f} ms"</span>) <span class="c"># :.3f → 소수점 3자리</span>
<span class="o">🏆 결과: 5문제 중 4문제 정답! (80점)
평균 시간: 0.012 ms</span></code></pre>
<p>미션 3의 성능 표(소수점 자리 맞춤)와 미션 2의 결과 화면은 f-string 하나로 해결됩니다.</p>
<h3>input(): 사용자 입력 받기</h3>
<p><code>input()</code>은 안내 문구를 출력하고, 사용자가 Enter를 칠 때까지 기다렸다가 <strong>항상 문자열(str)</strong>을 돌려줍니다. 숫자로 쓰려면 변환이 필요합니다.</p>
<pre><code>raw = <span class="fn">input</span>(<span class="s">"선택: "</span>) <span class="c"># 사용자가 " 1 " 입력했다면 raw는 " 1 "</span>
raw = raw.strip() <span class="c"># 앞뒤 공백 제거 → "1" (미션 2 필수 요구)</span>
choice = <span class="fn">int</span>(raw) <span class="c"># 문자열 → 정수. "abc"면 ValueError 발생(7장)</span></code></pre>
<div class="callout link"><span class="t">미션 연결</span>
<p>미션 2 §3 공통 입력 기준의 첫 항목이 바로 "입력 앞뒤 공백 제거 후 처리"입니다. <code>input().strip()</code>을 습관으로 만들면 요구사항 하나가 자동으로 해결됩니다. 빈 입력(그냥 Enter)은 <code>raw == ""</code>로 검사합니다.</p></div>
<h3>한 줄에서 숫자 여러 개 읽기 — 미션 3의 행 입력</h3>
<p>미션 3 모드 1은 "한 줄에 숫자 3개를 공백으로 구분해" 입력받습니다. <code>split()</code>이 문자열을 공백 기준으로 쪼개 리스트로 만들어 줍니다.</p>
<pre><code>line = <span class="fn">input</span>().strip() <span class="c"># "0 1 0"</span>
parts = line.split() <span class="c"># ["0", "1", "0"] ← 아직 문자열</span>
row = [<span class="fn">float</span>(p) <span class="k">for</span> p <span class="k">in</span> parts] <span class="c"># [0.0, 1.0, 0.0] ← 숫자로 변환</span>
<span class="fn">print</span>(<span class="fn">len</span>(row)) <span class="c"># 3 — 열 개수 검증에 사용</span></code></pre>
<p>마지막 줄의 <code>[float(p) for p in parts]</code>는 <strong>리스트 컴프리헨션</strong>으로, "parts의 각 항목 p를 float(p)로 바꾼 새 리스트"라는 뜻입니다. 5장의 for문을 한 줄로 줄인 표현이라, 지금은 관용구로 외워 두어도 됩니다.</p>
<div class="callout ex"><span class="t">연습 2</span>
<p>한 줄을 입력받아 ① 공백을 제거하고 ② 공백 기준으로 쪼갠 뒤 ③ 숫자 개수가 3개인지 <code>len()</code>으로 확인해 "OK" 또는 "입력 형식 오류: 각 줄에 3개의 숫자를 공백으로 구분해 입력하세요."를 출력하는 프로그램을 작성하세요. 미션 3의 입력 검증 문구 그대로입니다.</p></div>
</section>
<section class="ch prose" id="ch3">
<div class="ch-head">
<div class="chno">Chapter 3</div>
<h2>리스트와 딕셔너리</h2>
<div class="chips"><span class="chip m2">미션 2 · 퀴즈 목록·선택지</span><span class="chip m3">미션 3 §1 · n×n 데이터 구조</span></div>
</div>
<h3>리스트: 순서가 있는 묶음</h3>
<pre><code>choices = [<span class="s">"Guido"</span>, <span class="s">"Linus"</span>, <span class="s">"Bjarne"</span>, <span class="s">"James"</span>]
choices[<span class="num">0</span>] <span class="c"># "Guido" — 인덱스는 0부터!</span>
choices[<span class="num">3</span>] <span class="c"># "James"</span>
<span class="fn">len</span>(choices) <span class="c"># 4</span>
choices.append(<span class="s">"Anders"</span>) <span class="c"># 끝에 추가 → 퀴즈 추가 기능의 핵심</span>
choices.pop(<span class="num">1</span>) <span class="c"># 인덱스 1 항목을 꺼내며 제거 → 퀴즈 삭제 보너스</span>
<span class="k">del</span> choices[<span class="num">0</span>] <span class="c"># 그냥 지우기만 할 때</span></code></pre>
<p>삭제 후에는 뒤 항목들의 인덱스가 하나씩 당겨진다는 점만 주의하세요. 미션 2 보너스 4(퀴즈 삭제)는 사용자에게 번호를 받아 <code>quizzes.pop(번호 - 1)</code> 하고 파일에 다시 저장하면 됩니다.</p>
<div class="callout warn"><span class="t">흔한 실수 — 인덱스와 정답 번호</span>
<p>미션 2에서 정답은 <strong>1~4번</strong>으로 관리하지만 리스트 인덱스는 <strong>0~3</strong>입니다. 사용자가 3을 입력하면 <code>choices[3 - 1]</code>이 해당 선택지입니다. 이 <code>-1</code>을 빠뜨리는 것이 퀴즈 게임에서 가장 흔한 버그입니다.</p></div>
<h3>딕셔너리: 이름표가 붙은 묶음</h3>
<p>딕셔너리(dict)는 <strong>키 → 값</strong> 쌍의 모음입니다. JSON과 구조가 같아서, 9장에서 파일 저장을 배우면 dict가 그대로 <code>state.json</code>이 됩니다.</p>
<pre><code>quiz = {
<span class="s">"question"</span>: <span class="s">"Python의 창시자는?"</span>,
<span class="s">"choices"</span>: [<span class="s">"Guido"</span>, <span class="s">"Linus"</span>, <span class="s">"Bjarne"</span>, <span class="s">"James"</span>],
<span class="s">"answer"</span>: <span class="num">1</span>,
}
quiz[<span class="s">"question"</span>] <span class="c"># 키로 값 꺼내기</span>
quiz[<span class="s">"answer"</span>] = <span class="num">2</span> <span class="c"># 값 바꾸기</span>
quiz.get(<span class="s">"hint"</span>) <span class="c"># 없는 키 → None (오류 대신). 검증에 유용</span>
<span class="s">"answer"</span> <span class="k">in</span> quiz <span class="c"># True — 키 존재 확인 (스키마 검증)</span></code></pre>
<p><code>None</code>은 "값이 없음"을 나타내는 특별한 값입니다. <code>get()</code>이 없는 키에 대해 돌려주는 것이 바로 None이고, 검사할 때는 <code>==</code> 대신 <code>if x is None:</code>을 쓰는 것이 관례입니다. 9장에서 <code>data["filters"].get(f"size_{n}")</code>의 결과가 None인지 확인해 스키마 오류를 걸러낼 때 그대로 사용합니다.</p>
<h3>중첩 구조 ① — 딕셔너리의 리스트 (미션 2)</h3>
<p>퀴즈 여러 개는 "dict를 담은 list"입니다. <code>state.json</code>의 <code>quizzes</code>가 정확히 이 모양입니다.</p>
<pre><code>quizzes = [
{<span class="s">"question"</span>: <span class="s">"..."</span>, <span class="s">"choices"</span>: [...], <span class="s">"answer"</span>: <span class="num">3</span>},
{<span class="s">"question"</span>: <span class="s">"..."</span>, <span class="s">"choices"</span>: [...], <span class="s">"answer"</span>: <span class="num">2</span>},
]
quizzes[<span class="num">0</span>][<span class="s">"question"</span>] <span class="c"># 첫 퀴즈의 문제 텍스트</span></code></pre>
<h3>중첩 구조 ② — 리스트의 리스트 = 2차원 배열 (미션 3)</h3>
<p>미션 3의 패턴과 필터는 "행(row)들의 리스트"로 표현합니다. <code>pattern[i][j]</code>는 i행 j열의 값입니다.</p>
<pre><code>cross = [ <span class="c"># 3×3 십자가 필터</span>
[<span class="num">0</span>, <span class="num">1</span>, <span class="num">0</span>],
[<span class="num">1</span>, <span class="num">1</span>, <span class="num">1</span>],
[<span class="num">0</span>, <span class="num">1</span>, <span class="num">0</span>],
]
cross[<span class="num">1</span>][<span class="num">2</span>] <span class="c"># 1행 2열 → 1</span>
<span class="fn">len</span>(cross) <span class="c"># 행 수 = 3</span>
<span class="fn">len</span>(cross[<span class="num">0</span>]) <span class="c"># 열 수 = 3 → "필터와 패턴 크기 일치" 검증에 사용</span></code></pre>
<div class="callout link"><span class="t">미션 연결</span>
<p>미션 3 §1 데이터 구조 요구사항 — "n×n 패턴 저장, 특정 위치 값 읽기/쓰기, 3×3~25×25 처리" — 는 리스트의 리스트와 <code>len()</code>만으로 전부 충족됩니다. 별도의 배열 라이브러리가 필요 없고, 쓰면 오히려 규정 위반(외부 라이브러리 금지)입니다.</p></div>
<div class="callout ex"><span class="t">연습 3</span>
<p>① 퀴즈 dict 2개를 담은 <code>quizzes</code> 리스트를 만들고, 두 번째 퀴즈의 3번 선택지를 출력하세요. ② 3×3 X 패턴을 리스트의 리스트로 만들고 가운데 값(<code>[1][1]</code>)을 출력하세요.</p></div>
</section>
<section class="ch prose" id="ch4">
<div class="ch-head">
<div class="chno">Chapter 4</div>
<h2>조건문과 값 비교</h2>
<div class="chips"><span class="chip m2">미션 2 · 메뉴 분기·정답 판정</span><span class="chip m3">미션 3 §6 · epsilon 비교 정책</span></div>
</div>
<h3>if / elif / else</h3>
<p>메뉴에서 번호에 따라 다른 기능을 실행하는 것이 조건문의 전형적인 쓰임입니다.</p>
<pre><code><span class="k">if</span> choice == <span class="num">1</span>:
play_quiz()
<span class="k">elif</span> choice == <span class="num">2</span>:
add_quiz()
<span class="k">elif</span> choice == <span class="num">5</span>:
<span class="fn">print</span>(<span class="s">"저장 후 종료합니다."</span>)
<span class="k">else</span>:
<span class="fn">print</span>(<span class="s">"⚠️ 잘못된 입력입니다. 1-5 사이의 숫자를 입력하세요."</span>)</code></pre>
<p>파이썬은 <strong>들여쓰기(스페이스 4칸)</strong>가 블록을 결정합니다. 콜론(<code>:</code>) 다음 줄부터 들여 쓴 부분까지가 해당 조건에 속합니다.</p>
<h3>비교·논리 연산자와 범위 검사</h3>
<pre><code>answer == <span class="num">3</span> <span class="c"># 같다 (= 하나는 대입, 두 개가 비교!)</span>
answer != <span class="num">3</span> <span class="c"># 다르다</span>
<span class="num">1</span> <= n <= <span class="num">4</span> <span class="c"># 범위 검사 — "허용 범위 밖 숫자" 걸러내기</span>
raw == <span class="s">""</span> <span class="c"># 빈 입력 검사</span>
a > b <span class="k">and</span> c > d <span class="c"># 둘 다 참일 때</span>
<span class="k">not</span> quizzes <span class="c"># 리스트가 비었으면 True → "퀴즈가 없는 경우" 처리</span></code></pre>
<h3>부동소수점은 == 로 비교하면 안 된다</h3>
<p>float는 이진 소수라서 미세한 오차를 갖습니다. 미션 3이 epsilon 정책을 <strong>필수</strong>로 요구하는 이유입니다.</p>
<pre><code><span class="fn">print</span>(<span class="num">0.1</span> + <span class="num">0.2</span>) <span class="o">0.30000000000000004</span>
<span class="fn">print</span>(<span class="num">0.1</span> + <span class="num">0.2</span> == <span class="num">0.3</span>) <span class="o">False ← 함정!</span></code></pre>
<p>그래서 "두 점수가 같다(동점)"는 <strong>차이가 아주 작은 허용오차(epsilon)보다 작다</strong>로 정의합니다.</p>
<pre><code>EPSILON = <span class="num">1e-9</span> <span class="c"># 0.000000001 — 상수는 대문자 이름 관례</span>
<span class="k">if</span> <span class="fn">abs</span>(score_a - score_b) < EPSILON:
verdict = <span class="s">"UNDECIDED"</span> <span class="c"># 동점 → 판정 불가</span>
<span class="k">elif</span> score_a > score_b:
verdict = <span class="s">"A"</span>
<span class="k">else</span>:
verdict = <span class="s">"B"</span></code></pre>
<div class="callout link"><span class="t">미션 연결</span>
<p>이 여덟 줄이 미션 3 §6 "점수 비교 정책"과 §6-1 "판정 규칙"의 완성형입니다. <code>abs()</code>는 절댓값을 구하는 내장 함수입니다. 판정 결과가 expected와 같으면 PASS, 다르면 FAIL을 출력하는 것도 <code>if verdict == expected:</code> 한 줄입니다.</p></div>
<div class="callout ex"><span class="t">연습 4</span>
<p><code>score_a = 0.9</code>, <code>score_b = 0.8999999999999999</code>일 때 위 epsilon 판정 코드가 무엇을 출력하는지 예측한 뒤 실행해 확인하세요. 그다음 EPSILON을 <code>1e-20</code>으로 바꾸면 결과가 왜 달라지는지 설명해 보세요.</p></div>
</section>
<section class="ch prose" id="ch5">
<div class="ch-head">
<div class="chno">Chapter 5</div>
<h2>반복문과 2차원 순회</h2>
<div class="chips"><span class="chip m2">미션 2 · 메뉴 루프·퀴즈 출제</span><span class="chip m3">미션 3 §5 · MAC 연산 직접 구현</span></div>
</div>
<h3>for: 정해진 목록을 순회</h3>
<pre><code><span class="k">for</span> quiz <span class="k">in</span> quizzes: <span class="c"># 퀴즈를 하나씩 꺼내며</span>
<span class="fn">print</span>(quiz[<span class="s">"question"</span>])
<span class="k">for</span> i <span class="k">in</span> <span class="fn">range</span>(<span class="num">4</span>): <span class="c"># i = 0, 1, 2, 3</span>
<span class="fn">print</span>(<span class="s">f"{i + 1}. {quiz['choices'][i]}"</span>)
<span class="k">for</span> idx, quiz <span class="k">in</span> <span class="fn">enumerate</span>(quizzes, start=<span class="num">1</span>): <span class="c"># 번호와 함께</span>
<span class="fn">print</span>(<span class="s">f"[{idx}] {quiz['question']}"</span>) <span class="c"># → 퀴즈 목록 화면</span></code></pre>
<h3>while: 조건이 참인 동안 반복</h3>
<p>"종료를 선택할 때까지 메뉴를 계속 보여준다"는 <code>while True</code> + <code>break</code>가 표준 패턴입니다. <strong>for는 횟수·목록이 정해졌을 때, while은 언제 끝날지 사용자에게 달렸을 때</strong> 씁니다 — 이 구분이 미션 2 과제 목표 중 하나입니다.</p>
<pre><code><span class="k">while</span> <span class="k">True</span>:
show_menu()
choice = <span class="fn">input</span>(<span class="s">"선택: "</span>).strip()
<span class="k">if</span> choice == <span class="s">"5"</span>:
<span class="k">break</span> <span class="c"># 반복 탈출 → 프로그램 종료 수순</span>
<span class="k">if</span> choice <span class="k">not</span> <span class="k">in</span> (<span class="s">"1"</span>, <span class="s">"2"</span>, <span class="s">"3"</span>, <span class="s">"4"</span>):
<span class="fn">print</span>(<span class="s">"⚠️ 잘못된 입력입니다. 1-5 사이의 숫자를 입력하세요."</span>)
<span class="k">continue</span> <span class="c"># 아래를 건너뛰고 다음 반복(재입력)으로</span>
...</code></pre>
<h3>중첩 for — 2차원을 훑는 법, 그리고 MAC 연산</h3>
<p>바깥 for가 행을, 안쪽 for가 열을 돕니다. 미션 3의 핵심인 MAC(곱하고 누적해서 더하기) 연산은 중첩 for 안에서 곱을 누적하는 것이 전부입니다.</p>
<pre><code><span class="k">def</span> <span class="fn">mac</span>(pattern, filt):
<span class="s">"""같은 위치끼리 곱해 모두 더한 점수를 반환 (외부 라이브러리 금지 → 직접 구현)"""</span>
n = <span class="fn">len</span>(pattern)
total = <span class="num">0.0</span> <span class="c"># 누적(Accumulate)용 변수</span>
<span class="k">for</span> i <span class="k">in</span> <span class="fn">range</span>(n): <span class="c"># 행</span>
<span class="k">for</span> j <span class="k">in</span> <span class="fn">range</span>(n): <span class="c"># 열</span>
total += pattern[i][j] * filt[i][j] <span class="c"># 곱하고(Multiply) 더한다</span>
<span class="k">return</span> total
score = <span class="fn">mac</span>([[<span class="num">0</span>,<span class="num">1</span>,<span class="num">0</span>],[<span class="num">1</span>,<span class="num">1</span>,<span class="num">1</span>],[<span class="num">0</span>,<span class="num">1</span>,<span class="num">0</span>]],
[[<span class="num">0</span>,<span class="num">1</span>,<span class="num">0</span>],[<span class="num">1</span>,<span class="num">1</span>,<span class="num">1</span>],[<span class="num">0</span>,<span class="num">1</span>,<span class="num">0</span>]])
<span class="fn">print</span>(score) <span class="o">5.0 ← 미션 문서의 Case 1과 동일</span></code></pre>
<p>N×N 입력이면 곱셈이 N² 번 실행됩니다. 3×3은 9번, 25×25는 625번 — <strong>이것이 미션 3 리포트에 쓸 O(N²)의 근거</strong>입니다. 코드에서 직접 세어 보여줄 수 있으니 리포트 작성이 쉬워집니다.</p>
<h3>중첩 컴프리헨션과 평탄화 — 2차원을 1차원으로</h3>
<p>2장의 <code>[float(p) for p in parts]</code>는 for절이 하나인 기본형이었습니다. for절은 여러 개 이어 쓸 수 있고, 읽는 규칙은 하나입니다: <strong>for절들은 왼쪽부터 순서대로 중첩 for문이 되고, 맨 앞의 표현식이 가장 안쪽에 들어간다.</strong></p>
<pre><code>flat = [value <span class="k">for</span> row <span class="k">in</span> matrix <span class="k">for</span> value <span class="k">in</span> row]
<span class="c"># 위 한 줄은 아래 중첩 for문과 완전히 같다</span>
flat = []
<span class="k">for</span> row <span class="k">in</span> matrix: <span class="c"># ① 첫 번째 for절 — 바깥 루프</span>
<span class="k">for</span> value <span class="k">in</span> row: <span class="c"># ② 두 번째 for절 — 안쪽 루프</span>
flat.append(value) <span class="c"># ③ 맨 앞의 표현식 — 가장 안쪽</span></code></pre>
<p>3×3 십자가에 적용하면 <code>[0,1,0,1,1,1,0,1,0]</code> — 2차원 리스트가 길이 N²의 1차원 리스트로 <strong>평탄화(flatten)</strong>됩니다. 이때 <code>(i, j)</code> 위치는 <code>flat[i * n + j]</code>에 대응합니다.</p>
<div class="callout warn"><span class="t">흔한 실수 — for절 순서</span>
<p><code>[value for value in row for row in matrix]</code>처럼 순서를 뒤집으면 첫 for절이 아직 정의되지 않은 <code>row</code>를 쓰게 되어 NameError가 납니다. <strong>앞의 for절이 만든 변수를 뒤의 for절이 쓴다</strong>(바깥→안 순서)고 기억하세요.</p></div>
<div class="callout link"><span class="t">미션 연결 — 보너스 과제 1</span>
<p>미션 3 보너스 1 "2차원 배열을 1차원 배열(길이 N²)로 변환해 접근 패턴 단순화"가 바로 이 문법입니다. 패턴과 필터를 각각 평탄화한 뒤 단일 루프 <code>for k in range(n * n): total += flat_p[k] * flat_f[k]</code>로 MAC을 구현하고, 10장의 <code>measure_ms()</code>로 전/후를 같은 입력·같은 반복 횟수로 측정해 비교하면 보너스 요구사항이 완성됩니다.</p></div>
<div class="callout ex"><span class="t">연습 5</span>
<p>위 <code>mac()</code>에 미션 문서의 Case 2(십자가 입력 × X 필터)를 넣어 1.0이 나오는지 확인하세요. 이어서 크기 N을 입력받아 N×N 십자가 패턴(가운데 행·열이 1)을 중첩 for로 자동 생성해 보세요 — 미션 3 보너스 과제 2의 절반이 끝납니다.</p></div>
</section>
<section class="ch prose" id="ch6">
<div class="ch-head">
<div class="chno">Chapter 6</div>
<h2>함수</h2>
<div class="chips"><span class="chip m2">미션 2 §7-2 · 기능별 함수 분리</span><span class="chip m3">미션 3 · 연산/판정/측정 함수</span></div>
</div>
<p>미션 2 제약 사항은 "모든 코드를 한 함수에 작성하지 말고 기능별로 분리"를 요구합니다. 함수는 <strong>입력(매개변수)을 받아 → 일을 하고 → 결과(반환값)를 돌려주는</strong> 이름 붙은 코드 블록입니다.</p>
<pre><code><span class="k">def</span> <span class="fn">calc_score</span>(correct, total): <span class="c"># correct, total = 매개변수</span>
<span class="k">if</span> total == <span class="num">0</span>:
<span class="k">return</span> <span class="num">0</span> <span class="c"># return을 만나면 즉시 종료</span>
<span class="k">return</span> <span class="fn">round</span>(correct / total * <span class="num">100</span>)
score = <span class="fn">calc_score</span>(<span class="num">4</span>, <span class="num">5</span>) <span class="c"># 호출 → 반환값 80을 score에 저장</span></code></pre>
<h3>값을 여러 개 돌려주기</h3>
<p>미션 3에서는 "점수와 소요 시간"처럼 결과가 여럿인 경우가 많습니다. 쉼표로 묶어 반환하고, 받을 때도 쉼표로 풀면 됩니다.</p>
<pre><code><span class="k">def</span> <span class="fn">judge</span>(score_cross, score_x, eps=<span class="num">1e-9</span>): <span class="c"># eps는 기본값 있는 매개변수</span>
<span class="k">if</span> <span class="fn">abs</span>(score_cross - score_x) < eps:
<span class="k">return</span> <span class="s">"UNDECIDED"</span>, <span class="k">False</span>
<span class="k">if</span> score_cross > score_x:
<span class="k">return</span> <span class="s">"Cross"</span>, <span class="k">True</span>
<span class="k">return</span> <span class="s">"X"</span>, <span class="k">True</span>
verdict, decided = <span class="fn">judge</span>(<span class="num">5.0</span>, <span class="num">1.0</span>) <span class="c"># "Cross", True</span></code></pre>
<h3>함수로 나누는 기준</h3>
<ul>
<li>한 함수는 한 가지 일만: <code>show_menu()</code>, <code>play_quiz()</code>, <code>load_state()</code>, <code>mac()</code>, <code>measure()</code>…</li>
<li>화면 출력과 계산을 분리하면 테스트와 디버깅이 쉬워집니다. <code>mac()</code>은 print 없이 값만 반환하고, 출력은 호출한 쪽이 담당하게 하세요. 미션 3의 "시간 측정은 I/O를 제외하고 연산 함수 구간 중심으로" 요구가 자연스럽게 충족됩니다.</li>
<li>같은 코드를 두 번 쓰게 되면 함수로 뽑을 신호입니다(예: 3×3 필터 A·B 입력 → <code>read_matrix(3)</code> 하나로).</li>
</ul>
<div class="callout ex"><span class="t">연습 6</span>
<p>2장 연습의 "한 줄 검증"을 확장해, 크기 n을 받아 n줄을 입력받고 2차원 리스트를 반환하는 <code>read_matrix(n)</code> 함수를 작성하세요(형식 오류 시 재입력은 다음 장에서 붙입니다). 이 함수 하나가 미션 3 모드 1의 필터 A·B·패턴 입력 세 곳에 재사용됩니다.</p></div>
</section>
<section class="ch prose" id="ch7">
<div class="ch-head">
<div class="chno">Chapter 7</div>
<h2>예외 처리와 안전한 입력</h2>
<div class="chips"><span class="chip m2">미션 2 §3 · 공통 입력/예외 기준</span><span class="chip m3">미션 3 · 재입력 유도·케이스 FAIL</span></div>
</div>
<p><code>int("abc")</code>는 <strong>ValueError</strong>라는 예외를 일으키며 프로그램을 즉사시킵니다. <code>try/except</code>는 "일단 시도하고, 사고가 나면 이 코드로 수습하라"는 구조입니다.</p>
<pre><code><span class="k">try</span>:
n = <span class="fn">int</span>(<span class="s">"abc"</span>) <span class="c"># 여기서 ValueError 발생 →</span>
<span class="k">except</span> ValueError:
<span class="fn">print</span>(<span class="s">"숫자가 아닙니다."</span>) <span class="c"># → 죽지 않고 여기로 점프</span></code></pre>
<h3>표준 패턴: 유효한 숫자를 받을 때까지 묻는 함수</h3>
<p>미션 2 §3의 네 가지 요구(공백 제거, 변환 실패, 범위 밖, 빈 입력)를 전부 처리하는 함수입니다. 이 함수 하나로 메뉴 선택·정답 입력·정답 번호 입력을 모두 해결합니다.</p>
<pre><code><span class="k">def</span> <span class="fn">read_int</span>(prompt, lo, hi):
<span class="s">"""lo~hi 범위의 정수를 받을 때까지 재입력을 유도한다."""</span>
<span class="k">while</span> <span class="k">True</span>:
raw = <span class="fn">input</span>(prompt).strip() <span class="c"># ① 공백 제거</span>
<span class="k">if</span> raw == <span class="s">""</span>: <span class="c"># ② 빈 입력</span>
<span class="fn">print</span>(<span class="s">f"⚠️ 입력이 비었습니다. {lo}-{hi} 숫자를 입력하세요."</span>)
<span class="k">continue</span>
<span class="k">try</span>:
n = <span class="fn">int</span>(raw) <span class="c"># ③ 변환 실패 대비</span>
<span class="k">except</span> ValueError:
<span class="fn">print</span>(<span class="s">f"⚠️ 잘못된 입력입니다. {lo}-{hi} 사이의 숫자를 입력하세요."</span>)
<span class="k">continue</span>
<span class="k">if</span> <span class="k">not</span> (lo <= n <= hi): <span class="c"># ④ 범위 밖</span>
<span class="fn">print</span>(<span class="s">f"⚠️ 잘못된 입력입니다. {lo}-{hi} 사이의 숫자를 입력하세요."</span>)
<span class="k">continue</span>
<span class="k">return</span> n <span class="c"># 유효하면 반환하고 끝</span>
choice = <span class="fn">read_int</span>(<span class="s">"선택: "</span>, <span class="num">1</span>, <span class="num">5</span>)
answer = <span class="fn">read_int</span>(<span class="s">"정답 입력: "</span>, <span class="num">1</span>, <span class="num">4</span>)</code></pre>
<h3>같은 패턴의 변형 — 텍스트 입력과 행렬 입력</h3>
<p>"유효할 때까지 while True로 되묻는다"는 뼈대는 어떤 입력에도 재사용됩니다. 미션 2의 퀴즈 추가(문제·선택지는 빈 문자열만 아니면 됨)와 미션 3 모드 1의 행 입력(6장 연습에서 미뤄 둔 재입력 유도)이 각각 이렇게 완성됩니다.</p>
<pre><code><span class="k">def</span> <span class="fn">read_text</span>(prompt):
<span class="s">"""빈 입력이 아닐 때까지 묻는다 — 퀴즈 문제/선택지 입력용."""</span>
<span class="k">while</span> <span class="k">True</span>:
raw = <span class="fn">input</span>(prompt).strip()
<span class="k">if</span> raw: <span class="c"># 비어 있지 않으면 참</span>
<span class="k">return</span> raw
<span class="fn">print</span>(<span class="s">"⚠️ 입력이 비었습니다. 다시 입력하세요."</span>)
<span class="k">def</span> <span class="fn">read_row</span>(n):
<span class="s">"""숫자 n개가 든 한 행을 받을 때까지 재입력을 유도한다."""</span>
<span class="k">while</span> <span class="k">True</span>:
parts = <span class="fn">input</span>().strip().split()
<span class="k">if</span> <span class="fn">len</span>(parts) != n: <span class="c"># 열 개수 검증</span>
<span class="fn">print</span>(<span class="s">f"입력 형식 오류: 각 줄에 {n}개의 숫자를 공백으로 구분해 입력하세요."</span>)
<span class="k">continue</span>
<span class="k">try</span>:
<span class="k">return</span> [<span class="fn">float</span>(p) <span class="k">for</span> p <span class="k">in</span> parts]
<span class="k">except</span> ValueError: <span class="c"># 숫자 파싱 실패</span>
<span class="fn">print</span>(<span class="s">"입력 형식 오류: 숫자만 입력하세요."</span>)
<span class="k">def</span> <span class="fn">read_matrix</span>(n):
<span class="s">"""n×n 행렬 입력 — 필터 A·B·패턴 세 곳에서 재사용."""</span>
<span class="k">return</span> [<span class="fn">read_row</span>(n) <span class="k">for</span> _ <span class="k">in</span> <span class="fn">range</span>(n)]</code></pre>
<p>이 세 함수로 미션 3 §2 모드 1 입력 검증(행/열 개수, 숫자 파싱, 재입력 유도)이 전부 충족됩니다. <code>read_text()</code>는 미션 2 퀴즈 추가 화면의 문제·선택지 4개 입력에 그대로 씁니다.</p>
<h3>Ctrl+C에도 비정상 종료하지 않기</h3>
<p>실행 중 Ctrl+C는 <strong>KeyboardInterrupt</strong>, 입력 스트림이 끊기면 <strong>EOFError</strong> 예외가 됩니다. 미션 2는 이때도 "저장 후 안전하게 종료"를 요구합니다. 프로그램의 최상위를 try로 감싸는 것이 정석입니다.</p>
<pre><code><span class="k">def</span> <span class="fn">main</span>():
game = QuizGame() <span class="c"># 8장에서 배울 클래스</span>
<span class="k">try</span>:
game.run() <span class="c"># 메뉴 루프 전체</span>
<span class="k">except</span> (KeyboardInterrupt, EOFError):
<span class="fn">print</span>(<span class="s">"\n입력이 중단되어 저장 후 종료합니다."</span>)
game.save() <span class="c"># 가능한 범위에서 저장</span>
<span class="k">if</span> __name__ == <span class="s">"__main__"</span>: <span class="c"># "직접 실행했을 때만 main()을 호출" 관용구</span>
<span class="fn">main</span>()</code></pre>
<div class="callout link"><span class="t">미션 연결</span>
<p>미션 3에서는 같은 도구를 다르게 씁니다 — 사용자 입력은 <code>read_matrix()</code> 안에서 재입력 유도, <code>data.json</code>의 케이스별 오류(크기 불일치 등)는 <strong>프로그램을 멈추지 말고 해당 케이스만 FAIL로 기록</strong>하고 다음 케이스로 넘어갑니다. "중단 없이 계속"이 채점 포인트입니다.</p></div>
<div class="callout ex"><span class="t">연습 7</span>
<p><code>read_int()</code>를 직접 타이핑해 실행하고 <code>abc</code>, <code>9</code>, 빈 Enter, <code> 3 </code>(공백 포함) 네 가지를 차례로 넣어 모두 의도대로 동작하는지 확인하세요. 그대로 미션 2 제출 코드에 쓸 수 있습니다.</p></div>
</section>
<section class="ch prose" id="ch8">
<div class="ch-head">
<div class="chno">Chapter 8</div>
<h2>클래스</h2>
<div class="chips"><span class="chip m2">미션 2 §4·§10 · Quiz / QuizGame 필수</span></div>
</div>
<p>미션 2는 <strong>클래스 2개 이상</strong>을 요구합니다. 클래스는 "관련된 데이터(속성)와 그 데이터를 다루는 함수(메서드)를 한 덩어리로 묶는 설계도"입니다. 퀴즈 한 문제가 가진 것(문제·선택지·정답)과 할 수 있는 일(출력·채점)을 묶으면 <code>Quiz</code> 클래스가 됩니다.</p>
<pre><code><span class="k">class</span> <span class="fn">Quiz</span>:
<span class="k">def</span> <span class="fn">__init__</span>(self, question, choices, answer):
<span class="s">"""인스턴스가 만들어질 때 자동 호출되는 초기화 메서드"""</span>
self.question = question <span class="c"># 속성(attribute)에 저장</span>
self.choices = choices <span class="c"># 선택지 4개 리스트</span>
self.answer = answer <span class="c"># 정답 번호 1~4</span>
<span class="k">def</span> <span class="fn">show</span>(self, number):
<span class="s">"""문제와 선택지를 출력한다."""</span>
<span class="fn">print</span>(<span class="s">f"[문제 {number}] {self.question}"</span>)
<span class="k">for</span> i, choice <span class="k">in</span> <span class="fn">enumerate</span>(self.choices, start=<span class="num">1</span>):
<span class="fn">print</span>(<span class="s">f" {i}. {choice}"</span>)
<span class="k">def</span> <span class="fn">check</span>(self, user_answer):
<span class="s">"""정답이면 True를 반환한다."""</span>
<span class="k">return</span> user_answer == self.answer</code></pre>
<p>설계도(클래스)에서 실제 물건(인스턴스)을 찍어내는 것이 호출입니다.</p>
<pre><code>q = <span class="fn">Quiz</span>(<span class="s">"Python의 창시자는?"</span>, [<span class="s">"Guido"</span>,<span class="s">"Linus"</span>,<span class="s">"Bjarne"</span>,<span class="s">"James"</span>], <span class="num">1</span>)
q.show(<span class="num">1</span>) <span class="c"># 메서드 호출 — self에는 q가 자동으로 들어간다</span>
q.check(<span class="num">1</span>) <span class="c"># True</span></code></pre>
<div class="callout warn"><span class="t">self가 어렵다면</span>
<p><code>self</code>는 "지금 이 메서드를 호출한 바로 그 인스턴스 자신"입니다. <code>q.check(1)</code>은 사실 <code>Quiz.check(q, 1)</code>과 같습니다. 그래서 모든 메서드의 첫 매개변수는 self이고, 호출할 때는 적지 않습니다. <code>__init__</code> 안에서 <code>self.question = question</code>처럼 <strong>self에 붙여 저장해야</strong> 다른 메서드에서도 꺼내 쓸 수 있습니다.</p></div>
<h3>QuizGame: 게임 전체를 관리하는 클래스</h3>
<p>두 번째 클래스는 게임의 상태(퀴즈 목록, 최고 점수)와 흐름(메뉴, 저장/불러오기)을 담당합니다. 역할 분담: <strong>Quiz는 문제 한 개, QuizGame은 게임 전체.</strong></p>
<pre><code><span class="k">class</span> <span class="fn">QuizGame</span>:
<span class="k">def</span> <span class="fn">__init__</span>(self):
self.quizzes = [] <span class="c"># Quiz 인스턴스들의 리스트</span>
self.best_score = <span class="num">0</span>
self.load() <span class="c"># 시작하자마자 state.json 불러오기(9장)</span>
<span class="k">def</span> <span class="fn">run</span>(self):
<span class="k">while</span> <span class="k">True</span>:
self.show_menu()
choice = <span class="fn">read_int</span>(<span class="s">"선택: "</span>, <span class="num">1</span>, <span class="num">5</span>)
<span class="k">if</span> choice == <span class="num">1</span>: self.play()
<span class="k">elif</span> choice == <span class="num">2</span>: self.add_quiz()
<span class="k">elif</span> choice == <span class="num">3</span>: self.show_list()
<span class="k">elif</span> choice == <span class="num">4</span>: self.show_score()
<span class="k">else</span>:
self.save()
<span class="fn">print</span>(<span class="s">"저장 후 종료합니다."</span>)
<span class="k">break</span></code></pre>
<p><code>play()</code>, <code>add_quiz()</code>, <code>show_list()</code>, <code>show_score()</code>, <code>save()</code>, <code>load()</code>는 각각 미션 2 기능 요구사항 6~11번과 1:1로 대응합니다. 이 뼈대를 채우는 것이 곧 미션 완수입니다.</p>
<div class="callout ex"><span class="t">연습 8</span>
<p>Quiz 클래스를 완성하고, 인스턴스 2개를 리스트에 담아 for문으로 <code>show()</code> → <code>read_int()</code>로 답 입력 → <code>check()</code> 결과에 따라 "✅ 정답입니다!" / "❌ 오답입니다!"를 출력하는 미니 게임을 만들어 보세요. 이것이 <code>play()</code> 메서드의 본체입니다.</p></div>
</section>
<section class="ch prose" id="ch9">
<div class="ch-head">
<div class="chno">Chapter 9</div>
<h2>파일과 JSON</h2>
<div class="chips"><span class="chip m2">미션 2 §11 · state.json 저장/복구</span><span class="chip m3">미션 3 §3 · data.json 로드·검증</span></div>
</div>
<p>프로그램을 꺼도 데이터가 남으려면 파일에 써야 합니다. JSON은 dict/list를 텍스트로 저장하는 표준 형식이라, 파이썬 자료구조와 1:1로 오갑니다.</p>
<h3>쓰기: dict → state.json</h3>
<pre><code><span class="k">import</span> json <span class="c"># 표준 라이브러리 — 파일 맨 위에서 가져온다</span>
state = {
<span class="s">"quizzes"</span>: [{<span class="s">"question"</span>: <span class="s">"..."</span>, <span class="s">"choices"</span>: [...], <span class="s">"answer"</span>: <span class="num">1</span>}],
<span class="s">"best_score"</span>: <span class="num">80</span>,
}
<span class="k">with</span> <span class="fn">open</span>(<span class="s">"state.json"</span>, <span class="s">"w"</span>, encoding=<span class="s">"utf-8"</span>) <span class="k">as</span> f:
json.dump(state, f, ensure_ascii=<span class="k">False</span>, indent=<span class="num">2</span>)</code></pre>
<ul>
<li><code>with open(...) as f:</code> — 블록이 끝나면 파일을 자동으로 닫아 줍니다. 항상 이 형태를 쓰세요.</li>
<li><code>encoding="utf-8"</code> — 미션 2 요구사항(한글 깨짐 방지).</li>
<li><code>ensure_ascii=False</code> — 한글을 <code>안</code> 같은 코드가 아니라 읽을 수 있는 글자로 저장.</li>
</ul>
<h3>읽기: 파일이 없거나 깨졌을 때까지 대비</h3>
<p>미션 2는 "파일 없음 → 기본 데이터 사용, 손상 → 안내 후 복구"를 요구합니다. 예외 타입별로 나눠 받으면 됩니다.</p>
<pre><code><span class="k">def</span> <span class="fn">load_state</span>():
<span class="k">try</span>:
<span class="k">with</span> <span class="fn">open</span>(<span class="s">"state.json"</span>, <span class="s">"r"</span>, encoding=<span class="s">"utf-8"</span>) <span class="k">as</span> f:
<span class="k">return</span> json.load(f) <span class="c"># 텍스트 → dict</span>
<span class="k">except</span> FileNotFoundError: <span class="c"># 첫 실행: 파일이 없다</span>
<span class="k">return</span> {<span class="s">"quizzes"</span>: DEFAULT_QUIZZES, <span class="s">"best_score"</span>: <span class="num">0</span>}
<span class="k">except</span> json.JSONDecodeError: <span class="c"># 파일이 깨졌다</span>
<span class="fn">print</span>(<span class="s">"⚠️ 저장 파일이 손상되어 기본 데이터로 시작합니다."</span>)
<span class="k">return</span> {<span class="s">"quizzes"</span>: DEFAULT_QUIZZES, <span class="s">"best_score"</span>: <span class="num">0</span>}</code></pre>
<div class="callout warn"><span class="t">클래스 ↔ JSON 변환</span>
<p>JSON은 dict/list만 저장할 수 있고 Quiz 인스턴스는 모릅니다. 저장 시 <code>{"question": q.question, "choices": q.choices, "answer": q.answer}</code>로 풀고, 불러올 때 <code>Quiz(d["question"], d["choices"], d["answer"])</code>로 되살리세요. Quiz에 <code>to_dict()</code> 메서드, <code>QuizGame.load()</code>에 복원 코드를 두면 깔끔합니다.</p></div>
<h3>미션 3: data.json 로드와 키 파싱</h3>
<p><code>data.json</code>은 <code>filters</code>(size_5·size_13·size_25)와 <code>patterns</code>(<code>size_{N}_{idx}</code> 키)로 구성됩니다. 딕셔너리 순회와 문자열 <code>split()</code>으로 키에서 N을 뽑아 맞는 필터를 고릅니다.</p>
<pre><code><span class="k">with</span> <span class="fn">open</span>(<span class="s">"data.json"</span>, <span class="s">"r"</span>, encoding=<span class="s">"utf-8"</span>) <span class="k">as</span> f:
data = json.load(f)
<span class="k">for</span> name, case <span class="k">in</span> data[<span class="s">"patterns"</span>].items(): <span class="c"># 키와 값을 함께 순회</span>
n = <span class="fn">int</span>(name.split(<span class="s">"_"</span>)[<span class="num">1</span>]) <span class="c"># "size_13_1" → ["size","13","1"] → 13</span>
filters = data[<span class="s">"filters"</span>].get(<span class="s">f"size_{n}"</span>)
pattern = case[<span class="s">"input"</span>]
<span class="k">if</span> filters <span class="k">is</span> <span class="k">None</span> <span class="k">or</span> <span class="fn">len</span>(pattern) != n: <span class="c"># 크기/스키마 검증</span>
<span class="fn">print</span>(<span class="s">f"{name}: FAIL (필터/크기 불일치)"</span>) <span class="c"># 케이스만 FAIL, 계속 진행</span>
<span class="k">continue</span>
...</code></pre>
<p>라벨 정규화도 dict 하나로 끝납니다. 요구사항의 "‘+’→Cross, ‘x’→X, ‘cross’→Cross"를 매핑 테이블로 만들면 if 사다리가 필요 없습니다.</p>
<pre><code>LABELS = {<span class="s">"+"</span>: <span class="s">"Cross"</span>, <span class="s">"cross"</span>: <span class="s">"Cross"</span>, <span class="s">"x"</span>: <span class="s">"X"</span>, <span class="s">"X"</span>: <span class="s">"X"</span>}
expected = LABELS.get(<span class="fn">str</span>(case[<span class="s">"expected"</span>]).strip().lower(), <span class="k">None</span>)</code></pre>
<div class="callout ex"><span class="t">연습 9</span>
<p>① dict 하나를 <code>test.json</code>으로 저장했다가 다시 읽어 같은 내용인지 확인하세요. ② 파일을 텍스트 편집기로 일부러 망가뜨린 뒤(중괄호 삭제) <code>load_state()</code>가 안내 메시지와 함께 기본값을 돌려주는지 확인하세요. 이 실험이 미션 2 §11의 채점 시나리오입니다.</p></div>
</section>
<section class="ch prose" id="ch10">
<div class="ch-head">
<div class="chno">Chapter 10</div>
<h2>시간 측정과 표준 라이브러리</h2>
<div class="chips"><span class="chip m3">미션 3 §6-2 · 성능 분석(10회 평균)</span><span class="chip m2">미션 2 보너스 · 랜덤 출제</span></div>
</div>
<h3>time.perf_counter — 연산 시간 재기</h3>
<p>미션 3은 "각 크기별 MAC 연산을 10회 반복 측정해 평균(ms)"을 요구합니다. <code>time.perf_counter()</code>는 고해상도 시계로, 두 번 읽어 차이를 구하면 경과 시간(초)입니다.</p>
<pre><code><span class="k">import</span> time
<span class="k">def</span> <span class="fn">measure_ms</span>(pattern, filt, repeat=<span class="num">10</span>):
<span class="s">"""MAC 연산만 repeat회 반복해 평균 소요 시간(ms)을 반환."""</span>
start = time.perf_counter()
<span class="k">for</span> _ <span class="k">in</span> <span class="fn">range</span>(repeat): <span class="c"># _ : 값을 안 쓰는 반복 변수 관례</span>
<span class="fn">mac</span>(pattern, filt) <span class="c"># 측정 구간에는 연산만! (I/O 제외)</span>
elapsed = time.perf_counter() - start
<span class="k">return</span> elapsed / repeat * <span class="num">1000</span> <span class="c"># 초 → ms, 평균</span></code></pre>
<p>성능 표 출력은 f-string 정렬 지시자로 맞춥니다. <code>{n:>2}</code>는 오른쪽 정렬 2칸, <code>{ms:>10.3f}</code>는 10칸에 소수점 3자리.</p>
<pre><code><span class="fn">print</span>(<span class="s">f"{'크기':<8}{'평균 시간(ms)':>14}{'연산 횟수':>10}"</span>)
<span class="k">for</span> n, ms <span class="k">in</span> results:
<span class="fn">print</span>(<span class="s">f"{n}×{n:<5}{ms:>14.3f}{n * n:>10}"</span>)</code></pre>
<div class="callout link"><span class="t">리포트로 연결</span>
<p>표에서 N이 5→25로 5배 되면 연산 횟수(N²)는 25배가 되고, 측정 시간도 대략 그 비율로 늘어납니다. "연산 횟수 열과 시간 열이 같은 비율로 증가한다"는 관찰이 README 결과 리포트의 O(N²) 근거 문단이 됩니다.</p></div>
<h3>random — 미션 2 보너스</h3>
<pre><code><span class="k">import</span> random
random.shuffle(quizzes) <span class="c"># 리스트를 제자리에서 섞기 → 랜덤 출제</span>
random.sample(quizzes, <span class="num">3</span>) <span class="c"># 3개만 뽑기 → 문제 수 선택 보너스</span></code></pre>
<h3>datetime — 날짜/시간 기록 (미션 2 보너스 5)</h3>
<p>점수 기록 히스토리 보너스는 "날짜/시간, 푼 문제 수, 점수"를 저장해야 합니다. 표준 라이브러리 <code>datetime</code>으로 현재 시각을 문자열로 만들면 JSON에 그대로 저장할 수 있습니다.</p>
<pre><code><span class="k">from</span> datetime <span class="k">import</span> datetime
stamp = datetime.now().strftime(<span class="s">"%Y-%m-%d %H:%M"</span>) <span class="c"># "2026-08-13 14:02"</span>
history.append({<span class="s">"when"</span>: stamp, <span class="s">"total"</span>: <span class="num">5</span>, <span class="s">"score"</span>: <span class="num">80</span>})
<span class="c"># history 리스트를 state.json에 함께 저장하면 보너스 5 완성 (9장 참고)</span></code></pre>
<h3>자주 쓰는 문자열·내장 도구 요약</h3>
<div class="tablewrap">
<table>
<thead><tr><th>도구</th><th>하는 일</th><th>미션에서의 쓰임</th></tr></thead>
<tbody>
<tr><td><code>s.strip()</code></td><td>앞뒤 공백 제거</td><td>모든 입력 전처리</td></tr>
<tr><td><code>s.split("_")</code></td><td>구분자로 쪼개 리스트로</td><td>size_13_1 키 파싱, 행 입력</td></tr>
<tr><td><code>s.lower()</code></td><td>소문자로</td><td>라벨 정규화(x/X)</td></tr>
<tr><td><code>abs(x)</code></td><td>절댓값</td><td>epsilon 비교</td></tr>
<tr><td><code>len(x)</code></td><td>길이/개수</td><td>행·열 검증, 퀴즈 개수</td></tr>
<tr><td><code>round(x, 1)</code></td><td>반올림</td><td>점수 표시</td></tr>
<tr><td><code>enumerate(xs, 1)</code></td><td>번호와 함께 순회</td><td>목록·선택지 출력</td></tr>
<tr><td><code>d.items()</code> / <code>d.get(k)</code></td><td>dict 순회 / 안전 조회</td><td>patterns 순회, 스키마 검증</td></tr>
</tbody>
</table>
</div>
<div class="callout ex"><span class="t">연습 10</span>
<p>5장에서 만든 십자가 패턴 생성기를 이용해 N = 3, 5, 13, 25 각각의 패턴·필터를 만들고, <code>measure_ms()</code>로 측정한 결과를 위 표 형식으로 출력해 보세요. 완성되면 미션 3 §6-2 성능 분석 요구사항이 그대로 끝납니다.</p></div>
</section>
<section class="ch prose" id="ch11">
<div class="ch-head">
<div class="chno">Chapter 11</div>
<h2>미션 조립 가이드</h2>
<div class="chips"><span class="chip m2">미션 2 · 전체 구조</span><span class="chip m3">미션 3 · 전체 구조</span></div>
</div>
<p>배운 조각을 어떤 순서로 붙이면 되는지, 두 미션의 권장 구조와 작업 순서입니다. 코드를 베끼는 게 아니라 <strong>각 단계에서 어느 장을 다시 펴면 되는지</strong>를 안내합니다.</p>
<h3>미션 2 — 퀴즈 게임 조립 순서</h3>
<ol>
<li><strong>뼈대</strong>: <code>read_int()</code>(7장) + 메뉴 <code>while</code> 루프(5장)만으로 "메뉴 → 잘못된 입력 처리 → 종료"가 도는 프로그램을 먼저 완성 → <em>커밋</em>.</li>
<li><strong>Quiz 클래스</strong>(8장) + 기본 퀴즈 5개를 코드에 정의 → <em>커밋</em>.</li>
<li><strong>퀴즈 풀기</strong>: 브랜치를 만들어(<code>git checkout -b feature/play</code>) for 루프로 출제·채점(5·8장), 끝나면 점수 계산(6장) → <em>커밋 후 main에 병합</em>.</li>
<li><strong>추가/목록/점수</strong>: <code>append</code>(3장), <code>enumerate</code>(5장), 최고 점수 갱신 비교(4장) → 기능마다 <em>커밋</em>.</li>
<li><strong>저장/불러오기</strong>: <code>state.json</code> 입출력(9장), Ctrl+C 안전 종료(7장) → <em>커밋</em>.</li>
<li><strong>QuizGame으로 정리</strong>(8장) → README 작성 → <em>커밋 · 푸시</em>, clone/pull 실습.</li>
</ol>
<p>이 순서 자체가 "최소 10개의 의미 있는 커밋"과 브랜치·병합 요구를 자연스럽게 채워 줍니다.</p>
<h3>미션 3 — Mini NPU 조립 순서</h3>
<ol>
<li><code>mac()</code> 함수(5장)를 만들고 미션 문서의 Case 1·2로 검증 — 5.0과 1.0이 나와야 합니다.</li>
<li><code>read_matrix(n)</code>(2·6·7장)으로 모드 1 입력 + 형식 오류 재입력.</li>
<li>epsilon 판정 <code>judge()</code>(4장) + 결과 출력.</li>
<li><code>data.json</code> 로드, 키 파싱, 크기 검증, 라벨 정규화(9장). 오류 케이스는 FAIL 기록 후 <code>continue</code>.</li>
<li><code>measure_ms()</code>(10장)로 3·5·13·25 성능 표 출력.</li>
<li>총 테스트/통과/실패 요약(카운터 변수 + f-string), README 리포트 작성(O(N²) 근거는 10장).</li>
</ol>
<h3>제출 전 파이썬 체크리스트</h3>
<ul>
<li>모든 <code>input()</code> 뒤에 <code>.strip()</code>이 있는가? 빈 입력·문자·범위 밖을 넣어도 안 죽는가?</li>
<li>Ctrl+C를 눌러도 안내 후 종료되는가? (M2: 저장까지 되는가?)</li>
<li><code>state.json</code>을 지우고 실행 / 일부러 깨뜨리고 실행 — 둘 다 살아남는가?</li>
<li>float 비교에 <code>==</code>를 쓴 곳이 없는가? (M3: epsilon 정책)</li>
<li>클래스가 2개 이상이고, 한 함수가 50줄을 넘지 않는가?</li>
<li>외부 라이브러리 import가 없는가? (<code>json</code>, <code>time</code>, <code>random</code>은 표준이라 허용)</li>
</ul>
<div class="callout link"><span class="t">마지막 조언</span>
<p>미션 1의 Docker/Git 실습에서 만든 습관 — "명령을 치고, 출력을 확인하고, 기록한다" — 를 파이썬에도 그대로 적용하세요. 한 장을 읽을 때마다 예제를 직접 실행해 출력을 확인하는 것이, 읽기만 하는 것보다 완주 확률을 훨씬 높입니다.</p></div>
</section>
<footer class="book prose">
<p>미션 파이썬 — Codyssey 미션 1·2·3 요구사항 기반 최소 학습 교재. 예제 코드는 Python 3.10+, 표준 라이브러리만 사용.</p>
</footer>
</main>
</div>
</body>
</html>