출시·고도화 중
동적 계획법 안내서 · 6/6
동적 계획법은 코딩 테스트에만 나오는 주제가 아닙니다. 매일 쓰는 많은 도구가 이름만 다를 뿐 안쪽에서 DP를 돌립니다. 이 장에서는 그런 곳들을 훑어보고, 작은 실무형 예제 두 개를 살펴본 뒤, DP 코드를 망가뜨리는 흔한 실수를 정리합니다.
D개일 때 O(ND) 시간에 도는 Myers 알고리즘을 씁니다.명령줄 도구는 사용자가 명령 이름을 잘못 입력했을 때 고쳐 쓸 이름을 추천할 수 있습니다. 한 행만 굴리는 편집 거리로 충분합니다.
def edit_distance(s: str, t: str) -> int:
prev = list(range(len(t) + 1))
for i, a in enumerate(s, start=1):
cur = [i] + [0] * len(t)
for j, b in enumerate(t, start=1):
cur[j] = prev[j - 1] if a == b else 1 + min(prev[j], cur[j - 1], prev[j - 1])
prev = cur
return prev[-1]
def suggest(word: str, commands: list[str], max_distance: int = 2) -> str | None:
best = min(commands, key=lambda c: edit_distance(word, c))
return best if edit_distance(word, best) <= max_distance else None
print(suggest("stauts", ["status", "commit", "push", "stash"])) # status
print(suggest("deploy", ["status", "commit", "push", "stash"])) # NonePython 표준 라이브러리의 difflib.get_close_matches도 비슷한 일을 하지만, 최소 편집 거리가 아니라 SequenceMatcher의 유사도 비율로 순위를 매깁니다.
모니터링 시스템이 응답 시간을 "fast" 또는 "slow"로만 관찰하고, 각 시점에 서버가 "ok"였는지 "degraded"였는지 추정하려고 합니다. 비터비는 시점 t의 각 상태마다 그 상태에서 끝나는 가장 좋은 경로의 확률과 직전 상태를 가리키는 포인터를 저장합니다.
def viterbi(observations, states, start, trans, emit):
best = [{s: (start[s] * emit[s][observations[0]], None) for s in states}]
for obs in observations[1:]:
prev = best[-1]
best.append({
s: max((prev[p][0] * trans[p][s] * emit[s][obs], p) for p in states)
for s in states
})
state = max(states, key=lambda s: best[-1][s][0])
path = [state]
for layer in reversed(best[1:]):
state = layer[state][1]
path.append(state)
return path[::-1]
states = ("ok", "degraded")
start = {"ok": 0.9, "degraded": 0.1}
trans = {"ok": {"ok": 0.95, "degraded": 0.05}, "degraded": {"ok": 0.2, "degraded": 0.8}}
emit = {"ok": {"fast": 0.9, "slow": 0.1}, "degraded": {"fast": 0.3, "slow": 0.7}}
print(viterbi(["fast", "fast", "slow", "slow", "slow", "fast"], states, start, trans, emit))
# ['ok', 'ok', 'degraded', 'degraded', 'degraded', 'degraded']실제 구현은 작은 확률을 여러 번 곱하면 0으로 언더플로가 나기 때문에 확률을 곱하는 대신 로그 확률을 더합니다.
functools.cache는 항목을 지우지 않으므로 오래 도는 서비스에서는 끝없이 커질 수 있습니다. lru_cache(maxsize=...)를 쓰거나 직접 비웁니다.from functools import cache, lru_cache
@cache
def total(items: tuple[int, ...]) -> int:
return sum(items)
try:
total([1, 2, 3])
except TypeError as error:
print("lists cannot be cache keys:", error)
print(total((1, 2, 3))) # 6
@lru_cache(maxsize=1024) # 가장 오래 쓰지 않은 항목부터 지움
def score(word: str) -> int:
return sum(map(ord, word))
score.cache_clear() # 직접 모두 비울 수도 있음
댓글 0개
로그인 · 로그인하면 댓글을 남길 수 있습니다.
첫 댓글을 남겨 보세요.