I have very little experience in programming with C++ and the following small "program" is the 2nd one I have ever written in that language. I am most interested in comments regarding naming conventions and the way the code is modularized. Also, can you tell whether the code sticks to idiomatic use of the language?
Here what I have so far:
directed_graph_node.h:
#ifndef DIRECTED_GRAPH_NODE_H
#define DIRECTED_GRAPH_NODE_H
#include <string>
#include <unordered_set>
#include <vector>
/*******************************************************************************
* This class implements a node in directed graphs. The adjacency list *
* invariant is that if there is a directed edge from u to v, u.m_out has a *
* pointer to v, and v.m_in has a pointer to u. *
*******************************************************************************/
class DirectedGraphNode {
public:
DirectedGraphNode(const std::string name);
void add_child(DirectedGraphNode& child);
bool has_child(DirectedGraphNode& query);
void remove_child(DirectedGraphNode& child);
// Child iterators.
std::unordered_set<DirectedGraphNode*>::const_iterator begin();
std::unordered_set<DirectedGraphNode*>::const_iterator end();
bool operator==(const DirectedGraphNode& other) const;
friend std::ostream& operator<<(std::ostream& os,
const DirectedGraphNode& node);
// Forward declaration.
class ParentIteratorProxy;
ParentIteratorProxy parents();
class ParentIteratorProxy {
public:
ParentIteratorProxy(const DirectedGraphNode* owner);
std::unordered_set<DirectedGraphNode*>::const_iterator begin();
std::unordered_set<DirectedGraphNode*>::const_iterator end();
private:
const DirectedGraphNode* mp_owner;
};
private:
std::string m_name;
std::unordered_set<DirectedGraphNode*> m_in;
std::unordered_set<DirectedGraphNode*> m_out;
};
#endif // DIRECTED_GRAPH_NODE_H
directed_graph_node.cpp:
#include <iostream>
#include <stdexcept>
#include <unordered_set>
#include "directed_graph_node.h"
/*******************************************************************************
* Constructs a new DirectedGraphNode with the given name. *
*******************************************************************************/
DirectedGraphNode::DirectedGraphNode(const std::string name) {
m_name = name;
}
/*******************************************************************************
* Creates a directed edge from this node to node 'child'. *
*******************************************************************************/
void DirectedGraphNode::add_child(DirectedGraphNode& child) {
m_out.insert(&child);
child.m_in.insert(this);
}
/*******************************************************************************
* Queries whether there is an arc (this, query). *
*******************************************************************************/
bool DirectedGraphNode::has_child(DirectedGraphNode& query) {
if (m_out.find(&query) == m_out.end()) {
if (query.m_in.find(this) != query.m_in.end()) {
// The adjacency list invariant is broken. See the header.
throw std::runtime_error("Adjacency list invariant broken. 1/2.");
}
return false;
} else if (query.m_in.find(this) == query.m_in.end()) {
throw std::runtime_error("Adjacency list invariant broken. 2/2.");
}
return true;
}
/*******************************************************************************
* Removes the edge (this, child). *
*******************************************************************************/
void DirectedGraphNode::remove_child(DirectedGraphNode& child) {
m_out.erase(&child);
child.m_in.erase(this);
}
/*******************************************************************************
* Compares by name this node to 'other'. *
*******************************************************************************/
bool DirectedGraphNode::operator ==(const DirectedGraphNode& other) const {
return this->m_name.compare(other.m_name) == 0;
}
/*******************************************************************************
* Returns a const iterator to a child node of this node. *
*******************************************************************************/
std::unordered_set<DirectedGraphNode*>::const_iterator
DirectedGraphNode::begin() {
return m_out.begin();
}
/*******************************************************************************
* Returns a const iterator to the end of child list. *
*******************************************************************************/
std::unordered_set<DirectedGraphNode*>::const_iterator
DirectedGraphNode::end() {
return m_out.end();
}
/*******************************************************************************
* Returns a proxy iterator over a node's parent nodes. *
*******************************************************************************/
DirectedGraphNode::ParentIteratorProxy
::ParentIteratorProxy(const DirectedGraphNode* p_owner) :
mp_owner(p_owner) {}
/*******************************************************************************
* Returns the first parent node in the parent list of the owner node. *
*******************************************************************************/
std::unordered_set<DirectedGraphNode*>::const_iterator
DirectedGraphNode::ParentIteratorProxy::begin() {
return mp_owner->m_in.begin();
}
/*******************************************************************************
* Returns an iterator pointing to the end of owner node's parent list. *
*******************************************************************************/
std::unordered_set<DirectedGraphNode*>::const_iterator
DirectedGraphNode::ParentIteratorProxy::end() {
return mp_owner->m_in.end();
}
/*******************************************************************************
* Returns an iterator over owner node's parent list. *
*******************************************************************************/
DirectedGraphNode::ParentIteratorProxy DirectedGraphNode::parents() {
return ParentIteratorProxy(this);
}
/*******************************************************************************
* Neatly prints a node. *
*******************************************************************************/
std::ostream& operator<<(std::ostream& os, const DirectedGraphNode& node) {
return os << "[DirectedGraphNode " << node.m_name << "]";
}
path_finder.h:
#ifndef PATHFINDER_H
#define PATHFINDER_H
#include <algorithm>
#include <unordered_map>
#include <vector>
#include "directed_graph_node.h"
using std::unordered_map;
using std::vector;
class PathFinder {
public:
virtual std::vector<DirectedGraphNode*>*
search(DirectedGraphNode& source,
DirectedGraphNode& target) = 0;
vector<DirectedGraphNode*>*
construct_path(DirectedGraphNode* touch,
unordered_map<DirectedGraphNode*,
DirectedGraphNode*>* p_parents_f,
unordered_map<DirectedGraphNode*,
DirectedGraphNode*>* p_parents_b) {
vector<DirectedGraphNode*>* p_path = new vector<DirectedGraphNode*>;
DirectedGraphNode* u = const_cast<DirectedGraphNode*>(touch);
while (u) {
p_path->push_back(u);
u = (*p_parents_f)[u];
}
std::reverse(p_path->begin(), p_path->end());
if (p_parents_b) {
u = (*p_parents_b)[touch];
while (u) {
p_path->push_back(u);
u = (*p_parents_b)[u];
}
}
return p_path;
}
};
#endif // PATHFINDER_H
bfs_path_finder.h:
#ifndef BFS_PATH_FINDER_H
#define BFS_PATH_FINDER_H
#include <deque>
#include <iostream>
#include <vector>
#include <unordered_map>
#include "directed_graph_node.h"
#include "path_finder.h"
/*******************************************************************************
* Implements a path finder using breadth-first search in unweighted digraphs. *
*******************************************************************************/
class BFSPathFinder : public PathFinder {
public:
std::vector<DirectedGraphNode*>* search(DirectedGraphNode& source,
DirectedGraphNode& target) {
m_queue.clear();
m_parent_map.clear();
// Initialize the state.
m_queue.push_back(&source);
m_parent_map[&source] = nullptr;
while (!m_queue.empty()) {
DirectedGraphNode* p_current = m_queue.front();
if (*p_current == target) {
// Reached the target.
return construct_path(p_current, &m_parent_map, nullptr);
}
m_queue.pop_front();
for (auto p_child : *p_current) {
if (m_parent_map.find(p_child) == m_parent_map.end()) {
m_parent_map.insert({p_child, p_current});
m_queue.push_back(p_child);
}
}
}
return nullptr;
}
private:
std::deque<DirectedGraphNode*> m_queue;
std::unordered_map<DirectedGraphNode*,
DirectedGraphNode*> m_parent_map;
};
#endif // BFS_PATH_FINDER_H
bibfs_path_finder.h:
#ifndef BIBFS_PATH_FINDER_H
#define BIBFS_PATH_FINDER_H
#include <deque>
#include <iostream>
#include <vector>
#include <unordered_map>
#include "directed_graph_node.h"
#include "path_finder.h"
/*******************************************************************************
* Implements a path finder using bidirectional breadth-first search for *
* unweighted digraphs. *
*******************************************************************************/
class BidirectionalBFSPathFinder : public PathFinder {
public:
std::vector<DirectedGraphNode*>* search(DirectedGraphNode& source,
DirectedGraphNode& target) {
m_queue1.clear();
m_queue2.clear();
m_parent_map1.clear();
m_parent_map2.clear();
m_distance_map1.clear();
m_distance_map2.clear();
// Initialize the state.
m_queue1.push_back(&source);
m_queue2.push_back(&target);
m_parent_map1[&source] = nullptr;
m_parent_map2[&target] = nullptr;
m_distance_map1[&source] = 0;
m_distance_map2[&target] = 0;
// A node where the two search frontiers "meet".
DirectedGraphNode* p_touch = nullptr;
// The best known cost of a shortest path.
size_t best_cost = std::numeric_limits<std::size_t>::max();
while (m_queue1.size() > 0 && m_queue2.size() > 0) {
if (p_touch != nullptr
&& m_distance_map1[m_queue1.front()]
+ m_distance_map2[m_queue2.front()] >= best_cost) {
// Termination condition met.
return construct_path(p_touch,
&m_parent_map1,
&m_parent_map2);
}
// A trivial load balancing.
if (m_queue1.size() < m_queue2.size()) {
// Once here, expand the forward search frontier.
DirectedGraphNode* p_current = m_queue1.front();
if (m_parent_map2.find(p_current) != m_parent_map2.end()) {
// Here, update to the shortest path is possible.
const size_t tmp = m_distance_map1[p_current] +
m_distance_map2[p_current];
if (best_cost > tmp) {
// Update the current best touch node.
best_cost = tmp;
p_touch = p_current;
}
}
m_queue1.pop_front();
// Expand the forward search frontier.
for (auto p_child : *p_current) {
if (m_parent_map1.find(p_child) == m_parent_map1.end()) {
m_parent_map1.insert({p_child, p_current});
m_distance_map1.insert({
p_child,
m_distance_map1[p_current] + 1
});
m_queue1.push_back(p_child);
}
}
} else {
// Once here, expand the backward search frontier.
DirectedGraphNode* p_current = m_queue2.front();
if (m_parent_map1.find(p_current) != m_parent_map1.end()) {
// Here, update to the shortest path is possible.
const size_t tmp = m_distance_map1[p_current] +
m_distance_map2[p_current];
if (best_cost > tmp) {
// Update the current best touch node.
best_cost = tmp;
p_touch = p_current;
}
}
m_queue2.pop_front();
// Expand the backward search.
for (auto p_parent : p_current->parents()) {
if (m_parent_map2.find(p_parent) == m_parent_map2.end()) {
m_parent_map2.insert({p_parent, p_current});
m_distance_map2.insert({
p_parent,
m_distance_map2[p_current] + 1
});
m_queue2.push_back(p_parent);
}
}
}
}
return nullptr;
}
private:
std::deque<DirectedGraphNode*> m_queue1;
std::deque<DirectedGraphNode*> m_queue2;
std::unordered_map<DirectedGraphNode*,
DirectedGraphNode*> m_parent_map1;
std::unordered_map<DirectedGraphNode*,
DirectedGraphNode*> m_parent_map2;
std::unordered_map<DirectedGraphNode*,
size_t> m_distance_map1;
std::unordered_map<DirectedGraphNode*,
size_t> m_distance_map2;
};
#endif // BIBFS_PATH_FINDER_H