JavaScript, JSUnit으로 풀어본 뉴질랜드 화폐 문제

1st JavaScript editor의 공짜 사용기한이 얼마 남지 않아서 JavaScript로 빨리 뭐라도 만들어봐야 겠다는 생각이 들었습니다. 프로젝트 업무 중에는 그런 일이 보이지 않아서 회사의 다른 팀원들이 하고 있는 스터디 모임에서 나왔던 http://blog.naver.com/i1j1jsy/70011900056뉴질랜드 화폐문제를 풀어보았습니다.

문제

원래 제가 문제를 봤던 URL : http://rdp.ahamoment.org/wiki/Dollars

요약

뉴질랜드 화폐는 $100, $50, $20, $10, $5 지폐와 $2, $1, 50센트, 20센트, 10센트, 5센트 동전으로 이루어져 있다. 주여진 합계액에 대해서 몇 가지의 방법으로 돈을 구성할 수 있는지 세는 프로그램을 작성해라.

예) 20센트는 4가지 방법으로 만들수 있다. : 1개의 20센트, 2개의 10센트, 10센트+ 2개의 5센트, 4개의 5센트

원래 문제는 파일에서 입력값을 받게 되어 있지만, JavaScript로 풀다보니 그 부분은 생략했습니다. 직접 풀어보실 분들은 풀이를 나중에 보세요~

풀이

<script language="JavaScript" type="text/JavaScript" src="app/jsUnitCore.js"></script>
<script language="JavaScript" type="text/JavaScript">

    var unitList = [100, 50, 20, 10, 5, 2, 1, 0.5, 0.2, 0.1, 0.05];


    function countWaysOfAmount(totalMoneyAmount, unitIndex, ways)\{
        if (unitIndex == null) unitIndex = 0;
        if (ways == null) ways = 0;

        var unit = unitList[unitIndex];
        var moneyLeft = totalMoneyAmount;

        do\{
          if (moneyLeft == 0) ways++;
          else if (unitIndex < unitList.length)
            ways = countWaysOfAmount(moneyLeft,unitIndex+1,ways);
          moneyLeft =  Math.round( (moneyLeft - unit) *100) /100;
        } while(moneyLeft >= 0)
        return ways;
    }

    function testDollars()\{
        assertEquals("0.05", countWaysOfAmount(0.05), 1);
        assertEquals("0.1",  countWaysOfAmount(0.1 ), 2);
        assertEquals("0.15", countWaysOfAmount(0.15), 2);
        assertEquals("0.2",  countWaysOfAmount(0.2 ), 4);
        assertEquals("2",  countWaysOfAmount(2), 293);
    }

    alert(countWaysOfAmount(2));
</script>

코드를 더 다듬을 여지가 있는 것 같지만 일단 올려봅니다

moneyLeft = Math.round( (moneyLeft - unit) *100) /100; 부분은 다른 언어들과 마찬가지로 JavaScript에서도 소숫점 계산이 정확하지 않아서 붙인 부분입니다. 이걸 안해주니 5가 나와야하는 값이 4.999999 같이 나오더군요. 이 문제만 아니면 moneyLeft -= unit 으로 간단하게 쓸수 있는데 말이죠, Java로 이걸 짠다면 BigDecimal을 쓰거나 별도의 클래스를 정의해야겠죠.

JSUnit 없이 로컬에서 수정해 보실 분은 `<script language="JavaScript" type="text/JavaScript" src="app/jsUnitCore.js"></script> 부분과 테스트 메서드만 지우고 돌려 보시면 되겠습니다.

Internet Explorer Developer Toolbar, 1st JavaScript editor, JSUnit, FireBug 를 한꺼번에 써서 코딩하니 이클립스가 부럽지 않았습니다 ^^; 그리고 jsunit에서 debug("로그"); 로 찍어보는 것도 상당히 유용했구요. 1st JavaScript edtior나 FireBug가 모두 디버거 기능이 훌륭하지만 반복문의 값을 추적하는 것은 break point 거는 것보다는 로그로 쫙 찍어보는 것이 편하더군요.

확장된 나선형 배열 출력 문제(Spiral Array)

지난 2월에 회사 워크샵에서 풀었던 나선형 배열 문제에 대한 글을 올린 적이 있었지요. ( http://blog.benelog.net/901106 )

알고보니 xper.org에도 이 문제가 올라와 있더군요 (http://xper.org/wiki/seminar/SpiralArray). 거기에 가면 다른 분들의 풀이도 많이 볼 수 있습니다. 저는 이 문제를 실컷 가지고 논 후에 더 이상 손 댈 것이 없다고 느껴질때 다른 분들의 풀이를 꼼꼼히 볼 생각입니다.

우연찮게 이에 대한 확장된 문제를 다른 블로그에서 보게 되었습니다. (http://blog.naver.com/itioma?Redirect=Log&logNo=40040118386 ). 그 포스트에서 본 문제에는 아래의 조건들이 더 추가되어 있었습니다.

  • 작성된 코드에서 한 글자만 수정해서 '숫자는 1씩 늘어난다’를 '숫자는 2씩 늘어난다’로 바꾸시오.

  • 코드에서 한 문장만 수정해서 시작점 좌표(x,y)를 마음대로 바꿀 수 있도록 만드시오

  • 코드에서 한 문장만 수정해서 시작할 때의 진행방향을 (위/아래/왼쪽/오른쪽 중에서 ) 바꿀 수 있도록 만드시오

  • 배열의 크기를 마음대로 정할 수 있도록 만드시오.

  • 코드에서 한 문장만 수정해서 '시계방향으로 90도’를 '반시계 반향으로 90’도로 바꾸시오

  • 코드에서 swich 문이나 else 문을 이용한 3단계 이상의 조건분기가 있다면 모두 제거하시오.

  • 코드에서 한 글자만 수정해서 '한칸씩 진행하며’를 두칸씩 건너뛰며’로 바꾸시오.

  • 1~7에서 수정되었던 문장/값/변수들이모두 하나의 함수 또는 블럭안에 존재하도록 고치시오.

그리고 숫자는 1에서 시작하고 , 채워지지 않은 부분이 있다면 0으로 표시된다는 점이 제가 처음에 풀었던 문제와 다르네요.

확장된 조건을 다 수용할 수 있는 만족시키는 코드를 다시 한번 만들어보았습니다.

소스코드

public class MatrixMain {
    public static void main(String[] args) {
       System.out.println("0.원래의 matrix");
       ExtMatrix mp = new ExtMatrix(6,6);
       mp.print();

       System.out.println("1.숫자가 2씩 늘어나게");
       mp.setIncrement(2);
       mp.print();

       System.out.println("2.시작점을 1,1로");
       mp.setStartPoint(1, 1);
       mp.print();

       System.out.println("3.시작방향을 아래부터");
       mp.setStartDirection(ExtMatrix.DOWN);
       mp.print();

       System.out.println("4.배열크기 바꾸는건 생성할때 파라미터 바꾸면 됨.");
       System.out.println();
       System.out.println("5.반시계 반향으로 회전");
       mp.setReverseClockWiseTurn();
       mp.print();

       System.out.println("6.그런 조건 분기 없음.");
       System.out.println();

       System.out.println("7.한칸씩이 아닌 두칸씩 건너뛰며.");
       mp.setMoveStep(2);
       mp.print();
    }
}

public class ExtMatrix {
    public static final int RIGHT = 0;
    public static final int DOWN = 1;
    public static final int LEFT = 2;
    public static final int UP  = 3;

    private static final int INIT_VALUE = 0;
    private static final int CLOCK_WISE_TURN = 1;
    private static final int REVERSE_CLOCK_WISE_TURN = -1;

    private int m = 0;
    private int n = 0;
    private int startX = 0;
    private int startY = 0;
    private int[][] matrix;
    private int startDirection = RIGHT;
    private int increment = 1;
    private int moveStep = 1;

    private int turnDirection = CLOCK_WISE_TURN;


    public ExtMatrix(int m, int n){
        this.m = m;
        this.n = n;
        matrix = new int[m][n];
    }

    public void setIncrement(int increment){
        this.increment = increment;
    }

    public void setStartPoint(int startX, int startY){
        this.startX = startX;
        this.startY = startY;
    }

    public void setClockWiseTurn(){
        turnDirection = CLOCK_WISE_TURN;

    }

    public void setReverseClockWiseTurn(){
        turnDirection = REVERSE_CLOCK_WISE_TURN;
    }

    public void setMoveStep(int moveStep){
        this.moveStep = moveStep;
    }

    public void setStartDirection(int startDirection){
        this.startDirection = startDirection;
    }

    public void print(){
        locateNumbers();
        for (int i=0;i< m ;i++){
            for (int j=0;j<n;j++) System.out.print(matrix[i][j] + "\t");
            System.out.println();
       }
       System.out.println();
    }

    private void locateNumbers() \{
       init();
       int[] startPosition = new int[]\{startX,startY};
       matrix[startX][startY]= 1;
       int direction = startDirection;
       while(move(startPosition ,direction)) direction= getNextDirection(direction);
    }

    private void init(){
        for (int i=0;i< m ;i++){
            for (int j=0;j<n;j++) matrix[i][j] = INIT_VALUE;
        }
    }

    private boolean move(int[] position, int direction){
        int nowNumber = matrix[position[0]][position[1]];
        boolean moved = false;
        int[] nextPosition =getNextPosition(position,direction);
        while(isMovable(nextPosition[0],nextPosition[1])){
            moved = true;
            nowNumber+= increment;
            position[0] = nextPosition[0];
            position[1] = nextPosition[1];
            matrix[position[0]][position[1]]= nowNumber;
            nextPosition = getNextPosition(position,direction);
        }
        return moved;
    }

    private int[] getNextPosition(int[] position,int direction){
        int x = position[0];
        int y = position[1];
        if (direction == RIGHT) y+= moveStep;
        else if (direction == DOWN) x+= moveStep;
        else if (direction == LEFT) y-= moveStep;
        else if (direction == UP)  x-= moveStep;
        return new int[]\{x,y};
    }

    private int getNextDirection(int direction){
        direction+= turnDirection;
        if (direction<0) direction+=4;
        direction = direction %4;
        return direction;
    }

    private boolean isMovable(int x, int y){
        if (x>=m) return false;
        if (y>=n) return false;
        if (x<0) return false;
        if (y<0) return false;
        if (matrix[x][y]!= INIT_VALUE) return false;
        return true;
    }
}

방향을 int로 나타내는 것이 처음에는 좋은 아이디어 라고 생각했는데 mp.setStartDirection(ExtMatrix.DOWN); 부분을 보니 타입안전열거형이나 enum을 도입해야지 좀더 코드가 이뻐질 것 같네요.

Sybase 날짜형(datetime)의 문자열 변환시 style number

sybase에서 select convert(varchar,getdate(),112)로 찍어보면 오늘 날짜가 20070831 형식으로 나오죠. 이런 형식들이 정리된 자료를 한 번 만들어 보았습니다.

제가 쓰는 sybase 버전은 12.5.3입니다. (select @@version)으로 확인할 수 있죠)

select convert(varchar,날짜데이터, convertType)` 형식으로 쓰고 *convertType * 위치에 숫자가 들어갈 때 옆에 적힌 형식대로 나온다고 보시면 됩니다. 예시로 옆에 찍힌 날짜는 2007년 8월27일입니다.

Style number

  • 0 = Aug 27 2007 5:28PM

  • 1 = 08/27/07

  • 2 = 07.08.27

  • 3 = 27/08/07

  • 4 = 27.08.07

  • 5 = 27-08-07

  • 6 = 27 Aug 07

  • 7 = Aug 27, 07

  • 8 = 17:23:35

  • 9 = Aug 27 2007 5:28:08:563PM

  • 10 = 08-27-07

  • 11 = 07/08/27

  • 12 = 070827

  • 13 = 07/27/08

  • 14 = 08/07/27

  • 15 = 27/07/08

  • 16 = Aug 23 2007 17:28:08

  • 18 = 15:17:08

  • 19 = 5:11:39:086PM

  • 20 = 17:12:30:633

  • 21 = 07/08/27

  • 22 = 07/08/27

  • 100 = Aug 27 2007 5:28PM

  • 101 = 08/27/2007

  • 102 = 2007.08.07

  • 103 = 27/08/2007

  • 104 = 27.08.2007

  • 105 = 27-08-2007

  • 106 = 27 Aug 2007

  • 107 = Aug 27, 2007

  • 108 = 17:28:08

  • 109 = Aug 27 2007 5:28:08:563PM

  • 110 = 08-27-2007

  • 111 = 2007/08/27

  • 112 = 20070827

  • 113 = 2007/27/08

  • 114 = 08/2007/27

  • 115 = 27/2007/08

  • 116 = Aug 23 2007 17:28:08

응용

  • select convert(char,GETDATE(),112) : 오늘날짜를 YYYYMMDD로

  • select convert(char(8), DATEADD(DD,-1,getdate()) ,112) : 현재 날짜 하루전을 yymmdd형식으로 출력

  • select convert(char(8), DATEADD(DD,-1,'20070827') ,112) : 2007년 8월 27일 전날을 출력. string → datetime은 convert라는 함수를 사용하지 않고 내부적(implicit)으로 자동으로 변경됩니다

  • select str_replace( convert(varchar,getdate(),20),':',null) : 현재 분일초,밀리세컨드까지: 152515853

  • select convert(varchar,GETDATE(),112) || str_replace( convert(varchar,getdate(),20),':',null) : 현재 연월일시분초밀리세컨드를 다 붙여서