In a recent coding interview I was asked to write a program which takes as input two text lines:
- The first one represents a graph, formatted as a sequence of undirected edges like
[A,B,10] [B,C,4]
- The second one represents two nodes, between which the shortes path is to be found, and the maximum acceptable distance, formatted like
A->C,20
.
the output is a representation of this shortest path, like A->C
.
/*************************
* BEGIN default includes
*************************/
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <queue>
#include <stack>
#include <string>
#include <bitset>
#include <cstdio>
#include <limits>
#include <vector>
#include <climits>
#include <cstring>
#include <cstdlib>
#include <fstream>
#include <numeric>
#include <sstream>
#include <iostream>
#include <algorithm>
#include <unordered_map>
/*************************
* END default includes
*************************/
#include <unordered_set>
#define DEBUG_QUIET 0
#define DEBUG_INFO 1
#define DEBUG_VERBOSE 2
#define DEBUG DEBUG_QUIET // Edit to change debug output level
#if DEBUG >= DEBUG_VERBOSE
#define PRINT_VERBOSE(x) x
#else
#define PRINT_VERBOSE(x)
#endif
#if DEBUG >= DEBUG_INFO
#define PRINT_INFO(x) x
#else
#define PRINT_INFO(x)
#endif
using namespace std;
class E1 : exception {};
class E2 : exception {};
class E3 : exception {};
class Graph {
public:
/**
* @brief Constructs a graph from a string.
* @param s The first line of input
*/
Graph(const string s) {
string::const_iterator it = s.begin();
bool done = false;
while (!done) {
expect(it,'[');
char n1 = *(it++);
expect(it,',');
char n2 = *(it++);
expect(it,',');
unsigned weight = extractNumber(it, s.end());
expect(it,']');
if (it==s.end()) done = true;
if (!done) expect(it,' ');
PRINT_VERBOSE(cout << "New edge: ["<< n1 << "," << n2 << "," << weight << "] " << endl; )
m_graphMap.insert(make_pair(n1,NeighborSet_t()));
NeighborSet_t* ns1 = &m_graphMap.find(n1)->second;
if (ns1->find(Node_t(n2))!=ns1->end()) { throw E2(); }
ns1->insert(Node_t(n2,weight));
m_graphMap.insert(make_pair(n2,NeighborSet_t()));
NeighborSet_t* ns2 = &m_graphMap.find(n2)->second;
if (ns2->find(Node_t(n1))!=ns2->end()) { throw E2(); }
ns2->insert(Node_t(n1,weight));
}
PRINT_VERBOSE(cout << "Parsing graph string complete" << endl);
}
/**
* @brief Computes the Shortest Path from two points.
* @param s The second line of input
* @return A representation of the Shortest Path according to the specification
*/
string sp(const string s) const {
char start_node, end_node;
unsigned maxdist;
parseSPTstring(s, start_node, end_node, maxdist);
PRINT_INFO(cout << "Searching for path from " << start_node << " to " << end_node << " (maxdist: " << maxdist << ")" << endl);
if (m_graphMap.find(start_node) == m_graphMap.end() || m_graphMap.find(end_node) == m_graphMap.end() )
throw E2(); // Not in graph!
GraphMap_t spt;
MinHeap_t minHeap; // Stores a set of nodes, ordered by distance
minHeap.insert(Node_t(start_node,0)); // Will insert again, but next insert will be ignored
PRINT_INFO( cout << "Initializing minHeap" << endl; )
Visited_t visited;
initMinHeap(start_node, visited, minHeap);
PRINT_INFO( cout << "Computing SPT" << endl; )
buildSPT(minHeap, spt);
PRINT_VERBOSE( visited = Visited_t(); printGraph(start_node,visited,spt); )
PRINT_VERBOSE( cout << "Generating SPT string" << endl; )
visited = Visited_t(); // Reset
return generateSPString(start_node,end_node,visited,spt,maxdist);
}
private:
/**********
* Types
**********/
typedef unordered_set<char> Visited_t;
class Node_t {
public:
const char name;
const unsigned distance;
bool operator== (const Node_t& other) const { return this-> name == other.name; }
bool operator< (const Node_t& other) const {
if (*this==other)
return false; // Same node, reflexively false
else
{
if (this->distance == other.distance)
return this->name < other.name; // Don't care, sort by name
else
return this->distance < other.distance;
}
}
Node_t(char n, unsigned d = UINT_MAX) : name(n), distance(d) {};
};
typedef set<Node_t> NeighborSet_t; // Can't use unordered_set because it needs to be hashable
typedef set<Node_t> MinHeap_t;
typedef unordered_map<char, NeighborSet_t> GraphMap_t;
/*********************************************
* Helper functions for parsing / debugging
*********************************************/
/**
* @brief Throws an exception if it finds unexpected characters.
* @param it An iterator over the input string
* @param c The expected character
*/
static void expect(string::const_iterator& it,char c){
PRINT_VERBOSE( cout << "Expecting '" << c << "', found '" << *it << "' "; )
if (!(c==*it)) throw E1(); // Invalid formatting of the input string
it++;
return;
}
/**
* @brief Useful to extract numbers of arbitraty digits.
* @param start Beginning of digits
* @param end End of the string
* @return The index of the last digit of this number
*/
static unsigned extractNumber(string::const_iterator& start, const string::const_iterator& end) {
string::const_iterator weight_end = find_if(start, end, not1(ptr_fun<int, int>(isdigit)));
unsigned weight;
try {
weight = stoi(string(start,weight_end));
} catch (invalid_argument) {
throw E1(); // Garbage in the input string
}
start = weight_end;
return weight;
}
/**
* @brief Parse path specification.
* @param s The second line of input
* @param start_node Will be overwritten with the start node name
* @param end_node Will be overwritten with the end node name
*/
static void parseSPTstring(const string s, char& start_node, char& end_node, unsigned& maxdist) {
string::const_iterator it = s.begin();
start_node = *(it++);
expect(it, '-');
expect(it, '>');
end_node = *(it++);
expect(it, ',');
maxdist = extractNumber(it, s.end());
if (!(s.end()==it)) throw E1(); // Trailing characters
}
/**
* @brief DFS recursive function to print a graph. For debugging.
* @param node The current root node
* @param visited Unordered set of visited node names
* @param g The graph
*/
static void printGraph(char node, Visited_t& visited, const GraphMap_t& g) {
if (visited.find(node)!=visited.end()){
PRINT_VERBOSE( cout << "Not visiting " << node << " again. "; )
return;
}
PRINT_VERBOSE( cout << "Visit " << node << ". ");
visited.insert(node);
const NeighborSet_t neighborSet = g.at(node);
for ( Node_t n : neighborSet) {
cout << "[" << node << "," << n.name << "," << n.distance << "] ";
printGraph(n.name, visited, g);
}
PRINT_VERBOSE( cout << "Step out " << node << ". " << endl);
}
/**
* @brief Function to print the minHeap. For debugging.
* @param minHeap The minHeap
*/
static void printMinHeap(const MinHeap_t& minHeap) {
for (MinHeap_t::iterator it = minHeap.begin(); it!=minHeap.end(); it++) {
cout << (*it).name << ":" << (*it).distance << "; ";
};
}
/**
* @brief Recursive function to generate a string representing the SP according to the specification.
* @param start_node Char name of the start of the path node
* @param end_node Char name of the end of the path node
* @param visited Unordered set of visited node names. SPT is an ADG, but may still print the same node multiple times
* @param spt Acyclic, directed graph representing the Shortest Path Tree
* @param maxdist Maximum allowed distance from the start node
* @return The string representation of the SP
*/
static string generateSPString(const char start_node, const char end_node, Visited_t& visited, GraphMap_t& spt, const unsigned maxdist){
if (visited.find(start_node)!=visited.end()){
PRINT_VERBOSE( cout << "Not visiting " << start_node << " again. "; )
return "";
}
PRINT_VERBOSE( cout << "Visit " << start_node << ". "; )
visited.insert(start_node);
stringstream ss;
if (start_node==end_node) {
ss << end_node;
return ss.str();
}
const NeighborSet_t neighborSet = spt.at(start_node);
if (neighborSet.empty()) return "";
for ( Node_t n : neighborSet) {
if (n.name==end_node) {
if (n.distance>maxdist)
throw E3();
}
ss << generateSPString(n.name, end_node, visited, spt, maxdist);
}
PRINT_VERBOSE( cout << "Step outside " << start_node << ". "; )
stringstream ss2;
if (!ss.str().empty())
ss2 << start_node << "->" << ss.str();
else
ss2 << ss.str();
PRINT_VERBOSE( cout << "Returning '" << ss2.str() << "'. "; )
return ss2.str();
}
/*************************
* Dijkstra Implementation
*************************/
/**
* @brief Recursive DFS to populate the minHeap.
* @param node Current root note
* @param visited Unordered set of already visited node names, to avoid loops since the graph is undirected
* @param minHeap The minHeap to be generated
*/
void initMinHeap(const char node, Visited_t& visited, MinHeap_t& minHeap) const{
if (visited.find(node)!=visited.end()){
PRINT_VERBOSE(cout << "Not visiting " << node << " again. ");
return;
}
PRINT_VERBOSE( cout << "Visit " << node << ". ");
visited.insert(node);
PRINT_VERBOSE( cout << "Add " << node << " to minHeap. ");
minHeap.insert(Node_t(node,UINT_MAX));
NeighborSet_t neighborSet = m_graphMap.at(node);
for ( Node_t n : neighborSet) {
initMinHeap(n.name, visited, minHeap);
}
PRINT_VERBOSE(cout << "Step out " << node << ". " << endl);
}
/**
* @brief Computes the SPT from the minHeap.
* @param minHeap The minHeap
* @param spt The SPT
*/
void buildSPT(MinHeap_t& minHeap, GraphMap_t& spt) const{
while (!minHeap.empty()) {
Node_t minNode = *(minHeap.begin());
PRINT_VERBOSE( cout << "Closest neighbor is " << minNode.name << ", dist: " << minNode.distance << ", deleting from minHeap. "; cout << endl; )
minHeap.erase(minHeap.find(minNode));
PRINT_VERBOSE( cout << "MinHeap: "; printMinHeap(minHeap); )
spt.insert(make_pair(minNode.name,NeighborSet_t()));
for (Node_t n : m_graphMap.at(minNode.name)) {
PRINT_VERBOSE( cout << "Find node " << n.name << "... "; )
MinHeap_t::iterator i = minHeap.find(Node_t(n.name));
if (i==minHeap.end()) {
PRINT_VERBOSE( cout << "not in minHeap, skipping. "; )
continue;
}
unsigned newDist = minNode.distance+n.distance;
if (newDist>(*i).distance){
PRINT_VERBOSE( cout << "New distance (" << newDist << ") is greater than the current one (" << (*i).distance << "). Continue."; )
continue;
}
PRINT_VERBOSE(cout << "Delete node " << (*i).name << ", distance " << (*i).distance << ". "; )
minHeap.erase(i);
PRINT_VERBOSE( cout << "MinHeap after erase: "; printMinHeap(minHeap); cout; )
PRINT_VERBOSE( cout << "Insert node " << n.name << ", distance " << newDist << ". "; )
minHeap.insert(Node_t(n.name,newDist));
PRINT_VERBOSE( cout << "MinHeap after insert: "; printMinHeap(minHeap); )
spt[minNode.name].insert(Node_t(i->name,newDist)); // Store the distance in the dual graph
}
PRINT_VERBOSE( cout << endl; )
}
PRINT_VERBOSE(cout << "SPT done." << endl; )
}
/*
* Members
*/
GraphMap_t m_graphMap = GraphMap_t();
};
int main() {
string edgeString;
getline(cin,edgeString);
string pathString;
getline(cin,pathString);
PRINT_INFO( cout << edgeString << endl << pathString << endl; )
try {
const Graph g = Graph(edgeString);
string spt = g.sp(pathString);
if (spt.empty()) {
cout << "E3";
return 0;
}
cout << spt << endl;
} catch (E1) {
cout << "E1" << endl;
return 0;
} catch (E2) {
cout << "E2" << endl;
return 0;
} catch (E3) {
cout << "E3" << endl;
return 0;
}
return 0;
}
My code was an implementation of the Dijkstra algorithm with min heap, which I thought was the most efficient algorithm for this problem, and I was passing all tests; nevertheless, I failed the interview.
How does my code look?