Home
Fractals
Tutorials
Books
Archive
My blog
My LinkedIn Profile

BOOKS i'm reading

Napoleon Hill Keys to Success: The 17 Principles of Personal Achievement, Napoleon Hill, ISBN: 978-0452272811
The 4-Hour Workweek: Escape 9-5, Live Anywhere, and Join the New Rich (Expanded and Updated), Timothy Ferriss, ISBN: 978-0307465351
The Fountainhead, Ayn Rand, ISBN: 0452273331

/*
 * 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;
}

Home :: Fractals :: Tutorials :: Books :: Archive :: My blog :: My LinkedIn Profile :: Contact