2016년 12월 6일 화요일

Action과 Func

C#에서는 delegate를 사용해서 여러가지 작업을 할 수 있습니다.


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

namespace Delegate
{
    class Program
    {
        private static void Plus(int a, int b)
        {
            Console.WriteLine("{0} + {1} = {2}", a, b, a + b);
        }
        private static void Minus(int a, int b)
        {
            Console.WriteLine("{0} - {1} = {2}", a, b, a - b);
        }
        private static void Multiply(int a, int b)
        {
            Console.WriteLine("{0} * {1} = {2}", a, b, a * b);
        }
        private static void Divide(int a, int b)
        {
            if(b != 0)
                Console.WriteLine("{0} / {1} = {2}", a, b, a / b);
        }
        private delegate void Function(int a, int b);
        static void Main(string[] args)
        {
            Function f = null;
            f += Plus;
            f += Minus;
            f += Multiply;
            f += Divide;
            f(1, 2);
            Console.WriteLine("-----------");
            f(2, 1);
            Console.WriteLine("-----------");
            f(3, 0);
        }
    }
}

인수 두개를 요구하는 함수를 Function으로 정의해 놓고, Function인 f를 만든 후, f에 Plus, Minus 등의 함수를 등록시키면, f를 한번 호출하는 것으로 등록된 함수들을 모두 호출할 수 있습니다.

그런데 사실 이런 일을 하기 위해서는 delegate를 사용할 필요도 없습니다. 이미 필요한 함수들이 정의되어 있기 때문입니다. Action<>이 바로 이미 정의된 delegate들입니다.,
바로 System에 다음과 같이 정의되어 있습니다.

namespace System
{
    public delegate void Action();
    public delegate void Action<in T1>(T1 arg1);
    public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2);
    public delegate void Action<in T1, in T2, in T3>(T1 arg1, T2 arg2, T3 arg3);
    public delegate void Action<in T1, in T2, in T3, in T4>(T1 arg1, T2 arg2, T3 arg3, T4 arg4);
    public delegate void Action<in T1, in T2, in T3, in T4, in T5>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5);
    public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6);
    public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7);
    public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8);
    public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9);
    public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10);
    public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11);
    public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12);
    public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12arg12, T13 arg13);
    public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14);
    public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15);
    public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15, in T16>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16);
}

위와같이 16개의 인수를 가질 수 있는 함수까지 만들어져 있습니다(사실 이렇게까지 많이 만들 필요는 없을것 같습니다만....)

그러므로 만약 위 프로그램을 Action으로 만든다면 다음과 같습니다.

        // private delegate void Function(int a, int b); 딜리게이트 제거
        static void Main(string[] args)
        {
            Action<int, int> f = null;  // delegate 대신 인수가 int 2개인 Action 선언
            f += Plus;
            f += Minus;
            f += Multiply;
            f += Divide;
            f(1, 2);
            Console.WriteLine("-----------");
            f(2, 1);
            Console.WriteLine("-----------");
            f(3, 0);
        }

최초의 프로그램과 완전히 똑같은 결과가 나오죠.


이와 거의 동일하게 Func<> 역시 존재합니다. Func는 리턴값까지 있는 delegate를 대신합니다.

        private static string Plus(int a, int b)
        {
            return string.Format("{0} + {1} = {2}", a, b, a + b);
        }
        private static string Minus(int a, int b)
        {
            return string.Format("{0} - {1} = {2}", a, b, a - b);
        }

        static void Main(string[] args)
        {
            Func<int, int, string> f = null; // int 2개를 인수로 하고 string을 리턴하는 함수 선언
            f += Plus;
            f += Minus;
            Console.WriteLine(f(1, 2));
            Console.WriteLine(f(2, 1));
        }

단, 이 경우에는 f에 등록된 함수들 중 가장 마지막으로 실행된 함수의 리턴값만이 남게 됩니다. 이점을 주의해야 합니다.

2016년 12월 2일 금요일

Flag Clear

길찾기 알고리즘에 있어서 이런 일이 흔히 생길 수 있습니다.

class Cell
{
private :
    bool isChecked = 0;
    bool canEnter;
    int coordX, coordY;
public :
    void Reset()
    {
        isChecked = false;
    }

    bool Search(Cell field[2048][2048], int goalX, goalY)
    {
        if(!canEnter)
            return false;
        if(goalX == coordX && goalY == coordY)
            return true;
        if(!isChecked)
        {
            isChecked = true;
            return field[CoordX + 1][CoordY].Search(field, goalX, goalY) ||
                   field[CoordX - 1][CoordY].Search(field, goalX, goalY) ||
                   field[CoordX][CoordY + 1].Search(field, goalX, goalY) ||
                   field[CoordX][CoordY - 1].Search(field, goalX, goalY);
        }
    }
};

class Building
{
private :
    Cell field[2048][2048];
public :
    bool CanGoTo(int fromX, int fromY, int toX, int toY)
    {
        for(int x = 0; x < 2048; ++x)
            for(int y = 0; y < 2048; ++y)
                field[x][y].Reset();   // 이전 탐색결과 지우기
        return field[fromX][fromY].Search(field, toX, toY);
};

매번 길을 찾기 전에 필드의 모든 셀에 대해 이전에 찾았던 길의 정보를 지워야 하죠.

그런데 위와 같이 모든 셀에 대해 플래그를 지우는데 약간의 시간이 지체될 수 있습니다. 2048 * 2048 = 4194304개의 셀에 대해 작업을 해야 하거든요.

이것을 이렇게 하면 어떨까요?


class Cell
{
public :
    static unsigned long currentChecked = 0;
private :
    unsigned long isChecked;
    bool canEnter;
    int coordX, coordY;
public :
    bool Search(Cell field[2048][2048], int goalX, goalY)
    {
        if(!canEnter)
            return false;
        if(goalX == coordX && goalY == coordY)
            return true;
        if(isChecked != currentChecked)
        {
            isChecked = currentChecked;
            return field[CoordX + 1][CoordY].Search(field, goalX, goalY) ||
                   field[CoordX - 1][CoordY].Search(field, goalX, goalY) ||
                   field[CoordX][CoordY + 1].Search(field, goalX, goalY) ||
                   field[CoordX][CoordY - 1].Search(field, goalX, goalY);
        }
    }
};

class Building
{
private :
    Cell field[2048][2048];
public :
    bool CanGoTo(int fromX, int fromY, int toX, int toY)
    {
        ++Cell::currentChecked;   // 이전 탐색결과 지우기
        return field[fromX][fromY].Search(field, toX, toY);
};


currentChecked를 바꾸는 것만으로 2048*2048개 모든 Cell의 isChecked가 갱신됩니다. currentChecked와 isChecked의 값이 같은지 다른지가 중요하므로 currentChecked에 오버플로우가 일어나도 상관이 없죠.

다만 한가지 문제는, 어느 한 셀이 0xFFFFFFFF회 동안 한번도 참조되지 않는다면 그 셀은 isChecked가 true인 상태가 된다는 것입니다.

이럴 가능성이 없거나, 무시해도 된다면 상관없지만, 이럴 가능성을 무시할 수 없다면 다음과 같은 추가코드가 필요해집니다.


class Cell
{
public :
    static unsigned long currentChecked = 0;
private :
    unsigned long isChecked = 0;
    bool canEnter;
    int coordX, coordY;
public :
    void Reset()
    {
        isChecked = 0;
    }

    bool Search(Cell field[2048][2048], int goalX, goalY)
    {
        if(!canEnter)
            return false;
        if(goalX == coordX && goalY == coordY)
            return true;
        if(isChecked != currentChecked)
        {
            isChecked = currentChecked;
            return field[CoordX + 1][CoordY].Search(field, goalX, goalY) ||
                   field[CoordX - 1][CoordY].Search(field, goalX, goalY) ||
                   field[CoordX][CoordY + 1].Search(field, goalX, goalY) ||
                   field[CoordX][CoordY - 1].Search(field, goalX, goalY);
        }
    }
};

class Building
{
private :
    Cell field[2048][2048];
public :
    bool CanGoTo(int fromX, int fromY, int toX, int toY)
    {
        // 이전 탐색결과 지우기
        if(Cell::currentChecked == 0xFFFFFFFF)
        {
            // 오버플로우 나기 전에 초기화
            ++Cell::currentChecked
            for(int x = 0; x < 2048; ++x)
                for(int y = 0; y < 2048; ++y)
                    field[x][y].Reset();
            
        }
        else
            ++Cell::currentChecked;
        return field[fromX][fromY].Search(field, toX, toY);
};

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);
}

2016년 9월 30일 금요일

상속(inheritance)과 속성(component)

OOP언어의 특징 중 하나가 '상속(inheritance)'입니다. 주로 다형성(Polymorphism) - 다른 객체를 동일하게 취급하기 위해 사용합니다.

그런데 실제로 상속을 사용하다 보면 오히려 더 어려운 코드가 만들어지는 경우가 있습니다.

만약 동물의 왕국을 상속을 통해 만든다면...
public class Animal
{
    public virtual void Bite()
    {
        // do Nothing
    }
    public virtual void Swim()
    {
        // do Nothing
    }
    public virtual void Run()
    {
        // do Nothing
    }
    public virtual void Fly()
    {
        // do Nothing
    }
}

public class Avian : Animal
{
    public override void Fly()
    {
        // do Fly
    }
}

public class Mammal : Animal
{
}


public class Seagal : Avian
{
}

public class Penguin : Avian
{
    public override void Swim()
    {
        // do Swim
    }
    public override void Fly()
    {
        // do Nothing - Avian은 날 수 있는데 Penguin은 날지 못하므로
    }
}

public class Ostrich : Avian
{
    public override void Run()
    {
        // do Run
    }
    public override void Fly()
    {
        // do Nothing - Avian은 날 수 있는데 Ostrich는 날지 못하므로
    }
}

public class Bat : Mammal
{
    public override void Fly()
    {
        // do Fly
    }
}

public class Whale : Mammal
{
    public override void Swim()
    {
        // do Swim
    }
}

public class Dog : Mammal
{
    public override void Bite()
    {
        // Do bite
    }
    public override void Run()
    {
        // do Run
    }
}


.....
List<Animal> Zootopia = new List<Animal>();

Zootopia.Add(new Penguin());
Zootopia.Add(new Seagal());
Zootopia.Add(new Ostrich());
Zootopia.Add(new Bat());
Zootopia.Add(new Dog());
Zootopia.Add(new Whale());

이정도가 될 수 있겠죠. 전체적으로 정돈되지 않고, 중복되는 코드(While.Swim(), Penguin.Swim()), 복잡한 가상함수관계(Animal.Fly(), Avian.Fly(), Ostrich.Fly(), Bat.Fly()), 그리고 덩치가 큰 base 클래스(class Animal) 등에 의해 알아보기 힘든 코드가 됩니다.

이런 단점을 보완하기 위해서 컴포넌트(Component)를 사용할 수 있습니다. 상속관계를 본체(Animal)가 아니라 컴포넌트에 적용하는 것입니다.

// Components
public class Component
{
    public virtual void Do
    {
        // do Nothing
    }
}

public class Bite : Component
{
    public override void Do
    {
        // do Bite
    }
}

public class Swim : Component
{
    public override void Do
    {
        // do Swim
    }
}

public class Run : Component
{
    public override void Do
    {
        // do Run
    }
}

public class Fly : Component
{
    public override void Do
    {
        // do Fly
    }
}




public class Animal : List<Component>
{
}

public class Avian : Animal
{
    Add(new Fly());  // 조류의 경우는 기본적으로 날아다님
}

public class Mammal : Animal
{
}


public class Seagal : Avian
{
    public Seagal()
    {
    }
}

public class Penguin : Avian
{
    public Penguin()
    {
        Add(new Swin());
        RemoveFly();     // 리스트에서 Fly 제거
    }
}

public class Ostrich : Avian
{
    public Ostrich()
    {
        Add(new Run());
        RemoveFly();     // 리스트에서 Fly 제거
    }
}

public class Bat : Mammal
{
    public Bat()
    {
        Add(new Fly());
    }
}

public class Whale : Mammal
{
    public Whale()
    {
        Add(new Swim());
    }
}

public class Dog : Mammal
{
    public Dog()
    {
        Add(new Bite());
        Add(new Run());
    }
}


.....
List<Animal> Zootopia = new List<Animal>();

Zootopia.Add(new Penguin());
Zootopia.Add(new Seagal());
Zootopia.Add(new Ostrich());
Zootopia.Add(new Bat());
Zootopia.Add(new Dog());
Zootopia.Add(new Whale());

이렇게 각자가 가진 속성만 정의하면 되므로 가독성이 높아질 수 있습니다.

2016년 8월 31일 수요일

C# User defined Enumerable

1, 2, 3, 4, 5....의 순으로 인수를 얻어오기 위한 방식은 간단합니다.

for (int k = 1; k <= 5; ++k)
{
    Console.WriteLine(p);
};


그런데 이를테면 2, 3, 5, 7, 11, ... 순으로 소수를 얻어오려면 IEnumerable을 사용하면 좀 더 간단히 할 수 있습니다. Iterator를 사용해서 말입니다.

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

namespace PrimeNumber
{
    internal class PrimeNumber
    {
        List<int> m_Prime = new List<int>();

        internal IEnumerable<int> Prime()
        {
            m_Prime.Add(2);
            yield return 2; // 최초에 2 리턴

            for (int num = 3; ; num += 2)
            {
                // 소수 갯수가 100개 넘었다면 끝
                if (m_Prime.Count >= 100)
                    yield break;

                // 소수계산 공식
                bool isPrime = true;
                foreach (int p in m_Prime)
                {
                    if (num % p == 0)
                    {
                        isPrime = false;
                        break;
                    }
                    if (p * p > num)
                        break;
                }

                if (isPrime)
                {
                    m_Prime.Add(num);
                    yield return num; // 소수를 찾았으면 소수 리턴
                }
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            PrimeNumber prime = new PrimeNumber();
            foreach (var p in prime.Prime())
                Console.WriteLine(p);
        }
    }
}

어디서든지 yield return을 만나면 일단 리턴을 한 후, 다음 함수가 불리면 yield문 이후부터 다시 실행 시작합니다. 반면 yield break는 완전한 중지로 Main()의 foreach구문을 빠져나오게 됩니다.

소수찾기 루틴을 코딩하기가 복잡하다면? 다음과 같이 하면 됩니다.

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

namespace PrimeNumber
{
    internal class PrimeNumber
    {
        List m_Prime = new List();

        internal IEnumerable<int> Prime()
        {
            yield return 2;
            yield return 3;
            yield return 5;
            yield return 7;
            yield return 11;
            yield return 13;
            yield return 17;
            yield return 19;
            yield return 23;
            yield return 29;
            yield return 31;
            yield return 37;
            yield return 41;
            yield return 43;
            yield return 47;
            yield return 53;
            yield return 59;
            yield return 61;
            yield return 67;
            yield return 71;
            yield return 73;
            yield return 79;
            yield return 83;
            yield return 89;
            yield return 97;
            yield return 101;
            yield return 103;
            yield return 107;
            yield return 109;
            yield return 113;
            yield return 127;
            yield return 131;
            yield return 137;
            yield return 139;
            yield return 149;
            yield return 151;
            yield return 157;
            yield return 163;
            yield return 167;
            yield return 173;
            yield return 179;
            yield return 181;
            yield return 191;
            yield return 193;
            yield return 197;
            yield return 199;
            yield return 211;
            yield return 223;
            yield return 227;
            yield return 229;
            yield return 233;
            yield return 239;
            yield return 241;
            yield return 251;
            yield return 257;
            yield return 263;
            yield return 269;
            yield return 271;
            yield return 277;
            yield return 281;
            yield return 283;
            yield return 293;
            yield return 307;
            yield return 311;
            yield return 313;
            yield return 317;
            yield return 331;
            yield return 337;
            yield return 347;
            yield return 349;
            yield return 353;
            yield return 359;
            yield return 367;
            yield return 373;
            yield return 379;
            yield return 383;
            yield return 389;
            yield return 397;
            yield return 401;
            yield return 409;
            yield return 419;
            yield return 421;
            yield return 431;
            yield return 433;
            yield return 439;
            yield return 443;
            yield return 449;
            yield return 457;
            yield return 461;
            yield return 463;
            yield return 467;
            yield return 479;
            yield return 487;
            yield return 491;
            yield return 499;
            yield return 503;
            yield return 509;
            yield return 521;
            yield return 523;
            yield return 541;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            PrimeNumber prime = new PrimeNumber();
            foreach (var p in prime.Prime())
                Console.WriteLine(p);
        }
    }
}

Iterator에 대하여

다음과 같은 데이터의 묶음이 있다고 가정합시다.

class SomeClass
{
    ......
};
SomeClass SomeClassArray[1024]; // 1024개의 배열

이때 SomeClassArray의 모든 원소를 하나씩 꺼내기 위한 일반적인 방법은 다음과 같습니다.

    int k;

    for(k = 0; k < 1024; ++k)
        SomeClassArray[k].Read();

    for(k = 0; k < 1024; ++k)
        SomeClassArray[k].Process();

    for(k = 0; k < 1024; ++k)
        SomeClassArray[k].Write();

    for(k = 0; k < 1024; ++k)
        Func(&SomeClassArray[k]);

하지만 이 방법만 있는 것은 아니죠. SomeClassArray의 모든 원소를 하나씩 꺼내기 위한 다른 한가지 방법은 반복자(Iterator)를 사용하는 방법도 있습니다.

class SomeClassIterator // 반복자 정의
{
private :
    SomeClass *SomeClassArray;
    int        Size;
    int        Finger;
public :
    SomeClassIterator(SomeClass *arr, int size)
    {
        SomeClassArray = arr;
        Size = size;
        Finger = 0;
    }
    ~SomeClassIterator(void)
    {
    }
    SomeClass *operator ->(void)
    {
        return Finger >= Size ? nullptr : SomeClassArray[Finger];
    }
    SomeClass *operator *(void)
    {
        return Finger >= Size ? nullptr : SomeClassArray[Finger];
    }
    void operator ++(void)
    {
        ++Finger;
    }
    void Home(void)
    {
        Finger = 0;
    }
    bool IsEnd(void)
    {
        return Finger >= Size;
    }
};
    SomeClassIterator iter(SomeClassArray, 1024);  // 반복자 선언

    for(iter.Home(); !iter.IsEnd(); ++iter)
        iter->Read();

    for(iter.Home(); !iter.IsEnd(); ++iter)
        iter->Process();

    for(iter.Home(); !iter.IsEnd(); ++iter)
        iter->Write();

    for(iter.Home(); !iter.IsEnd(); ++iter)
        Func(*iter);

그런데 반복자는 왜 사용해야 할까요? 위에서 보듯 SomeClassIterator라는 클래스가 추가되는 등 오히려 더 복잡해지기만 하는데 말입니다.


만약 SomeClass와 SomeClassArray가 다음과 같이 바뀌면 어떻게 될까요?

class SomeClass
{
    ......
};
SomeClass2
{
    ....
    SomeClass Member;
};
SomeClass2 SomeClassArray[1024]; // 1024개의 배열

이 경우에 일반적인 for 루프를 사용했다면 다음과 같이 바꿔야 합니다.
    int k;

    for(k = 0; k < 1024; ++k)
        SomeClassArray[k].Member.Read();

    for(k = 0; k < 1024; ++k)
        SomeClassArray[k].Member.Process();

    for(k = 0; k < 1024; ++k)
        SomeClassArray[k].Member.Write();

    for(k = 0; k < 1024; ++k)
        Func(&SomeClassArray[k].Member);

더구나 저런 루프는 프로그램 전체에 흩어져 있을 것이므로 모든 for루프를 찾아 고치지 않으면 안되죠.

만약 iterator를 사용한다면
class SomeClassIterator // 반복자 정의
{
private :
    SomeClass2 *SomeClassArray;
    int         Size;
    int         Finger;
public :
    SomeClassIterator(SomeClass2 *arr, int size)
    {
        SomeClassArray = arr;
        Size = size;
        Finger = 0;
    }
    ~SomeClassIterator(void)
    {
    }
    SomeClass *operator ->(void)
    {
        return Finger >= Size ? nullptr : SomeClassArray[Finger].Member;
    }
    SomeClass *operator *(void)
    {
        return Finger >= Size ? nullptr : SomeClassArray[Finger].Member;
    }
    void operator ++(void)
    {
        ++Finger;
    }
    void Home(void)
    {
        Finger = 0;
    }
    bool IsEnd(void)
    {
        return Finger >= Size;
    }
};
    SomeClassIterator iter(SomeClassArray, 1024);  // 반복자 선언

    for(iter.Home(); !iter.IsEnd(); ++iter)
        iter->Read();

    for(iter.Home(); !iter.IsEnd(); ++iter)
        iter->Process();

    for(iter.Home(); !iter.IsEnd(); ++iter)
        iter->Write();

    for(iter.Home(); !iter.IsEnd(); ++iter)
        Func(*iter);

와 같이 SomeClassIterator클래스만 수정하면 프로그램의 다른 부분은 전혀 고칠 필요가 없습니다. 이것은 버그 가능성도 낮아지고, 유지보수비용이 감소함을 뜻합니다.

심지어, SomeClass와 SomeClassArray가 다음과 같이 리스트로 바뀌는 등 구조가 완전히 바뀌더라도

class SomeClass
{
    ......
    SomeClass *Next;
};
SomeClass *SomeClassArray; // 리스트구조의 첫항

만약 이렇게 바뀐다면, 일반적인 for루틴을 사용했을 경우는 다음과 같이 바뀌어야 합니다만,

    SomeClass *k;

    for(k = SomeClassArray; k != nullptr; k = k->Next)
        k->Read();

    for(k = SomeClassArray; k != nullptr; k = k->Next)
        k->Process();

    for(k = SomeClassArray; k != nullptr; k = k->Next)
        k->Write();

    for(k = SomeClassArray; k != nullptr; k = k->Next)
        Func(k);

만약 iterator를 사용한다면

class SomeClassIterator // 반복자 정의
{
private :
    SomeClass *SomeClassArray;
  //int        Size;
    SomeClass *Finger;
public :
    SomeClassIterator(SomeClass *arr, int size)
    {
        SomeClassArray = arr;
      //Size = size;
        Finger = arr;
    }

    ~SomeClassIterator(void)
    {
    }
    SomeClass *operator ->(void)
    {
        return Finger;
    }
    SomeClass *operator *(void)
    {
        return Finger;
    }
    void operator ++(void)
    {
        if(Finger != nullptr)
            Finger = Finger->Next;
    }
    void Home(void)
    {
        Finger = SomeClassArray;
    }
    bool IsEnd(void)
    {
        return Finger == nullptr;
    }
};
    SomeClassIterator iter(SomeClassArray, 1024);  // 반복자 선언

    for(iter.Home(); !iter.IsEnd(); ++iter)
        iter->Read();

    for(iter.Home(); !iter.IsEnd(); ++iter)
        iter->Process();

    for(iter.Home(); !iter.IsEnd(); ++iter)
        iter->Write();

    for(iter.Home(); !iter.IsEnd(); ++iter)
        Func(*iter);


와 같이 역시 Iterator 부분만 고치는 것으로 끝납니다.

즉 iterator는 반복루틴 자체를 추상화함으로써 유지보수비용을 줄이는 기법입니다.

2016년 8월 20일 토요일

동일한 여러개 인수 전달

함수를 호출할 때 컴파일러가 인수 타입을 체크


void Func(int a, ClassTypeA *b, StructTypeB &c, int *d);

main()
{
    int varA;
    ClassTypeA clsB;
    StructTypeB strC;

    Func(varA, &clsB, strC, &varA);  // 컴파일 성공
    Func(&clsB, varA, &varA, strC);  // 컴파일 에러
}


하지만 만약 이 여러개의 인수가 동일한 타입을 가지고 있다면?


void Func(int id, int height, int weight, int birthYear, int score, int familyNumber)
{
    printf("Height %d\n", height);
    printf("Weight %d\n", weight);
}

main()
{
    int Id = 1;
    int weight = 67;      // 몸무게
    int height = 175;    // 키
    int birth = 1992;   // 태어난 해
    int family = 4;    // 가족 수
    int score = 96;   // 성적


    Func(Id, height, weight, birth, score, family);  // 컴파일 성공
    Func(Id, weight, height, birth, family, score);  // 컴파일 성공
    Func(family, height, score, birth, weight, Id);  // 컴파일 성공
}

즉 프로그래머의 실수를 컴파일러가 찾을 수 없다는 뜻.


C/C++

struct Arg
{
    int Id;
    int height;           // 키
    int weight;          // 몸무게
    int birthYear;      // 태어난 해
    int score;         // 성적
    int familyNumber; // 가족 수
};

void Func(Argument *arg)
{
    printf("Height %d\n", arg->height);
    printf("Weight %d\n", arg->weight);
}

main()
{
    Argument arg;
    arg.Id = 1;
    arg.weight = 67;          // 몸무게
    arg.height = 175;        // 키
    arg.birthYesr = 1992;   // 태어난 해
    arg.familyNumber = 4;  // 가족 수
    arg.score = 96;       // 성적


    Func(&arg);  // 컴파일 성공
}

물론

    arg.weight = 175;
    arg.height = 67;

와 같은 실수는 감지 못하지만 이것은 눈으로 볼 수 있으므로 실수할 가능성 적음


Lua

루아에서는 타입 자체가 없으므로 이런 버그가 발생할 가능성 더 큼. 루아에서는 테이블을 이용하여 간단히 구현 가능

function Func(m)
   print("Height " .. m.height)
   print("weight " .. m.weight)
end

Func({Id = 1,
      weight = 67;      -- 몸무게
      height = 175;     -- 키
      birthyear = 1992, -- 태어난 해
      familyNumber = 4; -- 가족 수
      score = 96;       -- 성적
     }
    )


C#

C#에서는 변수 이름으로 인수를 전달하는 기능이 있으므로 위와 같이 인수 순서가 헷갈릴 때 유용함


namespace Space
{
    class Program
    {
        static void Func(int id, int height, int weight,
                         int birthYear, int score, int familyNumber)
        {
            Console.WriteLine("Height {0}", height);
            Console.WriteLine("Weight {0}", weight);
        }
        static void Main(string[] args)
        {
            int Id = 1;
            int weight = 67;      // 몸무게
            int height = 175;    // 키
            int birth = 1992;   // 태어난 해
            int family = 4;    // 가족 수
            int score = 96;   // 성적

            Func(id:Id, score:score, weight:weight,
                 familyNumber:family, height:height, birthYear:birth);  // 컴파일 성공
        }
    }
}

2016년 7월 10일 일요일

C++에서의 for each

C++ 0x버전에서 새로 추가된 for each문은 배열이나 벡터 등에 대해 다음과 같이 사용할 수 있습니다.


main()
{
    int arr[100];
    input(arr);
    for each(int a in arr)
        printf("a = %d\n", a);
}

하지만 이때 for each의 변수를 reference로 선언된다면 내부적으로 const로 정의되므로 내부변수를 수정하는 것에는 사용 불가입니다.



class Student
{
private :
    char Name[32];
    int Kor;
    int Mat;
    int Eng;
public :
    Student();
    ~Student();
    void LoadFromFile();
};

위와 같은 변수에 대해서

main()
{
    Student arr[100];
    for each(Student a in arr)
        a.LoadFromFile();
}

와 같이 한다면, a에는 arr 각 원소의 복사본이 들어가므로 정작 arr 원소들에 로드되지는 않습니다. 로드된 데이터는 arr의 원소가 아니라 arr의 원소의 복사본에 들어갔다가, 메모리 해제되며 사라지죠.

그렇다고 해서


main()
{
    Student arr[100];
    for each(Student &a in arr)
        a.LoadFromFile();
}

이렇게 하면 a는 반드시 const reference여야 하므로 컴파일 에러가 납니다.


이 경우에는 배열이 아닌 포인터의 배열을 사용해서 다음과 같이 코딩해야 합니다.

main()
{
    Student *arr[100];
    for(int k = 0; k < 100; ++k)
        arr[k] = new Student;
    for each(Student *a in arr)
        a->LoadFromFile();
}