c++corecursion

How to accomplish corecursion in C++?


I'm working on a C++ project that requires frequent interaction with a tree structure, which means lots of recursive functions, and I'm looking for ways to improve the code. I came across corecursion the other day, and I'm interested in exploring this strategy for my application.

However, I haven't been able to find any examples of how to accomplish corecursion with C++. To make my question specific, how can I do this tree traversal using corecursion in C++?

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

If doing this is just a bad idea, let me know. That said, having some answer to this on the internet would be useful for those trying to do this in the future. There are no questions on SO matching [c++] corecursion and the rest of the internet seems devoid of useful information on the subject.


Solution

  • The following is almost identical to the given python implementation and you can use it now in production:

    Live On Coliru

    #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 << " ";
    }
    

    To avoid allocations/deallocations I'd probably do it this way:

    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();
            }
        });
    }