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

[Java] 자바로 프로그래밍 입문하기: 3.2. 자료형 생성하기 (2)

스톱워치

  객체지향 프로그래밍의 특징 중 하나는 추상 프로그래밍 객체를 생성해 현실의 객체를 손쉽게 모델링한다는 개념입니다. 간단한 예제로, 스톱워치를 살펴보죠. 다음 API를 구현합니다.

<Stopwatch>의 API

  다시 말해 우리가 구현할 스톱워치는 옛날 옛적의 기본형 스톱워치입니다. 생성 시 시간이 흐르기 시작하고, elapsedTime() 메소드를 호출함으로써 생성 이후로 얼마가 지났는지만 확인할 수 있습니다. 알람이 울리는 스톱워치를 상상하셨을 수도 있겠지만, 그건 상상으로만 남깁시다. 스톱워치를 리셋하고 싶나요? 정지 및 다시시작을 하고 싶나요? 랩(구간) 타이머 기능을 추가하고 싶나요? 이러한 것들은 과제로 남겨두기로 하죠.

  <Stopwatch>의 구현은 자바 시스템 메소드인 System.currentTimeMillis()를 사용합니다. 이는 현재 시각을 long 자료형 값으로 반환해주죠. 정확히는 UTC 기준으로 1970년 1월 1일 자정 이후로 지난 시간을 밀리초 단위로 반환해줍니다. 

  <Stopwatch>는 생성 시각을 인스턴스 변수에 담고, elapsedTime() 메소드가 호출될 때마다 현재 시각과 생성 시각의 차를 클라이언트에게 반환해주면 되는 것입니다. 이보다 더 단순할 수 있을까요? <Stopwatch>는 째깍거릴 필요가 없습니다.(물론 여러분 컴퓨터 내부의 시스템 클락이 <Stopwatch>와 그 외 수많은 자료형들을 위해 끊임없이 째깍거리고 있습니다) 단순히 클라이언트에게 시간을 재고있다는 환상을 줄 뿐이죠.

  왜 클라이언트가 직접 System.currentTimeMillis()를 쓰면 안 될까요? 물론 우리는 그렇게 할 수 있죠. 하지만 스톱워치를 더 높은 단계로 추상화할수록 클라이언트는 구조를 이해하고 유지보수하기 쉬워집니다.

  테스트 클라이언트는 늘 하던대로 만들면 됩니다. 두 개의 <Stopwatch> 객체를 생성하고, 서로 다른 계산을 하여 그 시간을 재봅니다. 그리고 시간이 얼마나 흘렀는지 출력해보는 것이죠.


프로그램 3.2.2: Stopwatch

public class Stopwatch { 

    private final long start;

    public Stopwatch() {
        start = System.currentTimeMillis();
    } 

    public double elapsedTime() {
        long now = System.currentTimeMillis();
        return (now - start) / 1000.0;
    }

    public static void main(String[] args) {
        int n = Integer.parseInt(args[0]);

        // sum of square roots of integers from 1 to n using Math.sqrt(x).
        Stopwatch timer1 = new Stopwatch();
        double sum1 = 0.0;
        for (int i = 1; i <= n; i++) {
            sum1 += Math.sqrt(i);
        }
        double time1 = timer1.elapsedTime();
        StdOut.printf("%e (%.2f seconds)\n", sum1, time1);

        // sum of square roots of integers from 1 to n using Math.pow(x, 0.5).
        Stopwatch timer2 = new Stopwatch();
        double sum2 = 0.0;
        for (int i = 1; i <= n; i++) {
            sum2 += Math.pow(i, 0.5);
        }
        double time2 = timer2.elapsedTime();
        StdOut.printf("%e (%.2f seconds)\n", sum2, time2);
    }
} 

% java Stopwatch 1000000
1.0
19.961538461538463


히스토그램

  자료형 인스턴스 변수는 배열이 될 수도 있습니다. <Histogram>은 주어진 구간 [0, N)에서 특정 사건의 발생 빈도를 담는 배열을 사용하고, StdStats.plotBars()를 이용해 히스토그램을 그려내는 프로그램입니다. 다음 API를 구현합니다.

<Histogram>의 API

  <Histogram>과 같이 단순한 클래스를 이용함으로써, 모듈화 프로그래밍의 장점을 실현할 수 있습니다.(코드 재사용, 작은 프로그램의 독립적인 개발 등) 또한 자료로부터 분리될 수 있는 장점도 있습니다. 클라이언트는 자료를 저장하는 방법에 대해 고민하지 않아도 됩니다. 단순히 히스토그램을 생성하고 addDataPoint()를 적절히 호출만 하면 됩니다.

  이러한 예제들을 우리가 공부할 때, 클라이언트 코드를 신중히 고려하는 것은 중요합니다. 우리가 구현하는 각 클래스들은 본질적으로 자바 언어를 확장하고, 새로운 자료형의 변수를 선언하고, 그들을 값으로 초기화하고, 연산을 수행합니다. 모든 클라이언트 프로그램은 개념적으로 우리가 작성했던 첫번째 프로그램과 별반 다르지 않습니다. 

  여러분은 어떤 자료형과 연산이든 간에 여러분의 클라이언트 코드가 필요한 것이라면 직접 정의할 수 있는 능력을 얻게된 것입니다. 이번 경우에는 <Histogram>을 이용해서 클라이언트 코드의 가독성을 강화시켜주는 것이죠. addDataPoint()의 호출은 단순히 추가되는 자료에만 집중할 수 있게 되는 것입니다.

  <Histogram>이 없다면, 히스토그램을 생성하는 코드와 히스토그램을 연산하는 코드, 히스토그램을 표현하는 코드 등 수많은 코드들을 한 프로그램에 몰아넣어야 합니다. 이는 이해하기도 어려우며, 유지보수하기도 쉽지 않습니다. "자료 및 해당 자료와 관련된 연산들을 명확하게 분리할 수 있다면, 그렇게 해야 합니다." 

  자료형이 클라이언트 코드에서 어떻게 사용될지 이해했다면, 구현을 생각해볼 단계가 된 것입니다. 구현은 인스턴스 변수로 그 특징이 결정됩니다. <Histogram>은 각 점에 대한 빈도를 담는 배열을 사용하며, double 값인 max로 가장 높은 막대의 수치를 저장합니다. 이는 draw() 메소드를 호출할 때 그림의 스케일링에 사용됩니다.


프로그램 3.2.3: Histogram


public class Histogram {
    private final double[] freq;   // freq[i] = # occurences of value i
    private double max;            // max frequency of any value

    // Create a new histogram. 
    public Histogram(int n) {
        freq = new double[n];
    }

    // Add one occurrence of the value i. 
    public void addDataPoint(int i) {
        freq[i]++; 
        if (freq[i] > max) max = freq[i]; 
    } 

    // draw (and scale) the histogram.
    public void draw() {
        StdDraw.setYscale(-1, max + 1);  // to leave a little border
        StdStats.plotBars(freq);
    }
 
    public static void main(String[] args) {
        int n = Integer.parseInt(args[0]);       // number of coins
        int trials = Integer.parseInt(args[1]);  // number of trials

        // create the histogram
        Histogram histogram = new Histogram(n+1);
        for (int t = 0; t < trials; t++) {
            histogram.addDataPoint(Bernoulli.binomial(n));
        }

        // display using standard draw
        StdDraw.setCanvasSize(500, 100);
        histogram.draw();
    } 
} 

% java Histogram 50 1000000

터틀 그래픽(Turtle graphics)

  "프로그램에서 작업의 단위를 나눌 수 있다면, 그렇게 해야 합니다." 객체지향 프로그래밍에서, 우리는 작업에 상태를 포함하도록 이러한 문구를 확장합니다. 소량의 상태는 계산을 단순화하는데 매우 유용할 수 있습니다. 우리는 터틀 그래픽을 생각해볼 것입니다. 다음 API를 따르죠.

<Turtle>의 API

  거북이가 단위 정사각형 안에 살고 있고, 움직이는대로 그림이 그려진다고 상상해봅시다. 특정한 거리만큼 직진해서 가거나, 특정한 각도만큼 왼쪽(반시계 방향)으로 회전할 수 있습니다. API에 의하면, 우리가 거북이를 생성했을 때 거북이를 특정한 방향으로 특정한 점에 놓아야 합니다. 또한 goForward()와 turnLeft()를 이용하여 그림을 그려낼 수 있습니다.

  예를 들어, (0, .5)에 60도로 <Turtle>을 생성했다고 생각해 봅시다. 직진으로 가다가, 어느 순간 반시계 방향으로 120도 회전합니다. 이를 한 번 더 반복하고, 또 한 번 더 반복하면 비로소 삼각형 하나를 만들 수 있습니다. 곧, 모든 클라이언트는 거북이에게 하여금 일련의 직진과 회전 단계를 연속해서 밟게 할 것입니다.

첫 터틀 그래픽

  <Turtle>은 API의 구현이며, StdDraw를 사용합니다. 이는 세 인스턴스 변수를 사용합니다: 거북이의 좌표와, x축을 기준으로 한 현재 방향입니다. 구현할 두 메소드는 이 값들을 변화시키는 것입니다. 따라서 인스턴스 변수들은 final일 필요가 없습니다. 

  turnLeft(delta)는 현재 각도를 delta만큼 더하며, goForward(step)은 step의 크기만큼 앞으로 나아갈 수 있게 x좌표와 y좌표를 변경해주어야 합니다. 이는 삼각함수를 이용해서 해결할 수 있습니다. 다음 그림을 참고해보세요.

거북이 삼각법

  <Turtle>의 테스트 클라이언트는 명령행 인자로부터 정수 N을 받아 N각형을 그려냅니다. 만약 여러분들이 기초 해석 기하학(elementary analytic geometry)에 관심이 있다면, 이것이 잘 동작함을 검증해보세요. 

  어쨌거나, 다각형에서 각 점의 좌표를 얻는데 어떻게 해야할지 고민해보세요. 테스트 클라이언트는 거북이를 아주 멋진 방법으로 움직입니다. 요컨대, 터틀 그래픽은 모든 기하학적 모양을 추상화하는 데 유용하게 사용될 수 있습니다. 예를 들어서 N을 크게 만들면, 원에 가까운 모양을 얻어낼 수 있습니다.

  <Turtle>을 다른 용도로도 사용할 수 있습니다. 프로그램은 <Turtle> 객체의 배열을 생성할 수 있고, 이를 함수의 인자로 넘겨서 사용하는 것이죠. 서로 다른 좌표, 서로 다른 각도로 동시에 그려지는 터틀 그래픽, 어떤 멋진 모양이 나올지 상상해보세요.


프로그램 3.2.4: Turtle graphics

public class Turtle {
    private double x, y;     // turtle is at (x, y)
    private double angle;    // facing this many degrees counterclockwise from the x-axis
// start at (x0, y0), facing a0 degrees counterclockwise from the x-axis public Turtle(double x0, double y0, double a0) { x = x0; y = y0; angle = a0; } // rotate orientation delta degrees counterclockwise public void turnLeft(double delta) { angle += delta; } // move forward the given amount, with the pen down public void goForward(double step) { double oldx = x; double oldy = y; x += step * Math.cos(Math.toRadians(angle)); y += step * Math.sin(Math.toRadians(angle)); StdDraw.line(oldx, oldy, x, y); }
    public static void main(String[] args) {
    	int n = Integer.parseInt(args[0]);
        double angle = 360.0 / n;
        double step  = Math.sin(Math.toRadians(angle/2.0));   // sin(pi/n)
        Turtle turtle = new Turtle(0.5, 0.0, angle/2.0);
        for (int i = 0; i < n; i++) {
            turtle.goForward(step);
            turtle.turnLeft(angle);
        }
    }
}


스피라 미라빌리스(Spira miraabilis)

  굉장히 작은 단계로 나누어져 움직이는 터틀 그래픽을 상상해보세요. 어떤 그림이 그려질까요? 아마 <Turtle>의 테스트 클라이언트에서 이미 경험해보셨을 수도 있습니다. 로그 나선이 그려지는 것이죠. 이러한 곡선은 자연에서 아주 많이 찾아볼 수 있습니다.

  <Spiral>은 이러한 곡선의 구현입니다. N과 붕괴 인수(decay factor)를 명령행 인자로 받아 10번 회전할 때까지 거북이를 이용해 그림을 그립니다. N은 나선의 모양을 결정합니다. 직접 <Spiral>을 사용하여 각 인자가 어떻게 나선의 행위를 제어하게 되는지 이해해보세요.

  로그 나선은 1638년 르네 데카르트가 처음으로 이야기 되었습니다. 야코프 베르누이는 이러한 수학적 속성에 큰 인상을 받아 이를 스피라 미라빌리스(기적의 나선)라 이름 붙였습니다. 또한 이를 그의 묘지에 새겨달라 부탁하기도 했죠.

  이러한 모양은 전혀 연관이 없는 자연의 곳곳에서 찾아볼 수 있습니다. 말그대로 경이로운 나선인 셈입니다. 앵무 조개(nautilus shell), 나선 은하, 태풍의 구름 등에서 나타나죠. 매가 먹이를 향해 접근하는 경로와 균일한 자기장에서 나타나는 입자의 모양들 역시 이러한 나선 모양을 이룹니다.

앵무 조개

나선 은하

태풍


프로그램 3.2.5: Spira mirabilis

public class Spiral {

    public static void main(String[] args) { 
        int n         = Integer.parseInt(args[0]);     // # sides if decay = 1.0
        double decay  = Double.parseDouble(args[1]);   // decay factor

        double angle  = 360.0 / n;
        double step   = Math.sin(Math.toRadians(angle/2.0));
        Turtle turtle = new Turtle(0.5, 0.0, angle/2.0);
        for (int i = 0; i < 10*n; i++) {
            step /= decay; 
            turtle.goForward(step);
            turtle.turnLeft(angle);
        }

    }
} 

  

% java Spiral 1440 1.0004


계속.

댓글

이 블로그의 인기 게시물

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

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

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