-
Notifications
You must be signed in to change notification settings - Fork 99
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Eulerian paths added, true by default
- Loading branch information
Showing
15 changed files
with
600 additions
and
70 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,7 +21,7 @@ | |
*.exe | ||
*.out | ||
*.app | ||
*_test | ||
*_tests | ||
|
||
#built or generated project files | ||
.deps/* | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
#ifndef EULERIAN_PATHS_H | ||
#define EULERIAN_PATHS_H | ||
|
||
#include <vector> | ||
#include <map> | ||
|
||
#include "geometry.hpp" | ||
|
||
/* This finds a minimal number of eulerian paths that cover the input. | ||
* The number of paths returned is equal to the number of vertices | ||
* with odd edge count divided by 2. | ||
* | ||
* To use, first get paths. Each path is a vector of n points that | ||
* represents n-1 line segments. Each path is considerd | ||
* bidirectional. | ||
* | ||
* After adding paths, build the Eulerian paths. The resulting paths | ||
* cover all segments in the input paths with the minimum number of | ||
* paths as described above. | ||
*/ | ||
|
||
namespace eulerian_paths { | ||
|
||
bool operator !=(const point_type& x, const point_type& y) { | ||
return std::tie(x.x(), x.y()) != std::tie(y.x(), y.y()); | ||
} | ||
|
||
bool operator ==(const point_type& x, const point_type& y) { | ||
return std::tie(x.x(), x.y()) == std::tie(y.x(), y.y()); | ||
} | ||
|
||
bool operator !=(const point_type_fp& x, const point_type_fp& y) { | ||
return std::tie(x.x(), x.y()) != std::tie(y.x(), y.y()); | ||
} | ||
|
||
bool operator ==(const point_type_fp& x, const point_type_fp& y) { | ||
return std::tie(x.x(), x.y()) == std::tie(y.x(), y.y()); | ||
} | ||
|
||
template <typename point_t, typename linestring_t, typename multi_linestring_t, typename point_less_than_p = std::less<point_t>> | ||
multi_linestring_t get_eulerian_paths(const multi_linestring_t& paths) { | ||
// Create a map from vertex to each path that starts or ends (or | ||
// both) at that vertex. It's a map to an input into the input | ||
// paths. | ||
std::multimap<point_t, size_t, point_less_than_p> vertex_to_unvisited_path_index; | ||
for (size_t i = 0; i < paths.size(); i++) { | ||
auto& path = paths[i]; | ||
if (path.size() < 2) { | ||
// Valid path must have a start and end. | ||
continue; | ||
} | ||
point_t start = path.front(); | ||
point_t end = path.back(); | ||
vertex_to_unvisited_path_index.emplace(start, i); | ||
vertex_to_unvisited_path_index.emplace(end, i); | ||
} | ||
|
||
// Given a point, make a path from that point as long as possible | ||
// until a dead end. Assume that point itself is already in the | ||
// list. | ||
std::function<void(const point_t&, linestring_t*)> make_path = [&] (const point_t& point, linestring_t* new_path) -> void { | ||
// Find an unvisited path that leads from point, any will do. | ||
auto vertex_and_path_index = vertex_to_unvisited_path_index.find(point); | ||
if (vertex_and_path_index == vertex_to_unvisited_path_index.end()) { | ||
// No more paths to follow. | ||
return; | ||
} | ||
size_t path_index = vertex_and_path_index->second; | ||
auto& path = paths[path_index]; | ||
if (point == path.front()) { | ||
// Append this path in the forward direction. | ||
new_path->insert(new_path->end(), path.cbegin()+1, path.cend()); | ||
} else { | ||
// Append this path in the reverse direction. | ||
new_path->insert(new_path->end(), path.crbegin()+1, path.crend()); | ||
} | ||
vertex_to_unvisited_path_index.erase(vertex_and_path_index); // Remove from the first vertex. | ||
point_t& new_point = new_path->back(); | ||
// We're bound to find one, otherwise there is a serious error in | ||
// the algorithm. | ||
auto range = vertex_to_unvisited_path_index.equal_range(new_point); | ||
for (auto iter = range.first; iter != range.second; iter++) { | ||
if (iter->second == path_index) { | ||
// Remove the path from the last vertex | ||
vertex_to_unvisited_path_index.erase(iter); | ||
break; | ||
} | ||
} | ||
// Continue making the path from here. | ||
make_path(new_point, new_path); | ||
}; | ||
|
||
// Only call this when there are no vertices with odd edge count. | ||
// This will traverse a path and, if it finds an unvisited edge, | ||
// will make a Euler circuit there and stitch it into the current | ||
// path. This continues until the end of the path. | ||
auto stitch_loops = [&] (linestring_t *euler_path) -> void { | ||
for (size_t i = 0; i < euler_path->size(); i++) { | ||
// Does this vertex have any unvisited edges? | ||
if (vertex_to_unvisited_path_index.count((*euler_path)[i]) > 0) { | ||
// Make a path from here. We don't need the first element, it's already in our path. | ||
linestring_t new_loop{}; | ||
make_path((*euler_path)[i], &new_loop); | ||
// Now we stitch it in. | ||
euler_path->insert(euler_path->begin()+i+1, new_loop.begin(), new_loop.end()); | ||
} | ||
} | ||
}; | ||
|
||
// We use Hierholzer's algorithm to find the minimum cycles. First, | ||
// make a path from each vertex with odd path count until the path | ||
// ends. | ||
std::vector<point_t> odd_vertices; | ||
// First find all vertices with odd count. We do it separate from | ||
// the loop below because the below loop will modify | ||
// vertex_to_unvisited_path_index and invalidate the iterator. | ||
for (auto iter = vertex_to_unvisited_path_index.cbegin(); | ||
iter != vertex_to_unvisited_path_index.cend();) { | ||
auto& vertex = iter->first; | ||
if (vertex_to_unvisited_path_index.count(vertex) % 2 == 1) { | ||
odd_vertices.push_back(vertex); | ||
} | ||
// Advance to a different vertex. | ||
iter = vertex_to_unvisited_path_index.upper_bound(vertex); | ||
} | ||
|
||
multi_linestring_t euler_paths{}; | ||
for (auto iter = odd_vertices.cbegin(); iter != odd_vertices.cend(); iter++) { | ||
auto& vertex = *iter; | ||
// We check again because it might have become even after being | ||
// connected to a different path. | ||
if (vertex_to_unvisited_path_index.count(vertex) % 2 == 1) { | ||
// Make a path starting from vertex with odd count. | ||
linestring_t new_path; | ||
new_path.push_back(vertex); | ||
make_path(vertex, &new_path); | ||
euler_paths.push_back(new_path); | ||
} | ||
} | ||
|
||
// At this point, all remaining vertices have an even number of | ||
// edges. So if we make a path from one, it is sure to end back | ||
// where it started. We'll go over all our current Euler paths and | ||
// stitch in loops anywhere that there is an unvisited edge. | ||
for (auto& euler_path : euler_paths) { | ||
stitch_loops(&euler_path); | ||
} | ||
|
||
// Anything remaining is on islands. Make all those paths, too. No | ||
// stitching needed because all vertices have an even number of | ||
// edges. | ||
while(vertex_to_unvisited_path_index.size() > 0) { | ||
const auto vertex = vertex_to_unvisited_path_index.cbegin()->first; | ||
linestring_t new_path; | ||
new_path.push_back(vertex); | ||
make_path(vertex, &new_path); | ||
// We can stitch right now because all vertices already have | ||
// even number of edges. | ||
stitch_loops(&new_path); | ||
euler_paths.push_back(new_path); | ||
} | ||
|
||
return euler_paths; | ||
} | ||
|
||
} //namespace eulerian_paths | ||
#endif //EULERIAN_PATHS_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
#define BOOST_TEST_MODULE eulerian paths tests | ||
#include <boost/test/included/unit_test.hpp> | ||
|
||
#include "eulerian_paths.hpp" | ||
|
||
using std::vector; | ||
using namespace eulerian_paths; | ||
|
||
BOOST_AUTO_TEST_SUITE(eulerian_paths_tests); | ||
|
||
struct PointLessThan { | ||
bool operator()(const point_type& a, const point_type& b) const { | ||
return std::tie(a.x(), a.y()) < std::tie(b.x(), b.y()); | ||
} | ||
}; | ||
|
||
BOOST_AUTO_TEST_CASE(do_nothing_points) { | ||
BOOST_CHECK(0==0); | ||
get_eulerian_paths<point_type, linestring_type, multi_linestring_type, PointLessThan>({{{1,1},{2,2},{3,3}}}); | ||
} | ||
|
||
// 3x3 grid connected like a window pane: | ||
// 1---2---3 | ||
// | | | | ||
// 4---5---6 | ||
// | | | | ||
// 7---8---9 | ||
BOOST_AUTO_TEST_CASE(window_pane) { | ||
BOOST_CHECK(0==0); | ||
vector<vector<int>> euler_paths = get_eulerian_paths<int, vector<int>, vector<vector<int>>>({ | ||
{1,2}, | ||
{2,3}, | ||
{4,5}, | ||
{5,6}, | ||
{7,8}, | ||
{8,9}, | ||
{1,4}, | ||
{4,7}, | ||
{2,5}, | ||
{5,8}, | ||
{3,6}, | ||
{6,9}, | ||
}); | ||
int edges_visited = 0; | ||
for (size_t i = 0; i < euler_paths.size(); i++) { | ||
edges_visited += euler_paths[i].size()-1; | ||
for (size_t j = 0; j < euler_paths[i].size(); j++) { | ||
printf("%d ", euler_paths[i][j]); | ||
} | ||
printf("\n"); | ||
} | ||
BOOST_CHECK(edges_visited == 12); | ||
BOOST_CHECK(euler_paths.size() == 2); | ||
} | ||
|
||
// 3x3 grid connected like a window pane, but corners are longer paths: | ||
// 1---2---3 | ||
// | | | | ||
// 4---5---6 | ||
// | | | | ||
// 7---8---9 | ||
BOOST_AUTO_TEST_CASE(window_pane_with_longer_corners) { | ||
BOOST_CHECK(0==0); | ||
vector<vector<int>> euler_paths = get_eulerian_paths<int, vector<int>, vector<vector<int>>>({ | ||
{4,5}, | ||
{5,6}, | ||
{4,7,8}, | ||
{2,5}, | ||
{5,8}, | ||
{6,9,8}, | ||
{4,1,2}, | ||
{2,3,6}, | ||
}); | ||
int edges_visited = 0; | ||
for (size_t i = 0; i < euler_paths.size(); i++) { | ||
edges_visited += euler_paths[i].size()-1; | ||
for (size_t j = 0; j < euler_paths[i].size(); j++) { | ||
printf("%d ", euler_paths[i][j]); | ||
} | ||
printf("\n"); | ||
} | ||
BOOST_CHECK(edges_visited == 12); | ||
BOOST_CHECK(euler_paths.size() == 2); | ||
} | ||
|
||
// Bridge | ||
// 5---2---1---6 | ||
// | | | | | ||
// 3---4 7---8 | ||
BOOST_AUTO_TEST_CASE(bridge) { | ||
BOOST_CHECK(0==0); | ||
vector<vector<int>> euler_paths = get_eulerian_paths<int, vector<int>, vector<vector<int>>>({ | ||
{5,2}, | ||
{2,1}, | ||
{1,6}, | ||
{3,4}, | ||
{7,8}, | ||
{5,3}, | ||
{2,4}, | ||
{1,7}, | ||
{6,8}, | ||
}); | ||
int edges_visited = 0; | ||
for (size_t i = 0; i < euler_paths.size(); i++) { | ||
edges_visited += euler_paths[i].size()-1; | ||
for (size_t j = 0; j < euler_paths[i].size(); j++) { | ||
printf("%d ", euler_paths[i][j]); | ||
} | ||
printf("\n"); | ||
} | ||
BOOST_CHECK(edges_visited == 9); | ||
BOOST_CHECK(euler_paths.size() == 1); | ||
} | ||
|
||
// Disjoint Loops | ||
// 5---2 1---6 0---9 | ||
// | | | | | ||
// 3---4 7---8 | ||
BOOST_AUTO_TEST_CASE(disjoint_loops) { | ||
BOOST_CHECK(0==0); | ||
vector<vector<int>> euler_paths = get_eulerian_paths<int, vector<int>, vector<vector<int>>>({ | ||
{5,2}, | ||
{1,6}, | ||
{3,4}, | ||
{7,8}, | ||
{5,3}, | ||
{2,4}, | ||
{1,7}, | ||
{6,8}, | ||
{0,9}, | ||
}); | ||
int edges_visited = 0; | ||
for (size_t i = 0; i < euler_paths.size(); i++) { | ||
edges_visited += euler_paths[i].size()-1; | ||
for (size_t j = 0; j < euler_paths[i].size(); j++) { | ||
printf("%d ", euler_paths[i][j]); | ||
} | ||
printf("\n"); | ||
} | ||
BOOST_CHECK(edges_visited == 9); | ||
BOOST_CHECK(euler_paths.size() == 3); | ||
} | ||
|
||
BOOST_AUTO_TEST_SUITE_END() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.