코드워즈로 공부를 하고 있는데, 메모이제이션을 통해 피보나치 수열을 계산하는 함수의 효율을 개선하는 문제를 풀게되었다.
kata 분석하기
def fibonacci(n):
if n in [0, 1]:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
문제에서는 n이 0에 도달할 때까지 재귀함수 형태로 반복해서 fibonacci 함수를 호출하고 있다. 정상적으로 작동하는 함수이지만, 이 형태 그대로 함수를 돌리게 되면 fibonacci(0)과 fibonacci(1)이 엄청나게 많이 메모리에 쌓이게 된다. 같은 값임에도 불구하고 다르게 처리해야하는 비효율성이 생긴다. 때문에 메모리에 부담도 많이가고, 시간도 엄청 오래걸리게 된다.
그렇다면 이 문제는 어떻게 풀 수 있을까?
fibonacci(0)과 fibonacci(1)을 미리 메모리값에 저장해두고, 불필요한 연산들은 줄여보자.
이럴 때 메모리에 값을 저장하고 호출하는 것이 Memoization(메모이제이션)이다.
그리고 메모이제이션은 동적계획법의 일부라고 한다.
동적계획법과 메모이제이션
- 동적계획법: 복잡한 문제를 간단한 여러 개의 문제로 나누어 푸는 방법, 최단 경로 문제, 행렬의 제곱 문제 등의 최적화에 사용된다. 이것은 동적 계획법은 문제를 해결하기 위한 모든 방법을 검토하고, 그 중에 최적의 풀이법을 찾아내기 때문이다.
- 메모이제이션: 컴퓨터 프로그램이 동일한 계산을 반복해야 할 때, 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산의 반복 수행을 제거하여 프로그램 실행 속도를 빠르게 하는 기술
메모이제이션 ⊂ 동적계획법(Dynamic Programming)
Memoization in Python
dictionary 사용하기
말그대로 필요한 값을 딕셔너리에 저장한 후, 호출하여 사용해보자!
fib_memo = {}
def fibonacci(n):
if n in fib_memo:
return fib_memo[n]
else:
fib_memo[n] = n if n < 2 else fibonacci(n-2) + fibonacci(n-1)
return fib_memo[n]
# 번외: 리스트도 쓸 수 있지 않을까?
def fibonacci(n):
"""
- len(x) = n+1 인 list x를 만든다.
- x[0]=0, x[1]=1 로 지정
- x[n] = x[n-2] + x[n-1] (n>=2)
- 사실 dict와 다른 것이 전혀 없다. dict에서의 key역할을 리스트에서는 index가 대체한다.
"""
x = [i for i in range(n+1)]
for idx in range(2, n+1):
x[idx] = x[idx-2] + x[idx-1]
return x[n]
functools.lru_cache() 사용하기
l(L)ru_cache의 lru는 뭘까? Least Recently Used를 의미한다. 해당 함수를 사용하면 과거 사용했던 함수의 리턴 결과를 캐시할 수 있다. 데코레이터 형태로 사용한다. 예를 들어 func()라는 함수를 인자를 다르게 담아 호출한다고 해보자.
호출 순서 | func(1) | func(2) | func(3) | func(2) | func(1) | func(4) | func(3) |
메모리 저장 여부 | O | O | O | X(기존 캐시 사용) | X(기존 캐시 사용) | O | X(기존 캐시 사용) |
캐싱 여부 | X(최초호출) | X(최초호출) | X(최초호출) | O | O | X(최초호출) | O |
위의 이미지처럼 2번 이상 호출하는 경우 메모리에서 값을 가져오는 형태이다.
functools.lru_cache()는 함수의 리턴 결과를 캐시해주며 보통 데코레이터 형태로 사용한다. 인자값으로 maxsize를 넣을 수 있으며, 캐시항목이 maxsize를 넘어가는 경우에는 호출빈도가 가장 낮은 것부터 캐시에서 삭제한다. 만약 위의 테이블에서 @functools.lru_cache(maxsize=2)를 설정했다면, func(1)과 func(2) 까지만 저장되고, func(3)이 호출되는 순간 func(1)은 지워질 것이다. 때문에 maxsize의 크기를 적절하게 설정하는 것도 프로그래밍의 효율성에 중요한 요소이다.( 숫자가 크면 차지하는 메모리가 커지고, 적다면 값을 반복해서 호출하게될 수 있기 때문)
이를 이용하여 피보나치 수열을 아래와 같이 풀 수 있다.
from functools import lru_cache
@lru_cache()
def fibonacci(n):
if n < 2:
return n
else:
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(10))
print(fibonacci.cache_info()) # 캐시 정보를 알 수 있다.
참고자료
피보나치 수 - 위키백과, 우리 모두의 백과사전
피보나치 수를 이용한 사각형 채우기 수학에서, 피보나치 수(영어: Fibonacci numbers)는 첫째 및 둘째 항이 1이며 그 뒤의 모든 항은 바로 앞 두 항의 합인 수열이다. 처음 여섯 항은 각각 1, 1, 2, 3, 5, 8
ko.wikipedia.org
동적 계획법 - 위키백과, 우리 모두의 백과사전
수학과 컴퓨터 과학, 그리고 경제학에서 동적 계획법(動的計劃法, dynamic programming)이란 복잡한 문제를 간단한 여러 개의 문제로 나누어 푸는 방법을 말한다. 이것은 부분 문제 반복과 최적 부분
ko.wikipedia.org
Mastering Memoization in Python
Understanding Function Caching in Python
towardsdatascience.com
파이썬과 컴퓨터 사이언스(기본 알고리즘): 동적 계획법과 분할 정복 - 잔재미코딩
프로그래밍 연습 피보나치 수열: n 을 입력받아서 다음과 같이 계산됨 n 을 입력받았을 때 피보나치 수열로 결과값을 출력하세요 함수를 fibonacci 라고 하면, fibonacci(0):0 fibonacci(1):1 fibonacci(2):1 fibon
www.fun-coding.org
[Python 으로 푸는 Leetcode]146. LRU Cache
Design a data structure that follows the constraints of a Least Recently Used (LRU) cache.Implement the LRUCache class:LRUCache(int capacity) Initiali
velog.io
functools — 고차 함수와 콜러블 객체에 대한 연산 — Python 3.9.7 문서
docs.python.org
아 이번글은 뭔가 특별한 것은 없지만 뿌듯한데 검색 알고리즘 많이타면 좋겠다 ㅎㅎ
'기술스택을 쌓아보자' 카테고리의 다른 글
[git/조각조각깃/조깃]깃 커밋메시지 수정하기 git commit amend (0) | 2021.10.04 |
---|---|
[파이썬/python] collections.Counter 사용하기 (0) | 2021.09.27 |
s3 lambda object 코드 테스트 작업 및 공부 [진행중] (0) | 2021.08.21 |
도커 콘테이너와 로컬에 파일 전송하기 및 내려받기 (0) | 2021.03.13 |
[jupyter] .ipynb 파일 내의 변수를 다른 곳에서 import 하기(주피터 매직커맨드/ %store) (0) | 2021.01.12 |
댓글