From a50f8ba2df0a1e25ab18df3b5107818abf6b9afc Mon Sep 17 00:00:00 2001 From: dmatetelki Date: Fri, 6 Jan 2017 13:52:51 +0100 Subject: [PATCH] import/export graph to plain text --- lib/graph/graph_plaintext.hpp | 59 +++++++++++++++++++++ test/CMakeLists.txt | 1 + test/graph/fixture.hpp | 98 +++++++++++++++++++++++------------ test/graph/test_plaintext.cpp | 51 ++++++++++++++++++ 4 files changed, 177 insertions(+), 32 deletions(-) create mode 100644 lib/graph/graph_plaintext.hpp create mode 100644 test/graph/test_plaintext.cpp diff --git a/lib/graph/graph_plaintext.hpp b/lib/graph/graph_plaintext.hpp new file mode 100644 index 0000000..8efbf6e --- /dev/null +++ b/lib/graph/graph_plaintext.hpp @@ -0,0 +1,59 @@ +#include "graph.hpp" + +#include +#include + +// format: 1 line = 1 node +// first line followed by it's neighbours. +// separator is an empty new line +// std::string vertexSerializer(const V& v) converts V into std::string +// V vertexCreator(const std::string&s) converts std::string to V + +template +Graph readGraphFromPlainText(const std::string& filename, F vertexCreator) +{ + std::ifstream file(filename); + if (!file.good()) + throw std::runtime_error("Failed to open " + filename + " to read."); + + Graph g; + std::string line; + while (std::getline(file, line)) + if (!line.empty()) + break; + + bool new_entry = true; + V current_vertex; + while (std::getline(file, line)) { + if (line.empty()) { + new_entry = true; + } else { + if (new_entry) { + current_vertex = vertexCreator(line); + g.addVertex(current_vertex); + new_entry = false; + } else { + g.addEdge(current_vertex, vertexCreator(line)); + } + } + } + return g; +} + +template +void writeGraphToPlainText(const Graph& g, const std::string& filename, F vertexSerializer) +{ + std::ofstream file; + file.open (filename); + if (!file.is_open()) + throw std::runtime_error("Failed to open " + filename + " to write."); + + for (const auto cit : g) { + file << vertexSerializer(cit) << std::endl; + for (const auto cit2 : g.neighboursOf(cit)) + file << vertexSerializer(cit2) << std::endl; + + file << std::endl; + } + file.close(); +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 16299e9..d64e859 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -8,6 +8,7 @@ graph/test_priority_queue.cpp graph/test_quad_tree.cpp graph/test_graph_algorithms.cpp graph/test_marching_squares.cpp +graph/test_plaintext.cpp test_main.cpp) diff --git a/test/graph/fixture.hpp b/test/graph/fixture.hpp index ebb0830..1ca3f84 100644 --- a/test/graph/fixture.hpp +++ b/test/graph/fixture.hpp @@ -4,6 +4,8 @@ #include #include +#include +#include inline std::size_t hash_f(float f) { return std::hash()(f); } inline std::size_t hash_2f(float f1, float f2) { return hash_f(f1) ^ (hash_f(f2) << 1); } @@ -20,8 +22,17 @@ struct float2 { inline bool operator ==(const float2& v1, const float2& v2) { return v1.x == v2.x && v1.y == v2.y; } inline bool operator !=(const float2& v1, const float2& v2) { return !(v1 == v2); } inline float pow2(float f) { return f*f; } -inline float dist(const float2& v1, const float2& v2) { return sqrt(pow2(v2.x - v1.x) + pow2(v2.y - v1.y)); } - +inline float distance(const float2& v1, const float2& v2) { return sqrt(pow2(v2.x - v1.x) + pow2(v2.y - v1.y)); } +inline float lenght(const float2& v) { return sqrt(pow2(v.x) + pow2(v.y)); } +inline float angle(const float2& v) { return asin(v.y /lenght(v)); } +inline bool operator <(const float2& v1, const float2& v2) +{ + const float l1 = lenght(v1); + const float l2 = lenght(v2); + if (l1 < l2) return true; + if (l1 > l2) return false; + return (angle(v1) < angle(v2)); +} namespace std { template <> @@ -33,13 +44,34 @@ namespace std { class distanceOf2float2s : public std::function { public: - float operator()(float2 a, float2 b) { return dist(a, b); } + float operator()(float2 a, float2 b) { return distance(a, b); } }; class distanceOf2ints : public std::function { public: float operator()(int a, int b) { return abs(a-b); } }; + + template + std::string to_string_with_precision(const T a_value, const int n = 6) + { + std::ostringstream out; + out << std::fixed << std::setprecision(n) << a_value; + return out.str(); + } +} + +inline float2 float2creator(const std::string& line) +{ + std::stringstream ss(line); + float f1, f2; + ss >> f1 >> f2; + return float2(f1, f2); +} + +inline std::string float2serializer(const float2& f2) +{ + return std::to_string_with_precision(f2.x, 3) + " " + std::to_string_with_precision(f2.y, 3); } constexpr std::size_t numberOfEdges(std::size_t number_of_rows, std::size_t number_of_columns) { @@ -68,7 +100,7 @@ std::vector::Edge>* createEdges(std::size_t number_of_rows, st retval->reserve(number_of_edges); // inside - for (std::size_t c = 1; c < number_of_columns-1; ++c) + for (std::size_t c = 1; c < number_of_columns-1; ++c) { for (std::size_t r = 1; r < number_of_rows-1; ++r) { retval->push_back(typename Graph::Edge(float2(r, c), float2(r-1, c-1))); retval->push_back(typename Graph::Edge(float2(r, c), float2(r-1, c))); @@ -79,36 +111,38 @@ std::vector::Edge>* createEdges(std::size_t number_of_rows, st retval->push_back(typename Graph::Edge(float2(r, c), float2(r+1, c))); retval->push_back(typename Graph::Edge(float2(r, c), float2(r+1, c+1))); } + } - // top & bottom row - for (std::size_t r = 1; r < number_of_rows-1; ++r) { - retval->push_back(typename Graph::Edge(float2(r, 0), float2(r-1, 0))); - retval->push_back(typename Graph::Edge(float2(r, 0), float2(r+1, 0))); - retval->push_back(typename Graph::Edge(float2(r, 0), float2(r-1, 1))); - retval->push_back(typename Graph::Edge(float2(r, 0), float2(r, 1))); - retval->push_back(typename Graph::Edge(float2(r, 0), float2(r+1, 1))); - - retval->push_back(typename Graph::Edge(float2(r, number_of_columns-1), float2(r-1, number_of_columns-1))); - retval->push_back(typename Graph::Edge(float2(r, number_of_columns-1), float2(r+1, number_of_columns-1))); - retval->push_back(typename Graph::Edge(float2(r, number_of_columns-1), float2(r-1, number_of_columns-1-1))); - retval->push_back(typename Graph::Edge(float2(r, number_of_columns-1), float2(r, number_of_columns-1-1))); - retval->push_back(typename Graph::Edge(float2(r, number_of_columns-1), float2(r+1, number_of_columns-1-1))); - } + // top & bottom row + for (std::size_t r = 1; r < number_of_rows-1; ++r) { + retval->push_back(typename Graph::Edge(float2(r, 0), float2(r-1, 0))); + retval->push_back(typename Graph::Edge(float2(r, 0), float2(r+1, 0))); + retval->push_back(typename Graph::Edge(float2(r, 0), float2(r-1, 1))); + retval->push_back(typename Graph::Edge(float2(r, 0), float2(r, 1))); + retval->push_back(typename Graph::Edge(float2(r, 0), float2(r+1, 1))); + + retval->push_back(typename Graph::Edge(float2(r, number_of_columns-1), float2(r-1, number_of_columns-1))); + retval->push_back(typename Graph::Edge(float2(r, number_of_columns-1), float2(r+1, number_of_columns-1))); + retval->push_back(typename Graph::Edge(float2(r, number_of_columns-1), float2(r-1, number_of_columns-1-1))); + retval->push_back(typename Graph::Edge(float2(r, number_of_columns-1), float2(r, number_of_columns-1-1))); + retval->push_back(typename Graph::Edge(float2(r, number_of_columns-1), float2(r+1, number_of_columns-1-1))); + } + + // left & right column + for (std::size_t c = 1; c < number_of_columns-1; ++c) { + retval->push_back(typename Graph::Edge(float2(0, c), float2(0, c-1))); + retval->push_back(typename Graph::Edge(float2(0, c), float2(0, c+1))); + retval->push_back(typename Graph::Edge(float2(0, c), float2(1, c-1))); + retval->push_back(typename Graph::Edge(float2(0, c), float2(1, c))); + retval->push_back(typename Graph::Edge(float2(0, c), float2(1, c+1))); + + retval->push_back(typename Graph::Edge(float2(number_of_rows-1, c), float2(number_of_rows-1, c-1))); + retval->push_back(typename Graph::Edge(float2(number_of_rows-1, c), float2(number_of_rows-1, c+1))); + retval->push_back(typename Graph::Edge(float2(number_of_rows-1, c), float2(number_of_rows-1-1, c-1))); + retval->push_back(typename Graph::Edge(float2(number_of_rows-1, c), float2(number_of_rows-1-1, c))); + retval->push_back(typename Graph::Edge(float2(number_of_rows-1, c), float2(number_of_rows-1-1, c+1))); + } - // left & right column - for (std::size_t c = 1; c < number_of_columns-1; ++c) { - retval->push_back(typename Graph::Edge(float2(0, c), float2(0, c-1))); - retval->push_back(typename Graph::Edge(float2(0, c), float2(0, c+1))); - retval->push_back(typename Graph::Edge(float2(0, c), float2(1, c-1))); - retval->push_back(typename Graph::Edge(float2(0, c), float2(1, c))); - retval->push_back(typename Graph::Edge(float2(0, c), float2(1, c+1))); - - retval->push_back(typename Graph::Edge(float2(number_of_rows-1, c), float2(number_of_rows-1, c-1))); - retval->push_back(typename Graph::Edge(float2(number_of_rows-1, c), float2(number_of_rows-1, c+1))); - retval->push_back(typename Graph::Edge(float2(number_of_rows-1, c), float2(number_of_rows-1-1, c-1))); - retval->push_back(typename Graph::Edge(float2(number_of_rows-1, c), float2(number_of_rows-1-1, c))); - retval->push_back(typename Graph::Edge(float2(number_of_rows-1, c), float2(number_of_rows-1-1, c+1))); - } // corners retval->push_back(typename Graph::Edge(float2(0, 0), float2(0, 1))); diff --git a/test/graph/test_plaintext.cpp b/test/graph/test_plaintext.cpp new file mode 100644 index 0000000..cd1663b --- /dev/null +++ b/test/graph/test_plaintext.cpp @@ -0,0 +1,51 @@ +#include + +#include "../catch.hpp" + +#include "fixture.hpp" + +#include // remove file + +inline int intCreator(const std::string& s) { return std::stoi(s); } +inline std::string intSerializer(int i) { return std::to_string(i); } + +/// @note is there a smareter way to do this? +inline std::string s2s(const std::string& s) { return s; } + + +TEST_CASE( "Plain text import/export", "[IO]" ) { + + SECTION("Invalid files") { + const std::string root_file("/root/graph_dump.txt"); + const Graph g1; + CHECK_THROWS ( writeGraphToPlainText(g1, root_file, intSerializer) ); + CHECK_THROWS ( readGraphFromPlainText(root_file, intCreator) ); + } + + SECTION("vertices are strings") { + const std::string fileName("/tmp/graph_dump.txt"); + const std::vector v{ "one", "two", "three", "... and four"}; + Graph g1(v); + g1.addEdge(v[0], v[1]); + g1.addEdge(v[0], v[2]); + g1.addEdge(v[2], v[3]); + + writeGraphToPlainText(g1, fileName, s2s); + const Graph g2 = readGraphFromPlainText(fileName, s2s); + REQUIRE ( g1 == g2 ); + + remove(fileName.c_str()); + } + + SECTION("vertices are float coordinates") { + const std::string fileName("/tmp/graph_dump.txt"); + const std::vector::Edge>* edges = createEdges(4, 4); + const Graph g1(*edges); + writeGraphToPlainText(g1, fileName, float2serializer); + const Graph g2 = readGraphFromPlainText(fileName, float2creator); + REQUIRE ( g1 == g2 ); + + remove(fileName.c_str()); + delete edges; + } +}