이전에 C#으로 SICP에 나온 Infinite Streams을 구현 해본적이 있는데
이번에는 C++로 구현 해보았습니다. 실제로 이런 Lazy한 기법들이 사용될 곳이 있을지는 생각을 더 해봐야 겠지만 일단은 공부 한다는 차원에서 .... 해볼만한 것 같습니다.


만든 Infinite를 사용하는 main코드는 아래와 같은 모양으로 생겼습니다.
(wcout를 쓰기 위해서 그냥 tchar를 사용하지 않았습니다.)

bool isEven(int i) { return (i%2 == 0); }
 
int wmain(int argc, wchar_t* argv[])
{
    fibs()->filter(isEven)->take(10)->out(std::wcout);
    return 0;
}

눈치가 빠르신 분들은 벌써 아셨겠지만 코드는...
피보나치수열(fibs)를 얻어서, 이 중 짝수만 걸러내서(filter) 그 중에서 제일 앞에 10개만(take) 출력(out)하겠다는 코드 입니다. 물론 C++에서 저렇게 exception처리도 없이 ->연산자를 중복해서 사용하는 것은 좋지 않지만 실제로 사용하는 코드가 아니니 이해해 주시기 바랍니다.

아무튼 그래서 피보나치 수열 중에서 짝수 중 앞에 10개는 아래와 같습니다.
위 코드가 아래와 같은 결과를 나오게 만들면 되겠지요?
시간 있으신 분들은 제가 한 방법 말고 다른 방법으로 구현 해보셔도 재미있을 것 같네요 ^^

2
8
34
144
610
2584
10946
46368
196418
832040


- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
소스보기 :


- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

소스를 직접 까보기 귀찮은신 분들을 위해서..
주요 부분 소스를 붙입니다.. (메모리는.. shared_ptr을 썼다가 지저분해서 뺐습니다.)

class fibo_stream : public make_stream<int>
{
public:
    fibo_stream()
        : make_stream<int>(1, 1)
    {}
    virtual IStream<int>* _tail()
    {
        IStream<int>* super_tail = make_stream<int>::_tail();
        return new fibo_stream(super_tail->head(),
                                head()+super_tail->head());
    }
protected:
    fibo_stream(int head, int tail)
        : make_stream<int>(head, tail)
    {}
};
IStream<int>* fibs() { return new fibo_stream(); }

피보나치 수열 스트림의 포인트는 항상 두개의 값만 계산해 놓는다는 겁니다. 즉, 미리 앞서나가지 않는 것 입니다.


덧)
아래 wafe님의 지적을 받아서 표준이 아닌 문법을 고쳐서 g++에서도 컴파일 될 수 있도록 했습니다.


Posted by U∙Seung

얼마전에 C# 3.0으로 만든 QuickSort를 포스팅한 이후로도 C#에 대한 생각을 평소 많이 하고 있다.
다만 C#을 Main 언어로 코딩한다면 좋겠지만 아직은 C++을 많이 쓰는 상황이라서 아쉬움이 많다.

C#의 새로운 Functional Feature들을 보면서 떠오르는 것은 단연 SICP였다.

사용자 삽입 이미지

 이 책은 MIT에서 강의용 교재로 쓰이는 책인데, 얼마전 우리나라에도 번역본이 나와서 인기를 끌고 있으며(컴퓨터 프로그램의 구조와 해석), 최근 있었던 Microsoft DevDays 2007에서도 김명호 박사님의 KeyNote에서 소개 되어서 더욱 유명세를 타기도 한 바로 그 책이다.

 암튼 책에 나오는 몇가지 Sample들을 C#으로 다시 만들어 보았다.
 Scheme은 Type이 없고, 괄호를 중심으로 하는 표현방식이라  조금만 코드가 길어지면 머리가 복잡해지는데 반해, C#은 Type이 분명하고, Syntax도 익숙하여 훨씬 Writablity가 높았다. Type의 축복이랄까;;


 몇가지 예제들을 공유하여 본다. 비교하면서 보는 것도 재미가 있을 것 같다.
 예전에 Javascript로도 만든 것도 참조 하면 괜찮을지 모르겠다.


#1. 간단한 함수 - 홀짝 판단

LISP
(define (odd? n)
 (if (= n 0)
   #f
   (even? (- n 1))))

(define (even? n)
 (if (= n 0)
   #t
   (odd? (- n 1))))


C#
static bool IsEven(this int num)

{

    return (num == 0) ? true : IsOdd(num - 1);

}


static bool IsOdd(this int num)

{

    return (num == 0) ? false : IsEven(num - 1);

}




#2. Prime 판단하기


LISP
(define (smallest-divisor n)
 (find-divisor n 2))
(define (find-divisor n test-divisor)
 (cond ((> (square test-divisor) n) n)
       ((divides? test-divisor n) test-divisor)
       (else (find-divisor n (+ test-divisor 1)))))
(define (divides? a b)
 (= (remainder b a) 0))

(define (prime? n)
 (= n (smallest-divisor n)))


C#
public static bool IsPrime(this int num)

{

    Func<int, int> FindSmallestDivisor = divisor =>

    {

        if (divisor * divisor > num) return num;

        if (num % divisor == 0) return divisor;

        return FindSmallestDivisor(divisor+1);

    };

    return (FindSmallestDivisor(2) == num);

}




#3. 무한대 수열 다루기 - 자연수 만들기

LISP

(define (cons-stream a b) (cons a (delay b)))

(define (stream-car stream) (car stream))

(define (stream-cdr stream) (force (cdr stream)))

(define (integers-starting-from n)
  (cons-stream n (integers-starting-from (+ n 1))))

(define integers (integers-starting-from 1)


C#

static IEnumerable<T> ConsStream<T>(this T t, IEnumerable<T> ts)

{

    yield return t;

    foreach (var nextT in ts)

        yield return nextT;

}


static

IEnumerable<int> IntegerStrartingFrom(int num)

{

    foreach (var item in 
        num.ConsStream(IntegerStrartingFrom(num + 1)))

        yield return item;

}


static IEnumerable<int> Numbers()

{

    return IntegerStrartingFrom(1);

}


static IEnumerable<int> Primes()

{

    return Numbers().Where(n => n.IsPrime());

}



#4. 무한대 수열 다루기(2) - 피보나치 수열 만들기

LISP
(define fibs
  (cons-stream 1
               (cons-stream 1
                            (add-streams (stream-cdr fibs) fibs)))))

(define (add-streams s1 s2)
  (stream-map + s1 s2))

(define (stream-map proc . argstreams)
  (if (null? (car argstreams))
    the-empty-stream
    (cons-stream
      (apply proc (map stream-car argstreams))
      (apply stream-map (cons proc (map stream-cdr argstreams))))))


C#

static IEnumerable<int> Fibs()

{

    foreach (var item in

        ConsStream(1,
        ConsStream(1, Fibs().Plus(Fibs().Tails()))))

        yield return item;

}

 

public static IEnumerable<int> Plus(this IEnumerable<int> ts1, IEnumerable<int> ts2)

{

    return ts1.Map(ts2, (t1, t2) => t1 + t2);

}

 

public static IEnumerable<T> Map<T>(this IEnumerable<T> ts1, IEnumerable<T> ts2, Func<T, T, T> Proc)

{

    foreach (var item in 
        Proc(ts1.Head(), ts2.Head())
        .ConsStream(Map(ts1.Tails(), ts2.Tails(), Proc)))

        yield return item;

}






#5. 공유 변수 - Counter 만들기


LISP

(define counter (make-cnt 0))

(define (make-cnt count)
 (lambda ()
   (set! count (+ count 1))
   count))


C#

public static Func<int> MakeCounter(int initialNumber)
{
    return () => (++initialNumber);
}

static void Main(string[] args)
{
    Func<int> Counter = MakeCounter(10);
    Counter();
    Counter();
    Debug.WriteLine(Counter());

}

위의 결과는 13


Posted by U∙Seung

우리 학교 전산학과 2학년에 Scheme을 배우는 과목이 있다. Scheme은 Functional Programming을 지원하는 언어로 우리가 주로 사용하는 C, JAVA 등의 Procedural programming과 또 다른 세계를 보여준다. 오늘은 비슷한 결과를 낼 수 있는 Javascript로 Scheme 스타일을 좀 따라해봤다.

이 둘 사이에는 여러가지 차이가 있는데 가장 큰 차이는 Functional Language가 Referential transparency를 제공한다는 점에 있다. 자세한 사항을 알아서 찾아보시고;;;

일단 예제 코드를 보자.


var cons = function(a, b) { return function (f) { return f(a, b); } };
var car = function(c) { return c(function(a, b) { return a;}) }
var cdr = function(c) { return c(function(a, b) { return b;})
매우 간단한 세가지 함수를 만들었다.


document.writeln( car(cons("a", "b")) );
document.writeln( car(cdr(cons("a", cons("b", "c")))) );
테스트를 해보면? 결과는 무엇이 나올까?


a
b
대충 짐작 했듯이 cons는 두 값을 pair로 만들어 주고,
car는 첫번째 값을 cdr은 두번째 값을 return하는 매우 심플한 예제다.








var list = function() {
  arg = arguments;
var helper = function(i) {
     if (arg.length == i) return null;
     return cons(arg[i], helper(i+1));
};
return helper(0);
}
var list_ref = function(items, n) {
return (n)? list_ref(cdr(items), n-1) : car(items);
}
var length = function(items) {
return (items)? length(cdr(items))+1 : 0;
}
머리가 아프더라도 마음을 비우고 보면, 가볍게 보인다.


list1 = list(1, 3, list(2, 4, 5), 7, 9);
document.writeln( list_ref(list1, 3) );
document.writeln( length(list_ref(list1, 2)) );
앞에서 만든 cons를 이용해서 list를 만들고, list에 있는 값들을 control하는 함수 들이다.


7
3
결과 위와 같이 7, 3이 출력되게 된다.
왜 그런지는 쉽게 생각할 수 있을 듯 하다.








var $plus = function(a, b) { return a+b; }
var $pair = function(item) {
try { a = car(item); b = cdr(item); } catch(e) { return false; }
return true;
}

var map = function(proc, items) {
return (items)? cons( proc(car(items)), map(proc, cdr(items)) ) : null;
}
var accumulate = function(proc, initial, items) {
return (items)? proc( car(items), accumulate(proc, initial, cdr(items)) ) : initial;
}
var count_leaves = function(t) {
return accumulate($plus, 0,
  map(function(a) { return ($pair(a)) ? count_leaves(a) : 1; }, t)
);
}
마지막은 그래도 재미있는 예제로.. map과 accumulate함수 그리고 이를 이용한 트리의 노드 개수를 세는 함수이다.


document.writeln( count_leaves(list ("+", 5, list("*", list("+", 3, 9) , 4) )) )


7
총 노드가 7개(+, 5, *, +, 3, 9 ,4)  이므로...

더 재미있는 예제들은.. 나중에...
혹은 여기를 참조하세요.
Posted by U∙Seung