先回顧一個數列的概念:按一定次序排列的一列 數 稱爲數列...(請參見百度百科:數列)
幾個簡單的數列:
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表達式描述通項公式:
public static Func<int, int> fun1 = n => 1;
//數列2 通項公式
public static Func<int, int> fun2 = n => n;
//數列3 通項公式
public static Func<int, int> fun3 = n => n * n;
lambda表達式是不是與數學公式很像啊!
再來看生成算法,這裏用了一個不一般的擴展:
/// 生成隊列的前count項
/// </summary>
/// <param name="func">通項公式</param>
/// <param name="count">生成的數量</param>
/// <returns>隊列前count項</returns>
public static IEnumerable<int> GetSequence(this Func<int, int> func, int count)
{
for (int i = 0; i < count; i++) yield return func(i);
}
相信大家見的擴展大多針對類(object, string)、接口(IEnumerable<T>)進行擴展,針對Func(委託)估計對大多數人來說都是第一次。
這個擴展就是標題中說的“委託擴展”,感覺很怪吧,很彆扭吧,很別管太多,看看怎麼調用吧:
{
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開始,這裏不計較。
遞推遞歸算法如下,容易理解效率確很低!!
{
if (n > 1) return GetFibonacci(n - 1) + GetFibonacci(n - 2);
else return n;
}
本文是爲了引出遞推遞歸委託,暫不是算法的效率
下面就要大(改)變(形)態了。
不考慮 <1 的情況
與數學通項式對比一下,何其相似!這就是我們的“遞推遞歸委託”!
考慮所有情況,完成Fibonacci,如下
實在感嘆c#精簡的語法,一句代碼可以表示一個遞推遞歸!
調用測試下吧!
{
//委託擴展方法 + 遞推遞歸委託
int[] fibonacciSequence = Fibonacci.GetSequence(12).ToArray();
}
當然這個生成算法效率不是一般的低!
最後給出一個數學推導出的精確算法
//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, 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(0, 1);
public static void Test8()
{
//測試 generate1
Point p1 = g1(new Point(0, 1), f, 3);
for (int i = 0; i < 12; i++)
Console.WriteLine(g1(new Point(0, 1), 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 void Test9()
{
//測試 generate3
Point p3 = g3(new Point(0, 1), 3);
for (int i = 0; i < 12; i++)
Console.WriteLine(g3(new Point(1,0), i));
}
幾種算法的優點和缺點大家來評判吧!