[Java] 자바로 프로그래밍 입문하기: 1.3. 조건문과 반복문 (2)
유한합(Finite sum) 계산 프로그램
<PowersOfTwo>에서 사용된 계산 기법은 여러분이 자주 쓰게 될 것입니다. 두 개의 변수를 사용해서, 하나는 반복문을 컨트롤하고 하나는 계산 결과를 쌓아가는 것입니다. <프로그램 1.3.5, Harmonic>은 같은 기법을 사용해 유한합 HN = 1 + 1/2 + 1/3 + ... + 1/N 을 계산합니다.
프로그램 1.3.5: Harmonic numbers
public class Harmonic { public static void main(String[] args) { // Compute the Nth Harmonic number. int N = Integer.parseInt(args[0]); double sum = 0.0; for (int i = 1; i <= N; i++) { // Add the ith term to the sum sum += 1.0 / i; } System.out.println(sum); } } |
1.5
% java Harmonic 10
2.9289682539682538
% java Harmonic 10000
9.898706036044348
프로그램 1.3.6: Newton's Method
public class Sqrt { public static void main(String[] args) { double c = Double.parseDouble(args[0]); double epsilon = 1e-15; double t = c; while (Math.abs(t - c / t) > epsilon * t) { // Replace t by the average of t and c/t. t = (c / t + t) / 2.0; } System.out.println(t); } } |
1.414213562373095
% java Sqrt 2544545
1595.1630010754388
<프로그램 1.3.6>은 뉴턴-랩슨 방법으로도 유명한 제곱근의 계산입니다. 이 원리에 대해서는 간단히만 설명할 것이므로, 궁금하다면 직접 찾아보시는 것을 권합니다. 이는 미분 가능한 함수에서의 근사값을 구하는 방법으로, 이 프로그램에서 사용한 수식은 다음과 같습니다.
프로그램 1.3.7: Converting to binary
public class Binary { public static void main(String[] args) { // Print binary representation of N. int N = Integer.parseInt(args[0]); int v = 1; while (v <= N / 2) v = 2 * v; // Now v is the largest power of 2 <= N. int n = N; while (v > 0) { // Cast out powers of 2 in decreasing order. if (n < v) System.out.print(0); else { System.out.print(1); n -= v; } v = v / 2; } System.out.println(); } } |
10011
% java Binary 100000000
101111101011110000100000000
<프로그램 1.3.7>은 십진수의 수를 이진수의 수로 변환해주는 프로그램입니다. 이진수의 가장 큰 자릿수부터, 각 자릿수가 1인지 0인지 판단합니다. 예를 들어 19라면, 각각 16, 8, 4, 2, 1을 비교해가면서 큰지 작은지를 판단해 각 자릿수가 1인지 0인지 판단합니다. 따라서, 19 = 10011(2)이라는 결과를 출력합니다. 다음 그림을 참고해보세요.
프로그램 1.3.8: Gambler's ruin simulation
public class Gambler { public static void main(String[] args) { // Run T experiments that start with $stake // and terminate on $0 or $goal. int stake = Integer.parseInt(args[0]); int goal = Integer.parseInt(args[1]); int T = Integer.parseInt(args[2]); int bets = 0; int wins = 0; for (int t = 0; t < T; t++) { // Run one experiment. int cash = stake; while (cash > 0 && cash < goal) { // Simulate one bet. bets++; if (Math.random() < 0.5) cash++; else cash--; } // Cash is either 0 (ruin) or $goal (win) if (cash == goal) wins++; } System.out.println(100 * wins / T + "% wins"); System.out.println("Avg # bets: " + bets / T); } } |
50% wins
Avg # bets: 100
% java Gambler 50 250 100
19% wins
Avg # bets: 11050
% java Gambler 500 2500 100
21% wins
Avg # bets: 998071
<프로그램 1.3.8>은 여지껏 우리가 작성했던 프로그램들과는 성격이 조금 다릅니다. 이 프로그램은 현실에서 일어날 수 있는 일들을 시뮬레이션하여 우리의 결정을 도와주는 프로그램입니다. 다양한 예제들이 있겠지만, '도박꾼의 파산'으로 유명한 상황을 예제로 보여드리겠습니다.
딜러와의 게임을 해서 1달러를 얻거나, 혹은 잃을 수 있습니다. 승률이 50%인 공평한 도박이라 하더라도, 이를 무한하게 반복하면 어떻게 될까요? 아마 언젠가 도박꾼은 반드시 돈을 전부 잃고 파산하게 될 것입니다. 무한하게 반복하다보면 자기가 가진 돈만큼 연속으로 패배할 날이 올테니까요.
하지만 도박꾼이 목표 자금을 정해놓고, 그 목표 자금을 달성했을 때 뒤도 돌아보지 않고 그 자리를 떠날 수 있다면 어떨까요? 도박꾼이 딜러와의 게임에서 승리자가 될 가능성은 얼마일까요?
프로그램 1.3.9: Factoring integers
public class Factors { public static void main(String[] args) { // Print the prime factors of N. long N = Long.parseLong(args[0]); long n = N; for (long i = 2; i <= n / i; i++) { // Test whether i is a factor. while (n % i == 0) { // Cast out and print i factors, n /= i; System.out.print(i + " "); } // Any factors of n are greater than i. } if (n > 1) System.out.print(n); System.out.println(); } } |
2 2 2 7 13 13 397
<프로그램 1.3.9>는 양의 정수를 소인수분해하는 프로그램입니다. 우리는 이 프로그램을 통해 컴퓨터의 위력을 체감할 수 있습니다. 10억정도의 숫자도 1초 안에 소인수분해를 마칠 수 있습니다. 반복문은 우리에게 어려운 문제들을 해결할 수 있게 해줍니다.
조건문과 반복문의 다른 구성들
다음 기술할 것들은 여러분이 자주 사용하진 않을 것이지만, 꼭 알아두어야 할 구문들입니다.
break문
가끔은 반복문의 중간에서 그 즉시 반복문을 탈출하고 싶을 때가 있습니다. 자바에선 break문으로 이런 문제를 해결할 수 있습니다. 다음 코드 조각은 1보다 큰 정수가 소수인지 아닌지를 효과적으로 판별합니다.
int i; for (i = 2; i <= N / i; i++) if (N % i == 0) break; if (i > N / i) System.out.println(N + " is prime"); |
이 코드에서 for 반복문을 탈출하는 방법은 두 가지입니다: (N % i == 0)이 참이어서 break문을 통해 탈출하거나, for문의 종료로 탈출하는 것입니다. 전자의 경우 N이 i로 나누어 떨어졌으므로, 소수가 아닙니다.
여기서 유의해야 할 사항은, 만약 for문 내부에서 i를 선언했다면, i의 범위는 for문 내부이므로 바깥 if문에서 사용될 수 없다는 사실입니다.
for (int i = 2; i <= N / i; i++) if (N % i == 0) break; if (i > N / i) // Compile error, the variable i is undefined. System.out.println(N + " is prime"); |
continue문
반복문에서 이하 구문들을 전부 실행하고 다음 반복으로 넘어가는 것이 아닌, 중간에 나머지 시퀀스를 건너 뛰고 바로 다음 반복으로 넘어가는 것도 있습니다. 바로 continue문입니다. 반복문 바디에서 continue 가 실행되었을 때, 제어 흐름은 그 즉시 증감문(increment statement)으로 넘어가며 다음 루프를 순회하게 됩니다.
switch문
if와 if-else문은 제어 흐름의 방향을 하나 혹은 두 개정도로만 선택할 수 있습니다. 하지만 가끔은 작업이 두 개 이상의 상호독립적인 선택에 놓일 때도 있습니다. 우리는 이전에 if-else문을 엮어서 사용하는 방법을 배웠었지만, switch문을 이용한 해결책도 있습니다.
변수 day의 값이 0부터 6사이의 값에 따라 각기 다른 요일을 출력하게 되는 코드 조각입니다.
switch (day) { case 0: System.out.println("Sun"); break; case 1: System.out.prinltn("Mon"); break; case 2: System.out.prinltn("Tue"); break; case 3: System.out.prinltn("Wed"); break; case 4: System.out.prinltn("Thu"); break; case 5: System.out.prinltn("Fri"); break; case 6: System.out.prinltn("Sat"); break; } |
do-while문
do { <statements> } while { <boolean expression>); |
이 템플릿은 사실 우리가 배운 while문과 별반 다르지 않습니다.
while (<boolean expression>) { <statements> } |
단 하나만 제외하면 말이죠. 처음에는 조건 확인을 하지 않는다는 것입니다. 즉 먼저 실행(do)하고, 반복(while)합니다. 가끔은 이런 do-while문이 유용한데, 예를 들어 좌표 평면 위에 무작위 점을 생성하는 문제를 생각해보죠. 이 점은 2*2 사각형 안에 있어야 하지만, 그 사각형을 내접하는 원 안쪽에 있어야 합니다. 이 점의 x좌표와 y좌표를 무작위로 생성하는 프로그램입니다.
do { // Scale x and y to be random in (-1, 1). x = 2.0*Math.random() - 1.0; y = 2.0*Math.random() - 1.0; } while (Math.sqrt(x*x + y*y) > 1.0); |
이를 가장 간단하게 해결하는 방법은, 우선 2*2 사각형 안에 점을 무작위로 생성하고 그 좌표가 내접원 안에 있는지 검증하는 것입니다. 만약 아니라면, 다시 무작위 생성 및 검증을 반복하는 것이죠.
처음 반복문을 시작할 때, x와 y의 값이 정해지지도 않았는데 조건식에 대입해볼 수는 없죠. 따라서 do-while문을 통해 x와 y의 값을 먼저 정하고, 반복을 시작합니다.
무한 반복문(Infinite loops)
반복문을 작성하기 전에는, 다음 문제에 대해 필히 생각해보셔야 합니다: 반복문의 조건이 항상 만족된다면?
<BadHellos>는 이런 무한 반복문의 예시를 보여줍니다. 요즘에야 터미널 윈도우에 글자를 표시하는 것으로 print를 사용하므로, 그 결과를 OS의 능력 내에서 무한하게 보여줄 수 있습니다. 하지만 만약 print의 행위가 종이에 글자를 인쇄하는 것이었다면 어떨까요? 여러분은 종이를 다 써버리거나, 프린터의 전원을 서둘러 꺼야 했을 것입니다.
public class BadHellos { public static void main(String[] args) { System.out.println("1st Hello"); System.out.println("2nd Hello"); System.out.println("3rd Hello"); int i = 4; while (i > 3) { System.out.println(i + "th Hello"); i++; } } } |
1st Hello
2nd Hello
3rd Hello
4th Hello
5th Hello
6th Hello
...
Eclipse에서는 터미널의 빨간 초록색 사각형 버튼을 누르면 해당 프로그램을 정지할 수 있습니다. 대부분의 터미널 환경에선 Ctrl-C 혹은 Ctrl-Z 를 이용해 정지할 수 있습니다.
둘째로, 아무런 일이 일어나지 않을 수도 있습니다. 여러분의 프로그램이 무한 반복문에 갇혀서 탈출하지 못하고 그 안에서 아무런 출력도 하지 않는다면, 이건 마치 프로그램이 아무것도 하지 않는 것처럼 보입니다. 이런 일은 가끔 일어나므로, 여러분들은 반복문의 구성을 신중히 해야할 것입니다. 버그를 찾아내는 쉬운 방법 중 하나는, System.out.println()과 같은 것을 이용해 값을 추적해보는 것입니다.
이런 의도치 않은 무한 반복문은 심각한 문제를 초래할 수도 있으므로, 언제나 신중하게 작성해야 합니다.
조건문과 반복문을 배우는데에는 왕도가 없습니다. 여러분들은 반드시 프로그램을 직접 작성해보고, 실행해보아야 합니다. 그 어떤 프로그래머들도 첫 술에 배부르려고 하지 않습니다. 곧 여러분들도 프로그램이 무엇이고 어떤 것을 하는 것인지에 대해 차근차근 이해할 날이 올 것입니다.
다음 챕터에서 우리는, 기존에 다뤄보았던 데이터들보다 훨씬 더 많은 데이터들을 정의하고 처리해볼 것입니다.
1.3. 조건문과 반복문 끝.
댓글
댓글 쓰기