pythonic tools - itertools

4 minute read

대망의 itertools. python3 공식 문서의 순서대로 정리했다.

  • Infinite iterator 3개, 아닌 것 12개. combination은 따로 정리해야겠다.

Infinite iterators

count

시작과 step은 알지만 끝을 모를 때 사용할만한 함수

  • python3의 zip은 python2의 izip의 기능을 수행한다.
>>> from itertools import count

>>> for number, letter in zip(count(12, 22), ['a', 'b', 'c', 'd', 'e']):
...    print('{0}:{1}'.format(number, letter))

12:a
34:b
56:c
78:d
100:e

>>> counter = count(start=7)
>>> print(next(counter))
7
>>> print(next(counter))
8

cycle

유한 시퀀스를 무한 시퀀스로 바꿔준다.

>>> from itertools import cycle

>>> currency = cycle(['Dollar', 'Euro', 'Won'])

>>> for i in range(4):
...    print(next(currency))

Dollar
Euro
Won
Dollar

repeat

cycle과 달리 반복 횟수를 지정할 수 있다.

>>> from itertools import repeat
>>> print(list(repeat('Hello, world!', 3)))
['Hello, world!', 'Hello, world!', 'Hello, world!']

finite iterators

accumulate

앞의 인자들의 합 + 해당 자리의 인자를 값으로 한다. 말 그대로 누적

>>> from itertools import accumulate
>>> a = accumulate([1, 2, 3])
>>> list(a)
[1, 3, 6]

chain

iterable이 아닌 애들을 iterable처럼 만들어주고, chain처럼 이어 준다.

  • * 를 통해 flatten 가능
>>> from itertools import chain
>>> a = ('a', 'c', 'd')
>>> b = [[1, 2], [1, 2]]

>>> print(list(chain(a, b)))
['a', 'c', 'd', [1, 2], [1, 2]]
>>> print(list(chain(a, *b)))
['a', 'c', 'd', 1, 2, 1, 2]

chain.from_iterable

iterable들을 chain처럼 이어준다.

>>> c = chain.from_iterable(['abc', 'ddf'])
>>> print(list(c))
['a', 'b', 'c', 'd', 'd', 'f']

>>> c = chain.from_iterable([[1, 2], [1, 2]])
>>> print(list(c))
[1, 2, 1, 2]

compress

두 번째 인자로 첫 번째 인자들 중 iterable을 만들 요소를 선택한다.

>>> from itertools import compress
>>> b = compress('abCD', [0,1,0,1])
>>> list(b)
['b', 'D']

dropwhile

조건식이 충족되지 않는 순간부터 sequence가 시작된다.

>>> from itertools import dropwhile
>>> a = dropwhile(lambda x: x < 3, range(5))
>>> list(a)
[3, 4]

groupby

그룹핑한 결과와 그룹핑 그룹에서 계산한 대푯값을 던지는 iterator.

>>> from itertools import groupby
>>> a = [[1, 2, 3], [1, 1], [2, 2]]
# keyfunc를 이용하여 원하는 계산 값 출력 가능
>>> for key, group in groupby(a, key=sum):
...     print(list(group))
...     print(key)
...
[[1, 2, 3]]
6       # sum([1, 2, 3])
[[1, 1]]
2
[[2, 2]]
4

>>> a = 'abcaaabb'
>>> for key, group in groupby(a, lambda x: x*3):
...     print(list(group))
...     print(key)
...
['a']
aaa
['b']
bbb
['c']
ccc
['a', 'a']
aaa
['b', 'b']
bbb

islice

원하는 부분만 이용할 수 있다.

>>> from itertools import islice

'''
islice(iterable, stop)
islice(iterable, start, stop[, step])
'''
>>> currency = cycle(['Dollar', 'Euro', 'Won'])
>>> for i in islice(currency, 2):
...    print(i)
Dollar
Euro

>>> for i in islice(currency, 0, 7, 2):
...    print(i)
...
Won # 위에서 0, 1을 이미 생성했기 때문에 지금의 0은 'Won'
Euro
Dollar
Won

starmap

iterable들을 function에 넣어 계산한 값을 던져 준다. map과 다르게 계산한 요소가 묶여있을 때(pre-zipped) 사용이 가능하다.

The difference between map() and starmap() parallels the distinction between function(a,b) and function(*c).

>>> from itertools import starmap
>>> a = [(2, 5), (3, 3), (4, 2)]
>>> for i in starmap(lambda x, y: x**y, a):
...     print(i)
...
32
27
16

>>> for i in map(pow, a):
...     print(i)
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: pow expected at least 2 arguments, got 1

takewhile

dropwhile의 반대. 조건문이 true일 때 까지만 iterable이 진행된다. 한번 false 되면 이후 다시 조건문을 충족해도 끝.

>>> from itertools import takewhile
>>> a = takewhile(lambda x: x<5, [1, 3, 5, 7, 2, 1])
>>> for i in a:
...     print(i)
...
1
3

tee

input으로 넣은 iterable을 n개 만큼 복사한다. tee(iterable, n=2)

  1. 원본이 사용되면 복사본도 사용이 불가능
  2. 반대로 복사본 중 하나가 사용되면 원본 사용 불가능 (같이 복사된 다른 복사본은 사용 가능)
>>> from itertools import tee
>>> origin = (x for x in range(10) if x < 6)
>>> copy1, copy2 = tee(origin, 2)

>>> print(list(origin))
[0, 1, 2, 3, 4, 5]
>>> print(list(copy1))
[]
  • 원본이 사용되면 복사본 사용이 불가능해진다.
>>> origin = iter([1, 2, 3])
>>> copy1, copy2, copy3 = tee(origin, 3)

>>> print(list(copy1))
[1, 2, 3]
>>> print(list(origin))
[]
>>> print(list(copy2))
[1, 2, 3]
>>> print(list(copy3))
[1, 2, 3]
  • 반대의 경우, 원본은 사용 불가능하지만, 함께 복사된 다른 복사본들은 사용이 가능하다.

zip_longest

zip과 달리 iterable간의 길이가 다를 경우, 빈 자리를 fillvalue로 정해 준 값이 채운다.

  • 대상 iterable이 무한 시퀀스가 되는 경우에는 islice, takewhile 등을 이용해 유한 시퀀스로 바꿔야 한다.
>>> from itertools import zip_longest
>>> a = zip_longest('abcd', [1, 2], fillvalue='-')
>>> for i in a:
...     print(i)
...
('a', 1)
('b', 2)
('c', '-')
('d', '-')

>>> for i in zip('abcd', [1, 2]):
...     print(i)
...
('a', 1)
('b', 2)

마무리

아직 combination은 끝내지 못했지만, 대부분의 python itertools를 정리했다. 이렇게 정리한 것을 프로젝트할 때 한번 써보면 확실히 기억에 남긴 하는데, 코드 짤 일이 그리 많지는 않으니 조금 아쉽다.

친절하게도 python 공식 문서에는 itertools를 이용한 recipes가 존재한다. 대강 훑어봐도 유용한 애들이 많다. combination 다음에는 얘들을 정리해야겠다.


References

Tags:

Updated:

Leave a comment