1. Beginning
The story comes from a question from a friend:
The four of my friends played LOL games. In the first game, select positions: mid laner, top laner, ADC, and support; in the second game, newly joined partners must choose top laner, and the four players can choose positions to: mid laner, jungler, ADC, and support; required, each of the four players in the second game is not allowed to choose the same position as the first game. How many positions are there in the two games?
For people like me who don’t understand games, they can’t understand it. So this version comes:
There are 4 people and 4 chairs. Each person sits on one chair in the first game. The second chair is removed and the fifth chair is added. Each person sits on one chair. And each person cannot sit on the same chair as the first game. Ask the two games in a comprehensive way, how many possible situations are there?
My initial thought was like this, and the 4 people were called ABCD: the possible number in the first game is 4*3*2*1=24. If A chooses the second chair in the first game, then A has 4 possibilities, otherwise A has 3 possibilities. For B, if A chooses B's chair in the first game, B has 3 possibilities, otherwise B has 2 possibilities (I have selected the first game and A's second game)... I was dizzy when I thought of this, and the more I got.
2. Original for nesting
It was originally a math problem, and there were so many types of things that should be calculated from knowledge, but I suddenly had an idea, and it would be better to use a computer to describe it thoroughly. First, it can provide a correct answer for various guesses, and second, it may be possible to infer (mathematically) calculation methods from the answer. Then I wrote the first edition:
static Seat data = new Seat(); public static void Run() { for (int a = 0; a < 4; a++) { if ((0, a)) //The first round number is 0. If someone has already sat down.continue; (0, a, "A"); //The first round number is 0. A sits on a chair.for (int b = 0; b < 4; b++) { if ((0, b)) continue; (0, b, "B"); for (int c = 0; c < 4; c++) { if ((0, c)) continue; (0, c, "C"); for (int d = 0; d < 4; d++) { if ((0, d)) continue; (0, d, "D"); for (int a2 = 0; a2 < 5; a2++) { if (a2 == 1) continue; if ((1, a2)) //The second round number 1continue; if ((0, a2, "A")) //If A sits in the first game A2 chaircontinue; (1, a2, "A"); for (int b2 = 0; b2 < 5; b2++) { if (b2 == 1) continue; if ((1, b2)) continue; if ((0, b2, "B")) continue; (1, b2, "B"); for (int c2 = 0; c2 < 5; c2++) { if (c2 == 1) continue; if ((1, c2)) continue; if ((0, c2, "C")) continue; (1, c2, "C"); for (int d2 = 0; d2 < 5; d2++) { if (d2 == 1) continue; if ((1, d2)) continue; if ((0, d2, "D")) continue; (1, d2, "D"); ++; //Add 1 if possible("{0,5} {1}", , ); (1, d2); } (1, c2); } (1, b2); } (1, a2); } (0, d); } (0, c); } (0, b); } (0, a); //A got up (release the seat)} }
Partial run results:
illustrate:
It's a person's name
2. "." means no one
3. The location is the seat
4.-The first game on the left is the second game on the right
5. The number is the serial number
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
It is calculated to be 264 types. From the answer, each 11 types are one group, and the sitting method of the first game in the group is the same, which means that for each situation in the first game, there are 11 different possibilities in the second game. The possibility of the first game is 24, so the answer is 24*11=264. And why there are 11 possibilities in the second game, I will talk about it later.
3. Want to write a chain
The theme is here, like the first version just now, it is too rigid and troublesome to write.
It would be great if I could write code like this:
("A").Try("B").Try("C").Try("D").Try2("A").Try2("B").Try2("C").Try2("D").Write();
The usual logic of such code is to execute the Try("A") method, and then execute the Try("B") method of the object it returns... that is, the Try("B") method is executed only once, and what I want is that the Try("B") method is called n times by the Try("A") internal loop, and the Try("C") method is called m times by the Try("B") method. It is not difficult to understand why you should pursue such an effect if you think about the first version of the for set. If Try("A") is executed and then Try("B"), then Try("B") will definitely not be called many times, so the execution of Try("A") must be delayed, and the execution of all Try and Try2 is also delayed. Since lambda expressions are inherently characterized by delayed calculations, the second edition was quickly written:
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) //Inning 1{ for (int i = 0; i < 4; i++) { if ((0, i)) continue; (0, i, name); if (action == null) { (); } else { action(); } (0, i); } } public static void Try2(string name, Action action) //Inning 2{ for (int i = 0; i < 5; i++) { if (i == 1) continue; if ((1, i)) continue; if ((0, i, name)) continue; (1, i, name); if (action == null) { ++; ("{0,5} {1}", , ); } else { action(); } (1, i); } }
The structure is more reasonable and the logic is clearer, but a bunch of lambda nesting is too ugly and it is not the effect I want. What I want is something like this:
("A").Try("B").Try("C").Try("D").Try2("A").Try2("B").Try2("C").Try2("D").Write();
4. Continue to approach the target.
Because of the delay, you must first "tell" the reference of the method to be called. When the previous level executes for, the next level method can be called. So I thought of a "callback chain"
Therefore, the execution chain method is to construct the callback chain, and the final method then starts the entire logic to be executed by calling a method of the header.
Delay calculation is borrowed and learned from Linq. The process of constructing Linq is not executed, and it is not actually executed until the ToList, First and other methods are executed.
I want to construct a callback chain to be a fixed method. Here I randomly use the very short name T, and the method to be executed during later calculations of each step can be flexibly specified. So there is the 3rd edition:
static Seat data = new Seat(); //Borrow Seat to save datapublic Seat2(string name, Seat2 parent, Action<Seat2> method) { = name; = parent; if (parent != null) = this; = method; } public static void Run() { new Seat2("A", null, me => ()) .T("B", me => ()) .T("C", me => ()) .T("D", me => ()) .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 ((0, i)) continue; (0, i, ); if ( != null) { (); } (0, i); } } public void Try2() { for (int i = 0; i < 5; i++) { if (i == 1) continue; if ((1, i)) continue; if ((0, i, )) continue; (1, i, ); if ( != null) { (); } (1, i); } }
Five. Decoupling
This method of calling is satisfactory. However, the operation framework is coupled with specific algorithms. If the operation framework can be extracted, it will be much more convenient to write specific algorithms in the future. So after hard extraction, testing, and pitfalls, the 4th edition finally appeared:
//Operation frameworkclass ComputeLink<T> where T : ISeat { ComputeLink<T> Parent { get; set; } //The parent node, that is, the previous level nodeComputeLink<T> Child { get; set; } //Sub-node, that is, the next-level nodeT Obj { get; set; } //The algorithm object corresponding to the current node can be regarded as a business objectpublic ComputeLink(T obj, ComputeLink<T> parent, Action<T> method) { if (obj == null) throw new ArgumentNullException("obj"); = obj; = x => method((T)x); if (parent != null) { = parent; = this; = ; } } 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 //The header of the link list{ get { if (null != ) return ; return this; } } public void Action() //Start (delayed) the entire calculation{ var head = ; (); } } interface ISeat { ISeat Child { get; set; } Action<ISeat> Method { get; set; } }
.Why is the 4th edition ISeat3 instead of ISeat4? Because I didn’t treat the 1st edition as the 1st edition, because it was too primitive. After the 2nd edition appeared, I deleted the 1st edition. I just wrote the first edition again in order to write this article. So I originally thought that ISeat3 was naturally ranked in the fourth edition.
The specific "algorithm" is very simple:
class Seat3 : ISeat3 { static Seat data = new Seat(); string Name { get; set; } public Seat3(string name) { = name; } /// <summary> /// Decoupled version/// </summary> public static void Run() { var sql = ComputeLink<Seat3> .New(new Seat3("A"), m => ()) .Do(new Seat3("B"), m => ()) .Do(new Seat3("C"), m => ()) .Do(new Seat3("D"), m => ()) .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 => ()); (); } public Action<ISeat3> Method { get; set; } public ISeat3 Child { get; set; } public void Try() { for (int i = 0; i < 4; i++) { if ((0, i)) continue; (0, i, ); if ( != null) { (); } (0, i); } } public void Try2() { for (int i = 0; i < 5; i++) { if (i == 1) continue; if ((1, i)) continue; if ((0, i, )) continue; (1, i, ); if ( != null) { (); } (1, i); } } public void Print() { ++; ("{0,5} {1}", , ); } }
Seat3 is simple to write, and (inside the Run method) looks comfortable. The effect of nested loops is achieved through chain writing. Yes, that's what I want!
It's very much like linq, so I directly named the variable sql.
•For Try and Try2, it is best to pass the method to be called from the parameters, but this will increase the parameter complexity of New and Do in the Run method, destroying the beauty, so after trade-offs, Child and Method are passed in through attributes. I'm not sure if this is good or not. Please give me some advice.
•There is another detail, which is the code (line number 12) in the ComputeLink construction method = x => method((T)x); . I originally wrote = method; the compiled failed because I cannot convert Action<ISeat3> into Action<T> . Although T must have implemented ISeat3, forced conversion is not possible. I remember that an article I read before mentioned that I hope that the version of C# can have a feature called "covariance", which is likely to refer to this. Since this Action<ISeat3> cannot be converted to Action<T>, but ISeat3 can be converted to T, so I wrapped a thin shell and became = x => method((T)x); . If there is a better way, please let me know.
6. Why are there 11 possibilities in the second game
Looking back to solve why there are 11 possibilities for a certain first inning, the second inning has been 11 possibilities.
It might be suppose that the choice of the first game is A to choose chair 1, B to choose chair 2, C to choose chair 3, and D to choose chair 4.
The second game is divided into two categories:
If B chooses chair No. 5
There are only 2 possibilities:
A B C D .-D . A C B
A B C D .-C . D A B
If B chooses chair No. 5,
Then ACD may choose the No. 5 chair, and there are 3 possibilities. B has 3 possibilities for choosing (chairs 1, 3, 4). Once B is determined, there is only one possibility for A and C.
So 11 = 2 + 3 * 3
7. Conclusion
A math problem leads to multi-layer loop nesting, and finally achieves the effect of the chain call I want through encapsulation, which I am very satisfied. This is also my first time designing a delay calculation, and it feels strong. If a new scenario requires delay calculation, I think it will be much easier to write with this experience. If you need multi-layer for algorithm problems, you can easily implement them.
You have seen this, please give me a thumbs up, it would be even better if you could give me some opinions.
Complete code:
using System; using ; using ; namespace ConsoleApplication { class Seat { static Seat data = new Seat(); public static void Run() { //(); //return; for (int a = ; a < ; a++) { if ((, a)) //The number of the first round. If someone has already sat down.continue; (, a, "A"); //The number of the first round. A sits on a chair.for (int b = ; b < ; b++) { if ((, b)) continue; (, b, "B"); for (int c = ; c < ; c++) { if ((, c)) continue; (, c, "C"); for (int d = ; d < ; d++) { if ((, d)) continue; (, d, "D"); for (int a = ; a < ; a++) { if (a == ) continue; if ((, a)) //The number of the first roundcontinue; if ((, a, "A")) //If A sits in the chair in the first gamecontinue; (, a, "A"); for (int b = ; b < ; b++) { if (b == ) continue; if ((, b)) continue; if ((, b, "B")) continue; (, b, "B"); for (int c = ; c < ; c++) { if (c == ) continue; if ((, c)) continue; if ((, c, "C")) continue; (, c, "C"); for (int d = ; d < ; d++) { if (d == ) continue; if ((, d)) continue; if ((, d, "D")) continue; (, d, "D"); ++; //Add to the number of possible cases("{,} {}", , ); (, d); } (, c); } (, b); } (, a); } (, d); } (, c); } (, b); } (, a); //A got up (release the seat)} } 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 ((, i)) continue; (, i, name); if (action == null) { (); } else { action(); } (, i); } } public static void Try(string name, Action action) { for (int i = ; i < ; i++) { if (i == ) continue; if ((, i)) continue; if ((, i, name)) continue; (, i, name); if (action == null) { ++; ("{,} {}", , ); } else { action(); } (, i); } } public Seat() { seats[, ] = "."; seats[, ] = "."; } private string[,] seats = new string[, ]; public void UnSelected(int game, int i) { (game == && i != || game == && i != ); (seats[game, i] != null); seats[game, i] = null; } public void Selected(int game, int i, string name) { (game == && i != || game == && i != ); (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 ("{} {} {} {} {}-{} {} {} {} {}", seats[, ], seats[, ], seats[, ], seats[, ], seats[, ], seats[, ], seats[, ], seats[, ], seats[, ], seats[, ]); } } public int Count { get; set; } } class Seat { static Seat data = new Seat(); //Borrow data from Seat save methodSeat 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) { = name; = parent; if (parent != null) = this; = method; } /// <summary> /// Coupled version/// </summary> public static void Run() { new Seat("A", null, me => ()) .T("B", me => ()) .T("C", me => ()) .T("D", me => ()) .T("A", me => ()) .T("B", me => ()) .T("C", me => ()) .T("D", me => ()) .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 => ()); } public void Start() { var head = ; (head); } public Seat Head { get { if (null != ) return ; return this; } } public void Try() { for (int i = ; i < ; i++) { if ((, i)) continue; (, i, ); if ( != null) { (); } (, i); } } public void Try() { for (int i = ; i < ; i++) { if (i == ) continue; if ((, i)) continue; if ((, i, )) continue; (, i, ); if ( != null) { (); } (, i); } } public void Print() { ++; ("{,} {}", , ); } public override string ToString() { return (); } } class ComputeLink<T> where T : ISeat { ComputeLink<T> Parent { get; set; } //The parent node, that is, the previous level nodeComputeLink<T> Child { get; set; } //Sub-node, that is, the next-level nodeT Obj { get; set; } //The algorithm object corresponding to the current node can be regarded as a business objectpublic ComputeLink(T obj, ComputeLink<T> parent, Action<T> method) { if (obj == null) throw new ArgumentNullException("obj"); = obj; = x => method((T)x); if (parent != null) { = parent; = this; = ; } } 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 //The header of the link list{ get { if (null != ) return ; return this; } } public void Action() //Start (delayed) the entire calculation{ var head = ; (); } } 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) { = name; } /// <summary> /// Decoupled version/// </summary> public static void Run() { var sql = ComputeLink<Seat> .New(new Seat("A"), m => ()) .Do(new Seat("B"), m => ()) .Do(new Seat("C"), m => ()) .Do(new Seat("D"), m => ()) .Do(new Seat("A"), m => ()) .Do(new Seat("B"), m => ()) .Do(new Seat("C"), m => ()) .Do(new Seat("D"), m => ()) .Do(new Seat(""), m => ()); (); } public Action<ISeat> Method { get; set; } public ISeat Child { get; set; } public void Try() { for (int i = ; i < ; i++) { if ((, i)) continue; (, i, ); if ( != null) { (); } (, i); } } public void Try() { for (int i = ; i < ; i++) { if (i == ) continue; if ((, i)) continue; if ((, i, )) continue; (, i, ); if ( != null) { (); } (, i); } } public void Print() { ++; ("{,} {}", , ); } public override string ToString() { return (); } } }
The above content is the relevant knowledge about C# expressing loop nesting using chain method that the editor introduced to you. I hope it will be helpful to everyone!