Published on

python 리스트 컴프리헨션과 reduce 사용방법

Authors

같은 목적의 코드를 짜더라도 여러가지 방법으로 만들 수 있는게 코딩의 재미인 것 같습니다. 특히 프로그래머스나 백준 문제를 풀면서 정말 각양각색으로 다르게 짠 코드를 보면 신기함, 재미, 좌절, 열정 등 다양한 감정마저 떠오릅니다. 이번에는 리스트 컴프리헨션을 사용하면서 고민했던 다른 방법에 대한 기록과 그 과정에서 Reduce를 어떻게 활용할 수 있는지 기록합니다.

1. 리스트 병합

리스트 병합(중첩)에는 다양한 방법이 있습니다.

# Before
[[1, 2, 3], [4, 5], [6, 7, 8, 9]]

# After
[1, 2, 3, 4, 5, 6, 7, 8, 9]

리스트 병합은 다차원(?)으로 구성되어 있는 구조를 위에 처럼 평탄화 하는 작업입니다.

1.1 리스트 컴프리헨션

가장 쉽고 직관적으로 떠올린 방법은 리스트 컴프리헨션이고 아래와 같습니다.

origin_list = [[1, 2, 3], [4, 5], [6, 7, 8, 9]]

flattened_list = [item for sublist in origin_list for item in sublist]
# 출력: [1, 2, 3, 4, 5, 6, 7, 8, 9]


1.2 itertools.chain() 사용

chain 는 itertools 안에 있는 함수로, 중첩 리스트를 간단하게 평탄화할 수 있습니다.

import itertools

origin_list = [[1, 2, 3], [4, 5], [6, 7, 8, 9]]

flattened_list = list(itertools.chain(*origin_list))
# 출력: [1, 2, 3, 4, 5, 6, 7, 8, 9]

특징이라고 하면 원래 함수의 목적이 여러 이터러블(iterable)을 하나의 이터러블로 이어주는 역할을 합니다. *nested_list를 통해 리스트의 요소를 개별적으로 전달함으로써 리스트를 평탄화합니다.

1.3 sum()

리스트 특성상 + 가 가능하고, 이는 sum 함수를 사용할 수 있음을 의미하기도 합니다. 그러나 이 방법은 다른 방법들에 비해 성능이 다소 떨어질 수 있고, 용도 목적과 조금 어긋난다고 생각합니다.(개인적으로 저는 지양하고자 합니다..)

origin_list = [[1, 2, 3], [4, 5], [6, 7, 8, 9]]

flattened_list = sum(origin_list, [])
 # 출력: [1, 2, 3, 4, 5, 6, 7, 8, 9]

1.4 reduce()

reduce() 함수는 2. 에서 자세하게 다루겠지만, 고급 문법으로 사용되기 딱 좋은 함수입니다. 그 이유는 주어진 함수를 iterable객체(반복 가능한 객체)의 각 요소에 순차적으로 적용하는 함수 이기 때문이고, 그로 인해 함수형 프로그래밍에 최적화된 메서드라고 할 수 있습니다.

from functools import reduce

origin_list = [[1, 2, 3], [4, 5], [6, 7, 8, 9]]

flattened_list = reduce(lambda x, y: x + y, origin_list)
# 출력: [1, 2, 3, 4, 5, 6, 7, 8, 9]

2. reduce() 다양한 활용

reduce() 공식문서의 내용은 다음과 같습니다

Apply function of two arguments cumulatively to the items of iterable, from left to right, so as to reduce the iterable to a single value. For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates ((((1+2)+3)+4)+5). The left argument, x, is the accumulated value and the right argument, y, is the update value from the iterable. If the optional initializer is present, it is placed before the items of the iterable in the calculation, and serves as a default when the iterable is empty. If initializer is not given and iterable contains only one item, the first item is returned.

핵심은 reduce() 함수는 두 개의 인자를 받는 함수를 반복적으로 적용하여 이터러블(iterable)을 하나의 값으로 축소하는 것이 목적입니다.

2.1 딕셔너리 병합

여러 개의 딕셔너리를 하나로 합치는 경우, 특히 동일한 키가 있을 때 값을 결합하거나 덮어쓰는 데 사용할 수 있습니다. 이는 dict자체를 컴프리헨션 문법으로 사용하여도 구현 가능하긴 한 부분 입니다.

여기서는 여러 딕셔너리를 하나의 딕셔너리로 병합하고 있으며, 유의할 점은 동일한 키가 있을 경우 마지막 딕셔너리의 값이 우선 됩니다.

from functools import reduce

dict_list = [
    {'a': 1, 'b': 2},
    {'b': 3, 'c': 4},
    {'c': 5, 'd': 6}
]

merged_dict = reduce(lambda d1, d2: {**d1, **d2}, dict_list)
# 출력: {'a': 1, 'b': 3, 'c': 5, 'd': 6}

첫 번째 딕셔너리와 두 번째 딕셔너리를 병합하고, 그 결과를 세 번째 딕셔너리와 다시 병합하는 식으로 모든 딕셔너리를 하나로 통합합니다. 여러 데이터 소스에서 수집된 정보를 하나의 딕셔너리로 결합해야 할 때 사용하기 좋은 방식인 것 같습니다.

지금은 단일 리스트 이지만, 리스트가 여러개라면 더 유용하게 작동할 것 입니다. {**d1, **d2}는 두 딕셔너리를 병합하는 Python의 문법입니다.

2.2 문자열

당연히 문자열도 이터러블 안에 존재한다면 아래처럼 활용할 수 있고, 포매팅이 필요한 부분에서 사용할 수도 있습니다.

from functools import reduce

log_messages = [
    "[INFO] 시작",
    "[WARNING] 위험감지됨!",
    "[ERROR] 에러발생~"
]

log_output = reduce(lambda x, y: f"{x}\n{y}", log_messages)
# 출력:
# [INFO] 시작
# [WARNING] 위험감지됨!
# [ERROR] 에러발생

2.3 다중 조건 필터링

reduce()의 장점을 활용한 부분입니다.

5로 나누어 떨어지고 10보다 큰 값만 필터링 하고 싶을때 아래 코드입니다.

from functools import reduce

data = [15, 3, 20, 7, 10, 8, 30]


filtered_data = reduce(lambda x, y: x + [y] if y % 5 == 0 and y > 10 else x, data, [])
# 출력: [15, 20, 30]

x는 현재까지 필터링된 리스트이고, y는 현재 처리 중인 요소입니다. 각 요소를 검사하여 조건에 맞으면 결과 리스트에 추가하고, 그렇지 않으면 현재 결과 리스트를 그대로 유지합니다. 이 경우, 조건에 맞는 데이터만 추출할 수 있습니다. 복잡한 데이터 필터링이 필요한 경우 유용합니다.

2.4 딕셔너리 키값에 대한 연산

특정 키에 대해 값을 더하는 함수 생성이 필요한 경우 아래처럼 사용할 수 있습니다.

from functools import reduce


def merge_dicts_on_key(key):
    return lambda dicts: reduce(lambda d1, d2: {**d1, **{key: d1.get(key, 0) + d2.get(key, 0)}}, dicts)

dict_list = [{'a': 1, 'b': 2}, {'a': 2, 'b': 3}, {'a': 3, 'b': 4}]
sum_dicts_on_a = merge_dicts_on_key('a')
result = sum_dicts_on_a(dict_list)
# 출력: {'a': 6, 'b': 2}

reduce 문서

Python 을 처음 접하면서 메인으로 사용하는 만큼 문법적인 특성은 많이 파악했다고 생각했지만, 함수형 프로그래밍을 고려한다면 아직 한~참 멀은 것 같습니다.

hongreat 블로그의 글을 봐주셔서 감사합니다!