C#用鏈式方法表達迴圈嵌套_C#教程

來源:互聯網
上載者:User

一.起緣

故事緣於一位朋友的一道題:

朋友四人玩LOL遊戲。第一局,分別選擇位置:中單,上單,ADC,輔助;第二局新加入的夥伴要選上單,四人可選位置變為:中單,打野,ADC,輔助;要求,第二局四人每人不得選擇和第一局相同的位置,請問兩局綜合考慮有多少種位置選擇方式?

對於像我這邊不懂遊戲的人來講,看不懂。於是有了這個版本:

有4個人,4隻椅子,第一局每人坐一隻椅子,第二局去掉第2隻椅子,增加第5隻椅子,每人坐一隻椅子,而且每個人不能與第一局坐相同的椅子。問兩局綜合考慮,共有多少種可能的情況?

我一開始的想法是這樣的,4個人就叫ABCD:第1局可能數是4*3*2*1=24,如果A第1局選了第2張椅,則A有4種可能,否則A有3種可能。對B來講,如果A選了B第一局的椅,則B有3種可能,否則B有2種可能(排隊自己第一局和A第二局已選)……想到這裡我就暈了,情況越分越多。

二.原始的for嵌套

本來是一道數學題,應該由知識算出來有多少種,但我突然有個想法,不如用電腦窮舉出出來。一來可以為各種猜測提供一個正確的答案,二來或許可以從答案反推出(數學上的)計算方法。然後就寫了第1版:

static Seat data = new Seat();public static void Run(){for (int a = 0; a < 4; a++){if (data.IsSelected(0, a)) //第1局編號0。如果已經被人坐了。continue;data.Selected(0, a, "A"); //第1局編號0。A坐a椅。for (int b = 0; b < 4; b++){if (data.IsSelected(0, b))continue;data.Selected(0, b, "B");for (int c = 0; c < 4; c++){if (data.IsSelected(0, c))continue;data.Selected(0, c, "C");for (int d = 0; d < 4; d++){if (data.IsSelected(0, d))continue;data.Selected(0, d, "D");for (int a2 = 0; a2 < 5; a2++){if (a2 == 1)continue;if (data.IsSelected(1, a2)) //第2局編號1continue;if (data.IsSelected(0, a2, "A")) //如果第1局A坐了a2椅continue;data.Selected(1, a2, "A");for (int b2 = 0; b2 < 5; b2++){if (b2 == 1)continue;if (data.IsSelected(1, b2))continue;if (data.IsSelected(0, b2, "B"))continue;data.Selected(1, b2, "B");for (int c2 = 0; c2 < 5; c2++){if (c2 == 1)continue;if (data.IsSelected(1, c2))continue;if (data.IsSelected(0, c2, "C"))continue;data.Selected(1, c2, "C");for (int d2 = 0; d2 < 5; d2++){if (d2 == 1)continue;if (data.IsSelected(1, d2))continue;if (data.IsSelected(0, d2, "D"))continue;data.Selected(1, d2, "D");data.Count++; //可能的情況數加1Console.WriteLine("{0,5} {1}", data.Count, data.Current);data.UnSelected(1, d2);}data.UnSelected(1, c2);}data.UnSelected(1, b2);}data.UnSelected(1, a2);}data.UnSelected(0, d);}data.UnSelected(0, c);}data.UnSelected(0, b);}data.UnSelected(0, a); //A起身(釋放坐椅)}} 

部分運行結果:

說明:

1.ABCD是人名
2.“.”代表沒有人
3.位置是是座位
4.-左邊是第1局,右邊是第2局
5.數字是序號

1 A B C D .-B . A C D
2 A B C D .-C . A B D
3 A B C D .-D . A B C
4 A B C D .-D . A C B
5 A B C D .-B . D A C
6 A B C D .-C . B A D
7 A B C D .-D . B A C
8 A B C D .-C . D A B
9 A B C D .-B . D C A
10 A B C D .-D . B C A
11 A B C D .-C . D B A
12 A B D C .-B . A D C
...
262 D C B A .-B . C D A
263 D C B A .-B . D C A
264 D C B A .-C . D B A

算出來是264種。從答案上來看是每11種是一組,一組中第1局的坐法是相同的,也就是說對於第一局的每一種情況,第2局都是有11種不同的可能。而第一局的可能性是24,所以答案是24*11=264。而第2局為什麼是11種可能,後面再說。

三.想要鏈式寫法

主題來了,像剛才的第1版的寫法太死板太麻煩了。

如果能像這樣寫代碼就爽了:

obj.Try("A").Try("B").Try("C").Try("D").Try2("A").Try2("B").Try2("C").Try2("D").Write(); 

而這樣的代碼通常的邏輯是執行Try("A")方法,然後執行Try("A")它return的對象的Try("B")方法……,即是Try("B")方法只被執行1次,而我希望的是Try("B")方法被Try("A")內部迴圈調用n次,Try("C")方法又被Try("B")方法調用m次。想想第1版的for套for不難明白為什麼要追求這樣的效果。如果Try("A")執行完了,再去執行Try("B"),那麼Try("B")肯定不會被調用多次,所以得延遲Try("A")的執行,同理也延遲所有Try和Try2的執行。由於lambda表達天生有延遲計算的特性,於是很快寫出了第2版:

public static void Run2(){Try("A",() => Try("B",() => Try("C",() => Try("D",() => Try2("A",() => Try2("B",() => Try2("C",() => Try2("D",null))))))));}public static void Try(string name, Action action) //第1局{for (int i = 0; i < 4; i++){if (data.IsSelected(0, i))continue;data.Selected(0, i, name);if (action == null){Console.WriteLine(data.Current);}else{action();}data.UnSelected(0, i);}}public static void Try2(string name, Action action) //第2局{for (int i = 0; i < 5; i++){if (i == 1)continue;if (data.IsSelected(1, i))continue;if (data.IsSelected(0, i, name))continue;data.Selected(1, i, name);if (action == null){data.Count++;Console.WriteLine("{0,5} {1}", data.Count, data.Current);}else{action();}data.UnSelected(1, i);}}

結構更合理,邏輯更清晰,但是一堆lambda嵌套,太醜了,也不是我要的效果,我要的是類似這樣的:

obj.Try("A").Try("B").Try("C").Try("D").Try2("A").Try2("B").Try2("C").Try2("D").Write();

四.繼續向目標逼近。

由於要延遲,所以必須先把要被調用的方法的引用“告訴”上一級,當上一級執行for的時候,就能調用下一級的方法。於是我想到了一個“回調鏈”

所以,執行鏈式方法是在構造回調鏈,最後的方法再通過調用鏈頭(Head)的某個方法啟動真正要執行的整個邏輯。

延遲計算是從Linq借鑒和學習來的,構造Linq的過程並沒有執行,等到了執行ToList, First等方法時才真正去執行。

我想構造回調鏈每一步都是一個固定的方法,這裡隨便起用了T這個極簡短名稱,而每一步後期計算時要執行的方法可靈活指定。於是有了第3版:

static Seat data = new Seat(); //借用Seat儲存資料public Seat2(string name, Seat2 parent, Action<Seat2> method){this.Name = name;this.Parent = parent;if (parent != null)parent.Child = this;this.Method = method;}public static void Run(){new Seat2("A", null, me => me.Try()).T("B", me => me.Try()).T("C", me => me.Try()).T("D", me => me.Try()).T("A", me => me.Try2()).T("B", me => me.Try2()).T("C", me => me.Try2()).T("D", me => me.Try2()).P().Start();}public Seat2 T(string name, Action<Seat2> method){return new Seat2(name, this, method);}public void Try(){for (int i = 0; i < 4; i++){if (data.IsSelected(0, i))continue;data.Selected(0, i, this.Name);if (this.Child != null){this.Child.Method(this.Child);}data.UnSelected(0, i);}}public void Try2(){for (int i = 0; i < 5; i++){if (i == 1)continue;if (data.IsSelected(1, i))continue;if (data.IsSelected(0, i, this.Name))continue;data.Selected(1, i, this.Name);if (this.Child != null){this.Child.Method(this.Child);}data.UnSelected(1, i);}} 

五.解耦

這種調用方式,是滿意了。但是運算架構與具體的演算法耦合在一起,如果能把運算架構提取出來,以後寫具體的演算法也方便許多。於是經過苦逼的提取,測試,踩坑,最終出現了第4版:

//運算架構class ComputeLink<T> where T : ISeat{ComputeLink<T> Parent { get; set; } //父節點,即上一級節點ComputeLink<T> Child { get; set; } //子節點,即下一級節點T Obj { get; set; } //當前節點對應的演算法對象,可以看作業務對象public ComputeLink(T obj, ComputeLink<T> parent, Action<T> method){if (obj == null)throw new ArgumentNullException("obj");this.Obj = obj;this.Obj.Method = x => method((T)x);if (parent != null){this.Parent = parent;parent.Child = this;parent.Obj.Child = this.Obj;}}public static ComputeLink<T> New(T obj, Action<T> method){return new ComputeLink<T>(obj, null, method);}public ComputeLink<T> Do(T obj, Action<T> method){return new ComputeLink<T>(obj, this, method);}public ComputeLink<T> Head //鏈表的頭{get{if (null != this.Parent)return this.Parent.Head;return this;}}public void Action() //啟動(延遲的)整個計算{var head = this.Head;head.Obj.Method(head.Obj);}}interface ISeat{ISeat Child { get; set; }Action<ISeat> Method { get; set; }}

p.s.為什麼第4版是ISeat3而不是ISeat4呢,因為我本不把第1版當作1個版本,因為太原始了,出現第2版後,我就把第1版給刪除了。為了寫這篇文章才重新去寫第1版。於是原本我當作第3版的ISeat3自然地排到了第4版。

具體的"演算法"就很簡單了:

class Seat3 : ISeat3{static Seat data = new Seat();string Name { get; set; }public Seat3(string name){this.Name = name;}/// <summary>/// 解耦的版本/// </summary>public static void Run(){var sql = ComputeLink<Seat3>.New(new Seat3("A"), m => m.Try()).Do(new Seat3("B"), m => m.Try()).Do(new Seat3("C"), m => m.Try()).Do(new Seat3("D"), m => m.Try()).Do(new Seat3("A"), m => m.Try2()).Do(new Seat3("B"), m => m.Try2()).Do(new Seat3("C"), m => m.Try2()).Do(new Seat3("D"), m => m.Try2()).Do(new Seat3(""), m => m.Print());sql.Action();}public Action<ISeat3> Method { get; set; }public ISeat3 Child { get; set; }public void Try(){for (int i = 0; i < 4; i++){if (data.IsSelected(0, i))continue;data.Selected(0, i, this.Name);if (this.Child != null){this.Child.Method(this.Child);}data.UnSelected(0, i);}}public void Try2(){for (int i = 0; i < 5; i++){if (i == 1)continue;if (data.IsSelected(1, i))continue;if (data.IsSelected(0, i, this.Name))continue;data.Selected(1, i, this.Name);if (this.Child != null){this.Child.Method(this.Child);}data.UnSelected(1, i);}}public void Print(){data.Count++;Console.WriteLine("{0,5} {1}", data.Count, data.Current);}}

Seat3寫起來簡單,(Run方法內部)看起來舒服。通過鏈式寫法達到嵌套迴圈的效果。對,這就是我要的!

它很像linq,所以我直接給變數命名為sql。

•對於Try和Try2來講,要調用的方法最好從參數傳來,但是這樣就會增加Run方法中New和Do的參數複雜性,破壞了美感,所以經過權衡,Child和Method通過屬性傳入。這個我也不確定這樣做好不好,請各位大俠指點。

•還有一個細節,就是ComputeLink構造方法中的(行號12的)代碼 this.Obj.Method = x => method((T)x); 。我原來是這樣寫的 this.Obj.Method = method; 編譯不通過,原因是不能把 Action<ISeat3> 轉化為 Action<T> ,雖然T一定實現了ISeat3,強制轉化也不行,想起以前看過的一篇文章裡面提到希望C#以後的版本能擁有的一特性叫“協變”,很可能指的就是這個。既然這個 Action<ISeat3> 不能轉化為 Action<T> 但是ISeat3是可以強制轉化為T的,所以我包了一層薄薄的殼,成了 this.Obj.Method = x => method((T)x); ,如果有更好的辦法請告訴我。

六.第2局為什麼是11種可能

回過頭來解決為什麼對於一個確定的第1局,第2局有11種可能。

不妨假設第1局的選擇是A選1號椅,B選2號椅,C選3號椅,D選4號椅。

第2局分為兩大類情況:

如果B選了第5號椅

則只有2種可能:

A B C D .-D . A C B
A B C D .-C . D A B

如果B選了不是第5號椅,

則ACD都有可能選第5號椅,有3種可能。B有3種選的可能(1,3,4號椅),B一旦確定,A和C也只有一種可能

所以11 = 2 + 3 * 3

七.結論

由一道數學題牽引出多層迴圈嵌套,最終通過封裝達到了我要的鏈式調用的效果,我是很滿意的。這也是我第設計一次延遲計算,感覺強烈。如果新的情境需要用到延遲計算我想有了這次經驗寫起來會順手許多。如果是需要多層for的演算法題都可以比較方便的實現了。

你都看到這裡了,為我點個贊吧,能說一下看法就更好了。

完整代碼:

using System;using System.Linq;using System.Diagnostics;namespace ConsoleApplication{class Seat{static Seat data = new Seat();public static void Run(){//Seat.Run();//return;for (int a = ; a < ; a++){if (data.IsSelected(, a)) //第局編號。如果已經被人坐了。continue;data.Selected(, a, "A"); //第局編號。A坐a椅。for (int b = ; b < ; b++){if (data.IsSelected(, b))continue;data.Selected(, b, "B");for (int c = ; c < ; c++){if (data.IsSelected(, c))continue;data.Selected(, c, "C");for (int d = ; d < ; d++){if (data.IsSelected(, d))continue;data.Selected(, d, "D");for (int a = ; a < ; a++){if (a == )continue;if (data.IsSelected(, a)) //第局編號continue;if (data.IsSelected(, a, "A")) //如果第局A坐了a椅continue;data.Selected(, a, "A");for (int b = ; b < ; b++){if (b == )continue;if (data.IsSelected(, b))continue;if (data.IsSelected(, b, "B"))continue;data.Selected(, b, "B");for (int c = ; c < ; c++){if (c == )continue;if (data.IsSelected(, c))continue;if (data.IsSelected(, c, "C"))continue;data.Selected(, c, "C");for (int d = ; d < ; d++){if (d == )continue;if (data.IsSelected(, d))continue;if (data.IsSelected(, d, "D"))continue;data.Selected(, d, "D");data.Count++; //可能的情況數加Console.WriteLine("{,} {}", data.Count, data.Current);data.UnSelected(, d);}data.UnSelected(, c);}data.UnSelected(, b);}data.UnSelected(, a);}data.UnSelected(, d);}data.UnSelected(, c);}data.UnSelected(, b);}data.UnSelected(, a); //A起身(釋放坐椅)}}public static void Run(){Try("A",() => Try("B",() => Try("C",() => Try("D",() => Try("A",() => Try("B",() => Try("C",() => Try("D",null))))))));}public static void Try(string name, Action action){for (int i = ; i < ; i++){if (data.IsSelected(, i))continue;data.Selected(, i, name);if (action == null){Console.WriteLine(data.Current);}else{action();}data.UnSelected(, i);}}public static void Try(string name, Action action){for (int i = ; i < ; i++){if (i == )continue;if (data.IsSelected(, i))continue;if (data.IsSelected(, i, name))continue;data.Selected(, i, name);if (action == null){data.Count++;Console.WriteLine("{,} {}", data.Count, data.Current);}else{action();}data.UnSelected(, i);}}public Seat(){seats[, ] = ".";seats[, ] = ".";}private string[,] seats = new string[, ];public void UnSelected(int game, int i){Debug.Assert(game == && i != || game == && i != );Debug.Assert(seats[game, i] != null);seats[game, i] = null;}public void Selected(int game, int i, string name){Debug.Assert(game == && i != || game == && i != );Debug.Assert(seats[game, i] == null);seats[game, i] = name;}public bool IsSelected(int game, int a){return seats[game, a] != null && seats[game, a] != ".";}public bool IsSelected(int game, int a, string name){return seats[game, a] == name;}public string Current{get{return string.Format("{} {} {} {} {}-{} {} {} {} {}",seats[, ], seats[, ], seats[, ], seats[, ], seats[, ],seats[, ], seats[, ], seats[, ], seats[, ], seats[, ]);}}public int Count { get; set; }}class Seat{static Seat data = new Seat(); //借用Seat儲存法的資料Seat Parent { get; set; }Seat Child { get; set; }string Name { get; set; }Action<Seat> Method { get; set; }public Seat(string name, Seat parent, Action<Seat> method){this.Name = name;this.Parent = parent;if (parent != null)parent.Child = this;this.Method = method;}/// <summary>/// 耦合的版本/// </summary>public static void Run(){new Seat("A", null, me => me.Try()).T("B", me => me.Try()).T("C", me => me.Try()).T("D", me => me.Try()).T("A", me => me.Try()).T("B", me => me.Try()).T("C", me => me.Try()).T("D", me => me.Try()).P().Start();}public Seat T(string name, Action<Seat> method){return new Seat(name, this, method);}public Seat P(){return new Seat("Print", this, me => me.Print());}public void Start(){var head = this.Head;head.Method(head);}public Seat Head{get{if (null != this.Parent)return this.Parent.Head;return this;}}public void Try(){for (int i = ; i < ; i++){if (data.IsSelected(, i))continue;data.Selected(, i, this.Name);if (this.Child != null){this.Child.Method(this.Child);}data.UnSelected(, i);}}public void Try(){for (int i = ; i < ; i++){if (i == )continue;if (data.IsSelected(, i))continue;if (data.IsSelected(, i, this.Name))continue;data.Selected(, i, this.Name);if (this.Child != null){this.Child.Method(this.Child);}data.UnSelected(, i);}}public void Print(){data.Count++;Console.WriteLine("{,} {}", data.Count, data.Current);}public override string ToString(){return this.Name.ToString();}}class ComputeLink<T> where T : ISeat{ComputeLink<T> Parent { get; set; } //父節點,即上一級節點ComputeLink<T> Child { get; set; } //子節點,即下一級節點T Obj { get; set; } //當前節點對應的演算法對象,可以看作業務對象public ComputeLink(T obj, ComputeLink<T> parent, Action<T> method){if (obj == null)throw new ArgumentNullException("obj");this.Obj = obj;this.Obj.Method = x => method((T)x);if (parent != null){this.Parent = parent;parent.Child = this;parent.Obj.Child = this.Obj;}}public static ComputeLink<T> New(T obj, Action<T> method){return new ComputeLink<T>(obj, null, method);}public ComputeLink<T> Do(T obj, Action<T> method){return new ComputeLink<T>(obj, this, method);}public ComputeLink<T> Head //鏈表的頭{get{if (null != this.Parent)return this.Parent.Head;return this;}}public void Action() //啟動(延遲的)整個計算{var head = this.Head;head.Obj.Method(head.Obj);}}interface ISeat{ISeat Child { get; set; }Action<ISeat> Method { get; set; }}class Seat : ISeat{static Seat data = new Seat();string Name { get; set; }public Seat(string name){this.Name = name;}/// <summary>/// 解耦的版本/// </summary>public static void Run(){var sql = ComputeLink<Seat>.New(new Seat("A"), m => m.Try()).Do(new Seat("B"), m => m.Try()).Do(new Seat("C"), m => m.Try()).Do(new Seat("D"), m => m.Try()).Do(new Seat("A"), m => m.Try()).Do(new Seat("B"), m => m.Try()).Do(new Seat("C"), m => m.Try()).Do(new Seat("D"), m => m.Try()).Do(new Seat(""), m => m.Print());sql.Action();}public Action<ISeat> Method { get; set; }public ISeat Child { get; set; }public void Try(){for (int i = ; i < ; i++){if (data.IsSelected(, i))continue;data.Selected(, i, this.Name);if (this.Child != null){this.Child.Method(this.Child);}data.UnSelected(, i);}}public void Try(){for (int i = ; i < ; i++){if (i == )continue;if (data.IsSelected(, i))continue;if (data.IsSelected(, i, this.Name))continue;data.Selected(, i, this.Name);if (this.Child != null){this.Child.Method(this.Child);}data.UnSelected(, i);}}public void Print(){data.Count++;Console.WriteLine("{,} {}", data.Count, data.Current);}public override string ToString(){return this.Name.ToString();}}}

以上內容是小編給大家介紹的C#用鏈式方法表達迴圈嵌套的相關知識,希望對大家有所協助!

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.