티스토리 뷰

반응형

reduce 사용하기

reduce 함수는 리스트의 원소들을 중첩하여 하나의 값으로 만드는 과정을 처리하는 함수이다. 이 함수는 functools 모듈내에 정의되어 있으므로 사용을 위해서는 해당 함수를 import 명령으로 반입해야 한다.


from functools import reduce

reduce 함수는 세 개의 인자를 받는데,

  1. 첫 번째 인자는 두 개의 인자를 받아서 1개의 값을 리턴하는 함수이다.
  2. 두번째 인자는 리스트이다.
  3. 세번째 인자는 초기값이다. 주어지지 않은 경우, 리스트의 첫 번째 원소를 사용한다.

reduce 함수의 동작 원리에 대해서 조금 짚고 넘어가자. 사실 많은 예를 접해서 이 함수가 어떻게 돌아가는지를 보고 나면 감을 잡을 수 있는 부분이기도 하다. reduce 함수는 특정한 결과값을 준비하고, 리스트의 각 원소를 순차적으로 준비된 결과값과 함께 축약 함수에 집어 넣는다. 그리하여 계산된 값은 새로운 결과값이 되고 다음번에는 새로운 결과값과 그 다음 원소를 같은 함수에 집어 넣는다. 즉, 주어진 수식(함수)를 반복적으로 호출하면서 2개의 값을 1개로 합쳐나가는 과정을 반복하여 리스트를 한개의 값으로 축약한다. (그래서 이름이 reduce이다.)

보다 빠른 이해를 돕기 위해서 리스트의 원소의 합을 구하는 sum 함수를 reduce를 써서 구현해보도록 하자. 그전에 리스트의 원소의 합을 구하는 과정을 다음과 같이 풀어썼다고 가정하고 살펴본다.


def add(a, b):
	return a + b
	
a = [1, 2, 3, 4, 5]
acc = 0  # 누산할 값

for i in a:
    acc = add(acc, i)

print(acc)

  1. 리듀스에 쓸 함수는 늘 두 개의 인자를 받아서 하나의 값으로 줄이는 형태의 함수이다. 해당 리턴값은 다음 차례의 반복에서 첫 번째 인자로 들어가게 된다.
  2. 계산의 중간과정을 저장하는 임시 변수를 하나 사용한다. 이 변수는 보통 초기값을 주는 것이 필요하다.
  3. 리스트의 각 원소에 대해서 중간결과값과 리스트의 원소가 축약작업을 수행하는 함수에 전달되고, 그 결과가 다시 중간 결과로 저장된다.
  4. 모든 과정을 거치고 더 이상 처리할 원소가 없다면, 중간 누적값이 최종 결과가 된다.

reduce를 직접 작성하기

그렇다면 거꾸로 우리는 reduce 함수를 직접 작성해볼 수도 있을 것이다. 물론 이 예는 이해를 돕기 위한 것이며, 굳이 멀쩡히 잘 돌아가는 함수를 재정의해서 쓸 필요는 없다.


def myReduce(f, array, initial):
    acc = initial  # 임시 결과는 초기값이며
    for x in array:
        acc = f(acc, x) # 임시 결과가 각 원소를 처리함수에 넣고 축약
    return acc # 최종결과

하지만 실질적으로 우리에게 도움이 되는 것은 이 reduce를 어떻게 쓰느냐는 것이다. 리스트와 관련된 거의 대부분의 함수는 (머리를 조금 더 써야 할 수는 있지만) 사실 이 reduce로 작성하는 것이 가능하다.

먼저 합계를 구하는 sum을 리듀스로 구현하는 방법을 보자.


def mySum(array):
    return reduce(lambda x, y: x + y, array, 0)

위에서 본 바와 같이 합계는 0부터 시작해서 각 원소를 더한 누계를 쌓아나가는 방식으로 계산하면 된다. 그런데 여기서 lambda(람다)가 등장했다. 더하기나, 곱하기, 대소 비교 등의 간단한 수식을 이용해서 함수를 접어 나갈때, 이러한 간단한 식을 매번 미리 함수로 만드는 것은 너무 번거로운 일이다. (맨위의 mySum에서는 add라는 함수를 미리 만들어서 썼지만, 그냥 단순히 덧셈하는 계산을 함수로 만들기는 귀찮지 않은가?)

lambda는 간단한 표현식을 함수처럼 다룰 수 있게 해주는 표현이다. 굳이 말하자면 이름이 없는 익명함수인 셈인데, 요즘은 자바스크립트니 하는 언어들에서 클로져라는 개념으로 많이 알려져 있는 기법이다. lambda 표현식을 사용하는 문법은 다음과 같다.


lambda 파라미터, 파라미터.. : 표현식

람다식에서 사용되는 익명함수의 본체는 단항 혹은 다항으로 표현되는 간단한 식이다. 보통 이런 식을 표현식(expression)이라고 한다. 파라미터의 개수는 1개, 2개, 3개...등으로 제한이 없다. 다만 람다식을 사용하는 시점에는 파라미터 개수가 맞아야 한다. 두 수를 더하는 식은 lambda x, y: x + y로 작성할 수 있으며, 세 수를 더하는 식은 lambda x, y, z: x + y + z로 만들 수 있다.

같은 방식으로 두 수를 곱하는 람다식을 이용하면 리스트의 원소의 누적 곱을 계산할 수 있다.


def myProduct(array):
    return reduce(lambda x, y: x * y, array, 1)

사실 max, min, sum, len 등등이 다 리듀스로 표현가능하다.


def myMax(array):
    return reduce(lambda x, y: x if x > y else y, array)

def myMin(array):
    return reduce(lambda x, y: x if x < y else y, array)

def myLen(array):
    return reduce(lambda x, y: x + 1, array, 0)

심지어 map, filter도 리듀스로 표현할 수 있다.


def myMap(f, array):
    return reduce(lambda x, y: x + [f(y)], array, [])

filter의 경우에는 직접 한번 리듀스로 구현해보는 것도 좋겠다.


리스트는 파이썬에서 가장 중요하고, 가장 유용하며 또 그만큼 많이 쓰이는 자료구조이고, 리듀스는 맵과 필터와 더불어 리스트 구조를 사용함에 있어서 가장 중요한 함수중 하나이다. 특히 맵, 필터, 리듀스는 굳이 파이썬이 아니더라도 요즘 인기를 얻어가고 있는 함수형 언어에서 필수적으로 익혀야하는 교양과 같은 개념이라 볼 수 있다.


원래 리듀스는 파이썬의 기본함수에 속했으나, 파이썬의 창시자이자 파이썬 프로젝트 전체를 관장하고 있는 귀도 반 로썸은 "파이썬은 함수형 언어가 아니다"라며 reduce를 기본함수에서 빼버렸다(!?!?!) 개인적으로 이 처사는 좀 이해하기 힘든데, 어쨌거나 기본함수에서 빠져버렸다고 해서 덜 중요한 것은 아니니, 꼭 알아두는 것이 좋다.


다음 번에는 리듀스와 더불어 중요하다고 몇 번이나 언급한 맵과 필터에 대해서 좀 더 자세히 알아보도록 하겠다.

반응형

'파이썬 how to' 카테고리의 다른 글

파이썬 사전 사용법  (0) 2017.05.19
파이썬 튜플 기초 사용법  (0) 2017.05.15
iPython 설치하는 방법  (3) 2017.05.09
맵과 필터 그리고 리스트 축약  (0) 2017.05.02
파이썬 리스트의 기본 사용법  (0) 2017.04.25
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함