如何在 C++ 中完成 corecursion?
How to accomplish corecursion in C++?
我正在从事一个 C++ 项目,该项目需要与树结构进行频繁交互,这意味着有很多递归函数,我正在寻找改进代码的方法。前几天我遇到了 corecursion,我有兴趣为我的应用程序探索这个策略。
但是,我还没有找到任何有关如何使用 C++ 完成 corecursion 的示例。为了使我的问题具体化,我如何在 C++ 中执行 this tree traversal using corecursion?
def bf(tree):
tree_list = [tree]
while tree_list:
new_tree_list = []
for tree in tree_list:
if tree is not None:
yield tree.value
new_tree_list.append(tree.left)
new_tree_list.append(tree.right)
tree_list = new_tree_list
如果这样做只是个坏主意,请告诉我。也就是说,在 Internet 上有 一些 对此的回答对于那些将来尝试这样做的人很有用。没有关于 SO 匹配的问题 [c++] corecursion
,互联网的其余部分似乎缺乏关于该主题的有用信息。
所以有一些方法。
您可以按照建议等待 await 关键字到达,但这似乎是长期的。如果您确实等待 await,here is someone implementing yield with await,至少乍一看应该可以在 C++ 中使用。
你可以写一个生成迭代器的助手,或者从 boost 借一个,然后用它做一个生成器。
您可以使用存储在 std::function
中的可变 lambda,也许 returning a std::experimental::optional
(或 boost 可选)。
但这些大多只是让它变得漂亮。所以让我们变得丑陋。我会用 C++14 写这个,因为懒惰。
struct generator {
using trees=std::vector<tree>;
trees m_cur;
trees m_next;
bool next(value* v){
while(true){
if (m_cur.empty()){
m_cur=std::move(m_next);
m_next.clear();
std::reverse(begin(m_cur),end(m_cur));
if(m_cur.empty())
return false;
}
auto t = m_cur.back();
m_cur.pop_back();
if(!t)continue;
*v = get_value(t);
m_next.push_back(get_left(t));
m_next.push_back(get_right(t));
return true;
}
}
generator(tree t):m_cur{t}{};
};
树型需要自由函数来取值、左右取值和运算符!判断它是否为空。而且它需要是可复制的(一个指针就可以)。
使用:
generator g(some_tree);
value v;
while(g.next(&v)){
std::cout<<v<<'\n';
}
现在这很丑陋——例如,我们手动维护状态。
我相信 await 会带来更神奇的方法,但这还没有标准化。
生成器迭代器会将丑陋的接口隐藏在迭代器外观后面,但状态仍然是手动管理的。
您也许可以使用 lambda 做一些有趣的事情,但我不确定 lambda 是否可以 return 它自己的类型。可能是。 (G:{}->{G,Maybe X} 或类似的东西)
现在,因为它很棒,这里是 n4134 提出的 await
/yield
解决方案。
template<class T0, class...Ts>
std::vector<std::decay_t<T0>> vec_of(T0&& t0, Ts&&... ts) {
return {std::forward<T0>(t0), std::forward<Ts>(ts)...};
}
auto breadth_first = [](auto&& tree){
auto tree_list = vec_of(decltype(tree)(tree));
while(!tree_list.empty()) {
decltype(tree_list) new_tree_list;
for(auto&& tree:tree_list) {
if (tree) {
yield get_value(tree);
new_tree_list.push_back(get_left(tree));
new_tree_list.push_back(get_right(tree));
}
}
tree_list = std::move(new_tree_list);
}
};
这基本上是 python 代码的逐行翻译。我确实写了一个 vec_of
辅助函数来替换 python.
中的 []
使用:
for(auto&& value : breadth_first(tree)) {
std::cout << value;
}
很漂亮。
有趣的问题。问题更多是关于 yield
的性质,虽然是协同迭代器。
基本上,如果你需要在 C++ 中 yield
,你需要实现一个类似迭代器的状态机,无论如何这几乎就是共同递归的想法。
一个工作示例需要实现一棵树 class,但这里是 BFS 的部分实现,使用的基本上是 wiki 文章中的策略(稍作修改,因为他们的算法有点傻)
for (bfs_generator i = bfs_generator(myTree);
i.is_good();
i.next())
{
print (i.value());
}
// Iterator-style operation overloads may be more appropriate, but I don't want to deal with the syntactic details
// I assume a standard C Node struct with left and right pointers implementation of your tree.
class bfs_iterator
{
// The Python example is a little strange because it expresses the state as two lists, when only one is necessary
std::queue<Node *> tree_list;
public:
bfs_iterator (Node * root)
{
tree_list.push_back(root);
}
bool is_good()
{
return tree_list.empty();
}
void next()
{
// Pop the front of the queue then add the children to the queue.
if (!tree_list.empty())
{
Node * node = tree_list.front();
tree_list.pop();
if (node->left)
tree_list.push(node->left);
if (node->right)
tree_list.push(node->right);
}
}
MyTree::value value()
{
return tree_list.front()->value;
}
};
从技术上讲,您不需要模仿他们的 yield
生成器策略来执行此操作。无需定义 class,您可以直接在代码中简单地使用队列。
这与维基百科算法略有不同,因为...维基算法并不理想。它们大部分相同,但维基百科示例有点像穷人的队列。
以下与给定的 python 实现几乎相同,您现在可以在生产中使用它:
#include <vector>
#include <iostream>
#include <boost/coroutine/all.hpp>
using namespace std;
struct Node {
char value;
Node *left;
Node *right;
};
using generator =
boost::coroutines::asymmetric_coroutine<decltype(Node::value)>::pull_type;
generator bf(Node *tree) { //def bf(tree):
return generator([=](auto &yield) { //
vector<Node *> tree_list = {tree}; // tree_list = [tree]
while (!tree_list.empty()) { // while tree_list:
vector<Node *> new_tree_list; // new_tree_list = []
for (auto tree : tree_list) { // for tree in tree_list:
if (tree != nullptr) { // if tree is not None:
yield(tree->value); // yield tree.value
new_tree_list.push_back(tree->left); // new_tree_list.append(tree.left)
new_tree_list.push_back(tree->right); // new_tree_list.append(tree.right)
} //
} //
tree_list = move(new_tree_list); // tree_list = new_tree_list
} //
}); //
} //
int main() {
Node a{'a'}, b{'b'}, c{'c'}, d{'d'}, e{'e'};
a.left = &b;
a.right = &c;
b.right = &d;
d.left = &e;
for (auto node_value : bf(&a))
std::cout << node_value << " ";
}
为了避免allocations/deallocations我可能会这样做:
generator bf(Node *tree) {
return generator([=](auto &yield) {
vector<Node *> tree_list = {tree}, new_tree_list;
while (!tree_list.empty()) {
for (auto tree : tree_list) {
if (tree != nullptr) {
yield(tree->value);
new_tree_list.push_back(tree->left);
new_tree_list.push_back(tree->right);
}
}
swap(tree_list, new_tree_list);
new_tree_list.clear();
}
});
}
我正在从事一个 C++ 项目,该项目需要与树结构进行频繁交互,这意味着有很多递归函数,我正在寻找改进代码的方法。前几天我遇到了 corecursion,我有兴趣为我的应用程序探索这个策略。
但是,我还没有找到任何有关如何使用 C++ 完成 corecursion 的示例。为了使我的问题具体化,我如何在 C++ 中执行 this tree traversal using corecursion?
def bf(tree):
tree_list = [tree]
while tree_list:
new_tree_list = []
for tree in tree_list:
if tree is not None:
yield tree.value
new_tree_list.append(tree.left)
new_tree_list.append(tree.right)
tree_list = new_tree_list
如果这样做只是个坏主意,请告诉我。也就是说,在 Internet 上有 一些 对此的回答对于那些将来尝试这样做的人很有用。没有关于 SO 匹配的问题 [c++] corecursion
,互联网的其余部分似乎缺乏关于该主题的有用信息。
所以有一些方法。
您可以按照建议等待 await 关键字到达,但这似乎是长期的。如果您确实等待 await,here is someone implementing yield with await,至少乍一看应该可以在 C++ 中使用。
你可以写一个生成迭代器的助手,或者从 boost 借一个,然后用它做一个生成器。
您可以使用存储在 std::function
中的可变 lambda,也许 returning a std::experimental::optional
(或 boost 可选)。
但这些大多只是让它变得漂亮。所以让我们变得丑陋。我会用 C++14 写这个,因为懒惰。
struct generator {
using trees=std::vector<tree>;
trees m_cur;
trees m_next;
bool next(value* v){
while(true){
if (m_cur.empty()){
m_cur=std::move(m_next);
m_next.clear();
std::reverse(begin(m_cur),end(m_cur));
if(m_cur.empty())
return false;
}
auto t = m_cur.back();
m_cur.pop_back();
if(!t)continue;
*v = get_value(t);
m_next.push_back(get_left(t));
m_next.push_back(get_right(t));
return true;
}
}
generator(tree t):m_cur{t}{};
};
树型需要自由函数来取值、左右取值和运算符!判断它是否为空。而且它需要是可复制的(一个指针就可以)。
使用:
generator g(some_tree);
value v;
while(g.next(&v)){
std::cout<<v<<'\n';
}
现在这很丑陋——例如,我们手动维护状态。
我相信 await 会带来更神奇的方法,但这还没有标准化。
生成器迭代器会将丑陋的接口隐藏在迭代器外观后面,但状态仍然是手动管理的。
您也许可以使用 lambda 做一些有趣的事情,但我不确定 lambda 是否可以 return 它自己的类型。可能是。 (G:{}->{G,Maybe X} 或类似的东西)
现在,因为它很棒,这里是 n4134 提出的 await
/yield
解决方案。
template<class T0, class...Ts>
std::vector<std::decay_t<T0>> vec_of(T0&& t0, Ts&&... ts) {
return {std::forward<T0>(t0), std::forward<Ts>(ts)...};
}
auto breadth_first = [](auto&& tree){
auto tree_list = vec_of(decltype(tree)(tree));
while(!tree_list.empty()) {
decltype(tree_list) new_tree_list;
for(auto&& tree:tree_list) {
if (tree) {
yield get_value(tree);
new_tree_list.push_back(get_left(tree));
new_tree_list.push_back(get_right(tree));
}
}
tree_list = std::move(new_tree_list);
}
};
这基本上是 python 代码的逐行翻译。我确实写了一个 vec_of
辅助函数来替换 python.
[]
使用:
for(auto&& value : breadth_first(tree)) {
std::cout << value;
}
很漂亮。
有趣的问题。问题更多是关于 yield
的性质,虽然是协同迭代器。
基本上,如果你需要在 C++ 中 yield
,你需要实现一个类似迭代器的状态机,无论如何这几乎就是共同递归的想法。
一个工作示例需要实现一棵树 class,但这里是 BFS 的部分实现,使用的基本上是 wiki 文章中的策略(稍作修改,因为他们的算法有点傻)
for (bfs_generator i = bfs_generator(myTree);
i.is_good();
i.next())
{
print (i.value());
}
// Iterator-style operation overloads may be more appropriate, but I don't want to deal with the syntactic details
// I assume a standard C Node struct with left and right pointers implementation of your tree.
class bfs_iterator
{
// The Python example is a little strange because it expresses the state as two lists, when only one is necessary
std::queue<Node *> tree_list;
public:
bfs_iterator (Node * root)
{
tree_list.push_back(root);
}
bool is_good()
{
return tree_list.empty();
}
void next()
{
// Pop the front of the queue then add the children to the queue.
if (!tree_list.empty())
{
Node * node = tree_list.front();
tree_list.pop();
if (node->left)
tree_list.push(node->left);
if (node->right)
tree_list.push(node->right);
}
}
MyTree::value value()
{
return tree_list.front()->value;
}
};
从技术上讲,您不需要模仿他们的 yield
生成器策略来执行此操作。无需定义 class,您可以直接在代码中简单地使用队列。
这与维基百科算法略有不同,因为...维基算法并不理想。它们大部分相同,但维基百科示例有点像穷人的队列。
以下与给定的 python 实现几乎相同,您现在可以在生产中使用它:
#include <vector>
#include <iostream>
#include <boost/coroutine/all.hpp>
using namespace std;
struct Node {
char value;
Node *left;
Node *right;
};
using generator =
boost::coroutines::asymmetric_coroutine<decltype(Node::value)>::pull_type;
generator bf(Node *tree) { //def bf(tree):
return generator([=](auto &yield) { //
vector<Node *> tree_list = {tree}; // tree_list = [tree]
while (!tree_list.empty()) { // while tree_list:
vector<Node *> new_tree_list; // new_tree_list = []
for (auto tree : tree_list) { // for tree in tree_list:
if (tree != nullptr) { // if tree is not None:
yield(tree->value); // yield tree.value
new_tree_list.push_back(tree->left); // new_tree_list.append(tree.left)
new_tree_list.push_back(tree->right); // new_tree_list.append(tree.right)
} //
} //
tree_list = move(new_tree_list); // tree_list = new_tree_list
} //
}); //
} //
int main() {
Node a{'a'}, b{'b'}, c{'c'}, d{'d'}, e{'e'};
a.left = &b;
a.right = &c;
b.right = &d;
d.left = &e;
for (auto node_value : bf(&a))
std::cout << node_value << " ";
}
为了避免allocations/deallocations我可能会这样做:
generator bf(Node *tree) {
return generator([=](auto &yield) {
vector<Node *> tree_list = {tree}, new_tree_list;
while (!tree_list.empty()) {
for (auto tree : tree_list) {
if (tree != nullptr) {
yield(tree->value);
new_tree_list.push_back(tree->left);
new_tree_list.push_back(tree->right);
}
}
swap(tree_list, new_tree_list);
new_tree_list.clear();
}
});
}