This question is follow up to Object Oriented Graph Hierarchy with Depth First Search. I tried to fulfill every point which @LokiAstari mentioned. I think I succeeded in everything except Visitor Pattern.
I came to implementation of a function void BiGraph::addAdjacentImpl(int fVertex, int sVertex)
in different way. Now it will do very heavy checks. Hope I didn't miss something. Also, I implemented check for keeping being a tree with ad-hoc approach (not sure about it). Every time edge is to be added, it is added only one way, and then traverse
is called to check if cycle will be created or not, so it is kind of online check. Could you please pay extra attention to this function and criticize it as much as possible, because it is very essential part.
Graph.cpp is the outcome of complains of linker. Seems like inlining of print
function call made the complains. The file implements only operator<<
s, so if clients write their own non-virtual functions I think it doesn't violate ODR which is ok, I hope I'm not missing anything.
Also I made heavy unit test, but it is just a bunch of try catch statements, everything was executed in a way it was waited to.
Graph.h
#ifndef GRAPH_H
#define GRAPH_H
#include <memory>
#include <vector>
#include <iostream>
class Graph
{
public:
void addAdjacent(int fVertex, int sVertex) { addAdjacentImpl(fVertex, sVertex); }
const std::vector<char> areConnected(int sourceVertex) const { return areConnectedImpl(sourceVertex); }
void print(std::ostream& os) const { printImpl(os); }
private:
virtual void printImpl(std::ostream& os) const = 0;
virtual void addAdjacentImpl(int fvertex, int svertex) = 0;
virtual const std::vector<char> areConnectedImpl(int sourcevertex) const = 0;
virtual void traverse(int sourcevVertex, std::vector<char>& areVisited) const = 0;
};
std::ostream& operator<<(std::ostream& os, Graph* g);
std::ostream& operator<<(std::ostream& os, const std::unique_ptr<Graph>& g);
std::ostream& operator<<(std::ostream& os, const std::shared_ptr<Graph>& g);
#endif
Graph.cpp
#include "graph.h"
std::ostream& operator<<(std::ostream& os, Graph* g)
{
g->print(os);
return os;
}
std::ostream& operator<<(std::ostream& os, const std::unique_ptr<Graph>& g)
{
g->print(os);
return os;
}
std::ostream& operator<<(std::ostream& os, const std::shared_ptr<Graph>& g)
{
g->print(os);
return os;
}
BiGraph.h
#ifndef BIGRAPH_H
#define BIGRAPH_H
#include "graph.h"
#include <map>
#include <vector>
#include <string>
#include <iostream>
class BiGraph :public Graph
{
std::vector<std::vector<int> > graph;
int maxVertexCount;
std::string name;
public:
BiGraph(int maxVertexCount_, std::string name_);
private:
virtual void printImpl(std::ostream& os) const override;
virtual void addAdjacentImpl(int fVertex, int sVertex) override;
virtual const std::vector<char> areConnectedImpl(int sourcevertex) const override;
virtual void traverse(int sourceVertex, std::vector<char>& areVisited) const override;
};
#endif
BiGraph.cpp
#include "BiGraph.h"
#include <exception>
#include <iostream>
BiGraph::BiGraph(int maxvertexcount_, std::string name_):
graph(maxvertexcount_),
maxVertexCount(maxvertexcount_),
name(name_)
{
}
void BiGraph::addAdjacentImpl(int fVertex, int sVertex)
{
if (fVertex == sVertex)
{
throw std::logic_error(name + ": Trying to add a loop");
}
if (fVertex <0 || fVertex > maxVertexCount)
{
throw std::out_of_range(name + ": trying to add non-existent vertex");
}
if (sVertex < 0 || sVertex > maxVertexCount)
{
throw std::out_of_range(name + ": trying to add non-existent vertex");
}
std::vector<int>::const_iterator cit;
cit = std::find(graph[fVertex].cbegin(), graph[fVertex].cend(), sVertex);
if (cit != graph[fVertex].cend())
{
throw std::logic_error(name + ": Connecting these vertices will make the graph no longer tree");
}
cit = std::find(graph[sVertex].cbegin(), graph[sVertex].cend(), fVertex);
if (cit != graph[sVertex].cend())
{
throw std::logic_error(name + ": Connecting these vertices will make the graph no longer tree");
}
graph[fVertex].push_back(sVertex);
std::vector<char> areVisited(maxVertexCount, false);
traverse(sVertex, areVisited);
if (areVisited[fVertex])
{
graph[fVertex].pop_back();
throw std::logic_error(name + ": Connecting these vertices will cause a cycle");
}
graph[sVertex].push_back(fVertex);
}
void BiGraph::traverse(int sourceVertex, std::vector<char>& areVisited) const
{
if (areVisited[sourceVertex])
return;
areVisited[sourceVertex] = true;
for (auto& x:graph[sourceVertex])
{
traverse(x, areVisited);
}
}
const std::vector<char> BiGraph::areConnectedImpl(int sourceVertex) const
{
std::vector<char> areVisited(maxVertexCount, false);
traverse(sourceVertex, areVisited);
return areVisited;
}
void BiGraph::printImpl(std::ostream& os) const
{
for (size_t i = 0; i < graph.size(); i++)
{
os << i << ": ";
for (auto& j:graph[i])
{
os << j << ' ';
}
os << std::endl;
}
}
GraphFactory.h
#ifndef GRAPHFACTORY_H
#define GRAPHFACTORY_H
#include "graph.h"
#include <memory>
#include <string>
class GraphFactory
{
public:
GraphFactory()=default;
std::unique_ptr<Graph> getBiGraph(int size, std::string name);
~GraphFactory()=default;
};
#endif
GraphFactory.cpp
#include "GraphFactory.h"
#include "BiGraph.h"
std::unique_ptr<Graph> GraphFactory::getBiGraph(int size, std::string name)
{
return std::make_unique<BiGraph>(size, name);
}