我正努力了解基于Egg Drop问题的以下代码行。特别是,我在理解memo_cyclic
函数时遇到了麻烦。您能否向我解释*args
的功能以及decorator
在此设置下的工作方式? f_memo.__name__ = f.__name__
是做什么的?为什么当我删除上面的行并包含@memo_cyclic
时,程序返回错误?
def memo_cyclic(f_cycle):
def decorator(f):
cache = {}
def f_memo(*args):
args = tuple(args)
if args not in cache:
cache[args] = f_cycle(*args)
cache[args] = f(*args)
return cache[args]
f_memo.__name__ = f.__name__
return f_memo
return decorator
fail = float("infinity")
@memo_cyclic(lambda *args: fail)
def f(n, lo, hi, fl=0, dr=0):
if lo == hi:
return 0 # only one floor is possible
if n+dr == 0:
return fail # out of eggs
if fl == 0:
n, dr = n+dr, 0 # pick up any dropped eggs
return 1 + min(f(n, lo, hi, fl-1, dr) if fl > 0 else fail, # go down one floor
f(n, lo, hi, fl+1, dr) if fl < hi else fail, # go up one floor
max(f(n-1, lo, fl, fl, dr), # drop egg (broken)
f(n-1, fl+1, hi, fl, dr+1)) # drop egg (unbroken)
if n > 0 and lo <= fl < hi else fail)
import sys
sys.setrecursionlimit(10000)
print [f(n, 0, n) for n in range(20)]
print [f(1, 0, n) for n in range(20)]
print f(2, 0, 99)
答案 0 :(得分:1)
所有关于装饰器的好教程都对此进行了解释。例如this answer。
但这是基本流程。考虑这种构造。
@decorator
def some_function (...):
...
实际上是以下内容的简称:
def some_function (...):
...
some_function = decorator(some_function)
但是在您的情况下,您有一个带有参数的装饰器。那是做什么的?好吧:
@complex_decorator(stuff)
def some_function (...):
...
简单地表示
def some_function (...):
...
some_function = complex_decorator(stuff)(some_function)
因此complex_decorator
必须是一个返回函数的函数。而且它返回的函数需要一个函数并返回一个新函数! (是的,这应该会使您的头在遇到头几次后旋转。但这完全合乎逻辑。)
现在,您复杂的装饰器是memo_cyclic
,它的一半太聪明了。让我们看看是否可以用注释和数字来解开它。请尝试按数字顺序阅读注释。它们通常是从外部进入的,因此请先跳过下面的函数,然后再尝试读取内部的函数。
# 1) This is a recursive cache that will avoid repeated work and
# also replace infinite cycles with a failure value that is
# f_cycle(whatever, the, arguments, were)
def memo_cyclic(f_cycle):
# 3) decorator will take our original function, and returns the
# replacement. By possibly confusing coincidence, the
# function was originally called f before (global namespace)
# and is locally called f here in scope. But it would work
# just fine if we locally called it g.
def decorator(f):
# 5) cache will have the values that we have produced
# before so that we can return them instead of
# having to calculate them a second time. This trick
# is called "memoizing"
cache = {}
# 6) f_memo will be our replacement for f. Note that *args
# here just means, "Turn whatever list of arguments we got
# before into an array."
def f_memo(*args):
# 9) A tuple is the same as an array except that it can't
# change. Because it can't change, Python will let us
# use it as the key to a dictionary.
args = tuple(args)
# 10) And now we check if the tuple is in the cache. If
# we have received this set of arguments before, the
# cache will be filled and we skip this. Else we have
# work to do.
if args not in cache:
# 11) We set the value to return upon hitting an
# infinite loop. Note that *args here means
# "turn a list back into a list of arguments
# before calling the function".
cache[args] = f_cycle(*args)
# 12) And now we recursively do the original
# calculation. Note that when f calls itself
# recursively, it will call what is bound to
# the name f. Which will _actually_ be the
# function f_memo. Which means that if it
# WOULD have gone into an infinite loop, it
# will INSTEAD return the failure value. (In
# our case, infinity.)
cache[args] = f(*args)
# 13) And whether we just calculated it, or had it
# from before, the answer should be cache[args].
return cache[args]
# 7) As a nicety, we make f_memo report itself as whatever
# name f had for itself. This will, for example, make
# stack backtraces look nicer.
f_memo.__name__ = f.__name__
# 8) Returning f_memo here with f_cycle and the old f
# bound to it tells Python to make it become the new f.
return f_memo
# 4) Returning decorator here with f_cycle already bound is what
# tells python to replace f with decorator(f).
return decorator
fail = float("infinity")
# 2) f_cycle will be a function that takes any arguments and
# returns infinity.
@memo_cyclic(lambda *args: fail)
def f (...)
...