Python으로 데이터들을 재조합하고 파싱 할 때 차원을 변경해야 하는 일이 종종 생긴다. 2차원 리스트를 1차원으로 변경시키는 방법은 대표적으로 itertools라는 라이브러리를 사용하는 방식, list comprehension, 좀 독특한 방법이지만 sum을 이용한 방법이 있다.
Itertools
itertools를 이용한 방법은 두 가지가 있다. *(Asterisk)을 이용한 방법과 from_iterable 메소드를 사용하는 방법이다.
우선 *(Asterisk)을 이용한 방법이다. Asterisk는 조금 생소할 수 있지만 Pythonic한 코드를 작성하도록 도와줄 수 있다. Asterisk의 사용은 아래와 같은 상황에서 사용할 수 있다.
- 곱셈 및 거듭제곱 연산으로 사용
- 리스트형 컨테이너 타입의 데이터를 반복 확장
- 가변 인자 (Variadic Arguments)를 사용
- 컨테이너 타입의 데이터를 Unpacking 수행
위 상황 중 "컨테이너 타입의 데이터를 Unpacking 수행"하는 경우를 이용한다. 아래는 소스코드이다.
import itertools
big_lst = [lst for i in range(10000)]
lst1 = list(itertools.chain(*big_lst))
그렇게 복잡하지는 않지만 *를 사용한다는 것이 부담될 수 있다. 그래서 더 편한 from_iterable를 사용하는 아래 방법이 있다.
import itertools
big_lst = [lst for i in range(10000)]
lst2 = list(itertools.chain.from_iterable(big_lst))
List Comprehension
List Comprehension을 이용한 방법은 리스트 안의 요소를 모두 꺼내 다시 1차원 리스트로 묶는 가장 머리로는 와닿는 방법이다. 아래는 소스코드이다.
big_lst = [lst for i in range(10000)]
lst3 = [y for x in big_lst for y in x]
Sum
sum을 이용한 방법은 생각지 못했는데 별로 빠르지는 않다. 아래는 소스코드이다.
big_lst = [lst for i in range(10000)]
lst4 = sum(big_lst, [])
이제 여러 방법들을 알게 되었으니 수행 속도를 비교해볼 필요가 있다.
import time
import itertools
big_lst = [lst for i in range(10000)]
# 1 iter1 => 2등
stime = time.time()
lst1 = list(itertools.chain(*big_lst))
print(time.time()-stime) # 0.039765119552612305
# 2 iter2 => 1등 (2등과 큰 차이는 없음)
stime = time.time()
lst2 = list(itertools.chain.from_iterable(big_lst))
print(time.time()-stime) # 0.03971099853515625
# 3 list comprehension => 3등
stime = time.time()
lst3 = [y for x in big_lst for y in x]
print(time.time()-stime) # 0.09392786026000977
# 4 sum 아주 느림 => 4등 꼴등(overflow 되는 느낌)
stime = time.time()
lst4 = sum(big_lst, [])
print(time.time()-stime) # 138.0987298488617
sum을 이용한 방식은 overflow가 발생했나 싶을 정도로 과하게 오래 걸렸다. 물론 sum을 이용할 때 big_lst의 크기를 상식선으로 줄이면 쓸만하지만 다른 방법에 비해 월등히 느리며, list comprehension보다 직관적이지도 않은 것 같아 굳이 쓸 필요 있나 싶다.
itertools 라이브러리를 이용한 방법은 두 가지 방법 모두 비슷한 속도가 나왔다. 그래도 확실하게 순위를 매겨놓고 싶어 한 번 더 테스트를 진행하였다. 소스코드는 아래와 같다.
import time
import itertools
lst = [i for i in range(1000)]
# 1 iter1
t1 = 0
for temp in range(10000):
big_lst = [lst for i in range(10000)]
stime = time.time()
lst1 = list(itertools.chain(*big_lst))
t1 = t1 + time.time() - stime
print(t1) # 142.61301279067993
# 2 iter2
t2 = 0
for temp in range(10000):
big_lst = [lst for i in range(10000)]
stime = time.time()
lst2 = list(itertools.chain.from_iterable(big_lst))
t2 = t2 + time.time() - stime
print(t2) # 140.23261642456055
그렇게 큰 수행 속도 차이는 없지만 2차원 리스트를 1차원으로 변환할 때 조금이나마 더 빠르고 가독성도 더 좋은 from_iterable을 사용하는 것을 추천한다.
참고 자료
https://blog.winterjung.dev/2017/04/21/list-of-lists-to-flatten
https://mingrammer.com/understanding-the-asterisk-of-python/
https://docs.python.org/3/library/functions.html#sum