BOOKS i'm reading
 |
/*
* Name : graph.cpp
* Purpose: Find the shortest distance between 2 vertices in a graph
* by using graph breadth-first traversal
* This program has been developped as an assignment during a remote interview process
* with facebook.
*
* Known limitation: Another approach would be required on a weighted graph
* such computing the minimum span tree of the graph.
*
* To know more about the graph breadth-first traversal algorithm,
* I recommend Robert Sedgewick excellent book on algorithms
*
* Author : Olivier Langlois <olivier@olivierlanglois.net>
*
* Date: October 05, 2009
*/
#include <ctype.h> // For isalpha(), islower()
#include <set>
#include <deque>
#include <bitset>
#include <vector>
#include <iostream>
#include <sstream> // For std::ostringstream
#include <iterator> // For std::ostream_iterator and std::iterator
#include <algorithm> // For std::copy(), count_if() and for_each()
#include <stdexcept> // For std::runtime_error
#include <functional> // For std::binary_function, bind2nd and not_equal_to
namespace {
const unsigned MAXNODES = 52;
// forward declaration
class Node;
/*
* Predicate to sort Node pointers by ascending order of their ids.
*/
class NodePtrPred : public std::binary_function<Node*,Node*,bool>
{
public:
bool operator()( const Node *a, const Node *b ) const;
};
/*
* class Node
*
* Purpose: Contains a reference to all neighbors nodes.
* Note that if the graph is undirected, all neighbors will
* also point to this Node.
*/
class Node
{
public:
typedef std::set<Node *,NodePtrPred> NeighborsCont_t;
Node(unsigned id);
static unsigned index( char a );
static char name( unsigned idx );
// getters
unsigned getId() const { return m_id; }
char getName() const { return name(getId()); }
const NeighborsCont_t &getNeighbors() const { return m_neighbors; }
bool operator<( const Node &a ) const { return getId()<a.getId(); }
friend std::ostream &operator<<( std::ostream &o, const Node &n );
void AddNeighbor( Node *n );
private:
unsigned m_id; // Unique node id.
NeighborsCont_t m_neighbors;
};
/*
* Predicate that sorts Node pointers based on the class operator<
*/
bool NodePtrPred::operator()( const Node *a, const Node *b ) const
{
return *a<*b;
}
Node::Node(unsigned id)
: m_id(id)
{
}
inline void Node::AddNeighbor( Node *n )
{
/*
* Insertion will silently fail if n is already in the set.
*/
m_neighbors.insert(n);
}
/*
* Convert a letter to the corresponding Node index.
*/
unsigned Node::index( char a )
{
if( !isalpha(a) )
{
throw std::runtime_error("Vertex name is limited to alpha chars for now");
}
if( islower(a) )
{
return a-'a';
}
else // isupper(a)
{
return 26+a-'A';
}
}
/*
* Convert a node index back to its name.
*/
char Node::name( unsigned idx )
{
if( idx >= MAXNODES )
{
throw std::runtime_error("Vertex index is limited to MAXNODES");
}
if( idx < 26 )
{
return 'a'+idx;
}
else
{
return idx-26+'A';
}
}
/*
* Output iterator adapter
*
* Purpose: Allows to traverse a container of pointers and dereference each of them
* when they are assigned to the output iterator.
*/
template<class iterator_t>
class Deref_output_iterator_adapter : public std::iterator<std::output_iterator_tag,void,void,void,void>
{
public:
explicit Deref_output_iterator_adapter(iterator_t &x) : m_iterator(x) {}
template<class T>
Deref_output_iterator_adapter &operator=( const T *x )
{
*m_iterator = *x; return *this;
}
Deref_output_iterator_adapter &operator*() { return *this; }
Deref_output_iterator_adapter &operator++() { ++m_iterator; return *this; }
Deref_output_iterator_adapter &operator++(int) { m_iterator++; return *this; }
private:
iterator_t &m_iterator;
};
/*
* operator= specialization for Node
*
* Note: The Deref_output_iterator_adapter is finally not that generic!
*/
typedef std::ostream_iterator<char> output_iterator_t;
template<>
template<>
Deref_output_iterator_adapter<output_iterator_t> &
Deref_output_iterator_adapter<output_iterator_t>::operator=( const Node *x )
{
*m_iterator = x->getName(); return *this;
}
/*
* output stream operator for the class Node.
*/
std::ostream &operator<<( std::ostream &o, const Node &n )
{
o << "Node " << n.getName() << " has " << n.m_neighbors.size()
<< " neighbors : ";
output_iterator_t out_it(o," ");
std::copy(n.m_neighbors.begin(),n.m_neighbors.end(),
Deref_output_iterator_adapter<output_iterator_t>(out_it));
return o;
}
/*
* class NodeContainer
*
* This is the class representing the graph.
*/
class NodeContainer
{
public:
NodeContainer();
~NodeContainer();
void AddEdge( char a, char b, bool bUndirected = true );
/*
* Input param:
* a (char) Node name. (Valid range is a-z & A-Z).
* idx (unsigned) Node index.
* Return value: A pointer on the requested node or NULL if not found.
*
* Note: Can throw a std::out_of_range exception if passed an invalid param
* such as an idx greater than MAXNODES.
* Can throw a std::runtime_error if the 'a' param is an invalid node name.
*/
Node *FindNode( char a ) { return FindNode(Node::index(a)); }
Node *FindNode( unsigned idx ) { return m_Nodes.at(idx); }
const Node *FindNode( char a ) const { return FindNode(Node::index(a)); }
const Node *FindNode( unsigned idx ) const { return m_Nodes.at(idx); }
/*
* Purpose: Find the shortest distance from node a to node b by using the
* graph breadth-first traversal algorithm.
*
* Return value: Number of hops between nodes or NOT_CONNECTED
*/
static const unsigned NOT_CONNECTED = static_cast<unsigned>(-1); // Will become the biggest unsigned value
unsigned distance( char a, char b ) const;
friend std::ostream &operator<<( std::ostream &o, const NodeContainer &c );
private:
/*
* Purpose: Check the existence of the node at index idx of m_Nodes vector
* and create a Node if not present.
*
* Return value: Node at idx.
*
* Note: Can throw a std::out_of_range exception if idx is invalid.
*/
Node *CreateNode( unsigned idx );
std::vector<Node*> m_Nodes;
};
NodeContainer::NodeContainer()
: m_Nodes(MAXNODES)
{
}
NodeContainer::~NodeContainer()
{
for( unsigned i = 0; i < m_Nodes.size(); ++i )
{
/*
* No need to check for NULL as it is harmless to delete NULL.
*/
delete m_Nodes[i];
}
}
/*
* CreateNode()
*
* Purpose: Private method to create a node at index idx only if it does not exists yet.
*/
Node *NodeContainer::CreateNode( unsigned idx )
{
if( !m_Nodes.at(idx) )
{
m_Nodes[idx] = new Node(idx);
}
return m_Nodes[idx];
}
/*
* AddEdge()
*
* Purpose: Add an edge between node a and node b. Make the edge
* bidirectional if bUndirected true
*/
void NodeContainer::AddEdge( char a, char b, bool bUndirected )
{
unsigned aIdx = Node::index(a);
unsigned bIdx = Node::index(b);
Node *nodeA = CreateNode(aIdx);
Node *nodeB = CreateNode(bIdx);
nodeA->AddNeighbor(nodeB);
if( bUndirected )
{
nodeB->AddNeighbor(nodeA);
}
}
/*
* distance()
*
* Purpose: Find the shortest distance from node a to node b by using the
* graph breadth-first traversal algorithm.
*
* Return value: Number of hops between nodes or NOT_CONNECTED
*
* To know more about the graph breadth-first traversal algorithm,
* I recommend Robert Sedgewick excellent book on algorithms
*/
unsigned NodeContainer::distance( char a, char b ) const
{
// Input validation
const Node *nodeA = FindNode(a);
const Node *nodeB = FindNode(b);
if( !nodeA || !nodeB )
{
return NOT_CONNECTED;
}
else if( a == b )
{
return 0;
}
// Store nodes reachable in the next hop
std::deque<const Node*> lQueue;
// Keep track of visited nodes to avoid graph cycles.
std::bitset<MAXNODES> lVisitedNodes;
// Initialize working vars
lVisitedNodes.set(nodeA->getId());
lQueue.push_front(nodeA);
unsigned numHop = 1;
// Main loop
while( !lQueue.empty() )
{
unsigned curIterNodeNum = lQueue.size();
for( unsigned i = 0; i < curIterNodeNum; ++i )
{
const Node *curNode = lQueue.back();
lQueue.pop_back();
Node::NeighborsCont_t::const_iterator first = curNode->getNeighbors().begin();
Node::NeighborsCont_t::const_iterator last = curNode->getNeighbors().end();
while( first != last )
{
unsigned curNodeId = (*first)->getId();
if( curNodeId == nodeB->getId() )
{
return numHop;
}
else if( !lVisitedNodes[curNodeId] )
{
lVisitedNodes.set(curNodeId);
lQueue.push_front(*first);
}
++first;
}
}
++numHop;
}
// The graph have been fully analyzed without find any path between a and b.
return NOT_CONNECTED;
}
/*
* class NodeCondOutputAndCount
*
* Auxiliary functor applied to all nodes inside NodeContainer stream output operator<<
*/
class NodeCondOutputAndCount
{
public:
NodeCondOutputAndCount( std::ostream &o )
: m_nodeNum(0), m_rO(o) {}
// getters
unsigned getNodeNum() const { return m_nodeNum; }
void operator()( Node *a )
{
if(a)
{
m_rO << *a << '\n';
++m_nodeNum;
}
}
private:
unsigned m_nodeNum;
std::ostream &m_rO;
};
std::ostream &operator<<( std::ostream &o, const NodeContainer &c )
{
/*
* If execution time was an issue, a counter could be added to NodeContainer class
* and would be incremented in method CreateNode().
*
* Note: I had to get rid of my cleverly crafted STL statement as I have realized that both
* tasks could be performed in 1 iteration. The tradeoff is some memory to store the nodes
* output in a temporary string.
*/
/*
unsigned nodeNum = std::count_if(c.m_Nodes.begin(),c.m_Nodes.end(),
std::bind2nd(std::not_equal_to<Node*>(),static_cast<Node*>(NULL)));
*/
std::ostringstream ost;
o << "The graph has " << std::for_each(c.m_Nodes.begin(),c.m_Nodes.end(),
NodeCondOutputAndCount(ost)).getNodeNum()
<< " vertices\n";
o << ost.str();
return o;
}
/*
* Sample graph definition consisting of an enumeration of edges connecting the graph nodes.
*/
const char * const SampleGraphEdges[] = {
"ab", "ac", "ad",
"bf", "ba", "bc",
"cb", "cd", "ce", "ca",
"da", "dc", "do",
"ef", "em", "ec",
"fe", "fg", "fb",
"gh", "gk", "gf",
"hj", "hi", "hg",
"il", "ij", "ih",
"ji", "jh", "jk", "jl",
"kj", "kg", "kn",
"lj", "li", "lA",
"mn", "me", "mp",
"nm", "nk", "nB",
"op", "od", "or",
"po", "pq", "pm",
"qp", "qC", "qs",
"ru", "ro", "rs", "rt",
"sq", "sx", "sr",
"tr", "tu", "tv",
"ut", "ur", "uw",
"vt", "vx", "vw",
"wu", "wv", "wy",
"xs", "xv", "xz",
"yw", "yH", "yF",
"zx", "zD", "zF",
"Al", "AB", "AE",
"Bn", "BA", "BC",
"CB", "Cq", "CD",
"DC", "Dz", "DE",
"EA", "ED", "EG", "EH",
"Fz", "Fy", "FG",
"GF", "GE", "GH",
"Hy", "HG", "HE"
};
/*
* fillGraph
*/
void fillGraph( NodeContainer &c )
{
/*
* You could do loop unrolling here if performance was an issue.
*/
for( unsigned i = 0; i < sizeof(SampleGraphEdges)/sizeof(const char * const); ++i )
{
c.AddEdge(SampleGraphEdges[i][0],SampleGraphEdges[i][1]);
}
}
/*
* printDistance
*/
void printDistance( NodeContainer &c, char a, char b, std::ostream &o )
{
unsigned res = NodeContainer::NOT_CONNECTED;
try
{
res = c.distance(a,b);
}
catch( std::exception &e )
{
std::cerr << "Exception caught while calling NodeContainer::distance() : " << e.what() << std::endl;
}
o << "The distance between " << a << " and " << b << " is : ";
if( res != NodeContainer::NOT_CONNECTED )
{
o << res;
}
else
{
o << "NOT CONNECTED";
}
o << '\n';
}
}
int main( int argc, char *argv[] )
{
try
{
NodeContainer SimpleGraph;
fillGraph(SimpleGraph);
std::cout << SimpleGraph << std::endl;
printDistance(SimpleGraph,'a','a',std::cout);
printDistance(SimpleGraph,'a','b',std::cout);
printDistance(SimpleGraph,'c','C',std::cout);
printDistance(SimpleGraph,'a','H',std::cout);
printDistance(SimpleGraph,'a','Z',std::cout);
}
catch( std::exception &e )
{
std::cerr << "Exception caught : " << e.what() << std::endl;
}
return 0;
}
|