2016년 11월 30일 수요일

임시변수의 범위를 명확하게

가끔 비슷한 부분을 복사해서 사용하는 경우가 있습니다. 몇줄 안되는 데다가 몇군데만 달라서 함수화하기도 애매한 경우에 말입니다.

    Player first = GetFirst();
    first.Prize(Prize.GoldMedal);
    newspaper.OlimpicArticle(first, Prize.GoldMedal);

    Player second = GetSecond();
    second.Prize(Prize.SilverMedal);
    newspaper.OlimpicArticle(second, Prize.SilverMedal);

    Player third = GetThird();
    first.Prize(Prize.BronzeMedal);
    newspaper.OlimpicArticle(third, Prize.BronzeMedal);

즉, 선수들에게 등수에 따라 각각 금메달, 은메달, 동메달을 주고, 신문에 기사를 올리는 코드입니다. 비슷비슷하기에 첫 세줄을 복사해서 일부만 수정한 코드입니다.

이런 식으로 코딩을 할 때 가장 문제가 되는 것이 수정을 잘못하는 것이죠. 위 코드에도 이미 버그가 있습니다. third에게는 아무것도 주지 않고 first에게 금메달과 동메달을 같이 주고 있죠.

이런 실수를 막기 위해, 저는 저런 경우에는 변수의 범위를 명확하게 설정하곤 합니다.
즉,

    {
        Player first = GetFirst();
        first.Prize(Prize.GoldMedal);
        newspaper.OlimpicArticle(first, Prize.GoldMedal);
    }
    {
        Player second = GetSecond();
        second.Prize(Prize.SilverMedal);
        newspaper.OlimpicArticle(second, Prize.SilverMedal);
    }
    {
        Player third = GetThird();
        first.Prize(Prize.BronzeMedal);   // first : 선언되지 않은 변수
        newspaper.OlimpicArticle(third, Prize.BronzeMedal);
    }

변수 first의 영역은 첫째 단락에서 끝나버렸으므로 세번째 나온 first에서 컴파일 에러를 볼 수 있습니다.

물론 변수를 선언할 필요가 없는 루아 같은 경우에는 쓸 수 없는 꼼수이긴 합니다만.

2016년 11월 23일 수요일

정확한 확률 조정 방법

흔히들 확률에 대해 불만을 가지는 사람들이 많습니다. 0.1% 확률이 정확하게 0.1%로 일어나느냐는 것이죠.
물론 내부에서 random()같은 확률함수를 사용하지만, 이 확률함수를 믿을수 있느냐는 문제도 있습니다.
만약 그렇다면 다음과 같은 방식으로 0.1%를 정확하게 만들 수 있습니다.


public class OneByThousent
{
    private bool[] array = new bool[1000];
    private int point = 0;

    public OneByThousent()
    {
        for(int k = 0; k < 1000; ++k)
            array[k] = false;
        array[0] = true;
    }
}

여기까지 하면 1000개의 배열에 하나의 true가 들어갑니다. 0.1% 확률이죠.
물론 2000개 배열에 true를 2개 넣든가 5000개 배열에 5개를 넣던가 해도 상관 없습니다.
그 다음 이 배열을 잘 섞어줍니다. 이것은 시스템의 random함수를 사용해도 됩니다.
컴파일러마다 조금씩 다르지만, 보통 정수난수를 발생하는 함수라면,

public class OneByThousent
{
    private bool[] array = new bool[1000];
    private int point = 0;

    public OneByThousent()
    {
        for(int k = 0; k < 1000; ++k)
            array[k] = false;
        array[0];

        Shuffle();
    }

    private void Shuffle()
    {
        for(int from = 0; from < 1000; ++from)
        {
            int to = random() % 1000;    // 1)

            // from과 to를 맞바꿈
            bool tmp = array[from];
            array[from] = array[to];
            array[to] = tmp;
        }

        point = 0;
    }
}

다음에 이 array에서 하나씩 꺼내옵니다.

public class OneByThousent
{
    private bool[] array = new bool[1000];
    private int point = 0;

    public OneByThousent()
    {
        for(int k = 0; k < 1000; ++k)
            array[k] = false;
        array[0];

        Shuffle();
    }

    private void Shuffle()
    {
        for(int from = 0; from < 1000; ++from)
        {
            int to = (int)(random() * 1000);

            // from과 to를 맞바꿈
            bool tmp = array[from];
            array[from] = array[to];
            array[to] = tmp;
        }

        point = 0;
    }

    public bool Get()
    {
        bool rtn = array[point];
        ++point;
        if(point > 1000)
            Shuffle();
        return rtn;
    }
}

이렇게 하면 Get()함수는 정확하게 0.1% 확률로 true를 리턴하게 될 것입니다. 비록 random()함수가 정확한 랜덤이 아니라고 해도 말입니다.




마찬가지로, 만약

public class OneByThousent
{
    private float[] array = new float[1000];
    private int point = 0;

    public OneByThousent()
    {
        for(int k = 0; k < 1000; ++k)
            array[k] = k / 1000F;
        array[0];

        Shuffle();
    }

    private void Shuffle()
    {
        for(int from = 0; from < 1000; ++from)
        {
            int to = (int)(random() * 1000);

            // from과 to를 맞바꿈
            float tmp = array[from];
            array[from] = array[to];
            array[to] = tmp;
        }

        point = 0;
    }

    public float Get()
    {
        float rtn = array[point];
        ++point;
        if(point > 1000)
            Shuffle();
        return rtn;
    }
}

이렇게 하면 0~1 사이에서 1/1000 단위로 균일하게 분포된 난수가 만들어집니다.


다만, 이런 정확한 확률은 shuffle이 여러번 일어날 때 - 컴퓨터가 오랜 시간 돌아갈 때 정확하게 나타납니다.

만약 1/(1백만)의 확률을 만들기 위해 저런 식으로 1백만개의 어레이를 만들었다고 합시다.
그런데 저 함수로 10만개의 확률을 계산한 이후 (점검 등으로) 컴퓨터가 내려가는 일이 반복된다면, 그것은 그냥 random()함수를 사용하는 것이나 다름이 없습니다. 만약 이런 정확한 확률이 필요하다면 이 어레이를 파일이나 DB에라도 저장해 놓고 컴퓨터를 재시작한 이후에도 다시 연결될 수 있도록 해야겠죠.

1) 이런 식으로 나머지연산을 사용하면 일정 범위의 난수를 쉽게 얻을 수 있습니다.
하지만 이럴 경우 주의를 해야 합니다.
언젠가 난수를 출력해 봤는데, 난수가 다음과 같이 나오더군요(물론 이것도 컴파일러에 따라 다릅니다).

random() % 2 : 1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,.......
random() % 4 : 1,3,2,0,1,3,2,0,1,3,2,0,1,3,2,0,1,3,2,0,1,3,2,0,........
random() % 8 : 6,1,7,4,3,0,5,2,6,1,7,4,3,0,5,2,6,1,7,4,3,0,5,2,..........

다른 수일 경우는 괜찮았는데, 저렇게 2의 제곱수의 나머지를 구하면 순서대로 나옵니다.

그러므로 가장 좋은 방법은 다음과 같습니다.

    int rnd = (int)((random() * 1000.0) / MAXRANDOM);

2016년 11월 15일 화요일

C++ : Class Capsulation

가끔씩 따로 관리하던 두 가지를 한꺼번에 관리해야 할 경우가 생길 수 있습니다.

이를테면 다음과 같은 애완동물호텔을 만들었는데...

class Pet : public Animal
{
public :
    virtual void Feed() = 0;
    virtual void Bath();                  // Pet bathing
};

class Dog : public Pet
{
public :
    bool Feed() override;        // Dog feed
};

class Cat : public Pet
{
public :
    bool Feed() override;        // Cat feed
}


class Hotel
{
private :
    std::list<Pet *> m_List;
public :
    ....
    void Care()
    {
        for each(Pet *pet in m_List)
        {
            pet->Feed();
            pet->Bath();
        }
    }
};

그런데 갑자기 주인이 자동차나 오토바이 보관까지 하겠다고 나선다면 어떨까요? 더구나 자동차나 오토바이는

class Car : public Machine
{
   virtual void Fual() = 0;
   void Wash();            // car washing
};

class MotorCycle : public Car
{
   void Fual() override;   // fual Gasolin
};

class Sedan : public Car
{
   void Fual() override;   // fual Gasolin
};

class Jeep : public Car
{
   void Fual() override;   // fual Desel
};

class F1Formular : public Car
{
   void Fual() override;   // fual High grade Gasolin
};

와 같이 전혀 관계없는 클래스라면 더욱 난감해집니다.

이 경우 이런 클래스들을 감싸는 클래스를 만들면 비교적 손쉽게 됩니다.

class GuestBase
{
public :
    virtual void Feed() = 0;
    virtual void Bath() = 0;
    virtual void Fuel() = 0;
};

class GuestPet : GuestBase
{
private :
    Pet *m_Pet;
public :
    virtual void Feed()
    {
        m_Pet->Feed();
    }
    virtual void Bath()
    {
        m_Pet->Bath();
    }
    virtual void Fuel()
    {
        // Do nothing
    }
};

class GuestMachine : GuestBase
{
private :
    Machine *m_Machine;
public :
    virtual void Feed()
    {
        // Do nothing
    }
    virtual void Bath()
    {
        m_Machine->Wash();
    }
    virtual void Fuel()
    {
        m_Machine->Fuel();
    }
};



class Hotel
{
private :
    std::list<GuestBase *> m_List;
public :
    ....
    void Care()
    {
        for each(Guest *guest in m_List)
        {
            guest->Feed();
            guest->Bath();
            guest->Fuel();
        }
    }
};

Java '...' is not an enclosing class

자바에서

public class ViewPort
{
    public class BaseView
    {
    }

    public class MainView extends BaseView
    {
        ....
    }

    public class SideView extends BaseView
    {
        ....
    }
}

public class MainClass
{
    private ViewPort currentView;

    public MainClass()
    {
        currentView = new ViewPort.MainView();
    }
}

이럴경우

'com.program.ViewPort' is not an enclosing class

가 나타날 때가 있습니다. 자바는 처음 시작한지라 왜 이런 에러가 나타나는지는 영...
하지만 이렇게 하니까 해결은 되는군요.

public class MainClass
{
    private ViewPort currentView;

    public MainClass()
    {
        currentView = new ViewPort().new MainView();
    }
}

또는

public class MainClass
{
    private ViewPort currentView;

    public MainClass()
    {
        ViewPort vp = new ViewPort();
        currentView = vp.new MainView();
    }
}

로 해야 합니다.

C# .. is

다음 프로그램을 봅시다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    public class BaseClass
    {
    }

    public class DerivedClass : BaseClass
    {
    }

    class Program
    {
        static void Main(string[] args)
        {
            BaseClass b = new BaseClass();
            BaseClass d = new DerivedClass();
            Console.WriteLine("b is BaseClass : {0}", b is BaseClass);
            Console.WriteLine("b is DerivedClass : {0}", b is DerivedClass);
            Console.WriteLine("d is BaseClass : {0}", d is BaseClass);
            Console.WriteLine("d is DerivedClass : {0}", d is DerivedClass);
        }
    }
}

실행하면 결과는 다음과 같습니다.

b is BaseClass : True
b is DerivedClass : False
d is BaseClass : True
d is DerivedClass : True

즉,

if(b is DerivedClass)
   .....



if(b as DerivedClass != null)
   .....

과 동일한 식이죠.


그러므로 obj라는 객체가 BaseClass인지 DerivedClass인지 알기 위해서는

if(obj is BaseClass)
    Console.WriteLine("obj is BaseClass");
else
    Console.WriteLine("obj is DerivedClass");

이것은 DerivedClass도 BaseClass이므로 항상 true가 됩니다.

그러므로

if(obj is DerivedClass)
    Console.WriteLine("obj is DerivedClass");
else
    Console.WriteLine("obj is BaseClass");

이렇게 해야 제대로 동작하게 되죠.

2016년 11월 8일 화요일

OOP 추상화(Abstraciton)

OOP의 특징 중 하나가 추상화(Abstraciton)입니다. 그런데 이 추상화가 뭘까요? 피카소의 그림일까요?

'추상화'란 것은, 어떤 기능을 가진 객체를 구현할때, 객체 외부에서는 그 기능의 정확한 묘사를 하지 않는 것입니다. 그 대신 그 기능의 '추상적 행위'를 묘사해야 합니다.

이를테면 다음과 같은 잠수함을 구현한다고 해 봅시다.

class Submarine
{
    private int x;    // x좌표
    private int y;    // y좌표
    private int depth;// 깊이

    public int GetX() { return x; }
    public int SetX(int x) { this.x = x; }
    ...........
}

void Function(Submarine ship)
{
    // 북쪽으로 이동
    ship.SetY(ship.GetY() - 1);


    // 수면으로 이동
    if(ship.GetDepth() > 0)
        ship.SetDepth(ship.GetDepth() - 1);
}

이런 식으로 해도 동작은 하겠죠. 하지만 이런 구현을 한다면 구태여 OOP 프로그램을 사용할 필요가 없습니다. 그냥 C로도 구현되는 수준이거든요. 오히려 C로 하면 저런 잡다한 멤버함수를 만들 필요도 없이 쉽게 끝납니다(하지만 그냥 C 프로그램에서도 이런 식의 프로그램은 좋지 않습니다*).

이것을 제대로 추상화를 구현한다면 이런 식이 되어야 합니다.

class Submarine
{
    private int x;    // x좌표
    private int y;    // y좌표
    private int depth;// 깊이

    public void ToEast() { ++x; }
    public void ToWest() { --x; }
    public void ToSouth() { ++y; }
    public void ToNorth() { --y; }

    public void Float()
    {
        if(depth > 0)
           --depth;
    }

    public void Sink() { ++depth; }
}

void Function(Submarine ship)
{
    // 북쪽으로 이동
    ship.ToNorth();

    // 수면으로 이동
    ship.Float();
}


이런 식으로 Submarine 밖에서는 Submarine의 멤버변수 자체를 생각하지 않고 코딩하는 것이 '추상화'입니다. x니 y니 따위를 생각하지 않고 오로지 '북쪽으로 이동'만 코딩하는 것이죠. '구체화'는 Submarine.ToNorth() 안에서 하고 말입니다.
캡슐화(Capsulation)는 멤버변수에 직접 접근이 힘들 뿐, 실제 멤버변수가 뭐가 있는지 알 수는 있다는 점에서, 추상화는 캡슐화보다 한단계 더 진행한 개념이라고 볼 수 있죠.

만약

class Ocean
{
    ......

    public bool IsReef(int x, int y)    // 암초가 있는가
    {
        .....
    }
}

class Submarine
{
    private int x;    // x좌표
    private int y;    // y좌표
    private int depth;// 깊이

    public int GetX() { return x; }
    public int SetX(int x) { this.x = x; }
    ...........
}

void Function(Ocean ocean, Submarine ship)
{
    // 북쪽으로 이동
    ship.SetY(ship.GetY() - 1);

    // 암초에 걸렸는지 체크
    if(ocean.IsReef(ship.GetX(), ship.GetY())
        ship.Destroyed();
}

이런 것은 '캡슐화'는 되었지만(x, y변수가 '캡슐화되었죠) '추상화'가 된 코드는 아닙니다. '추상화'를 시킨다면

class Ocean
{
    ......

    public bool IsReef(int x, int y)    // 암초가 있는가
    {
        .....
    }
}

class Submarine
{
    private int x;    // x좌표
    private int y;    // y좌표
    private int depth;// 깊이

    public void ToEast() { ++x; }
    public void ToWest() { --x; }
    public void ToSouth() { ++y; }
    public void ToNorth() { --y; }

    public bool IsSafe(Ocean ocean)
    {
        if(ocean.IsReef(x, y))  // 암초에 걸렸으면
            return false;
        // 다른 상황 체크

        // 모두 통과했으면 안전
        return true;
    }
    ...........
}

void Function(Ocean ocean, Submarine ship)
{
    // 북쪽으로 이동
    ship.ToNorth();

    // 배가 안전한지 체크
    if(ship.isSafe(ocean))
        ship.Destroyed();
}


가 되어야 합니다.


* 위에서 저런 식의 코딩은 C에서도 좋지 않다고 했는데, C에서의 코딩은 다음과 같이 하는 것이 좋습니다.

struct Ocean
{
    ......
}

struct Submarine
{
    int x;    // x좌표
    int y;    // y좌표
    int depth;// 깊이
}

void ToEast(Submarine ship) { ++ship.x; }
void ToWest(Submarine ship) { --ship.x; }
void ToSouth(Submarine ship) { ++ship.y; }
void ToNorth(Submarine ship) { --ship.y; }

bool IsSafe(Ocean ocean, Submarine ship)
{
    if(ocean.IsReef(ship.x, ship.y))  // 암초에 걸렸으면
        return false;
    // 다른 상황 체크

    // 모두 통과했으면 안전
    return true;
}

void Function(Ocean ocean, Submarine ship)
{
    // 북쪽으로 이동
    ToNorth(ship);

    // 배가 안전한지 체크
    if(isSafe(ocean, ship))
        Destroyed(ship);
}