[General technology] Using coroutine in different languages to implement a full Arrangement Algorithm (C ++/Lua/Python/C #)

Source: Internet
Author: User

The basic algorithm for implementing full sorting here is as follows (C ++ ):

 1 #include <algorithm>
2 #include <iostream>
3 #include <vector>
4
5 void perm(std::vector<int>& v, int pos = 0)
6 {
7 if (pos == v.size()) {
8 for (int i = 0; i < v.size(); ++i) {
9 std::cout << v[i] << ',';
10 }
11 std::cout << std::endl;
12 }
13 for (int i = pos; i < v.size(); ++i) {
14 std::swap(v[i], v[pos]);
15 perm(v, pos + 1);
16 std::swap(v[i], v[pos]);
17 }
18 }
19
20 int main()
21 {
22 std::vector<int> v;
23 for (int i = 1; i <= 4; ++i) v.push_back(i);
24 perm(v);
25 }

This is the most concise algorithm, that is, in each round of recursion, select a data item in sequence for the current reference location, move the reference location, and enter a deeper level of recursion.
The C ++ implementation here is simple, but not flexible enough. In the above example, only output can be printed for each State in a full arrangement. Of course, adding a function pointer for the perm function can slightly improve flexibility by replacing the print operation with different function behaviors. However, the flexibility I mentioned here is limited, mainly because the above algorithm can only process all the fully arranged states in one breath and cannot interrupt this process. Yes, you can also provide a container for the above perm function to collect all the sorting States. After the top-level perm function returns, you can further process the collected results. However, when the sorting status is too large, containers in the collection status occupy a large amount of memory. Can I regard all the permutation States of an array as a set and then use the iterator to access the entire set? The flexibility of the iterator can meet my needs. It is best that there is no data accumulation during the iteration process and there will be no memory usage.

 

The STL in C ++ has the following algorithm: STD: next_permutation.

Use:

 1 #include <algorithm>
2 #include <iostream>
3 #include <vector>
4
5 int main()
6 {
7 std::vector<int> v;
8 for (int i = 1; i <= 4; ++i) v.push_back(i);
9 do {
10 for (int i = 0; i < v.size(); ++i) {
11 std::cout << v[i] << ',';
12 }
13 std::cout << std::endl;
14 } while (std::next_permutation(v.begin(), v.end()));
15 }

Next_permutation can meet my needs. It is flexible, fast, and does not occupy any excess memory. However, my focus is not on it because it uses a more complex algorithm. For specific Algorithm Implementation, you can view the function's source code. In addition, Hou Jie's STL source code analysis provides a clear explanation of the algorithm's principles.

What I hope is that the algorithms are as concise and easy to understand as the first example, and they can be used as flexible as the iterator.

 

For this purpose, I think of coroutine.

C/C ++, in windows, a group of functions such as createfiber can create and switch coroutine. Since the coroutine in Lua is similar to the fiber in windows, I will go directly to the coroutine in Lua.

 1  function _perm(arr, pos) 
2 pos = pos or 1
3 if pos == #arr then
4 coroutine.yield(arr)
5 end
6 for i = pos, #arr do
7 arr[i], arr[pos] = arr[pos], arr[i]
8 _perm(arr, pos + 1)
9 arr[i], arr[pos] = arr[pos], arr[i]
10 end
11 end
12
13 function perm(arr)
14 return coroutine.wrap(function()
15 _perm(arr)
16 end)
17 end
18
19 for i in perm({1, 2, 3, 4}) do
20 table.foreach(i, function(_, v) io.write(v, ',') end)
21 print()
22 end

We can see that the above _ perm function is almost the same as the perm function of C ++, but the statement for printing the array is replaced with coroutine. yield.

_ Perm implements the main part of the algorithm, while perm encapsulates _ perm into a cost iterator and returns it to the outside for use. This implementation not only maintains the clarity of the algorithm, but also achieves extremely high flexibility.

Of course, coroutine does not have no overhead. coroutine actually opens up another stack space. Before the coroutine returned above iterates and destroys it, the memory outside the stack exists. However, it is certainly better than the solution that collects all States and accesses them one by one, because the stack frames in the coroutine cannot exceed n layers at most, and the memory occupied by the former is n !.

I have also tested before, porting the next_permutation Algorithm in STL to Lua, and then compare the efficiency with the coroutine version. The result is that the coroutine version is lost, but the time between the two is also close. After all, coroutine is a solution that uses space/time for clarity and controls software complexity. It should not be too demanding on its performance. Of course, it is best to use the next_permutation algorithm to solve the problem of full arrangement.

 

I should have finished this article after talking about the Lua coroutine version of the full-arrangement algorithm. But in fact, because of the different languages, the coroutine usage is different, coroutine in some languages has some limitations in usage. Therefore, coroutine in some languages may encounter some problems when solving specific problems.

For a long time, I didn't realize that the yield return of C # is actually a coroutine facility. When I recently learned python, someone pointed out that the coroutine of python is send and yield, I suddenly realized that.

Yield is extremely sharp when an iterator needs to be automatically generated. Therefore, whenever yield is introduced in various languages, it uses the generation of the iterator as an example. In fact, yield is able to exert its powerful power when implementing a multi-level delayed iteration function library like LINQ to object.

Compared with the coroutine of Lua/win-API, The coroutine in C #/python is more efficient, because its implementation does not use the stack, and the memory usage is less. However, the later coroutine has a limit in usage: yield should be placed in the function body of the same function, that is, in Python, after yield is not in the outer function, and simply enter the inner layer function and then yield. This is because the outer function calls the inner function and does not execute the code of the inner function body. The result of this call is only an expression and an iterator object.

This is a piece of Lua code:

 1 function bar()
2 coroutine.yield(2)
3 end
4
5 function foo()
6 coroutine.yield(1)
7 bar()
8 coroutine.yield(3)
9 end
10
11 for i in coroutine.wrap(foo) do
12 print(i)
13 end

It outputs 1, 2, 3.

This is a piece of Python code:

 1 def bar():
2 yield 2
3
4 def foo():
5 yield 1
6 bar()
7 yield 3
8
9 for i in foo():
10 print i

It outputs 1, 3.

Because their coroutine implementation solutions are different! When bar () is called in Python, only one iterator is returned, and no statements in the bar function body are executed!

In fact, the conclusion is already obvious: in C #/python, if yield needs to be transferred across multiple function frames, you cannot simply call the inner function containing yield, instead, we should replace this statement:

1 for i in foo(): yield i

Foo is the inner layer function containing yield. No matter whether the yield statement in foo is followed by an expression, that is, whether the above I is none or not, replace the Foo () call with the above statement in the outer function containing yield, in this way, foo can be extracted and each statement that should be executed in Foo can be executed.

Before I found that the yield return of C # Was the coroutine, I thought that yield could not be used to solve the problem of full arrangement until I thought carefully and came to the conclusion above. In this case, yield is equivalent to Lua's coroutine.

This is Python's full Arrangement Implementation:

 1 def perm(arr, pos = 0):
2 if pos == len(arr):
3 yield arr
4
5 for i in range(pos, len(arr)):
6 arr[pos], arr[i] = arr[i], arr[pos]
7 for _ in perm(arr, pos + 1): yield _
8 arr[pos], arr[i] = arr[i], arr[pos]
9
10 for i in perm([1,2,3,4]):
11 print i

Of course, C # is the same:

 1 static void Swap<T>(ref T a, ref T b)
2 {
3 T t = a;
4 a = b;
5 b = t;
6 }
7
8 static IEnumerable<int[]> Perm(int[] arr, int pos)
9 {
10 if (pos == arr.Length)
11 {
12 yield return arr;
13 }
14 for (int i = pos; i < arr.Length; ++i)
15 {
16 Swap(ref arr[i], ref arr[pos]);
17 foreach (var j in Perm(arr, pos + 1)) yield return j;
18 Swap(ref arr[i], ref arr[pos]);
19 }
20 }
21
22 static void Main(string[] args)
23 {
24 foreach (var i in Perm(new int[] { 1, 2, 3, 4 }, 0))
25 {
26 Console.WriteLine(string.Join(",", i.Select(j=>j.ToString()).ToArray()));
27 }
28 }

 

 

Although many cores cannot be used to make full use of the hardware performance, coroutine is an essential tool for achieving asynchronous logic with linear code.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

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.