文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

怎么使用Lambda表达式编写递归

2023-06-17 09:05

关注

本篇内容主要讲解“怎么使用Lambda表达式编写递归”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎么使用Lambda表达式编写递归”吧!

使用 Lambda 表达式构建递归函数

很多朋友认为这很容易,随手便可用 lambda 表达式写出一个阶乘递归:

Func<int, int> fact = x => x <= 1 ? 1 : x * fact(x - 1);

不过,很抱歉,这行代码是无法通过编译的,VS 提示:使用了未赋值的变量 fact。

有种简单的解决办法,把上面这行代码拆成两行:

Func<int, int> fact = null;  fact = x => x <= 1 ? 1 : x * fact(x - 1);

不过这种写法也有问题,老赵说得比较清楚,我就不在赘述了,请查看《使用Lambda表达式编写递归函数》一文中伪递归部分。

那么如何解决 lambda 表达式构建递归函数的问题呢?根据函数式编程理论,我们可以使用不定点组合子。

在学习不定点组合子之前,需要先了解更基础 &lambda; 演算。

&lambda; 演算

&lambda; 演算的基础请大家参考维基百科:

http://zh.wikipedia.org/wiki/Lambda_演算

http://en.wikipedia.org/wiki/Lambda_calculus

非形式化的描述

请确保你已经理解了文中几个表达式的等价关系:

(&lambda;f. f 3)(&lambda;x. x + 2) == (&lambda;x. x + 2) 3 == 3 + 2   (&lambda;x. &lambda;y. x - y) 7 2 == (&lambda;y.7 - y) 2 == 7 - 2

还清楚知道函数应用(application)的概念及其左结合性:

f x y == (f x) y

还有它的各种等价变换

f x y == (f x) y = (f(x))y = (f(x))(y) = f(x)(y)

归约

并会运用三个常用的规约(Reduction)

&alpha;-变换(&alpha;-conversion)

&beta;-归约(&beta;-reduction)

&eta;-变换(&eta;-conversion)

不动点组合子

请参考:

http://zh.wikipedia.org/wiki/不动点组合子

http://en.wikipedia.org/wiki/Fixed-point_combinator

定义

不动点组合子(Fixed-point combinator,或不动点算子,使用 FIX 表示)是计算其他函数的一个不动点的高阶函数。

不动点算子具有以下特性,对于任何函数 f 都有:

FIX ff = f (FIX f)

定义匿名的递归函数

不动点组合子允许定义匿名的递归函数,具体来说是将一个非递归的单步函数(只执行递归中的一步,a single step of this recursion,使用 g 表示)转换为递归函数。

如下是阶乘的单步函数定义:

g = &lambda;f. &lambda;n. (ISZERO n) 1 (MULT n (f (PRED n)))

FIX g 可获取到匿名的递归函数:

FIX g = &lambda;n. (ISZERO n) 1 (MULT n ((FIX g) (PRED n)))

向 FIX g 的参数 n 传入值 5,可最终得出:

FIX g 55 = 5 * (4 * (3 * (2 * (1 * 1)))) = 120

常用的不定点组合子

不动点组合子中最有名的(也可能是最简单的)是 Y 组合子:

Y = &lambda;f. (&lambda;x. f (x x)) (&lambda;x. f (x x))

另一个常见不动点组合子是图灵不动点组合子(阿兰&middot;图灵发现的):

&Theta; = (&lambda;x. &lambda;y. (y (x x y))) (&lambda;x.&lambda;y.(y (x x y)))

传值调用(call-by-value)

在 &lambda; 演算中,每个表达式(lambda term)都代表一个只有单独参数的函数,这个函数的参数本身也是一个只有单一参数的函数,同时,函数的值是又一个只有单一参数的函数。

根据此描述,可知 &lambda; 演算中函数只有一个参数,这个参数是一个函数,而不是一个值。而对于我们常见的递归(阶乘、斐波那契数列求值),参数都是值(自然数)。两者是不匹配的。

为了适当传值调用,需要将不动点组合子 &eta;-展开:

简单而言 &eta;-变换 是说 &lambda;x. f x 和 f 可以互相转换。从 f 这种简单形式 &eta;-变换 为 &lambda;x. f x 复杂形式,称为 &eta;-展开

对于 Y 组合子,通常是将其中的 (x x) &eta;-展开为  &lambda;y. x x y,由此得出传值调用版本的 Y组合子(也称为 Z 组合子):

Y = &lambda;f. (&lambda;x. f (&lambda;y. x x y)) (&lambda;x. f (&lambda;y. x x y))

如果不展开呢?会怎样?

如果使用不展开的不动点算子,也能写出可编译通过的代码,但最终执行会陷入死循环,直至堆栈溢出。

小结

后续章节将使用以下符号和名称,不再另行说明:

FIX:不动点组合子

g:单步函数

n:表示递归函数的参数(在阶乘、斐波那契数列求值中是一个自然数)

对于 FIX、g、n:

FIX g: 将会生成对应的递归函数

FIX g n: 将进行递归运算

&lambda; 演算表达式与 c# lambda 表达式的对应关系

&lambda;x. x + 2

&lambda;x. x + 2 在 c#中的 lambda 表达式可表式为:x => x+ 2;

假定 x 的 int 类型,可写作:

Func<int, int> f = x => x + 2;

相应 (&lambda;x. x + 2) 1 可写为:

var result = f(1);    // 结果为 3

&lambda;x. &lambda;y. x + y

复杂点,&lambda;x. &lambda;y. x + y 用 c# 的 lambda 表达式表示为:x => y => x + y;

x, y 类型为均整数时,可写作:

Func<int, Func<int, int>> f = x => y => x + y;

相应 (&lambda;x. &lambda;y. x + y) 1 2 便是:

var result = f(1)(2);     // 结果为 3

&lambda;x. &lambda;y. &lambda;z. x + y + z

再复杂些,&lambda;x. &lambda;y. &lambda;z. x + y + z 表示为:x => y=> z => x + y + z,三个参数都为 int 时 c# 代码:

Func<int, Func<int, Func<int, int>>> f = x => y => z => x + y + z;

可如下调用:

// (&lambda;x. &lambda;y. &lambda;z. x + y + z) 1 2 3  var result1 = f(1)(2)(3);    //结果为 6   // (&lambda;x. &lambda;y. &lambda;z. x + y + z) 1  &rarr;  &lambda;y. &lambda;z. 1 + y + z   Func<int, Func<int, int>> g = f(1);   // (&lambda;y. &lambda;z. 1 + y + z) 2  &rarr;  &lambda;z. 1 + 2 + z  &rarr;  &lambda;z. 3 + z  Func<int, int> h = g(2);  // (&lambda;z. 3 + z) 3  &rarr;  3 + 3  &rarr;  6  var result2 = h(3);        // 结果为 6

每 5 行,向 f 传入一个常量 1,返回一个新的方法 g;再经过第 7 行,向 g 传入常量 2,再次返回一个新方法 h。

方法 h 只能接受一个参数,***得出 h(3) = 6。

为什么不是  (x, y, z) => x + y + z?

也许你会有疑问,不就是 x、y、z 三个整数加起来嘛,为什么搞这么复杂,像下面这样不是更简单吗?

Func<int, int, int, int> f = (x, y, z) => x + y + z;  var result = f(1, 2, 3);

确实简单,不过:

在 &lambda; 演算中,每个表达式(lambda term)都代表一个只有单独参数的函数,这个函数的参数本身也是一个只有单一参数的函数,同时,函数的值是又一个只有单一参数的函数。

注意都是只有一个参数,对应到 c# 的 lambda 表达式,也应是一个参数,所以是:x => y=> z => x + y + z。

总结

&lambda; 演算表达式c# lambda 表达式
&lambda;x. x + 2x => x+ 2
&lambda;x. &lambda;y. x + yx => y => x + y
&lambda;x. &lambda;y. &lambda;z. x + y + zx => y=> z => x + y + z

好像有些规律:对于一个 Lambda terms,去掉“&lambda;”并把“.”替换为”=>”便可变成对应 lambda 表达式。(注意,这个规律不严谨!)

练习一下,看看下面这个如何转为 lambda 表达式:

&lambda;x. &lambda;n. (g (x x) n)

先对它进行一步演算得出:

&lambda;x. &lambda;n. (g (x(x)) (n))

运用上的的规律,可以写出 lambda 表达式:x => n => g((x(x))(n)

对于复杂点的如:&lambda;x. f ( &lambda;v. (x x) v),这条规律就不适用了。文后续部分会通过演算绕开这种复杂的转换,不对此进行讨论。

理解本文中的泛型和 lambda 表达式

对于上一部分使用的泛型和 lambda 表达式,尤其是下面这行代码,你需要花点时间去理理思路(因为后续章节中泛型要远比此复杂):

Func<int, Func<int, Func<int, int>>> f = x => y => z => x + y + z;

如果对你对泛型和 lambda 认识不是非常深刻的话,难度有点大,不妨先从下面这个简单点的开始:

Func<int, Func<int, int>> f = x => y => x + y;

换种写法,或许有助于理解:

Func<int, Func<int, int>>  f = x => {                    Func<int, int> g =  y => { return x + y ;};      return g;  };

本文简单阐述了 lambda 构建递归函数的问题,粗略提及 &lambda; 演算及不动点组合子的知识,并总结了下 &lambda; 演算表达式与 c# lambda 表达式的对应关系。

到此,相信大家对“怎么使用Lambda表达式编写递归”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯