Convert recursion function into stack calls

This commit is contained in:
Filip Sykala 2021-02-23 18:20:27 +01:00
parent 4baac072df
commit 5c2e368f7c

View File

@ -2061,14 +2061,22 @@ struct VoronoiGraph::ExPath : public VoronoiGraph::Path
using SideBranches = std::priority_queue<
VoronoiGraph::Path, std::vector<VoronoiGraph::Path>, OrderLengthFromLongest>;
using SideBranchesMap = std::map<const VoronoiGraph::Node *, SideBranches>;
// All side branches in Graph under node
// Map contains only node, which has side branche(s)
// There is NOT empty SideBranches in map !!!
SideBranchesMap side_branches;
// circles
// All circles in Graph under node
std::vector<VoronoiGraph::Circle> circles;
// every connected circle have two records(firs to second & second to first)
// index_to_circles i1, i2; --> connected_circle[i1] = i2; connected_circle[i2] = i1;
// alone circle does'n have record in connected_circle
// every connected circle have at least two records(firs to second & second to first)
// EXAMPLE with 3 circles(index to circles stored in this->circles are: c1, c2 and c3) connected together
// connected_circle[c1] = {c2, c3};
// connected_circle[c2] = {c1, c3};
// connected_circle[c3] = {c1, c2};
std::map<size_t, std::set<size_t>> connected_circle;
public:
ExPath() = default;
};
@ -2429,6 +2437,7 @@ void append_neighbor_branch(VoronoiGraph::ExPath &dst,
///
/// !! not work on circles(island with holes)
/// !! need restructuralization by branch from start path
/// !!! RECURRENT cause stack overflow
/// </summary>
/// <param name="start_path">IN/OUT path in Graph</param>
VoronoiGraph::ExPath create_longest_branch_from_node(
@ -2442,15 +2451,7 @@ VoronoiGraph::ExPath create_longest_branch_from_node(
VoronoiGraph::Path act_path = prev_path; // make copy
act_path.append(&node, distance_to_node);
const std::vector<VoronoiGraph::Node::Neighbor> &neighbors = node.neighbors;
size_t neighbor_count = neighbors.size();
std::priority_queue<VoronoiGraph::Path, std::vector<VoronoiGraph::Path>,
OrderLengthFromLongest>
side_branches;
//side_branches.reserve(neighbor_count - 1); // one neighbor is prev node
VoronoiGraph::ExPath::SideBranches side_branches;
// skip node on circle when circle start at this node
// vector --> multiple cirle could start at same node
@ -2461,10 +2462,9 @@ VoronoiGraph::ExPath create_longest_branch_from_node(
// when search on cirle store only branches not search for longest Path
std::vector<size_t> circle_indexes; // connected circles
std::vector<size_t> end_circle_indexes;
// Depth search for path of all neighbors --> fill paths
for (const VoronoiGraph::Node::Neighbor &neighbor : neighbors) {
for (const VoronoiGraph::Node::Neighbor &neighbor : node.neighbors) {
if (skip_nodes.find(neighbor.node) != skip_nodes.end()) continue;
// detection of circle
@ -2551,6 +2551,7 @@ VoronoiGraph::ExPath create_longest_branch_from_node(
return result;
}
// create result longest path from longest side branch
VoronoiGraph::Path longest_path(std::move(side_branches.top()));
side_branches.pop();
if (!side_branches.empty()) {
@ -2625,6 +2626,274 @@ void reshape_longest_path(VoronoiGraph::ExPath& path) {
}
}
/// <summary>
/// DTO for process node to depth search to get longest path in voronoi graph
/// </summary>
struct CallData
{
// actual proccessed node
const VoronoiGraph::Node *node;
// distance to this node from input node
double distance_to_node;
// path from start point to this node
// last one is actual node
VoronoiGraph::Path act_path;
// skip node when circle start - must end at this node
// set --> multiple cirle could start at same node
// previous node should be skiped to so it is initialized with it
std::set<const VoronoiGraph::Node *> skip_nodes; // circle
// store all circle indexes this node is lay on
// used to create connected circles structure
std::vector<size_t> circle_indexes;
// When circle_indexes is not empty node lays on circle
// and in this node is not searching for longest path only store side
// branches(not part of circle)
// indexes of circle ending in this node(could be more than one)
std::vector<size_t> end_circle_indexes;
// When end_circle_indexes == circle_indexes
// than it is end of circle (multi circle structure) and it is processed
// contain possible continue path
// possible empty
VoronoiGraph::ExPath::SideBranches side_branches;
// result for this node
VoronoiGraph::ExPath& result;
public:
CallData(VoronoiGraph::ExPath & result,
const VoronoiGraph::Node *node,
double distance_to_node)
: result(result), node(node), distance_to_node(distance_to_node)
{}
};
class IStackFunction
{
public:
using CallStack = std::stack<std::unique_ptr<IStackFunction>>;
virtual ~IStackFunction() = default;
virtual void process(CallStack& call_stack) = 0;
};
class PostProcessNeighbors;
/// <summary>
/// create on stack
/// 1 * PostProcessNeighbors
/// N * ExpandNode
/// </summary>
class EvaluateNeighbor : public IStackFunction
{
std::unique_ptr<PostProcessNeighbors> post_process_neighbor;
public:
EvaluateNeighbor(
VoronoiGraph::ExPath & result,
const VoronoiGraph::Node *node,
double distance_to_node = 0.,
const VoronoiGraph::Path &prev_path = VoronoiGraph::Path({}, 0.));
virtual void process(CallStack &call_stack);
};
/// <summary>
/// call after all neighbors are processed
/// </summary>
class PostProcessNeighbors : public CallData , public IStackFunction
{
public:
PostProcessNeighbors(
VoronoiGraph::ExPath & result,
const VoronoiGraph::Node *node,
double distance_to_node = 0.,
VoronoiGraph::Path prev_path = VoronoiGraph::Path({}, 0.) // make copy
): CallData(result, node, distance_to_node)
{
const VoronoiGraph::Node *prev_node =
(prev_path.path.size() >= 1) ? prev_path.path.back() : nullptr;
skip_nodes = {prev_node};
// append actual node
act_path = std::move(prev_path);
act_path.append(node, distance_to_node);
}
virtual void process([[maybe_unused]] CallStack &call_stack)
{
process();
}
void process()
{
// remember connected circle
if (circle_indexes.size() > 1) {
for (size_t circle_index : circle_indexes) {
for (size_t circle_index2 : circle_indexes) {
if (circle_index == circle_index2) continue;
result.connected_circle[circle_index].insert(
circle_index2);
}
}
}
// detect end of circles in this node
if (!end_circle_indexes.empty() &&
end_circle_indexes.size() == circle_indexes.size()) {
size_t circle_index = circle_indexes
.front(); // possible any of them
side_branches.push(
find_longest_path_on_circles(*node, circle_index, result));
circle_indexes.clear(); // resolved circles
}
// simple node on circle --> only input and output neighbor
if (side_branches.empty()) return;
// is node on unresolved circle?
if (!circle_indexes.empty()) {
// not search for longest path, it will eval on end of circle
result.side_branches[node] = side_branches;
return;
}
// create result longest path from longest side branch
VoronoiGraph::Path longest_path(std::move(side_branches.top()));
side_branches.pop();
if (!side_branches.empty()) {
result.side_branches[node] = side_branches;
}
longest_path.path.insert(longest_path.path.begin(), node);
result.path = std::move(longest_path.path);
result.length = distance_to_node + longest_path.length;
}
};
/// <summary>
/// Decimate data from Ex path to path
/// Done after ONE neighbor is procceessed.
/// Check if node is on circle.
/// Remember ended circle
/// Merge side branches and circle information into result
/// </summary>
class PostProcessNeighbor : public IStackFunction
{
CallData &data;
public:
VoronoiGraph::ExPath neighbor_path; // data filled in EvaluateNeighbor
PostProcessNeighbor(CallData &data): data(data) {}
virtual void process([[maybe_unused]] CallStack &call_stack)
{
process();
}
void process(){
bool is_circle_neighbor = false;
if (neighbor_path.path.empty()) { // neighbor node is on circle
for (VoronoiGraph::Circle &circle : neighbor_path.circles) {
const auto &circle_item = std::find(circle.path.begin(),
circle.path.end(), data.node);
if (circle_item == circle.path.end())
continue; // node is NOT on circle
size_t next_circle_index = &circle -
&neighbor_path.circles.front();
size_t circle_index = data.result.circles.size() +
next_circle_index;
data.circle_indexes.push_back(circle_index);
// check if this node is end of circle
if (circle_item == circle.path.begin()) {
data.end_circle_indexes.push_back(circle_index);
// !! this FIX circle lenght because at detection of
// circle it will cost time to calculate it
circle.length -= data.act_path.length;
// skip twice checking of circle
data.skip_nodes.insert(circle.path.back());
}
is_circle_neighbor = true;
}
}
append_neighbor_branch(data.result, neighbor_path);
if (!is_circle_neighbor)
data.side_branches.push(std::move(neighbor_path));
}
};
class ExpandNeighbor: public IStackFunction
{
CallData &data;
const VoronoiGraph::Node::Neighbor &neighbor;
public:
ExpandNeighbor(CallData & data,
const VoronoiGraph::Node::Neighbor &neighbor)
: data(data), neighbor(neighbor)
{}
virtual void process(CallStack &call_stack)
{
if (data.skip_nodes.find(neighbor.node) != data.skip_nodes.end())
return;
// detection of circle
auto circle_opt = create_circle(data.act_path, neighbor);
if (circle_opt.has_value()) {
size_t circle_index = data.result.circles.size();
data.circle_indexes.push_back(circle_index);
data.result.circles.emplace_back(std::move(circle_opt.value()));
return;
}
// create copy of path(not circles, not side_branches)
const VoronoiGraph::Node &next_node = *neighbor.node;
// is next node leaf ?
if (next_node.neighbors.size() == 1) {
VoronoiGraph::Path side_branch({&next_node}, neighbor.edge_length);
data.side_branches.push(std::move(side_branch));
return;
}
auto post_process_neighbor = std::make_unique<PostProcessNeighbor>(
data);
VoronoiGraph::ExPath &neighbor_path = post_process_neighbor
->neighbor_path;
call_stack.emplace(std::move(post_process_neighbor));
call_stack.emplace(
std::make_unique<EvaluateNeighbor>(
neighbor_path,
neighbor.node,
neighbor.edge_length,
data.act_path));
}
};
EvaluateNeighbor::EvaluateNeighbor(VoronoiGraph::ExPath & result,
const VoronoiGraph::Node *node,
double distance_to_node,
const VoronoiGraph::Path &prev_path)
: post_process_neighbor(
std::make_unique<PostProcessNeighbors>(result,
node,
distance_to_node,
prev_path))
{}
void EvaluateNeighbor::process(IStackFunction::CallStack &call_stack)
{
CallData &data = *post_process_neighbor;
call_stack.emplace(std::move(post_process_neighbor));
for (const VoronoiGraph::Node::Neighbor &neighbor : data.node->neighbors)
call_stack.emplace(std::make_unique<ExpandNeighbor>(data, neighbor));
}
/// <summary>
/// PRIVATE:
/// Extract the longest path from voronoi graph
@ -2632,11 +2901,33 @@ void reshape_longest_path(VoronoiGraph::ExPath& path) {
/// </summary>
/// <param name="start_path">IN/OUT path in Graph</param>
VoronoiGraph::ExPath create_longest_path(const VoronoiGraph::Node *start_node)
{
VoronoiGraph::ExPath longest_path;
IStackFunction::CallStack call_stack;
call_stack.emplace(std::make_unique<EvaluateNeighbor>(longest_path, start_node));
// depth search for longest path in graph
while (!call_stack.empty()) {
std::unique_ptr<IStackFunction> stack_function = std::move(call_stack.top());
call_stack.pop();
stack_function->process(call_stack);
// stack function deleted
}
reshape_longest_path(longest_path);
// after reshape it shoud be longest path for whole Voronoi Graph
return longest_path;
}
/// <summary>
/// PRIVATE:
/// Extract the longest path from voronoi graph
/// Restructuralize path by branch created from random point
/// </summary>
/// <param name="start_path">IN/OUT path in Graph</param>
VoronoiGraph::ExPath create_longest_path_recursive(const VoronoiGraph::Node *start_node)
{
VoronoiGraph::ExPath longest_path = create_longest_branch_from_node(*start_node);
reshape_longest_path(longest_path);
// after reshape it shoud be longest path for whole Voronoi Graph
return longest_path;
@ -2985,7 +3276,7 @@ TEST_CASE("Sample small islands", "[VoronoiSkeleton]")
{4 * size5, size5},
{3 * size5, size5}}};
size_t count_cirlce_lines = 10; // test stack overfrow
size_t count_cirlce_lines = 100; // test stack overfrow
double r_CCW = size / 2;
double r_CW = r_CCW - size / 6;
// CCW: couter clock wise, CW: clock wise
@ -3017,15 +3308,16 @@ TEST_CASE("Sample small islands", "[VoronoiSkeleton]")
cfg.max_length_for_one_support_point = 3 * size;
ExPolygons islands = {
triangle
, square
, sharp_triangle
, rect
, rect_with_hole
, triangle_with_hole
, rect_with_4_hole
, mountains
, double_circle
double_circle
// triangle
//, square
//, sharp_triangle
//, rect
//, rect_with_hole
//, triangle_with_hole
//, rect_with_4_hole
//, mountains
//, double_circle
//, frog_leg
};
for (auto &island : islands) {