| 650 | */ |
| 651 | template<typename Result, typename State, typename DownFn, typename UpFn> |
| 652 | std::optional<Result> TreeEvalMaybe(State root_state, DownFn downfn, UpFn upfn) const |
| 653 | { |
| 654 | /** Entries of the explicit stack tracked in this algorithm. */ |
| 655 | struct StackElem |
| 656 | { |
| 657 | const Node& node; //!< The node being evaluated. |
| 658 | size_t expanded; //!< How many children of this node have been expanded. |
| 659 | State state; //!< The state for that node. |
| 660 | |
| 661 | StackElem(const Node& node_, size_t exp_, State&& state_) : |
| 662 | node(node_), expanded(exp_), state(std::move(state_)) {} |
| 663 | }; |
| 664 | /* Stack of tree nodes being explored. */ |
| 665 | std::vector<StackElem> stack; |
| 666 | /* Results of subtrees so far. Their order and mapping to tree nodes |
| 667 | * is implicitly defined by stack. */ |
| 668 | std::vector<Result> results; |
| 669 | stack.emplace_back(*this, 0, std::move(root_state)); |
| 670 | |
| 671 | /* Here is a demonstration of the algorithm, for an example tree A(B,C(D,E),F). |
| 672 | * State variables are omitted for simplicity. |
| 673 | * |
| 674 | * First: stack=[(A,0)] results=[] |
| 675 | * stack=[(A,1),(B,0)] results=[] |
| 676 | * stack=[(A,1)] results=[B] |
| 677 | * stack=[(A,2),(C,0)] results=[B] |
| 678 | * stack=[(A,2),(C,1),(D,0)] results=[B] |
| 679 | * stack=[(A,2),(C,1)] results=[B,D] |
| 680 | * stack=[(A,2),(C,2),(E,0)] results=[B,D] |
| 681 | * stack=[(A,2),(C,2)] results=[B,D,E] |
| 682 | * stack=[(A,2)] results=[B,C] |
| 683 | * stack=[(A,3),(F,0)] results=[B,C] |
| 684 | * stack=[(A,3)] results=[B,C,F] |
| 685 | * Final: stack=[] results=[A] |
| 686 | */ |
| 687 | while (stack.size()) { |
| 688 | const Node& node = stack.back().node; |
| 689 | if (stack.back().expanded < node.subs.size()) { |
| 690 | /* We encounter a tree node with at least one unexpanded child. |
| 691 | * Expand it. By the time we hit this node again, the result of |
| 692 | * that child (and all earlier children) will be at the end of `results`. */ |
| 693 | size_t child_index = stack.back().expanded++; |
| 694 | State child_state = downfn(stack.back().state, node, child_index); |
| 695 | stack.emplace_back(node.subs[child_index], 0, std::move(child_state)); |
| 696 | continue; |
| 697 | } |
| 698 | // Invoke upfn with the last node.subs.size() elements of results as input. |
| 699 | assert(results.size() >= node.subs.size()); |
| 700 | std::optional<Result> result{upfn(std::move(stack.back().state), node, |
| 701 | std::span<Result>{results}.last(node.subs.size()))}; |
| 702 | // If evaluation returns std::nullopt, abort immediately. |
| 703 | if (!result) return {}; |
| 704 | // Replace the last node.subs.size() elements of results with the new result. |
| 705 | results.erase(results.end() - node.subs.size(), results.end()); |
| 706 | results.push_back(std::move(*result)); |
| 707 | stack.pop_back(); |
| 708 | } |
| 709 | // The final remaining results element is the root result, return it. |