본문 바로가기

흔한말 (collection)/Python

python map 함수의 비정함

300x250

프로그래밍을 공부하다보면 생각보다 매 순간이 리스트를 다루는 일의 연속이라는 것을 깨닫게 됩니다.
왜냐?
우리는 컴퓨터로 여러가지의 정보를 한꺼번에 처리하는 것으로써 다루는 도구의 효율을 높이기 때문입니다.

여러가지의 개체를 (예를 들면 회원가입된 여러 사람들의 이메일 주소 목록이라던지, 아니면 모델링 프로그램으로 작성된 수 백개의 기둥형상이라던지) 한꺼번에 계산해서 결국엔 처리결과 라는 새로운 리스트를 만들어 냅니다.

즉,

  1. [원본리스트] 에
  2. [어떤 규칙]을 적용해서
  3. [새로운 리스트]를 만들어 낸다
  4. 라는 것이죠.

기존방식으로 리스트 처리하기

위에 써진 대로 간단한 코드를 한번 만들어 볼까요?
먼저 숫자 5개가 있는 리스트를 준비합니다.

원본리스트 = [1,2,3,4,5]

그 다음에는 각 원소에다 어떤 규칙을 적용해 볼까요?
원소를 제곱한 다음 10을 더하는 규칙을 한번 써보죠.

"a**2 + 10"
'a**2 + 10'

이 규칙을 원본리스트의 모든 원소에 적용해 봅시다.

새로운리스트 = []
for a in 원본리스트:
    a처리결과 = a**2 + 10
    새로운리스트.append(a처리결과)

print(새로운리스트)
[11, 14, 19, 26, 35]

5개의 원소가 규칙대로 변환되어 새로운 리스트가 되었습니다.
이 과정을 간략화해보면
리스트의 각 원소마다 규칙 을 대입하고, 변화된 결과들을 모아 새로운 리스트로 만들죠.

패러다임의 전환

하던 일 자체를 함수로?

위의 과정을 잘 생각해 보면, 굵은 글씨를 input 값으로 받아 기울어진 글씨를 반환하는 하나의 함수로 생각해 볼 수 있습니다.
다시 말하자면,
어떤 함수 f가 있고, 수학적으로 표시하면
f(규칙, 리스트) = 새로운리스트
이런 함수가 생기는 것이죠

기명함수를 재료로 하는 map 함수의 활용

그런 함수 f를 직접 만들어 볼까요? 사실 이미 있습니다. 바로 map이라는 함수이지요.
일단은 규칙을 만들어볼까요?

def 규칙(숫자):
    return 숫자**2 + 10

규칙을 만들었으면 map 함수에 들어갈 재료가 다 완성된 것입니다. 코드를 짜볼까요?

새로운리스트 = list(map(규칙, 원본리스트))
print(새로운리스트)
[11, 14, 19, 26, 35]

익명함수를 재료로 하는 map 함수의 활용

간단한 규칙인 경우에는 매번 기명함수로 만들어 이름을 지어주는 수고를 줄이고 싶을 때도 많습니다.
그럴때 사용할 수 있는 문법이 바로 lambda 인데요.
아까의 규칙을 람다로 표현해보면 다음과 같습니다.

lambda a: a**2 + 10
<function __main__.<lambda>(a)>

그러니 map함수에 들어가는 재료 두개 중 하나인 함수를, 익명함수인 람다로 대체해 버려도 코드의 작동에는 아무 문제가 없겠죠?

새로운리스트 = list(map(lambda a: a**2 + 10, 원본리스트))
print(새로운리스트)
[11, 14, 19, 26, 35]

잘 되죠? 코드도 컴팩트해지고, 나중에 설명하겠지만, 원소들을 미리 계산하지않고
Generator를 통해 지연연산을 구현하기 때문에 map함수를 사용한 부분의 코드처리에 부하도 줄일 수 있습니다.

map 함수 DIY 해보기

참고로 map 함수를 직접 만들어보고 싶은 분들은 아래처럼 해보시면 됩니다.
(다음 포스트에서 아래 코드의 의미를 다시 설명할 테니 오늘은 똑같이 써보기만 하세요.)

def _map(f, iter):
    for a in iter:
        yield f(a)

직접만든 _map 함수를 써서 예제 코드를 다시 만들어볼까요?

새로운리스트 = list(_map(lambda a: a**2 + 10, 원본리스트))
print(새로운리스트)
[11, 14, 19, 26, 35]

아주 잘 되는 군요!!!
원본리스트의 개수를 좀 늘려볼까요?

원본리스트2 = list(range(1,100))
print(원본리스트2)
[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, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
새로운리스트2 = list(_map(lambda a: a**2 + 10, 원본리스트2))
print(새로운리스트2)
[11, 14, 19, 26, 35, 46, 59, 74, 91, 110, 131, 154, 179, 206, 235, 266, 299, 334, 371, 410, 451, 494, 539, 586, 635, 686, 739, 794, 851, 910, 971, 1034, 1099, 1166, 1235, 1306, 1379, 1454, 1531, 1610, 1691, 1774, 1859, 1946, 2035, 2126, 2219, 2314, 2411, 2510, 2611, 2714, 2819, 2926, 3035, 3146, 3259, 3374, 3491, 3610, 3731, 3854, 3979, 4106, 4235, 4366, 4499, 4634, 4771, 4910, 5051, 5194, 5339, 5486, 5635, 5786, 5939, 6094, 6251, 6410, 6571, 6734, 6899, 7066, 7235, 7406, 7579, 7754, 7931, 8110, 8291, 8474, 8659, 8846, 9035, 9226, 9419, 9614, 9811]

잘 하셨습니다!!!

결론

지금 까지 해온 과정의 의미를 복습해 볼까요?
프로그래밍의 요체는 내가 하는 행위를 다시 숙고해서, 반복 사용에 더욱더 편리해지도록 추상화를 시키는 것입니다.

즉, 리스트를 내 입맛대로 바꾼다는 context였던 for 문에서,
리스트와 규칙을 재료로 받아 새로운 리스트를 반환하는 하나의 함수를 만들어서 재사용성을 한 껏 높이게 된 것이죠.
우리가 만들어내는 규칙이 사실상 함수이기 때문에,
map 함수는 자신이 함수임에도, 다른 함수를 재료로 받아서 작동하는 Higher-Order Functions(고계함수) 라고 불립니다.
고계함수와 1급객체는 이어서 새 글로 포스팅 하겠습니다.

map 함수는 감성적으로 바라보면 비정할 정도로 예외를 두지않고 모든 원소에 동일한 기준을 부여해버리는 함수입니다.
우리가 코드를 짜는 상황을 생각해보면 다른 경우도 많죠?
원본리스트 중 일부만 규칙을 적용한다던지, 혹은 일정비율은 A규칙을 적용하고, 나머지는 B규칙을 적용한다던지 하는 일이 많습니다.

다분히 전체주의적인 map 함수에 맞서서, 다음 포스팅에서는 적용 대상의 예외를 만들어주는 filter 함수를 포스팅 하겠습니다.

반응형