纪念蛋滴

时间:2019-05-21 13:00:23

标签: python algorithm

我正努力了解基于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)

1 个答案:

答案 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 (...)
    ...
相关问题