关于递归和Lambda,用Fixed Point Combinator实现
为了简化起见,我们用一个最简单的递归例子来展开讨论。
计算 sum(1 to n),f(n) (n > 0,不考虑负数)可以表达为:
f = 1 when n = 1, f = n + f(n - 1) when n > 1。
一上来,很多人肯定想到这么写:
- C# code
Func<int, int> f = x => { if (x == 1) return 1; else return x + f(x - 1); };
cnblog的装配脑袋给出了最佳实践,Fixed Point Combinator,不动点组合子。老赵也引用了这一做法,但是没有给出解释。当然,数学好的朋友理解起来不难,但是大多数人,比如我,数学很菜。所以我想试着帮助老赵来解释下。
这一做法的本质是,将递归展开变成递推函数,然后计算。
假设 x = 1,那么 f(x) 被调用 1 次。我的表述可能不正规,姑且记作 f1(1) = f(1) = 1
假设 x = 2,那么 f(x) 被调用 2 次,求 f(2) 的函数也可以表述成 f2(2) = 2 + f1(1) = 2 + 1
假设 x = 3,那么 f(x) 被调用 3 次,求 f(3) 的函数也可以表述成 f3(3) = 3 + f2(2) = 3 + 2 + f1(1) = 3 + 2 + 1
所谓不动点,我的理解就是这里的f1 f2 f3 ...就是不动点。(我数学不好,这么理解是否可以请指正)
也就是说,对于任意 x = n,我们都可以找到一个等效的函数fn,比如说 f100 = 100 + 99 + 98 + ... + 1。
绕了一圈回来了,的确用递归算数字求和小题大做。但是这说明了递归可以被展开的事实。
然后我们想,既然 f(x) 可以用 fx(x) 来代替,那么我们可以这么做:给定一个x,我们不用f(x),而是展开成fn来计算。
比如:
Func<int, int> GetFn(n)
{
if (n == 1) return x => 1 //注意,当 n = 1 的时候,fn(n) = f(n),这就是说计算1+2+...+100的函数你拿来1+2+...+10肯定不对了。
if (n == 2) return x => 1 + 2;
if (x == 3) return x => 1 + 2 + 3;
...
}
调用 int sum = GetFn(n)(n);
你要说了,多么傻的办法,再说 n 也没有个头,你这个造到哪里去。
要是计算机自动能自动构造出一个求和的函数就好了。
重点来了。
我们从API调用者的角度看,我们需要这么一个函数:
输入一个原函数,自动生成一个可以计算n的展开函数(不动点函数)。
顺便说下,这种让计算机自动产生代码的编程方式叫做 meta programming(元编程)。
对于一个 Func<int, int>的原函数,它的自动生成不动点函数的调用可以这么写:
- C# code
static Func<T, TReturn> FixedPointCombinator<T, TReturn>(Func<Func<T, TReturn>, Func<T, TReturn>> func){}
怎么解决?
没错,就是这个公式:
fix(F)(n) = F(fix(F))(n) 参考:http://zh.wikipedia.org/wiki/%E4%B8%8D%E5%8A%A8%E7%82%B9%E7%BB%84%E5%90%88%E5%AD%90
- C# code
static Func<T, TReturn> FixedPointCombinator<T, TReturn>(Func<Func<T, TReturn>, Func<T, TReturn>> func){ return x => func(FixedPointCombinator(func))(x);}
函数有了,我们调用下:
- C# code
static void Main(string[] args){ var func = FixedPointCombinator<int, int>(f => x => { if (x == 1) return 1; else return x + f(x - 1); }); Console.WriteLine(func(100)); }
运算结果,5050,正确。
完整的程序:
- C# code
using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace ConsoleApplication1{ class Program { static Func<T, TReturn> FixedPointCombinator<T, TReturn>(Func<Func<T, TReturn>, Func<T, TReturn>> func) { return x => func(FixedPointCombinator(func))(x); } static void Main(string[] args) { var func = FixedPointCombinator<int, int>(f => x => { if (x == 1) return 1; else return x + f(x - 1); }); Console.WriteLine(func(100)); } }}
再想一想。
一般的递归就是直接调用递归函数,自己调用自己求解。
Fixed Point Combinator,将递归函数作为参数(模板),内部用递归方法构造不动点算子(展开递归函数),最后将对递归的调用转化为对不动点函数的调用,求解。
好了,我写完了,不知道大家理解了没有。其实很简单的。
[解决办法]
我的理解就是一方法套一匿名代理, 很有技巧。。。
------解决方案--------------------
Y Combinator
c#的实现
[解决办法]
[解决办法]
恩。很早之前看过Y算子。不过有简化的嵌套递归写法。
- C# code
using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace ConsoleApplication1{ class Program { static void Main(string[] args) { var func = null;//先声明为null就可以嵌套,编译通过了 func = f => x => (x == 1)?1:x + func(x - 1); }; Console.WriteLine(func(100)); } }}
[解决办法]
擦。多了
- C# code
using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace ConsoleApplication1{ class Program { static void Main(string[] args) { var func = null;//先声明为null就可以嵌套,编译通过了 func = x => (x == 1)?1:x + func(x - 1); }; Console.WriteLine(func(100)); } }}
[解决办法]
func = x => (x == 1) ? 1 : x + func(x - 1);
er...
没编译器。再修改一次。
[解决办法]
- C# code
//以前看到的using System;using System.Collections.Generic;public class Test{delegate T SelfApplicable<T>(SelfApplicable<T> self);private static void Main(){ SelfApplicable<Func<Func<Func<int, int>, Func<int, int>>, Func<int, int>>> Y = y => f => x => f(y(y)(f))(x); Func<Func<Func<int, int>, Func<int, int>>, Func<int, int>> Fix = Y(Y); Func<Func<int, int>, Func<int, int>> F = fac => x => x == 0 ? 1 : x * fac(x - 1); Func<int, int> factorial = Fix(F); System.Console.WriteLine(factorial(4).ToString());}}
[解决办法]
[解决办法]
[解决办法]
cnblog的装配脑袋
ps: 脑袋早期是混CSDN的,ID :Ninputer
[解决办法]
[解决办法]
再说两句题外话,刚看了cj和郑晖老师的Q&A.
回复中三句标黑的句子,两句是真传。(另外那句也是可以理解的 :) )
对于Programer
英语要比想象中更为重要
数学很重要
第二句没有全部标红,是因为Programer 所从事的内容是不一样的,数学对他们的重要性也是不一样的
但最基本的是数学带动的思维方式,可以帮助我们学会用归纳、抽象来应对变化,这个是核心。
虽然郑晖老师略感抱歉的表示没说什么具体的建议,但实则已将准则写了出来。
虽说 “真传一页纸,假传万卷书”
但没有万卷书做为基础,那一页纸的真传是领悟不了的。
这个道理,相信你深深懂得,我也只是给初学者铺路而已。
打雷啦!! 下雨收衣服啊!!!
[解决办法]
实在看不下去帖子里的内容。
过来支持两下。
其实我觉得数学和英语都很重要。
不过我认为还有一个更重要的在于坚持。
[解决办法]
呵呵,数学没看出来,到是在语文的前后语句的逻辑上,感觉有点奇怪,疑惑的地方是:
引用您的原话:
但是大多数人,比如我,数学很菜。所以我想试着帮助老赵来解释下。
为什么是这样的逻辑呢?数学越不好=》越应该去解释这种问题?呵呵,可能是我文科不好,没能理解您深层次上的逻辑=》数学越不好=》越应该去解释技术问题,但是就我个人看来感觉罗嗦上有点别扭!但是也许这么说也是没错的!
不好意思,如果是我指正错误,就当我没说!
[解决办法]
[解决办法]
[解决办法]
顶一个先。。
正在安装VS2010,以前也自学过一点Lamada,对此也非常感兴趣,无奈公司装的VS2005,这两天闲下来了看下递归和LAMADA,一会儿编译下9 楼 claymore1114 同学的代码。。
学习中。。。
此贴收藏了。。
[解决办法]
太蛋疼了,包括MSDN推荐的方法都很蛋疼,看看我的blog吧,看看什么才是lambda递归,其实非常非常非常的简单,几乎不需要任何冗余语句。要解决直接编译会编译错误的解决方案,就是先定义一个func f=null
更简洁的使用lambda递归
http://blog.csdn.net/aimeast/archive/2010/03/12/5375239.aspx
- C# code
static void Main(string[] args) { //这里要注意!! //必须要先赋值后才可以进行递归定义。否则会编译出错 Func<int, int> fac = null; fac = (x => x == 0 ? 1 : x * fac(x - 1)); for (int i = 0; i < 10; i++) { Console.WriteLine("{0} : {1}", i, fac(i)); } }
[解决办法]
既然这样你不如去学F#,F#解决这类问题更优雅...前两年研究了几天,后来没心思看了...
[解决办法]
这是产生式编程得一个非常特别得例子,特别之处在于,他是最普通,最本质得,所以,是非常特别得函数,
而元编程含义大于产生式编程,产生式编程是人类,元编程是人类社会!!!!!!!!!!!!
[解决办法]
同意
- C# code
Func<int, int> f = null;f = x => x == 1 ? 1 : x + f(x - 1);
[解决办法]
所有得递归问题都可以转换成递推得推论是,任何一个功能,能被动态产生得递推实现,而这个动态产生式可以是一个字符串,而字符串,有可以被任意替换,这就是说任何一个功能可以被动态得替换成另一个功能!!!!!!!!!!!!!!!!!!!!!!!!!
[解决办法]
http://hi.baidu.com/feixue/blog/item/e809baa1bd3c549c47106429.html
在参考了本贴及其它资料后的总结:
本文主要总结了一些资料中对Y组合子的推导,最后参考其中一份C#代码,并给出了C++0x中的实现.
个人认为,直真正实现Y组合子的是文中出现的第一份C#代码,其它的实现借助了对函数递归的支持.
1.直接递归不行,因为在定义中出现了自己;
2.通过参数的形式把递归的部分传进去(记为F);
3.假设目标为f,于是F(f) = f,问题转换为求F的不动点;
4.知道高阶Fix满足性质:Fix(F) = F(Fix(F)),现在目标是找到这样的Fix;
5.两个推导思路
5.1
构造一个式子,使之满足F(f) = f;
如果具有A(b) = F(b(b))的形式的函数则w(A) = F(w(A));
如果构造一个函数以F为参数,返回w(A)则就是Y组合子;
得到Y = g => (b=>g(b(b))) (b=>g(b(b))).
5.2
构造一个组合子Y,接受一个参数y表示本身,还有一个参数f表示一个高阶函数,使得能
返回f的不动点;
于是Y有形式Y = y => f => ???;
根据定义???部分就是:y(y)(f);
但是现在的结果平凡的,只是没有意义地展开,注意到不动点性质,于是???可以是
f(y(y)(f));
但是上面得到的结果在惰性计算的情况下才可行,于是要构造一个传值的:
Y = y => f => x => f(y(y)(f))(x);
于是最后Y(Y)为所求.
6.根据5.2在C#中的实现.
首先Y的类型的参数类型和Y一样于是:
delegate T SelfApplicable<T>(SelfApplicable<T> self)
这样就构造了一个委托,其参数类型是自身,返回类型待定.
注意到5.2中的结果:
f => x => f(y(y)(f))(x);是返回的表达式,这个表达式的类型是
Func<int, int>
f的类型可以2推导出来.
fac => x => x == 0 ? 1 : x * fac(x - 1);
可以看到参数类型是fac : Func<int, int>
返回类型是: Func<int, int>
于是最后的T : Func<Func<Func<int, int>, Func<int, int>>, Func<int, int>>
得到如下C#代码:
- C# code
using System;using System.Collections.Generic;public class Test{delegate T SelfApplicable<T>(SelfApplicable<T> self);private static void Main(){ SelfApplicable<Func<Func<Func<int, int>, Func<int, int>>, Func<int, int>>> Y = y => f => x => f(y(y)(f))(x); Func<Func<Func<int, int>, Func<int, int>>, Func<int, int>> Fix = Y(Y); Func<Func<int, int>, Func<int, int>> F = fac => x => x == 0 ? 1 : x * fac(x - 1); Func<int, int> factorial = Fix(F); System.Console.WriteLine(factorial(4).ToString());}}
[解决办法]
装配脑袋有些纠结于vb.net,所以对以前版本的vb.net对lamda只支持单行而不支持c#那种多行(例如
- C# code
Func<int, int> f = x =>{ var result = 0;begin: result += x; if (x == 1) return result; else { x--; goto begin; }};
[解决办法]
[解决办法]
不允许代码
- C# code
Func<int, int> f = x => { if (x == 1) return 1; else return x + f(x - 1); };
[解决办法]
既然只是编译器造成的问题(而不是运行时的问题),这就非常好解决,而不必使用很复杂的理论(我相信所有人都喜欢简单的方法办大事,而不喜欢复杂的方法办小事)。在一个需要lamda表达式得地方,如果你觉得不好写,那么就另外写一个实名的函数,然后在这个需要lamda表达式的地方调用它(或者表达式其中一部分调用它)这就行了。除非我们遇到了不允许编写自定义函数的开发平台,才需要去研究更复杂的理论。
[解决办法]
[解决办法]
lambda执行效率上还是欠佳,内置函数比不过设计的算法。不知道大家有没有注意
[解决办法]
- C/C++ code
//c++中,虽然在定义语句中并没有完全定义,但是在里面这个标识符已经可以用了//以前论坛中有人提到过的一个问题就是int x = x;何解function<int (int)> fac = [&](int x)->int{return x == 0 ? 1 : x * fac(x-1);};