-
Notifications
You must be signed in to change notification settings - Fork 0
/
05-loop.qmd
721 lines (517 loc) · 22.5 KB
/
05-loop.qmd
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
```{r}
#| include: false
source("_common.R")
```
# 반복 {#r-iter}
\index{반복}
## 변수 갱신 {#r-iter-update}
\index{갱신}
\index{변수!갱신}
대입문의 흔한 패턴은 변수를 갱신하는 대입문이다.
변수의 새로운 값은 이전 값에 의존한다.
``` {r}
#| label: r-iter-update
#| eval: false
x <- x + 1
```
상기 예제는 "현재 값 x에 1을 더해서 x를 새로운 값으로 갱신한다."
만약 존재하지 않는 변수를 갱신하면, 오류가 발생한다.
왜냐하면 x에 값을 대입하기 전에 R이 오른쪽을 먼저 평가하기 때문이다.
``` {r}
#| label: r-iter-update-error
#| eval: false
x <- x + 1
Error: object 'x' not found
```
변수를 갱신하기 전에 간단한 변수 대입으로 통상 먼저 초기화(initialize)한다.
\index{초기화}
:::{.panel-tabset}
### R
```{webr-r}
#| label: r-iter-update-init
x <- 0
x <- x + 1
```
### 파이썬
```{pyodide-python}
#| label: py-iter-update-init
x = 0
x = x + 1
```
:::
1을 더해서 변수를 갱신하는 것을 증가(increment)라고 하고,
1을 빼서 변수를 갱신하는 것을 감소(decrement)라고 한다.
\index{증가}
\index{감소}
## `while`문 {#r-iter-while}
\index{문장!while}
\index{while 문}
\index{반복!while}
\index{반복}
종종 반복적인 작업을 자동화하기 위해 컴퓨터를 사용한다.
오류 없이 동일하거나 비슷한 작업을 반복하는 일은 컴퓨터가 사람보다 잘한다.
반복이 매우 흔한 일이어서, R에서는 반복 작업을 쉽게 하도록 몇 가지 언어적 기능을 제공한다.
R에서 반복의 한 형태가 `while`문이다.
다음은 5에서부터 거꾸로 세어 마지막에 "Blastoff(발사)!"를 출력하는 간단한 프로그램이다.
:::{.panel-tabset}
### R
```{webr-r}
#| label: r-iter-while
n <- 5
while(n > 0) {
print(n)
n <- n -1
}
print("Blastoff(발사)!")
```
### 파이썬
```{pyodide-python}
#| label: py-iter-while
n = 5
while n > 0:
print(n)
n = n -1
print("Blastoff(발사)!")
```
:::
마치 영어를 읽듯이 `while`을 읽어 내려갈 수 있다.
`n`이 0보다 큰 동안에 `n`의 값을 출력하고 `n` 값에서 1만큼 뺀다. \index{실행 흐름}
0에 도달했을 때, `while`문을 빠져나가 "Blastoff(발사)!"를 화면에 출력한다.
좀 더 형식을 갖춰 정리하면, 다음이 `while`문에 대한 실행 흐름에 대한 정리이다.
1. 조건을 평가해서 **참(TRUE)** 혹은 **거짓(FALSE)**를 산출한다.
1. 만약 조건이 거짓이면, `while`문을 빠져나가 다음 문장을 계속 실행한다.
1. 만약 조건이 참이면, 몸통 부문의 문장을 실행하고 다시 처음 1번 단계로 돌아간다.
3번째 단계에서 처음으로 다시 돌아가는 반복을 하기 때문에 이런 종류의 흐름을 **루프(loop)**라고 부른다.
매번 루프 몸통 부문을 실행할 때마다, 이것을 **반복(iteration)**이라고 한다.
상기 루프에 대해 "5번 반복했다"고 말한다.
즉, 루프 몸통 부문이 5번 수행되었다는 의미가 된다.
\index{조건}
\index{루프}
\index{몸통 부문}
루프 몸통 부문은 반드시 하나 혹은 그 이상의 변수값을 바꾸어서
종국에는 조건식이 **거짓(FALSE)**이 되어 루프가 종료되게 만들어야 한다.
매번 루프가 실행될 때마다 상태를 변경하고 언제 루프가 끝날지 제어하는 변수를 **반복 변수(iteration variable)**라고 한다.
만약 반복 변수가 없다면, 루프는 영원히 반복될 것이고, 결국 **무한 루프(infinite loop)**에 빠질 것이다.
## 무한 루프 {#r-iter-infinite-loop}
\index{무한 루프}
\index{반복!무한}
프로그래머에게 무한한 즐거움의 원천은 아마도 "거품 내고, 헹구고, 반복" 이렇게 적혀있는 샴푸 사용법 문구가 무한 루프라는 것을 알아차릴 때일 것이다.
왜냐하면, 얼마나 많이 루프를 실행해야 하는지 말해주는 반복 변수(iteration variable)가 없어서 무한 반복하기 때문이다.
숫자를 거꾸로 세는 (countdown) 예제는 루프가 끝나는 것을 증명할 수 있다.
왜냐하면 `n` 값이 유한하고, `n`이 매번 루프를 돌 때마다 작아져서 결국 0에 도달할 것이기 때문이다.
다른 경우 반복 변수가 전혀 없어서 루프가 명백하게 무한 반복한다.
## 무한 반복과 `break` {#r-iter-infinite-loop-break}
\index{break 문}
\index{문장!break}
가끔 몸통 부문을 절반 진행할 때까지 루프를 종료해야 하는 시점인지 확신하지 못한다.
이런 경우 의도적으로 무한 루프를 작성하고 `break` 문을 사용하여 루프를 빠져나온다.
다음 루프는 명백하게 무한 루프(infinite loop)가 되는데 이유는 `while`문 논리 표현식이 단순히 논리 상수 참(TRUE)으로 되어 있기 때문이다.
:::{.panel-tabset}
### R
```{webr-r}
#| label: r-iter-break
n <- 10
while(TRUE) {
print(n)
n <- n - 1
}
print('완료!')
```
### 파이썬
```{pyodide-python}
#| label: py-iter-break
n = 10
while True:
print(n)
n = n - 1
print('완료!')
```
:::
실수로 상기 프로그램을 실행한다면, 폭주하는 R 프로세스를 어떻게 멈추는지 빨리 배우거나, 컴퓨터의 전원 버튼이 어디에 있는지 찾아야 할 것이다.
표현식 상수 값이 참(TRUE)이라는 사실로 루프 상단 논리 연산식이 항상 참 값이어서 프로그램이 영원히 혹은 배터리가 모두 소진될 때까지 실행된다.
이것이 역기능 무한 루프라는 것은 사실이지만, 유용한 루프를 작성하기 위해는 이 패턴을 여전히 이용할 것이다.
이를 위해 루프 몸통 부문에 `break`문을 사용하여 루프를 빠져나가는 조건에 도달했을 때,
루프를 명시적으로 빠져나갈 수 있도록 주의 깊게 코드를 추가해야 한다.
예를 들어, 사용자가 `done`을 입력하기 전까지 사용자로부터 입력값을 받는다고 가정해서 프로그램 코드를 다음과 같이 작성한다.
:::{.panel-tabset}
### R
```{webr-r}
#| label: r-iter-break-input
# while_input.R 파일을 $ rscript while_input.R 쉘에서 실행
while(TRUE) {
cat("> ")
user_input <- readLines(file("stdin"), 1)
if(user_input == "done") {
break
}
print(user_input)
}
```
### 파이썬
```{pyodide-python}
#| label: py-iter-break-input
while True:
line = input("> ")
if line == 'done':
break
print(line)
```
:::
루프 조건이 항상 참(TRUE)이어서 `break`문이 호출될 때까지 루프는 반복적으로 실행된다.
매번 프로그램이 꺾쇠 괄호로 사용자에게 명령문을 받을 준비를 한다.
사용자가 done을 타이핑하면, `break`문이 실행되어 루프를 빠져나온다.
그렇지 않은 경우 프로그램은 사용자가 무엇을 입력하든 메아리처럼 입력한 것을 그대로 출력하고
다시 루프 처음으로 되돌아간다. 다음 예제로 실행한 결과가 있다.
``` {r}
#| label: r-iter-break-input-run
#| eval: false
> hello there
hello there
> finished
finished
> done
> done
Error: object 'done' not found
```
`while` 루프를 이와 같은 방식으로 작성하는 것이 흔한데
프로그램 상단뿐만 아니라 루프 어디에서나 조건을 확인할 수 있고 수동적으로 "이벤트가 발생할 때까지 계속 실행" 대신에,
적극적으로 "이벤트가 생겼을 때 중지"로 멈춤 조건을 표현할 수 있다.
## `next`로 반복 종료 {#r-iter-stop}
\index{next 문}
\index{문장!next}
때때로 루프를 반복하는 중간에서 현재 반복을 끝내고,
다음 반복으로 즉시 점프하여 이동하고 싶을 때가 있다.
현재 반복 루프 몸통 부분 전체를 끝내지 않고 다음 반복으로 건너뛰기 위해 `next`문을 사용한다.
사용자가 "done"을 입력할 때까지 입력값을 그대로 복사하여 출력하는 루프 예제가 있다.
하지만 R 주석문처럼 해시(#)로 시작하는 줄은 출력하지 않는다.
:::{.panel-tabset}
### R
```{webr-r}
#| label: r-iter-break-input-next
# while_input_next.R 파일을 $ rscript while_input_next.R 쉘에서 실행
while(TRUE) {
line <- readline(prompt = '> ')
if(substr(line,1,1) == "#") {
next
}
if(line == 'done') {
break
}
print(line)
}
print("완료!")
```
### 파이썬
```{pyodide-python}
#| label: py-iter-break-input-next
while True:
line = input('> ')
if line.startswith("#"):
continue
if line == 'done':
break
print(line)
print('완료!')
```
:::
`next`문이 추가된 새로운 프로그램을 샘플로 실행했다.
``` {r}
#| label: r-iter-break-input-next-run
#| eval: false
> hello there
[1] hello there
> # 주석이라 출력하지 마세요!
> print this!
[2] print this!
> done
완료!
```
해시 기호(#)로 시작하는 줄을 제외하고 모든 줄을 출력한다.
왜냐하면, `next`문이 실행될 때, 현재 반복을 종료하고 `while`문 처음으로 돌아가서 다음 반복을 실행하게 되어서 `print`문을 건너뛴다.
## 명확한 루프 - `for` {#r-iter-for}
\index{for 문}
\index{문장!for}
때때로, 단어 리스트나, 파일의 줄, 숫자 리스트 같은 사물의 집합에 대해 루프를 반복할 때가 있다.
루프를 반복할 사물 리스트가 있을 때, `for`문을 사용해서 **확정 루프(definite loop)**를 구성한다.
`while`문을 **불확정 루프(indefinite loop)**라고 하는데,
왜냐하면 어떤 조건이 거짓(FALSE)이 될 때까지 루프가단순히 계속해서 돌기 때문이다.
하지만, `for` 루프는 확정된 항목의 집합에 대해서 루프가 돌게 되어서 집합에 있는 항목만큼만 실행된다.
`for`문이 있고, 루프 몸통 부문으로 구성된다는 점에서 `for` 루프 구문은 `while` 루프 구문과 비슷하다.
:::{.panel-tabset}
### R
```{webr-r}
#| label: r-iter-for-loop
friends <- c('철수', '정훈', '한표')
for (friend in friends) {
cat('안녕하세요:', friend, "\n")
}
print('완료!')
```
### 파이썬
```{pyodide-python}
#| label: py-iter-for-loop
friends = ['철수', '정훈', '한표']
for friend in friends:
print(f'안녕하세요: {friend}')
print('완료!')
```
:::
R 용어로, 변수 `friends`는 3개의 문자열을 가지는 벡터이며,
`for` 루프는 벡터 내 원소를 하나씩 찾아서 벡터에 있는 3개 문자열 각각에 대해 출력을 실행하여 다음 결과를 얻게 된다.
``` {r}
#| label: r-iter-for-loop-run
#| eval: false
안녕하세요: 철수
안녕하세요: 정훈
안녕하세요: 한표
[1] "완료!"
```
`for` 루프를 영어로 번역하는 것이 `while`문을 번역하는 것만큼 직접적이지는 않다.
하지만, 만약 `friends`를 집합(set)으로 생각한다면 다음과 같다.
`friends`라고 명명된 집합에서 `friend` 각각에 대해서 한 번씩 `for` 루프 몸통 부문에 있는 문장을 실행하라.
`for` 루프를 살펴보면, `for`와 `in`은 R 예약어이고 `friend`와 `friends`는 변수이다.
``` {r}
#| label: r-iter-for-loop-variables
#| eval: false
for (friend in friends) {
cat('안녕하세요:', friend, "\n")
}
```
특히, `friend`는 `for` 루프의 **반복 변수(iteration variable)**다.
변수 `friend`는 루프가 매번 반복할 때마다 변하고, 언제 `for` 루프가 완료되는지 제어한다.
반복 변수는 `friends` 변수에 저장된 3개 문자열을 순차적으로 훑어간다.
## 루프 패턴 {#r-iter-for-pattern}
종종 `for`문과 `while`문을 사용하여, 벡터나 리스트 항목, 파일 콘텐츠를 훑어 자료에 있는 가장 큰 값이나 작은 값 같은 것을 찾는다.
`for`나 `while` 루프는 일반적으로 다음과 같이 구축된다.
1. 루프가 시작하기 전에 하나 혹은 그 이상의 변수를 초기화한다.
1. 루프 몸통 부분에 각 항목에 대해 연산을 수행하고, 루프 몸통 부분의 변수 상태를 변경한다.
1. 루프가 완료되면 결과 변수의 상태를 확인한다.
루프 패턴의 개념과 작성을 시연하기 위해 숫자 벡터를 사용한다.
### 계수와 합산 루프 {#r-iter-for-pattern-sum}
예를 들어, 벡터의 항목을 세기(counting) 위해 다음과 같이 `for` 루프를 작성한다.
:::{.panel-tabset}
### R
```{webr-r}
#| label: r-iter-for-loop-count-webr
count <- 0
for(itervar in c(3, 41, 12, 9, 74, 15)){
count <- count + 1
}
message('횟수: ', count)
```
### 파이썬
```{pyodide-python}
#| label: py-iter-for-loop-count
count = 0
for itervar in [3, 41, 12, 9, 74, 15]:
count = count + 1
print(f'횟수: {count}')
```
:::
루프가 시작하기 전에 변수 `count`를 0으로 설정하고, 숫자 목록을 훑어갈 `for` 루프를 작성한다.
반복(iteration) 변수는 `itervar`라고 하고,
루프에서 `itervar`가 사용되지 않지만,
`itervar`는 루프를 제어하고 루프 몸통 부문 리스트의 각 값에 대해 한 번만 실행되게 한다.
루프 몸통 부문에서 리스트의 각 값에 대해 변수 `count` 값에 1을 더한다.
루프가 실행될 때, `count` 값은 "지금까지" 살펴본 값의 횟수가 된다.
루프가 종료되면, `count` 값은 총 항목 숫자가 된다.
총 숫자는 루프 맨 마지막에 얻어진다.
루프를 구성해서, 루프가 끝났을 때 기대했던 바를 얻었다.
숫자 집합의 갯수를 세는 또 다른 비슷한 루프는 다음과 같다.
:::{.panel-tabset}
### R
```{webr-r}
#| label: r-iter-for-loop-sum-webr
total <- 0
for(itervar in c(3, 41, 12, 9, 74, 15)){
total <- total + itervar
}
message('합계: ', total)
```
### 파이썬
```{pyodide-python}
#| label: py-iter-for-loop-sum
total = 0
for itervar in [3, 41, 12, 9, 74, 15]:
total += itervar
print(f'합계: {total}')
```
:::
상기 루프에서, 반복 변수(iteration variable)가 사용되었다.
앞선 루프에서처럼 변수 `count`에 1을 단순히 더하는 대신에,
각 루프가 반복을 수행하는 동안 실제 숫자(3, 41, 12, 등)를 작업 중인 합계에 덧셈을 했다.
변수 `total`을 생각해보면, total은 "지금까지 값의 총계"다.
루프가 시작하기 전에 `total`은 어떤 값도 살펴본 적이 없어서 0이다.
루프가 도는 중에는 `total`은 작업 중인 총계가 된다.
루프의 마지막 단계에서 `total`은 리스트에 있는 모든 값의 총계가 된다.
루프가 실행됨에 따라, `total`은 각 요소의 합계로 누적된다.
이 방식으로 사용되는 변수를 **누산기(accumulator)**라고 한다.
\index{누산기!합계}
계수(counting) 루프나 합산 루프는 특히 실무에서 유용하지는 않다.
왜냐하면 리스트에서 항목의 개수와 총계를 계산하는 `length()`와 `sum()` 함수가 각각 내장 함수로 있기 때문이다.
### 최대값과 최소값 루프 {#r-iter-for-min-max}
\index{루프!최대값}
\index{루프!최소값}
\index{NULL 특수값}
\index{특수값!NULL}
리스트, 벡터나 열(sequence)에서 가장 큰 값을 찾기 위해서, 다음과 같이 루프를 작성한다.
:::{.panel-tabset}
### R
```{webr-r}
#| label: r-iter-for-loop-min-max
largest <- NA
cat('시작 전:', largest, "\n")
for(itervar in c(3, 41, 12, 9, 74, 15)){
if(is.na(largest) || itervar > largest){
largest <- itervar
cat('반복 중:', itervar, largest, "\n")
cat('최대값:', largest, "\n")
}
}
cat('최대값:', largest, "\n")
```
### 파이썬
```{pyodide-python}
#| label: py-iter-for-loop-min-max
largest = None
print('시작 전:', largest)
for itervar in [3, 41, 12, 9, 74, 15]:
if largest is None or itervar > largest:
largest = itervar
print('반복 중:', itervar, largest)
print('최대값:', largest)
print('최대값:', largest)
```
:::
프로그램을 실행하면, 출력은 다음과 같다.
``` {r}
#| label: r-iter-for-loop-min-max-output
#| eval: false
시작 전: NA
반복 중: 3 3
최대값: 3
반복 중: 41 41
최대값: 41
반복 중: 74 74
최대값: 74
최대값: 74
```
변수 `largest`는 "지금까지 본 가장 큰 수"로 생각할 수 있다.
루프 시작 전에 `largest` 값은 상수 `NA`이다.
`NA`은 "빈(empty)" 변수를 표기하기 위해 변수에 저장하는 특별한 상수 값이다.
루프 시작 전에 지금까지 본 가장 큰 수는 `NA`이다.
왜냐하면 아직 어떤 값도 보지 않았기 때문이다.
루프가 실행되는 동안에, `largest` 값이 `NA`이면, 첫 번째로 본 값이 지금까지 본 가장 큰 값이 된다.
첫 번째 반복에서 `itervar`는 3이 되는데 `largest` 값이 `NA`이어서 즉시, `largest` 값을 3으로 갱신한다.
첫 번째 반복 후에 `largest`는 더 이상 `NA`가 아니다.
`itervar > largest`인지를 확인하는 복합 논리 표현식의 두 번째 부분은 "지금까지 본" 값보다 더 큰 값을 찾게 될 때 자동으로 동작한다.
"더 큰" 값을 찾게 되면 변수 `largest`에 새로운 값으로 대체한다.
`largest`가 3에서 41, 41에서 74로 변경되어 출력되어 나가는 것을 확인할 수 있다.
루프의 끝에서 모든 값을 훑어서 변수 `largest`는 리스트의 가장 큰 값을 담고 있다.
최소값을 계산하기 위해서는 코드가 매우 유사하지만 작은 변화가 있다.
:::{.panel-tabset}
### R
```{webr-r}
#| label: r-iter-for-loop-min
smallest <- NA
cat('시작 전:', smallest, "\n")
for(itervar in c(3, 41, 12, 9, 74, 15)){
if(is.na(smallest) || itervar < smallest){
smallest <- itervar
cat('최소값:', smallest, "\n")
}
}
cat('최소값:', smallest, "\n")
```
### 파이썬
```{pyodide-python}
#| label: py-iter-for-loop-min
smallest = None
print('시작 전:', smallest)
for itervar in [3, 41, 12, 9, 74, 15]:
if smallest is None or itervar < smallest:
smallest = itervar
print('최소값:', smallest)
print('최소값:', smallest)
```
:::
변수 `smallest`는 루프 실행 전에, 중에, 완료 후에 "지금까지 본 가장 작은" 값이 된다.
루프 실행이 완료되면, `smallest`는 벡터의 최소값을 담게 된다.
계수(counting)과 합산에서와 마찬가지로 R 내장 함수 `max()`와 `min()`은 이런 루프문 작성을 불필요하게 만든다.
다음은 R 내장 `min()` 함수의 간략 버전이다. `getAnywhere(min)`, `.Primitive("min")`을 입력해도 원소스코드를 볼 수는 없다.
R 콘솔에서 `min` 함수 내부 코드를 바로 살펴볼 수 없는데 이유는 `C`로 작성되었기 때문이다.
`names(methods:::.BasicFunsList)` 명령어를 통해 `.Primitive()` 함수를 파악할 수 있다.
:::{.panel-tabset}
### R
```{webr-r}
#| label: r-iter-for-min-function
min <- function(values) {
smallest <- NA
for(value in values){
if(is.na(smallest) || value < smallest){
smallest <- value
}
}
return(smallest)
}
```
### 파이썬
```{pyodide-python}
#| label: py-iter-for-min-function
def min(values):
smallest = None
for value in values:
if smallest is None or value < smallest:
smallest = value
return smallest
```
:::
가장 적은 코드로 작성한 함수 버전은 R에 이미 내장된 `min` 함수와 동등하게 만들기 위해 모든 `print`문을 삭제했다.
## 디버깅 {#r-iter-debug}
\index{디버깅!이분법}
\index{이분법, 디버깅 기법}
좀 더 큰 프로그램을 작성할 때, 좀 더 많은 시간을 디버깅에 보내는 자신을 발견할 것이다.
좀 더 많은 코드는 버그가 숨을 수 있는 좀 더 많은 장소와 오류가 발생할 기회가 있다는 것을 의미한다.
디버깅 시간을 줄이는 한 방법은 **"이분법에 따라 디버깅(debugging by bisection)"** 하는 것이다.
예를 들어, 프로그램에 100줄이 있고 한 번에 하나씩 확인한다면, 100번 단계가 필요하다.
대신에 문제를 반으로 나눈다.
프로그램 정확히 중간이나, 중간부분에서 점검한다.
`print`문이나, 검증 효과를 갖는 상응하는 대용물을 넣고 프로그램을 실행한다.
중간지점 점검 결과 잘못되었다면 문제는 양분한 프로그램 앞부분에 틀림없이 있다. 만약 정확하다면, 문제는 프로그램 뒷부분에 있다.
이와 같은 방식으로 점검하게 되면, 검토해야 하는 코드의 줄수를 절반으로 계속 줄일 수 있다.
단계가 100번 걸리는 것에 비해 6번 단계 후에 이론적으로 1 혹은 2줄로 문제 코드의 범위를 좁힐 수 있다.
실무에서, "프로그램의 중간"이 무엇인지는 명확하지 않고, 확인하는 것도 가능하지 않다.
프로그램 코드 라인을 세서 정확히 가운데를 찾는 것은 의미가 없다.
대신에 프로그램 오류가 생길 수 있는 곳과 오류를 확인하기 쉬운 장소를 생각하자.
점검 지점 앞뒤로 버그가 있을 곳과 동일하게 생각하는 곳을 중간지점으로 고르자.
## 용어 정의 {#r-iter-terminology}
- **누산기(accumulator)**: 더하거나 결과를 누적하기 위해 루프에서 사용되는 변수
\index{누산기}
- **계수(counter)**: 루프에서 어떤 것이 일어나는 횟수를 기록하는 데 사용되는 변수. 카운터를 0으로 초기화하고, 어떤 것의 "횟수"를 셀 때 카운터를 증가시킨다.
\index{계수}
- **감소(decrement)**: 변수 값을 감소하여 갱신
\index{감소}
- **초기화(initialize)**: 갱신될 변수의 값을 초기 값으로 대입
\index{초기화}
- **증가(increment)**: 변수 값을 증가시켜 갱신(통상 1씩)
\index{증가}
- **무한 루프(infinite loop)**: 종료 조건이 결코 만족되지 않거나 종료 조건이 없는 루프
\index{무한 루프}
- **반복(iteration)**: 재귀함수 호출이나 루프를 사용하여 명령문을 반복 실행
\index{반복}
## 연습문제 {.unnumbered #r-iter-ex}
1. 사용자가 "done"을 입력할 때까지 반복적으로 숫자를 읽는 프로그램을 작성하라.
"done"이 입력되면, 총계, 개수, 평균을 출력하라.
만약 숫자가 아닌 다른 것을 입력하게 되면, `tryCatch`를 사용하여 사용자 실수를 탐지해서
오류 메시지를 출력하고 다음 숫자로 건너뛰게 하라.
``` {r r-iter-ex01, eval=FALSE}
Enter a number: 4
Enter a number: 5
Enter a number: bad data
Invalid input
Enter a number: 7
Enter a number: done
16 3 5.33333333333
```
2. 위에서처럼 숫자 목록을 사용자로부터 입력받는 프로그램을 작성하라.
평균값 대신에 숫자 목록 최대값과 최소값을 출력하라.