{{ label!='' ? 'Label : ' : (q!='' ? '검색 : ' : '전체 게시글') }} {{ label }} {{ q }} {{ ('('+(pubs|date:'yyyy-MM')+')') }}

[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);
    }
}
% java Harmonic 2
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);
	}
}
% java Sqrt 2.0
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();
	}
}
% java Binary 19
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);
	}
}
% java Gambler 10 20 1000
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();
	}
}
% java Factors 3757208
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++;
		}
	}
}
% java BadHellos
1st Hello
2nd Hello
3rd Hello
4th Hello
5th Hello
6th Hello
...

  Eclipse에서는 터미널의 빨간 초록색 사각형 버튼을 누르면 해당 프로그램을 정지할 수 있습니다. 대부분의 터미널 환경에선 Ctrl-C 혹은 Ctrl-Z 를 이용해 정지할 수 있습니다.

  둘째로, 아무런 일이 일어나지 않을 수도 있습니다. 여러분의 프로그램이 무한 반복문에 갇혀서 탈출하지 못하고 그 안에서 아무런 출력도 하지 않는다면, 이건 마치 프로그램이 아무것도 하지 않는 것처럼 보입니다. 이런 일은 가끔 일어나므로, 여러분들은 반복문의 구성을 신중히 해야할 것입니다. 버그를 찾아내는 쉬운 방법 중 하나는, System.out.println()과 같은 것을 이용해 값을 추적해보는 것입니다.

  이런 의도치 않은 무한 반복문은 심각한 문제를 초래할 수도 있으므로, 언제나 신중하게 작성해야 합니다.



  조건문과 반복문을 배우는데에는 왕도가 없습니다. 여러분들은 반드시 프로그램을 직접 작성해보고, 실행해보아야 합니다.  그 어떤 프로그래머들도 첫 술에 배부르려고 하지 않습니다. 곧 여러분들도 프로그램이 무엇이고 어떤 것을 하는 것인지에 대해 차근차근 이해할 날이 올 것입니다. 

  다음 챕터에서 우리는, 기존에 다뤄보았던 데이터들보다 훨씬 더 많은 데이터들을 정의하고 처리해볼 것입니다.


1.3. 조건문과 반복문 끝.

댓글

이 블로그의 인기 게시물

[코딩의탑] 4층: 툰 쉐이딩

[코딩의탑] 3층: 바다 렌더링

[코딩의탑] 5층: 포탈(Portal), 더 나아가기