이번엔 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들을 잘 알고 있으면 적재적소에 잘 활용할 수 있겠다
'프로그래밍 > Python' 카테고리의 다른 글
[Django] 장고 기초 - (2) Template Language (0) | 2023.04.17 |
---|---|
[Django] 장고 기초 - (1) 개요 및 개발 환경 구성 (0) | 2023.04.14 |
[Python] 자료형 특집 - (1) 시퀀스 타입(List/Tuple/Range) (0) | 2023.03.24 |
[Python] 함수 사용법 - (2) sort(), sorted() (0) | 2023.03.19 |
[Python] 함수 사용법 - (1) enumerate() (0) | 2023.02.28 |