在一个表达式

时间:2017-09-30 01:41:55

标签: python recursion lambda fibonacci lambda-calculus

所以我最近一直在乱用Python,我试图找到一种方法在单个表达式中输出第n个斐波那契数列。这是我到目前为止编写的代码:

(lambda f: f if f<2 else (f-1)+(f-2))(n)
# n == 1 -> 1
# n == 2 -> 1
# n == 3 -> 3
# n == 4 -> 5
# n == 5 -> 7
....

然而,正如我在上面评论的那样,这只是输出一组奇数。我很困惑为什么会发生这种情况,因为如果我要将其重新编写为命名的lambda函数,它将看起来像这样:

f = lambda n: n if n<2 else f(f-1)+f(f-2)
# f(1) -> 1
# f(2) -> 1
# f(3) -> 2
# f(4) -> 3
...
# f(10) -> 55
...

现在我添加Lambda Calculus标签的原因是因为我不确定这个问题是否属于简单理解Python如何处理这个问题的范畴。我已经读过一些关于lambda演算中Y组合子的内容,但这对我来说是一种外语,并且无法从我发现的关于lambda演算的资源中得到任何东西。

现在,我试图在一行代码中执行此操作而不是命名它的原因是因为我想尝试将此lambda函数放入列表理解中。所以做这样的事情:

[(lambda f: f if f<2 else (f-1)+(f-2))(n) for n in range(10)]

并创建斐波纳契序列中前x个数字的数组。

我正在寻找的是一种在一个表达式中完成这一切的方法,如果属于Lambda演算的领域,我认为它确实存在,有人可以解释这是如何工作的。

随意提供JavaScript,C#或支持Lambda函数的其他类C语言的答案。

编辑:我已经找到了解决我尝试做的事情的方法:

[(lambda f: (lambda x: f(lambda v: x(x)(v)))(lambda x: f(lambda v: x(x)(v))))(lambda f:(lambda n: n if n<2 else f(n-1)+f(n-2)))(y) for y in range(10)]

我知道这根本不实用,不应该使用这种方法,但是我关心CAN这样做,而不是我应该这样做。

5 个答案:

答案 0 :(得分:3)

您需要将lambda分配给实际变量,然后调用 lambda中的lambda:

>>> g = lambda f: f if f < 2 else g(f-1)+g(f-2)
>>> [g(n) for n in range(10)]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

答案 1 :(得分:3)

我有一个符合您标准的单行解决方案,但它是我写过的最疯狂的代码之一。它没有使用列表理解,但它将动态解决方案和lambda函数混合在一行中。

fib = (lambda n: (lambda fib: fib(fib, [], n, None))(lambda fib, arr, i, _: arr if i == 0 else fib(fib, arr, i-1, arr.append(1) if len(arr) < 2 else arr.append(arr[-1]+arr[-2]))))

只是解释一下。第一部分(lambda fib: fib(fib, [], n, None))将lambda函数作为参数,然后使用它期望的参数调用它。这个技巧允许我们为lambda函数指定一个名称并将此名称传递给它自己。这就是魔术。

而是第二部分,核心功能lambda fib, arr, i, _: arr if i == 0 else fib(fib, arr, i-1, arr.append(1) if len(arr) < 2 else arr.append(arr[-1]+arr[-2])))使用另一种技巧来实现动态解决方案。第一个参数fib是对自身的引用,第二个参数arr是包含我们解决方案的数组,它从左到右填充,以递归方式fib完全调用n }次。当第三个参数i变为0时,递归结束。第四个参数是一个丑陋的技巧:函数不使用它,但它用于调用arr的追加方法。

这绝对是不太优雅的解决方案,但它也是最快的解决方案。我报告下面N=500的时间安排。

天真的解决方案是不可行的,但是在这里你可以找到在一系列时间计算一个元素的代码(这可能就是你想要混合lambda函数和递归):

(lambda n: ((lambda fib: fib(fib,n+1))(lambda fib, i: (1 if i <= 2 else fib(fib,i-2) + fib(fib,i-1)))))(N)

@cdlane提出的解决方案:

%timeit [0, 1] + [(4<<n*(3+n)) // ((4<<2*n)-(2<<n)-1) & ((2<<n)-1) for n in range(N)][1:]
10 loops, best of 3: 88.3 ms per loop

@lehiester提出的解决方案:

%timeit [int(round((lambda n: ((1+5**0.5)**n-(1-5**0.5)**n)/(2**n*5**0.5))(x))) for x in range(N)]
1000 loops, best of 3: 1.49 ms per loop

我丑陋的解决方案:

%timeit (lambda n: (lambda fib: fib(fib, [], n, None))(lambda fib, arr, i, _: arr if i == 0 else fib(fib, arr, i-1, arr.append(1) if len(arr) < 2 else arr.append(arr[-1]+arr[-2]))))(N)
1000 loops, best of 3: 434 us per loop

另一个不使用递归的丑陋且更快的解决方案:

%timeit (lambda n: (lambda arr, fib_supp: [arr] +  [fib_supp(arr) for i in xrange(n)])([], (lambda arr: arr.append(1) if len(arr) < 2 else arr.append(arr[-1]+arr[-2])))[0])(N)
1000 loops, best of 3: 346 us per loop

更新

最后,我找到了一种优雅的方式来制定单行功能。这个想法总是一样的,但使用 setitem 方法而不是追加。我使用的一些技巧可以在link找到。这种方法稍慢,但至少是可读的:

%timeit (lambda n: (lambda arr, fib_supp: any(fib_supp(i, arr) for i in xrange(2,n)) or arr)([1] * n, (lambda i, arr: arr.__setitem__(i,(arr[i-1]+arr[i-2])))))(N)
1000 loops, best of 3: 385 us per loop

答案 2 :(得分:2)

怎么样:

(lambda f: (4 << f * (3 + f)) // ((4 << 2 * f) - (2 << f) - 1) & ((2 << f) - 1))(n)

它不会以通常的方式启动序列:

0, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, ...

但是一旦你超过1,你就没事了。您可以在博客条目https://eidand.com/2015/03/28/authorization-system-with-owin-web-api-json-web-tokens/中找到详细说明以及大量相关信息。

在我的系统上,@ lehiester的基于黄金比率的解决方案在F71上脱轨,产生308061521170130,而不是308061521170129,并继续偏离那里。

答案 3 :(得分:2)

通过Python

lambda演算

由于这是用 lambda-calculus 标记的,而不是写一个依赖于特定于python的聪明技巧或语言特性的答案,我只会使用简单的lambdas

U = lambda f: f (f)

Y = U (lambda h: lambda f: f (lambda x: h (h) (f) (x)))

loop = Y (lambda recur: lambda acc: lambda a: lambda b: lambda n:
  acc if n == 0 else
    recur (acc + [a]) (a + b) (a) (n - 1))

fibonacci = loop ([]) (0) (1)

print (fibonacci (10))
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

当然我们使用了四个名为 lambdas UYloopfibonacci - 因为每个lambda都是纯函数,我们可以用其值

替换对其名称的任何引用
# in Y, replace U with its definition
Y = (lambda f: f (f)) (lambda h: lambda f: f (lambda x: h (h) (f) (x)))
# in loop, replace Y with its definition
loop = (lambda f: f (f)) (lambda h: lambda f: f (lambda x: h (h) (f) (x))) (lambda recur: lambda acc: lambda a: lambda b: lambda n:
  acc if n == 0 else
    recur (acc + [a]) (a + b) (a) (n - 1))
# in fibonacci, replace loop with its definition
fibonacci = (lambda f: f (f)) (lambda h: lambda f: f (lambda x: h (h) (f) (x))) (lambda recur: lambda acc: lambda a: lambda b: lambda n:
  acc if n == 0 else
    recur (acc + [a]) (a + b) (a) (n - 1)) ([]) (0) (1)

fibonacci现在是一个单纯的表达式 - 我们可以直接在print语句中调用lambda ......

# in print, replace fibonacci with its definition
print ((lambda f: f (f)) (lambda h: lambda f: f (lambda x: h (h) (f) (x))) (lambda recur: lambda acc: lambda a: lambda b: lambda n:
  acc if n == 0 else
    recur (acc + [a]) (a + b) (a) (n - 1)) ([]) (0) (1) (10))
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

再次使用其他程序

我写了一个非常similar answer(也在Python中),但在不同程序的上下文中 - 帮助查看技术的通用性可能会有用

再次使用其他语言

我们将在JavaScript中再次完成整个过程。 JS更适合演示这个练习,因为我们可以在浏览器中显示这里的代码,并且lambda语法更宽松(在代码格式方面) - 除此之外,你将会注意程序几乎相同

const U = f =>
  f (f)

const Y =
  U (h => f => f (x => h (h) (f) (x)))

const loop = Y (recur => acc => a => b => n =>
  n === 0
    ? acc
    : recur (acc.concat ([a])) (a + b) (a) (n - 1))

const fibonacci =
  loop ([]) (0) (1)

console.log (fibonacci (10))
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

// in Y, replace U with its definition
Y = (f => f (f)) (h => f => f (x => h (h) (f) (x)))
// in loop, replace Y with its definition
loop = (f => f (f)) (h => f => f (x => h (h) (f) (x))) (recur => acc => a => b => n =>
  n === 0
    ? acc
    : recur (acc.concat ([a])) (a + b) (a) (n - 1))
// in fibonacci, replace loop with its definition
fibonacci = (f => f (f)) (h => f => f (x => h (h) (f) (x))) (recur => acc => a => b => n =>
  n === 0
    ? acc
    : recur (acc.concat ([a])) (a + b) (a) (n - 1)) ([]) (0) (1)

fibonacci现在是一个单纯的表达式 - 我们可以直接在console.log语句中调用lambda ......

  

哦,它的确实也很快

console.time ('fibonacci (500)')
console.log ((f => f (f)) (h => f => f (x => h (h) (f) (x))) (recur => acc => a => b => n =>
  n === 0
    ? acc
    : recur (acc.concat ([a])) (a + b) (a) (n - 1)) ([]) (0) (1) (500))
console.timeEnd ('fibonacci (500)')
// [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ... 490 more items ]
// fibonacci (500): 3 ms

练习完美

lambda演算是我的favourite areas of study之一。只用Y-combinator,我花了无数个小时exploring its beauty and sophistication。我希望你发现这些探索很有帮助。如果您对主题有任何其他问题,请不要害羞地问^ _ ^

答案 4 :(得分:1)

你必须以某种方式为它指定一个名称才能使用递归定义 - 否则在Python中不可能使用递归lambda函数,因为它没有任何引用它的特殊反身关键字。

正如@TerryA所提到的,您可以使用this post中的技巧,以便在一个带有递归定义的语句中生成x斐波那契数字序列。

或者,您可以使用closed form,这会更快:

[int(round((lambda n: ((1+5**0.5)**n-(1-5**0.5)**n)/(2**n*5**0.5))(x)))
 for x in range(10)]

这假设x不是很大,因为浮点运算将在x=600周围溢出,并且在该点之前可能会有大的舍入误差 - 正如@cdlane指出的那样,这开始偏离实际x=71处的序列,即x in range(72)

编辑:@cdlane共享一个只有整数算术的封闭形式,理论上应该适用于任何x。我可能会用这个而不是上面的表达式。

[0, 1] + [(4<<n*(3+n)) // ((4<<2*n)-(2<<n)-1) & ((2<<n)-1)
          for n in range(10)][1:]