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