메인
투자 노트

itertools 모듈 - 이터레이터를 다룰때 필요한걸 찾아가

컨테이너로 바꿔서 메모리를 차지하는 짓을 안한 상태에서 이터레이터들을 다룰수 있는 도구들을 가지고 있다. 컨테이너가 아니기때문에 당연히 조작에 제한사항들이 있다 ex) 슬라이싱에서 전체('[ : ]')지정 이 불가능하다 (이터레이터의 끝은 StopIteration 예외를 raise하는 시점이지 공간적으로 정의되어 있는것이 아니기 때문이다)
파이썬 인터프리터에 import itertools를 하고 help(itertools)를 입력하면 document를 볼 수 있다
먼저 itertools를 import하고 사용할 제너레이터를 아무렇게 만들었다.
이 제너레이터는 doc string에서 설명한대로의 이터레이터를 만들어낸다
import itertools def jump(interval: int, count: int): """ 0부터 + interval 하면서 count번 이터레이션 한다 step인자를 넣은 range랑 유사하다(다르다) """ current = 0 for _ in range(count): current += interval yield current
Python
복사
itertools 모듈의 함수가 이터레이터를 반환할때 그걸 그대로 다시 인자로 사용해서 다른 메서드를 내포형으로 계속 사용할 수 있다

이터레이터 연결

chain - 이터레이터간 + 연산 → (이어붙이기)

chain(iterater,...)
Python
복사
it = itertools.chain(jump(3, 2), jump(-10, 4), range(3)) print(list(it)) --------------------------------------- [3, 6, -10, -20, -30, -40, 0, 1, 2]
Python
복사
풀어내서 하나가 된다

repeat - 하나의 값을 X번 반복하는 이터레이터 반환

repeat(arg,count)
Python
복사
count_4 = itertools.repeat(None, 4) # 반복횟수에 유용 inner_list_gen = itertools.repeat([1, 2, 3], 4) # 다차원 이터러블 생성 가능 hello = itertools.repeat("안녕", 4) # 안녕 print(list(count_4)) print(list(inner_list_gen)) print(list(hello))
Python
복사
[None, None, None, None] [[1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3]] ['안녕', '안녕', '안녕', '안녕']
Python
복사
itertools 모듈이 만드는것도 이터레이터이기 때문에 아레와같은 내포형 호출이 가능하다
inner_list_gen = itertools.repeat(itertools.repeat([1, 2, 3], 4), 2) ---------------------- [repeat([1, 2, 3], 4), repeat([1, 2, 3], 4)]
Python
복사
리스트나 튜플로 다차원상태를 바로 만들수는 없지만 다차원 이터레이터를 통한 루프가 필요한 경우 유용해 보인다

cycle - 입력된 이터레이터를 무한반복하는 이터레이터 반환

cycle(iterable)
Python
복사
it = itertools.cycle(range(10)) for i in it: print(i) ---------------- 0 ~ 9 를 무한 출력
Python
복사
아레와 같이 활용할 수 있다 - 루프를 돌면서 next()로 리스트를 만든다는 발상!!
it = itertools.cycle(jump(5, 4)) # 5씩 4회 건너뛰는 이터레이터를 무한 반복하는 이터레이터 lst = [next(it) for _ in range(10)] # 루프를 돌면서 next()로 리스트를 만든다는 발상!! print(lst) ---------------- [5, 10, 15, 20, 5, 10, 15, 20, 5, 10]
Python
복사

tee - 입력된 이터레이터 X개를 이터레이터에 넣는다.

tee(iterable,count)
Python
복사
it1, it2, it3 = itertools.tee(jump(3, 5), 3) print(list(it1), list(it2), list(it3)) ----------------------------- [3, 6, 9, 12, 15] [3, 6, 9, 12, 15] [3, 6, 9, 12, 15]
Python
복사
아레 모습이 이터레이터화 된다고 생각해라
[ [3, 6, 9, 12, 15], [3, 6, 9, 12, 15], [3, 6, 9, 12, 15] ]
Python
복사
사용 - 위의 예시처럼 언패킹하기-이터레이터 복제로 사용, 다중 for문이 필요할때 사용
주의 - 각 이터레이터를 소비하는 속도가 같지 않으면 덜 처리된게 남아서 메모리 사용량이 늘어난다

zip_longest - zip의 변종,가장 긴 이터레이터를 기준으로 이터레이션 하며 부족한 값은 fillvalue로 채운다

zip_longest(iterable,... , fillvalue = )
Python
복사
it = itertools.zip_longest(range(10), jump(20, 6), fillvalue=True) print(list(it)) ------------------------------ [(0, 20), (1, 40), (2, 60), (3, 80), (4, 100), (5, 120), (6, True), (7, True), (8, True), (9, True)]
Python
복사
실체화 시키면 튜플의 반복형태가 됨을 명심하고 있자

이터레이터를 필터링

islice - start,end,step을 지정해서 슬라이싱된 이터레이터를 반환한다

음수 매개변수를 사용할 수 없으며 키워드 인자도 사용할 수 없다
islice(iterable, start, end, step) # 매개변수는 모두 양수여야 한다 - 음수문법 사용불가
Python
복사
values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] first_five = itertools.islice(values, 5) print('앞에서 다섯 개:', list(first_five)) # 0 ~ 9 범위를 1 ~ 8 로 하고 step를 2로 하였다 middle_odds = itertools.islice(values, 2, 8, 2) print('중간의 홀수들:', list(middle_odds)) -------------------------------------- 앞에서 다섯 개: [1, 2, 3, 4, 5] 중간의 홀수들: [3, 5, 7]
Python
복사
it = itertools.islice(jump(4, 10), 0, 10, 3) print(list(it)) ------------------------- [4, 16, 28, 40]
Python
복사
전체 범위라 하여도 start,end인자가 필수이고 키워드로 인자지정이 불가능하다는 점이 특이하다

takewhile - predicate가 False를 반환하기 전까지만 이터레이션 한다 (True를 반환할때만 이터레이션 한다)

첫 값이 False를 반환하면 아무것도 이터레이션 하지 않는다
False를 받기 전까지 이터레이션 한다
_{ 뒤에 True가 있어도 False를 반환하면 종료되기 때문에 필터랑 다르다 )_
predicate → 술어,판단자 로 번역되며 bool 자료형을 반환하는 함수라고 보면 된다.
takewhile(collable,iterable) :::::::: takewhile(bool반환함수,iterable)
Python
복사
it = itertools.takewhile(lambda x: x <= 10, jump(3, 5)) print(list(it)) ------------------------ [3, 6, 9]
Python
복사
it = itertools.takewhile( lambda x: sum(x) <= 20, # predicate는 인자 하나만 받는다 -> 언패킹해서 못받는다 itertools.zip_longest(jump(3, 5), range(3, 13), fillvalue=10), )
Python
복사
받아서 언패킹하고 복잡한 로직을 거치는 등의 기능은 함수를 하나 정의하는게 좋다
위의 예시는 zip_longest를 iterable인자로 사용하는 예시이다
zip_longest의 원래 모습 [(3, 3), (6, 4), (9, 5), (12, 6), (15, 7), (0, 8), (0, 9), (0, 10), (0, 11), (0, 12)] --OUTPUT-- [(3, 3), (6, 4), (9, 5), (12, 6)]
Python
복사
OUTPUT을 보면 (12,6) 이후 (15,7)에서 합이 20이 넘어서 (12,6)까지만 이터레이션 된다는 것을 알 수 있다. (15,7)뒤에 sum(x) ≤ 20 == True 인것을이 많이 있지만 알지 못한 채 종료된다.
사용 - 복수의 이터레이터를 zip_logest로 병렬적으로 이터레이션 하면서 next()로 인출된 각 이터레이션 값들의 관계에 따라서 이터레이션을 할지 말지 정하는 등의 로직에 사용할 수 있겠다

dropwhile - takewhile과 반대다 predicate가 False를 반환할때부터 이터레이션 한다 (True를 반환하는것은 다 건너뛴다)

첫 값이 False를 반환하면 처음부터 끝까지 이터레이션 한다
False를 받는 순간부터 이터레이션 한다
dropwhile(collable,iterable) :::::::: dropwhile(bool반환함수,iterable)
Python
복사
values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] less_than_seven = lambda x: x < 7 it = itertools.dropwhile(less_than_seven, values) print(list(it)) ----------------- [7,8,9,10]
Python
복사
values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] predicate = lambda x: x < 5 < 8 it = itertools.dropwhile(predicate, values) print(list(it)) ------------------- [5, 6, 7, 8, 9, 10]
Python
복사

takewhile + dropwhile = everything

False를 받기 전까지 이터레이션 한다 + False를 받는 순간부터 이터레이션 한다
values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] predicate = lambda x: x < 5 < 8 it1 = itertools.takewhile(predicate, values) it2 = itertools.dropwhile(predicate, values) print(list(it1), list(it2)) ---------------------------- [1, 2, 3, 4] [5, 6, 7, 8, 9, 10]
Python
복사

filterfalse - filter내장함수와는 반대로 false를 반환하는 모든 원소들을 이터레이션한다

filter내장함수로 이터레이터를 처리할 수 있다는 사실 - 반환값을 만드는 내장함수는 대부분 컨테이너가 아니어도 값을 처리-반환할 수 있다 filter의 경우 filter객체를 반환한다 이렇듯 반환값이 이터레이터인것들이 있다 map,reduce등이 컨테이너를 바로 반환하지 않는다는것을 생각해라, dict.keys(),dict.items()등의 함수도 마찬가지다.
filterfalse(callable, iterable)
Python
복사
컨테이너로 만들어진 이터레이터는 루프로 컨테이너를 참조할것이다
values = range(1, 11) evens = lambda x: x % 2 == 0 filter_result = filter(evens, values) print("Filter:", list(filter_result)) filter_false_result = itertools.filterfalse(evens, values) print("Filter false:", list(filter_false_result)) ------------------------------------------------------ Filter: [2, 4, 6, 8, 10] Filter false: [1, 3, 5, 7, 9] ------------------------------------------------- list로 변환 전 Filter: <filter object at 0x042CE1F0> Filter false: <itertools.filterfalse object at 0x042CE490>
Python
복사
filter + filterfalse = everything
filter를 하고 그것을 뺴거나 반전시켜야 할때 반드시 filterfalse를 떠올리자

원소들이 조합되는 이터레이션 -순서가 중요하다는걸 명심하고 읽자-

accumulate - 이터레이션 하면서 fold연산

fold(접기)연산 = functools.reduce함수는 left fold함수이다. 컨테이너 값들이 누산되면서 한쪽 방향으로 줄어들기 때문이다.
accumulate(iterable, 이항함수)
Python
복사
이항함수를 넘겨주지 않고 이터레이터만 주면 주어진 이터레이터 원소의 합계를 누산한다
values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] sum_reduce = itertools.accumulate(values) print(list(sum_reduce)) ----------------------------------- [1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
Python
복사
이터레이터를 이터레이션을 하면서 값이 점점 커질것이다. - 마지막에는 sum(values)와 동일
이항 함수는 (지금까지의 결과),(현재 이터레이션 된 값)을 매개변수로 받는다.
values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] sum_reduce = itertools.accumulate(values, lambda now, new: (now + new) % 20) print(list(sum_reduce))
Python
복사
위 아레 둘다 똑같은 거다 - 똑같은 결과를 반환한다
def sum_modulo_20(first, second): output = first + second return output % 20 modulo_reduce = itertools.accumulate(values, sum_modulo_20) print(list(modulo_reduce))
Python
복사
---OUTPUT--- [1, 3, 6, 10, 15, 1, 8, 16, 5, 15]
Python
복사
이터레이션 되면서 값을 누적시켜서 어떤 연산을 하고싶을때 사용하자

product

이터레이터에 들어있는 요소들의 Cartesian product(데카르트 곱)을 반환한다
[1,2]와 [10,20]의 Cartesian product연산 값은 {(1,10),(1,20),(2,10),(2,20)} 이다
데카르트 곱 → 집합들 각각에서 요소 하나을 취해, 하나의 집합안에 조합할때 나오는 모든 경우의 수
product(iterable,... repeat:int= )
Python
복사
repeat 인자의 역할 : 입력된 iterable집합을 통채로 repeat만큼 복제시킨뒤 함수를 실행한다
iter1,iter2,iter3이 인자로 들어갔을때
1.
iter1(0번쨰),iter2(0번째),iter3(이터레이션)
2.
iter1(0번쨰),iter2(1번째),iter3(이터레이션)
이렇게 for루프 내포된것과 동일한 순서를 가진 튜플을 만들어낸다
single = itertools.product([1, 2], repeat=3) print("리스트 한 개 , 3회 복제:", list(single)) multiple = itertools.product([1, 2], ["a", "b"]) print("리스트 두 개:", list(multiple))
Python
복사
리스트 한 개 , 3회 복제: [(1, 1, 1), (1, 1, 2), (1, 2, 1), (1, 2, 2), (2, 1, 1), (2, 1, 2), (2, 2, 1), (2, 2, 2)] 리스트 두 개: [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')]
Python
복사
사용 : 리스트 컴프리헨션 , for루프를 깊이 내포시키면서 여러개의 이터러블 객체의 요소 조합을 모두 취득해야 할때 사용한다.

muliplication_table (ex 구구단) 같은걸 할 수 있다 - 좋은 예시

multiplication_table = itertools.product(range(1, 10), repeat=2) for x, y in multiplication_table: print(f"{x} X {y} = {x*y}")
Python
복사
구구단 결과 보기
3단 구구단 → 원래는 for루프를 3번이나 내포시켜야 가능하지만 이렇게 쉽게 된다
multiplication_table = itertools.product(range(1, 10), repeat=3) for x, y, z in multiplication_table: print(f"{x} X {y} X {z} = {x*y*z}")
Python
복사
3단 구구단 결과 보기 - 엄청 길다 주의해라
이런 조합을 단번에 언패킹해서 사용할 수 있게된다 - 강력하다
순열은 중복 없는 for다중 루프 돌듯이 경우의 수를 이터레이션 한다
조합은 동일한 요소 집합이 반복되지 않는다. 즉, (1,5)와 (5,1)이 함께 존재하지 않도록 한다 말그대로 딱 "조합"되는 경우의 수만 이터레이션 한다

permutations - 순열 이터레이터를 반환한다

permutations는 순열이라는 뜻이다
iterable요소 n개로 만들어내는 순열들을 쭉 이터레이션한다
permutations(iterable,n)
Python
복사
it = itertools.permutations([1, 2, 3, 4], 2) print(list(it))
Python
복사
[(1, 2), (1, 3), (1, 4), (2, 1), (2, 3), (2, 4), (3, 1), (3, 2), (3, 4), (4, 1), (4, 2), (4, 3)]
Python
복사
모든 원소들이 다 다르다고 가정하기때문에 iterable에 같은 값이 있어도 상관없이 작동한다
작동방식:
조합의 첫번째 값이 iterable[0]일때의 조합들
조합의 첫번째 값이 iterable[1]일때의 조합들
...
이렇게 인덱스가 빠른것을 기준으로 잡고 뒤의 요소들을 한바퀴 돌린다
이 순서대로 이터레이션 된다는걸 반드시 염두해 두어야 한다
it = itertools.permutations([1, 2, 3, 4], 3) print(list(it))
Python
복사
위 코드의 반환값
[(1, 2, 3), (1, 2, 4), (1, 3, 2), (1, 3, 4), (1, 4, 2), (1, 4, 3), (2, 1, 3), (2, 1, 4), (2, 3, 1), (2, 3, 4), (2, 4, 1), (2, 4, 3), (3, 1, 2), (3, 1, 4), (3, 2, 1), (3, 2, 4), (3, 4, 1), (3, 4, 2), (4, 1, 2), (4, 1, 3), (4, 2, 1), (4, 2, 3), (4, 3, 1), (4, 3, 2)]
Python
복사

combinations - 조합 이터레이터를 반환한다

combinations 는 조합이라는 뜻이다
combinations(iterable,n)
Python
복사
it = itertools.combinations([1, 2, 3, 4], 2) print(list(it))
Python
복사
[(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
Python
복사
permutations와 작동방식이 같다 다만 이전에 나온 조합과 같은것을 건너 뛴다는 점만 다르다

combinations_with_replacement - combinations와 같지만 원소의 반복을 허용한다 - 중복조합을 반환한다

그렇다고 permutations와 같은것이 아니다.
combinations_with_replacement(iterable, n)
Python
복사
it = itertools.combinations_with_replacement([1, 2, 3, 4], 2) print(list(it))
Python
복사
[(1, 1), (1, 2), (1, 3), (1, 4), (2, 2), (2, 3), (2, 4), (3, 3), (3, 4), (4, 4)]
Python
복사

permutations(순열) combinations(조합) combinations_with_replacement(중복조합) 의 차이점

p = itertools.permutations([1, 2, 3, 4], 2) c = itertools.combinations([1, 2, 3, 4], 2) c_r = itertools.combinations_with_replacement([1, 2, 3, 4], 2) print(f"순열:{list(p)}\n조합:{list(c)}\n중복조합:{list(c_r)}")
Python
복사
순열:[(1, 2), (1, 3), (1, 4), (2, 1), (2, 3), (2, 4), (3, 1), (3, 2), (3, 4), (4, 1), (4, 2), (4, 3)] 조합:[(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)] 중복조합:[(1, 1), (1, 2), (1, 3), (1, 4), (2, 2), (2, 3), (2, 4), (3, 3), (3, 4), (4, 4)]
Python
복사

순열 : ( a[n] , a[n+1 ~] ) 식의 루프

(튜플)내부 요소들이 중복되는 경우를 제외하고 모든 경우의 수를 이터레이션 한다.

조합: ( a[n] , a[n+1 ~] ) 에서 모든 요소를 집합이라고 보고 중복되지 않도록 한다

집합의 경우 >>> {1,2} == {2,1} True, (1,3),(3,1) 이 같이있을 경우 중복이다, 먼저 출력되는 (1,3)때문에 (3,1)은 발생하지 않는다
Python
복사
이때 아레와 같은 연산이 된다 - 실제로는 오류난다 하지만 개념적으로 딱 이 연산이다
>>> lst = [{1, 2}, {1, 2}] >>> set(lst) >>> set({1,2}) # 이런 결과! 실제로는 {1,2},{2,1}가 일단 순서를 기억하기 때문에 이중 뭘 남겨야 할지 모르므로 에러난다
Python
복사

중복 조합: 생성 튜플에 n개의 요소만 넣을 수 있는 상태에서 for루프 돌리는데 조합 연산과 동일하게 중복되는 집합은 건너 뛴다

[(1, 1), (1, 2), (1, 3), (1, 4), (2, 2), (2, 3), (2, 4), (3, 3), (3, 4), (4, 4)]
Python
복사
첫번째 요소가 2로 일때 두번쨰 요소도 2부터 시작하고 첫번째 요소가 3일때 두번째 요소고 3부터 시작한다

유의점

모든 요소를 다르다고 가정하기 때문에 만약 [1,1,2,2,3,4,5] 처럼 동일한 숫자가 들어있는 이터레이터로 연산한다면 중복이 여럿 생긴다.