c#擴展方法奇思妙用變態篇一:由Fibonacci數列引出“委託擴展”及“遞推遞歸委託”

 

先回顧一個數列的概念:按一定次序排列的一列 數 稱爲數列...(請參見百度百科:數列)
幾個簡單的數列:
      1, 1, 1, 1, 1, 1, 1...                //數列1
      0, 1, 2, 3, 4, 5, 6, 7...                //數列2
      0, 1, 4, 9, 16, 25, 36, 49...        //數列3
通項公式的定義:數列的第n項與項的序數這間的關係,也就是數列生成算法
上面幾個數列可表示爲
       An = F(n) = 1
       An = F(n) = n
       An = F(n) = n * n

有了數列和通項公式的定義,我們的任務就好描述了:
      用最簡潔的代碼描述通項公式,用最簡潔算法生成數列的前N個數。

在此要求下,用常規代碼是做不到簡潔的,這裏我們用lambda表達式描述通項公式:

        //數列1 通項公式
        public static Func<intint> fun1 = n => 1;
        
//數列2 通項公式
        public static Func<intint> fun2 = n => n;
        
//數列3 通項公式
        public static Func<intint> fun3 = n => n * n;

 

lambda表達式是不是與數學公式很像啊!

再來看生成算法,這裏用了一個不一般的擴展:

        /// <summary>
        
/// 生成隊列的前count項
        
/// </summary>
        
/// <param name="func">通項公式</param>
        
/// <param name="count">生成的數量</param>
        
/// <returns>隊列前count項</returns>

        public static IEnumerable<int> GetSequence(this Func<intint> func, int count)
        
{
            
for (int i = 0; i < count; i++yield return func(i);
        }

 

相信大家見的擴展大多針對類(object, string)、接口(IEnumerable<T>)進行擴展,針對Func(委託)估計對大多數人來說都是第一次。
這個擴展就是標題中說的“委託擴展”,感覺很怪吧,很彆扭吧,很別管太多,看看怎麼調用吧:

        public static void Test1()
        {
            
int[] ints1 = fun1.GetSequence(10).ToArray();      //1, 1, 1, 1
            int[] ints2 = fun2.GetSequence(10).ToArray();      //0, 1, 2, 3
            int[] ints3 = fun3.GetSequence(10).ToArray(); ;    //0, 1, 4, 9
        }

自我感覺比較簡潔,而且將生成數列(GetSequence)與數列算法(通項公式)分開,也達到了生成數列(GetSequence)的複用。

上面幾個數列比較簡單,現在來看Fibonacci,
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55...
用圖形表示如下:

這個序列在大家學習c語言遞推遞歸時都接觸過,這個序列很神奇,請參看維基百科:斐波那契數列
它的通項公式是 An = F(n) = n                          n =0, 1
                                       F(n-1) + F(n-2)      n>1
注意:關於這數列有的是從n從0開始,有的是從1開始,這裏不計較。

遞推遞歸算法如下,容易理解效率確很低!!

        public static int GetFibonacci(int n)
        {
            
if (n > 1return GetFibonacci(n - 1+ GetFibonacci(n - 2);
            
else return n;
        }

本文是爲了引出遞推遞歸委託,暫不是算法的效率
下面就要大(改)變(形)態了。

不考慮 <1 的情況

        public static Func<intint> Fibonacci = n => Fibonacci(n - 1+ Fibonacci(n - 2);

與數學通項式對比一下,何其相似!這就是我們的“遞推遞歸委託”!

考慮所有情況,完成Fibonacci,如下

        public static Func<intint> Fibonacci = n => n > 1 ? Fibonacci(n - 1+ Fibonacci(n - 2) : n;

實在感嘆c#精簡的語法,一句代碼可以表示一個遞推遞歸!
調用測試下吧!

        public static void Test2()
        {  
            
//委託擴展方法 + 遞推遞歸委託
            int[] fibonacciSequence = Fibonacci.GetSequence(12).ToArray();
        }

當然這個生成算法效率不是一般的低!

最後給出一個數學推導出的精確算法

        public static Func<intint> Fibonacci2 = n => (int)(5.0.Pow(-0.5* ((0.5 * (1 + 5.0.Pow(0.5))).Pow(n + 1- (0.5 * (1 - 5.0.Pow(0.5))).Pow(n + 1)));
        
//Pow擴展,簡化調用
        public static double Pow(this double x, double y)
        {
            
return Math.Pow(x, y);
        }


一點意見:像這樣代碼,最好是給封裝起來,否則會很麻煩的。
這篇文章是給極少數人看的(啓發一下),看完後封裝好給大多數人用。這是也“變態篇”系列文章的宗旨.
希望大家對 “委託擴展” 和 “遞推遞歸委託”提些看法,名字定義不太好,請指正!

---------------------------------------------------------------------------------------------------------- 
以下爲 2009年8月10日20時52分 追加內容
----------------------------------------------------------------------------------------------------------

看了大家的回覆,對我鼓勵很大,尤其是 裝配腦袋 給出的解法(在#7樓)給了我很大的啓發,於是我也忍不住把 裝配腦袋 的算法改進了一下:

public static Func<Point, Point> f = p => { p.X += p.Y; p.Y = p.X - p.Y; return p;  }; 
public static Func<Point, Func<Point, Point>int, Point> g1 = (p, f, n) =>  n > 0 ? g1(f(p), f, n - 1
) : p;
public static Func<Point, Func<Point, Point>int, Point> g2 = (p, f, n) =>  n > 0 ? f(g2(p, f, n - 1)) : new Point(01
); 

public static void
 Test8()
{
    
//測試 generate1

    Point p1 = g1(new Point(01), f, 3);
    
for (int i = 0; i < 12; i++
)
        Console.WriteLine(g1(
new Point(01
), f, i));
    
//測試 generate2

    Point p2 = g2(default(Point), f, 3);
    
for (int i = 0; i < 12; i++
)
        Console.WriteLine(g2(
default
(Point), f, i));
}

這裏用到Point (System.Drawing中的),因爲它能包含兩個整數,Fibonacci又是前兩項之和,所以...
以下是調試運行的結果:

輸出

兩列Fibonacci,不過第二列剛開始不對。
g1和g2是兩種算法,看上去很相似,有什麼不同呢,設個斷點單步調試(F11)下吧!
上面的代碼還不夠簡潔,最後將f與g1合在一起,如下:

public static Func<Point, int, Point> g3 = (p, n) => n > 0 ? g3(new Point(p.X + p.Y, p.X), n - 1) : p;

測試調用代碼如下:


        public static void Test9()
        
{
            
//測試 generate3
            Point p3 = g3(new Point(01), 3);
            
for (int i = 0; i < 12; i++)
                Console.WriteLine(g3(
new Point(1,0), i));
        }

幾種算法的優點和缺點大家來評判吧!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章