递归闭包(函数生成器)

Posted

技术标签:

【中文标题】递归闭包(函数生成器)【英文标题】:recursive closures (function generator) 【发布时间】:2012-04-06 12:19:02 【问题描述】:

我一直在学习函数式编程,我想到了组装数学运算符。 counting -> addition -> multiplication -> power -> ... 自然而然地出现了简单且最天真的代码来表达这一点,并且它有效!问题是我真的不知道为什么它工作得这么好并且输出这么大。

问题是: 这个函数的复杂度是多少?

代码在python中:

def operator(d):
        if d<=1:
                return lambda x,y:x+y
        else:
                return lambda x,y:reduce(operator(d-1),(x for i in xrange(y)))


#test 
f1 = operator(1)       #f1 is adition
print("f1",f1(50,52))  #50+52

f2 = operator(2)      #f2 is multiplication
print("f2",f2(2,20))  #2*20

f3 = operator(3)      #f3 is power, just look how long output can be
print("f3",f3(4,100)) #4**100 

f4 = operator(4)      #f4 is superpower, this one does not work that well
print("f4",f4(2,6))   #((((2**2)**2)**2)**2)**2

f5 = operator(5)      #f5 do not ask about this one, 
print("f5",f5(2,4))   #

输出(即时):

('f1', 102)
('f2', 40)
('f3', 1606938044258990275541962092341162602522202993782792835301376L)
('f4', 4294967296L)
('f5', 32317006071311007300714876688669951960444102669715484032130345427524655138867890893197201411522913463688717960921898019494119559150490921095088152386448283120630877367300996091750197750389652106796057638384067568276792218642619756161838094338476170470581645852036305042887575891541065808607552399123930385521914333389668342420684974786564569494856176035326322058077805659331026192708460314150258592864177116725943603718461857357598351152301645904403697613233287231227125684710820209725157101726931323469678542580656697935045997268352998638215525166389437335543602135433229604645318478604952148193555853611059596230656L)

【问题讨论】:

您的代码仅在递归被展平时才执行加法操作,所以我猜 Python bignums 在加法方面真的高效。 @Frederic:不过,添加的数量呈指数增长。尽管如此,这里提供的数字的数量并不是真正惊人的。我没有看到问题,更不用说具体的问题了。 您应该尝试对代码添加的数量进行基准测试 - 只需稍微更改 operator(1) 以在调用变量时递增变量。顺便说一句,我感觉您可能对Ackerman function 感兴趣:)。 @missingno Ackerman 实际上是以其他方式定义的。例如 operator(4)(2,3) 是 (22)**2 其中 ACK(4,0) 是 2**(22)-3 顺便en.wikipedia.org/wiki/Hyperoperation 【参考方案1】:

反汇编告诉你这里没有应用任何神奇的优化,它实际上只是对genexpr的减少。 Python 似乎可以胜任这项任务,即使它让您感到惊讶。

>>> import dis
>>> dis.dis(f3)
  5           0 LOAD_GLOBAL              0 (reduce)
              3 LOAD_GLOBAL              1 (operator)
              6 LOAD_DEREF               1 (d)
              9 LOAD_CONST               1 (1)
             12 BINARY_SUBTRACT     
             13 CALL_FUNCTION            1
             16 LOAD_CLOSURE             0 (x)
             19 BUILD_TUPLE              1
             22 LOAD_CONST               2 (<code object <genexpr> at 0x7f32d325f830, file "<stdin>", line 5>)
             25 MAKE_CLOSURE             0
             28 LOAD_GLOBAL              2 (xrange)
             31 LOAD_FAST                1 (y)
             34 CALL_FUNCTION            1
             37 GET_ITER            
             38 CALL_FUNCTION            1
             41 CALL_FUNCTION            2
             44 RETURN_VALUE

如果您专门查看您的f5(2,4) 调用,它实际上并没有执行这么多操作:

>>> counter = 0
>>> def adder(x, y):
...   global counter
...   counter += 1
...   return x + y
... 
>>> def op(d):
...   if d <= 1: return adder
...   return lambda x,y:reduce(op(d-1),(x for i in xrange(y)))
...
>>> op(5)(2,4)
32317006071311007300714876688669951960444102669715484032130345427524655138867890893197201411522913463688717960921898019494119559150490921095088152386448283120630877367300996091750197750389652106796057638384067568276792218642619756161838094338476170470581645852036305042887575891541065808607552399123930385521914333389668342420684974786564569494856176035326322058077805659331026192708460314150258592864177116725943603718461857357598351152301645904403697613233287231227125684710820209725157101726931323469678542580656697935045997268352998638215525166389437335543602135433229604645318478604952148193555853611059596230656L
>>> counter
65035
>>> counter = 0
>>> op(3)(4,100)
>>> counter
297

65k 加法,更不用说求幂的 297,对于经过优化的现代 CPU 来说甚至都不值得一提,所以这也难怪眨眼间就完成了。尝试增加其中一个参数,看看它如何非常快速达到快速评估的边界。

顺便说一句,operator 是一个内置模块,你不应该这样命名你自己的函数。

【讨论】:

我已经知道了,这让我最困扰。您知道如何更好地调试递归堆栈,因为我不知道如何在没有闭包的情况下实现主循环。 @ralu:什么?我不明白这个问题。 问题是,例如,我如何计算这个函数的输入复杂度。 @ralu:不知何故,后续问题与您最初的“问题”完全无关。请考虑在Mathematics 上提问,因为它似乎与编程无关。

以上是关于递归闭包(函数生成器)的主要内容,如果未能解决你的问题,请参考以下文章

内部有闭包的递归函数

第五篇 函数进阶

Python函数部分

9.23闭包函数/装饰器/迭代器/生成器

Python函数进阶:闭包装饰器生成器协程

闭包函数与迭代器生成器