2010년 12월 18일 토요일

map, reduce, filter 파이썬 고차함수(high-order function)

파이썬의 유명해진 것 중의 하나가 이 high-order function입니다

구글의 유명한 알고리즘중에 map-reduce 알고리즘이라는 것이 있는데

대용량 분산처리시스템에 쓰인것이다. 간단하게 설명하자면,

각각의 work을 쪼개서 여러시스템에 나누어주고 (map)

다시 그 결과를 합쳐서 (reduce) 반환해주는 것입니다

정확하게 이 high-order function은 파이썬에서 나온 것은 아닙니다

이전에 haskell 이라든지 , list, scheme, ML 등의 함수형언어(Functional language)에서 나온 개념인 것이죠

어쨌든, 이 함수형 패키지는 사람의 생각을 그대로 언어로 바꾸어주게됩니다

이게 무슨소리냐 하면

예를 들면,'빨강색을 가지고 있는 책' 또는 '가격이 1500만원 이상인 차량'

'키가 180이상인 성인남자 ' 이런것이 사람의 생각입니다

그럼 이것을 프로그램으로 짜려고 하면 어떻게 하는가..

전통적인 c나 자바언어라면 이런식으로 합니다

'빨강색을 가지고 있는 책' 의 예를 들어보겠습니다

for i=0; i < booklist.length() ; i++ ) {

   if (booklist[i].color == '빨강' ) 
       result.add(booklist[i]);

}

이런식으로 나와서 result를 반환할 것입니다

여기서 중요한 개념은 집합(list)입니다

(정확한 집합의 영어번역은 set인데, 여기서는 list를 집합이라고 표기하도록 하겠습니다)

중학교, 고등학교 1학기에 매번 나오던 개념이고. 수학적 개념으로의 집합이라고 보면 되겠습니다.

일단 수학적인 개념으로 위의 빨강차예제를 다시써보면 다음과 같습니다

Y = { x : x.색깔 = '빨강', 책리스트 } 이런식으로 수학적으로 나타낼 수가 있고, 이는 바로 다음과 같이

파이썬 high-order function(고차함수)는 그냥 이렇게 풀어쓰면 됩니다

result = filter ( lamdba( book , book.color=='빨강'), booklist)


사람의 생각이 수학적인 표현으로 옮겨지고 이를 다시 Program Language로 변환을 하게 되는데

수학 표현에서 PL로 옮겨질때는 하나의 개념이 더 추가가 됩니다

이것은 address(주소)입다. 기계는 메모리라는 것이 있기에 여기서 주소라는 개념이 추가가 되는데

즉 프로그래밍은 address의 개념을 입히는 동작인 것입니다

C언어의 Pointer가 이 address의 개념을 표현하는 것이고

하지만, 이 high-order function은 이 address의 개념을 무시합니다. 알필요가 없다는 것이죠.

그래서 사람의 생각에 address를 입히는 과정이 생략되고, 좀더 사람과 같이 생각을 할 수 있게 됩니다.

그러니깐 일단 써보면, 왜 편한지를 몸으로 알 수 있을 것입니다.

python yield

Generator (발생자) 는 icon이란 언어에서 나온것입니다.

여러가지 문서를 보다보면 제네레이터 이터레이터 이해할 수 없는 말들만 돌아 다닙니다

근본적으로 제네레이터 이터레이터는 무엇을 하기 위한 장치인가를 생각해보면

파이썬은 모든것을 list-sequence(수열)로 표현이 됩니다. 또 수학의 모든 수식들을 표현하기 위한 장치가 많이있습니다.

이것은 수학의 수열(sequence)의 정의와 같은 것입다 (피보나치등등)

이러한 리스트가 무한이면 어떻게 대처해야 하는가.. 에 대한 답이 제네레이터(발생자) 이터레이터(반복자)
yield(양보?) 이런것들있습니다

한글로 정확히 번역이 되지 않기에 그냥 제네레이터 이터레이터로 씁니다.

 def gen_num(N):
    for i in range(N):
        yield i

for x in gen_num(100) :
    print x,

----------------------------------------------------
0 1 2 3 4 5 6 7 8 9


사실 위의 예는 잘못된 것이다. 아래가 좀더 generator yield를 원래 의미대로 사용한 것입니다.

 def gen_num():
    while 1:
        yield i
        i++

a = gen_num()
for x in range(10) :
    print a.next(),

----------------------------------------------------
0 1 2 3 4 5 6 7 8 9


for문의 list에 gen_num으로 얻은 결과나, range(10)으로 대치를 하거나 결과는 같을 것입니다

그러면 왜 Generator방식을 사용하는가?

List로 통째로 넘기는 방식은 데이터량이 클 경우 모든 작업이 완전히 수행한 다음에 넘길수가 있습니다.
즉, 하나의 작업이 완전히 끝난후에 제어권을 넘기는 것이죠

즉, 소규모작업에서는 큰 문제가 없지만, 대형의 작업등에는 모든 작업이 끝나기를 기다릴 수가 없는 경우가 있습니다

따라서 이때는 중간 중간에 수행을 할 수 있게 yield라는 구문이 들어가게 되는 것입니다.

기존의 함수를 그대로 유지하면서 제어권만 넘기는 방식이다.

함수라는 것은 일단 실행이 되면 모든 것이 수행이 되고 끝나는 순간에 함수 내부의 모든 변수들을 반환하고

본체로 제어권을 넘겨주게 된다. 하지만, 이러한 함수가 빈번하게 발생을 한다면, 이렇게 stack방식으로 계속 반환을 한다면

이 비용도 만만치가 않게 되므로

그래서 위와 같은 Generator 방식을 사용할 수 있게 됩니다.

tree walker라든지, parser등에서 사용하면 유용할 것으로 생각됩니다.