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);  // 컴파일 성공
        }
    }
}