프로그래밍/Python

[Python] 함수 사용법 - (3) itertools

Churnobyl 2023. 3. 22. 15:36
728x90
반응형

 

이번엔 Python이 APL, Haskell, SML같은 2000년대 이전 프로그래밍 언어에  영향을 받아 캐스팅한 itertools 모듈에 대해 공부해보자. itertools 모듈은 순수 파이썬에서 간결하고 효율적으로 iterator를 만든다.

from itertools import product

def solution(board):
    for row in range(len(board)):
        for grid in range(len(board[row])):
            if board[row][grid] == 1:
                for i in list(product([-1, 0, 1], repeat=2)):
                    if row+i[0] >= len(board) or row+i[0] < 0:
                        pass
                    elif grid+i[1] >= len(board[row]) or grid+i[1] < 0:
                        pass
                    else:
                        if board[row+i[0]][grid+i[1]] == 0:
                            board[row+i[0]][grid+i[1]] = 2

    count = 0

    for j in board:
        count += j.count(0)
    return count
    
print(solution(
    [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 1, 0, 0], [0, 0, 0, 0, 0]])
print(solution(
    [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 1, 1, 0], [0, 0, 0, 0, 0]])

문제: 

 

위 풀이는 내 풀이로 itertools의 product함수를 사용했지만 itertools의 다양한 함수들을 정리해보고자 글을 쓴다


무한 반복자(Infinite iterators)

 

count([start=0], [step=1])

start=n부터 시작해 step=p 간격으로 값을 반환한다

 

  • Optional Parameter
    • start : 반복의 시작이 되는 값. 기본값 0
    • step: 간격. 기본값 1

아래의 예제를 보자

 

from itertools import count
import time

# 1
print(type(count(10)))


# 2
print(count(10))


# 3
for i in count(10):
    print(i, end=" ")
    time.sleep(1)


# 4
for i in count(10, 2):
    print(i, end=" ")
    time.sleep(1)


# 5
for i in map(lambda x: x ** 2, count()):
    print(i, end=" ")
    time.sleep(1)
    
# 6
print([1, 2, 3, 5, 2].count(2))

 

<class 'itertools.count'>
count(10)
10 11 12 13 ...
10 12 14 16 ...
0 1 4 9 16 25 36 49 64 ...
2

 

# 1

itertools.count()함수의 type은 class다

 

# 2

itertools.count()함수를 출력하면 자기 자신이 출력된다

 

# 3

start가 10부터 차례로 1씩 증가하는 무한대의 숫자가 출력된다

 

# 4

step이 2이므로 10부터 2씩 증가하는 무한대의 숫자가 출력된다

 

# 5

itertools의 iterator making function들은 주로 map(function, iterable)과 같은 함수와 환상적으로 쓰일 수 있다

#5는 0부터 시작하는 count()함수의 값에 제곱하는 함수를 적용한 결과이다

 

# 6

당연하게도 list클래스의 count()함수와는 다르다. list클래스의 count()함수는 list내의 해당 매개변수의 빈도를 출력한다.

 


cycle(iterable)

iterable한 객체의 요소를 차례대로 반복적으로 출력한다

 


아래의 예제를 보자

 

from itertools import cycle
import time

# 1
print(cycle("ABCDE"))


# 2
for i in cycle("ABCDE"):
    print(i, end=" ")
    time.sleep(1)
<itertools.cycle object at 0x00000272C1991640>
A B C D E A B C D E ...

 

# 1

itertools.cycle()함수를 출력하면 itertools.cycle객체의 주소가 출력됐다

 

# 2

iterable한 객체를 넣으면 순서대로 무한히 출력됐다

 

 


repeat(object[, times])

어떤 객체를 [time회 or 무한대] 반복 출력한다

  • Optional Parameter
    • times : 반복할 횟수. 기본값 무한대

아래의 예제를 보자

from itertools import repeat
import time

# 1
for i in repeat(10):
    print(i, end=" ")
    time.sleep(0.5)


# 2
for i in repeat(10, 3):
    print(i, end=" ")
    time.sleep(0.5)


# 3
for i in repeat("점심드시오!", 3):
    print(i)
10 10 10 10 10 10 10 ...
10 10 10
점심드시오! 점심드시오! 점심드시오!

 

# 1

옵셔널 파라미터 times가 없이 사용해 10이 무한대로 출력됐다

 

# 2

times를 지정해주어 지정한 횟수만큼 10이 출력됐다

 

# 3

repeat의 첫번째 인수에는 어떠한 객체도 들어갈 수 있으므로 '점심드시오!' 문자열 객체가 3번 출력됐다

 


길이 제한 반복자(Iterators terminating on the shortest input sequence)

 

accumulate(iterable[, function], [initial=0])

누적 합계 또는 function 지정 시 function의 누적된 결과를 반환하는 반복자를 만든다

이전 값현재 값 2개의 값을 비교하므로 function 사용시 매개변수는 2개가 필요하다

 

  • Optional Parameter
    • function: function 지정 시 function의 누적된 결과 반환
    • initial: 초기 반환값. iterable객체 요소 중 첫번째 값 지정

아래의 예제를 보자

 

from itertools import accumulate

array = [1, 5, 6, 4, 7, 2, 9, 2]

# 1
for i in accumulate(array):
    print(i, end=" ")

# 2
for i in accumulate(array, initial=567):
    print(i, end=" ")

# 3
for i in accumulate(array, max):
    print(i, end=" ")

# 4
array2 = [1, 2, 3, 4]
for i in accumulate(array2, lambda x, y: x + y * 1.1):
    print(i, end=" ")
1 6 12 16 23 25 34 36
567 568 573 579 583 590 592 601 603
1 5 6 6 7 7 9 9
1 3.2 6.5 10.9

 

# 1

이전 값과 현재 값을 더한 값을 리턴했다

 

# 2

초기 값을 567로 지정해줘서 첫번째 값이 567이 되었다

 

# 3

max()함수로 이전 값과 현재 값을 비교해 더 큰 값을 리턴했다

 

# 4

이전 값과 현재 값을 비교하므로 함수에 두개의 매개변수가 필요하다

따라서 (이전 값)+(현재 값) * 1.1이 리턴됐다

 


compress(data, selectors)

selectors의 True값을 기준으로 data를 필터링한다


아래의 예제를 보자

from itertools import compress

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

# 1
for i in compress(data, [True, False, False, True, False, False, True, True]):
    print(i, end=" ")

# 2
for i in compress(data, [1, 1, 0, 1, 1, 2, 0, 0]):
    print(i, end=" ")
1 4 7 8
1 2 4 5 6

 

# 1

selectors의 True에 해당하는 data값만 출력됐다

 

# 2

True과 동등한 int값인 1,2를 가진 selectors와 False인 0이 있다

True에 해당하는 data값만 출력됐다

 

groupby(iterable[,key=None])

연속적인 key와 group을 반환한다key의 값이 변경될 때마다 그룹을 새로 생성하므로 일반적으로 sorted()혹은 sort()함수로 정렬하고 사용한다

 

  • Optional Parameter
    • key : 함수이나 일반적으로 사용하지 않음

아래의 예제를 보자

from itertools import groupby

string = "AAABBAAABBBBCCCDDAAA"

# 1
print(dict(groupby(string)))

# 2
for k, g in groupby(string):
    print(k, end=" ")

# 3
for k, g in groupby(string):
    print(list(g), end=" ")

# 4
for k, g in groupby(sorted(string)):
    print(list(g), end=" ")
{'A': <itertools._grouper object at 0x0000029A26E55460>, 'B': <itertools._grouper object at 0x0000029A26C99580>, 'C': <itertools._grouper object at 0x0000029A26C990A0>, 'D': <itertools._grouper object at 0x0000029A26E55520>}
A B A B C D A
['A', 'A', 'A'] ['B', 'B'] ['A', 'A', 'A'] ['B', 'B', 'B', 'B'] ['C', 'C', 'C'] ['D', 'D'] ['A', 'A', 'A']
['A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A'] ['B', 'B', 'B', 'B', 'B', 'B'] ['C', 'C', 'C'] ['D', 'D']

 

# 1

유일한 key값과 _grouper객체 주소를 딕셔너리 형태로 출력했다

 

# 2

key값이 변경될 때마다 새로운 그룹을 생성하므로 위와 같이 A B A B C D A로 출력됐다

 

# 3

_grouper객체를 list로 감싸서 출력하면 같은 요소끼리 grouping된다

 

# 4

string을 sorting한 후에 groupby를 해서 각 요소끼리 그룹을 지었다

 

 


takewhile(predicate, iterable)  (< = >dropwhile)

predicate함수의 조건에 따라 False가 나올 때까지 iterable객체의 요소를 출력한다


아래의 예제를 보자

from itertools import takewhile

array = [1, 6, 2, 8, 4, 52]

# 1
for i in takewhile(lambda x: x < 8, array):
    print(i, end=" ")

# 2
for i in takewhile(lambda x: x < 9, array):
    print(i, end=" ")
1 6 2
1 6 2 8 4

 

# 1

1, 6, 2까지 출력되다 8을 만나 False가 되어 출력이 중단됐다

 

# 2

1, 6, 2, 8, 4까지 출력되다 52를 만나 False가 되어 출력이 중단됐다

 


filterfalse(predicate, iterable) 

predicate의 조건의 결과가 False인 것만 출력한다


아래의 예제를 보자

from itertools import filterfalse

array = [1, 7, 5, 23, 4, 2, 9, 12]

# 1
for i in filterfalse(lambda x: x < 5, array):
    print(i, end=" ")

# 2
for i in filterfalse(lambda x: x % 2, array):
    print(i, end=" ")
7 5 23 9 12
4 2 12

 

# 1

조건 x < 5가 False인 값만 출력됐다

 

# 2

조건 x % 2가 False인 값만 출력됐다

 

 


조합 반복자(Combinatoric iterators)

가장 유용!!

 

permutations(iterable[, r=None])

순열 함수 : 서로 다른 n개의 원소에서 중복 없이 순서에 상관있게 r개를 뽑는 경우의 수

순열의 수

  • Optional Parameter
    • r : 뽑을 갯수. 기본값 iterable객체의 길이

 


아래의 예제를 보자

from itertools import permutations

# 1
for i in permutations('ABC', 2):
    print(i, end=" ")

# 2
for i in permutations('ABC'):
    print(i, end=" ")
('A', 'B') ('A', 'C') ('B', 'A') ('B', 'C') ('C', 'A') ('C', 'B')
('A', 'B', 'C') ('A', 'C', 'B') ('B', 'A', 'C') ('B', 'C', 'A') ('C', 'A', 'B') ('C', 'B', 'A')

 

# 1

iterable객체 3개 중에 2개를 뽑아 나열하는 순열

중복이 허용되지 않는다

 

# 2

r을 지정해주지 않으면 iterable객체의 전체 길이가 r이 된다

 


combinations(iterable, r)

조합 함수 : 서로 다른 n개의 원소에서 중복 없이 순서에 상관없이 r개를 뽑는 경우의 수

조합의 수

 

  • n = r이 될 수 없으므로 combinations에서는 r은 필수 매개변수다
  • Optional Parameter
    • r : 뽑을 갯수

아래의 예제를 보자

from itertools import combinations

# 1
for i in combinations('ABC', 2):
    print(i, end=" ")

# 2
for i in combinations('ABC'):
    print(i, end=" ")

# 3
for i in combinations('ABCD', 2):
    print(i, end=" ")
('A', 'B') ('A', 'C') ('B', 'C')
TypeError: combinations() missing required argument 'r' (pos 2)
('A', 'B') ('A', 'C') ('A', 'D') ('B', 'C') ('B', 'D') ('C', 'D')

 

# 1

iterable객체 3개 중에 2개를 순서에 상관없이 나열했다

 

# 2

r을 지정해주지 않으면 r argument를 지정해달라는 TypeError가 발생한다

 

# 3

iterable객체 4개 중에 2개를 순서에 상관없이 나열했다

 

 


product(*iterables[, repeat=1])

중복순열 함수 : 서로 다른 n개의 원소에서 중복 있게 순서에 상관있게 r개를 뽑는 경우의 수

카테시안 곱(Cartesian Product)이라고도 부른다

카테시안 곱(= 데카르트 곱)

  • iterable객체를 두개 이상 매개변수로 추가할 수 있다
  • Optional Parameter
    • repeat : 뽑을 갯수

아래의 예제를 보자

from itertools import product

# 1
for i in product('ABC', repeat=2):
    print(i, end=" ")

# 2
for i in product('ABC', 'xyz'):
    print(i, end=" ")

# 3
for i in product('ABC', 'xyz', repeat=2):
    print(i, end=" ")
('A', 'A') ('A', 'B') ('A', 'C') ('B', 'A') ('B', 'B') ('B', 'C') ('C', 'A') ('C', 'B') ('C', 'C')
('A', 'x') ('A', 'y') ('A', 'z') ('B', 'x') ('B', 'y') ('B', 'z') ('C', 'x') ('C', 'y') ('C', 'z')
('A', 'x', 'A', 'y') ('A', 'x', 'A', 'z') ('A', 'x', 'B', 'x') ('A', 'x', 'B', 'y') ('A', 'x', 'B', 'z') ('A', 'x', 'C', 'x') ...

 

# 1

iterable객체를 2번 뽑아 순서에 상관있게 나열했다

 

# 2

두 iterable객체에서 각각 뽑아 순서에 상관있게 나열했다 (=카테시안 곱)

 

# 3

두 iterable객체에서 각각 2번씩 뽑아 순서에 상관있게 나열했다

 


combinations_with_replacement(iterables, r)

중복조합 함수 : 서로 다른 n개의 원소에서 중복 있게 순서에 상관없게 r개를 뽑는 경우의 수

 

  • Optional Parameter
    • r : 뽑을 갯수

아래의 예제를 보자

from itertools import combinations_with_replacement

# 1
for i in combinations_with_replacement('ABC', 2):
    print(i, end=" ")

# 2
for i in combinations_with_replacement('ABCD', 2):
    print(i, end=" ")
('A', 'A') ('A', 'B') ('A', 'C') ('B', 'B') ('B', 'C') ('C', 'C')
('A', 'A') ('A', 'B') ('A', 'C') ('A', 'D') ('B', 'B') ('B', 'C') ('B', 'D') ('C', 'C') ('C', 'D') ('D', 'D')

 

# 1

3개 문자열 중에 2개를 뽑아 중복있게 순서에 상관없이 나열했다

 

# 2

4개 문자열 중에 2개를 뽑아 중복있게 순서에 상관없이 나열했다

 

 


코드풀이

이제 코드를 하나씩 뜯어보자

전체코드

from itertools import product

def solution(board):
    for row in range(len(board)): # 1
        for grid in range(len(board[row])):
            if board[row][grid] == 1:  # 2
                for i in list(product([-1, 0, 1], repeat=2)):  # 3
                    if row+i[0] >= len(board) or row+i[0] < 0:  # 4
                        pass
                    elif grid+i[1] >= len(board[row]) or grid+i[1] < 0:
                        pass
                    else: # 5
                        if board[row+i[0]][grid+i[1]] == 0:  
                            board[row+i[0]][grid+i[1]] = 2

    count = 0

    for j in board:  # 6
        count += j.count(0)
    return count
    
    print(solution(
    [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 1, 0, 0], [0, 0, 0, 0, 0]])
    print(solution(
    [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 1, 1, 0], [0, 0, 0, 0, 0]])

# 1 

for row in range(len(board)): # 1
    for grid in range(len(board[row])):

numpy함수를 쓰지 않고 풀었기 때문에 중복 for문으로 board의 요소를 하나씩 읽어 간다

 

# 2

if board[row][grid] == 1:  # 2

그러다 지뢰가 숨겨져 있는 1값을 발견하면

 

# 3

for i in list(product([-1, 0, 1], repeat=2)):  # 3

지뢰 주변 셀을 모두 찾기 위해 product([-1, 0, 1], repeat=2)로 순서에 상관있게 중복있게 모든 경우의 수를 찾는다

위 리스트의 결과는 다음과 같다

[(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 0), (0, 1), (1, -1), (1, 0), (1, 1)]

# 4

if row+i[0] >= len(board) or row+i[0] < 0:  # 4
    pass
elif grid+i[1] >= len(board[row]) or grid+i[1] < 0:
    pass

i[0]은 y축값, i[1]은 x축값이라고 생각하자(row가 y이므로)

지뢰가 맵의 끝에 있다면(예를 들어 (0, -5)) i[0] = -1일 때 row+i[0] = -6으로 맵 범위를 벗어나게 되므로 이 경우일 때 pass로 다음 for문으로 넘어가도록 한다

grid에 대해서도 같은 방식으로 적용한다

 

 

# 5

else: # 5
    if board[row+i[0]][grid+i[1]] == 0:  
        board[row+i[0]][grid+i[1]] = 2

위 경우를 다 거치고 남은 것들은 실제로 맵 안에 있는 지뢰 주위의 셀이다

else문으로 이 값이 0이라면 지뢰 주위의 셀임을 알리기 위해 2를 넣어준다

 

# 6

for j in board:  # 6
    count += j.count(0)
return count

이제 안전한 지역은 0으로 남아있을 것이고 지뢰가 있는 위치는 1, 지뢰 주위의 위험구역은 2이므로

count변수를 초기화해주고 전체 맵의 0 개수를 세주면 된다

 


생각

다른 사람의 풀이를 보았을 때 나보다 짧고 쉽게 푼 풀이들도 있었다

하지만 이번 문제는 이 풀이가 마음에 든다

중복 for문으로 시간복잡도는 O(n^2)이지만 문제에서 주어진 n의 크기가 크지 않으므로 이러한 풀이도 괜찮다

 

프로그래밍은 반복문을 주로 사용하므로 이러한 iterator들을 잘 알고 있으면 적재적소에 잘 활용할 수 있겠다

 

반응형