Original question: Breadth First Search not SOLID enough
Here is my first attempt at refactoring. Instead of using a Tuple
, I used a STRUCT
and this made it more readable. I also extracted printing paths and finding paths into interfaces so someone could print their own messages or implement there own path finding algorithm. The graph class has a corresponding interface as well.
I am still not sure if I need all the path finding methods. Some of them are just one-liners, so I wonder if they are necessary. I also want to pass a file name as an args
parameter into main
, but I am not sure how to get that to my CreateGraph
method. Also, IGraph graph = new Graph().CreateGraph();
seems a bit weird. Is this normal? I am also curious if there is a way to better pass in messages to my PrintXXXPath...
methods, so I don't have to have so many of them.
Node class
public class Node
{
public string Name { get; private set; }
public List<Edge> Edges { get; private set; }
public Node(string name)
{
Name = name;
Edges = new List<Edge>();
}
public void AddEdge(Node targetNode, double weight)
{
Edges.Add(new Edge(targetNode,weight));
}
}
Edge class
public class Edge
{
public Node TargetNode { get; private set; }
public double Weight { get; private set; }
public Edge(Node targetNode, double weight)
{
TargetNode = targetNode;
Weight = weight;
}
}
QueueItem class
public class QueueItem
{
public Node Node { get; private set; }
public List<Edge> VisitedEdges { get; private set; }
public QueueItem(Node node, List<Edge> visitedEdges)
{
Node = node;
VisitedEdges = visitedEdges;
}
}
Path struct
public struct Path
{
public readonly Node StartNode;
public readonly Node EndNode;
public readonly string PathRepresentation;
public readonly int VisitedCount;
public readonly double TotalWeight;
public Path(Node startNode, Node endNode, string pathRepresentation, int visitedCount, double totalWeight)
{
StartNode = startNode;
EndNode = endNode;
PathRepresentation = pathRepresentation;
VisitedCount = visitedCount;
TotalWeight = totalWeight;
}
}
IGraph interface
public interface IGraph
{
void AddNode(string name);
Node GetNode(string name);
IGraph CreateGraph();
}
Graph class
public class Graph : IGraph
{
public Dictionary<string, Node> Nodes { get; private set; }
public Graph()
{
Nodes = new Dictionary<string, Node>();
}
public void AddNode(string name)
{
Nodes.Add(name, new Node(name));
}
public Node GetNode(string name)
{
if (Nodes.ContainsKey(name))
return Nodes[name];
return null;
}
public IGraph CreateGraph()
{
const int START_NODE_INDEX = 0;
const int END_NODE_INDEX = 1;
const int EDGE_WEIGHT_INDEX = 2;
var nodes = File.ReadAllText("graph.csv").Split(',');
IGraph graph = new Graph();
foreach (var n in nodes)
{
var node = n.Trim();
if (graph.GetNode(node[START_NODE_INDEX].ToString()) == null)
{
graph.AddNode(node[START_NODE_INDEX].ToString());
}
if (graph.GetNode(node[END_NODE_INDEX].ToString()) == null)
{
graph.AddNode(node[END_NODE_INDEX].ToString());
}
graph.GetNode(node[START_NODE_INDEX].ToString())
.AddEdge(graph.GetNode(node[END_NODE_INDEX].ToString()), Convert.ToInt32(node[EDGE_WEIGHT_INDEX].ToString()));
}
return graph;
}
}
IPathFinder interface
public interface IPathFinder
{
Path GetShortestPath(Node startNode, Node endNode);
List<Path> GetPathsWithMinWeight(Node startNode, Node endNode, double minWeight, bool inclusive);
List<Path> GetPathsWithMaxWeight(Node startNode, Node endNode, double maxWeight, bool inclusive);
List<Path> GetPathsWithExactWeight(Node startNode, Node endNode, double weight);
List<Path> GetAllPaths(Node startNode, Node endNode);
List<Path> GetPathsWithMinStops(Node startNode, Node endNode, int minStops, bool inclusive);
List<Path> GetPathsWithMaxStops(Node startNode, Node endNode, int maxStops, bool inclusive);
List<Path> GetPathsWithExactStops(Node startNode, Node endNode, int stops);
Path GetPath(Node startNode, Node endNode, IEnumerable<Edge> visitedEdges);
string GetPathRepresentation(Node startNode, Node endNode, IEnumerable<Edge> visiteEdges);
}
PathFinder class
public class PathFinder : IPathFinder
{
public string GetPathRepresentation(Node startNode, Node endNode, IEnumerable<Edge> visiteEdges)
{
var pathRepresentation = new StringBuilder();
pathRepresentation.AppendFormat("{0}->", startNode.Name);
foreach (var visitedEdge in visiteEdges)
{
if (visitedEdge.TargetNode == endNode)
{
pathRepresentation.Append(endNode.Name);
}
else
{
pathRepresentation.AppendFormat("{0}->", visitedEdge.TargetNode.Name);
}
}
return pathRepresentation.ToString();
}
public Path GetPath(Node startNode, Node endNode, IEnumerable<Edge> visitedEdges)
{
var visitedPaths = visitedEdges as IList<Edge> ?? visitedEdges.ToList();
string pathRepresentation = GetPathRepresentation(startNode, endNode, visitedPaths);
double totalWeight = visitedPaths.Aggregate<Edge, double>(0,
(current, visitedEdge) => current + visitedEdge.Weight);
return new Path(startNode, endNode, pathRepresentation, visitedPaths.Count(), totalWeight);
}
public List<Path> GetAllPaths(Node startNode, Node endNode)
{
var paths = new List<Path>();
var queue = new Queue<QueueItem>();
queue.Enqueue(new QueueItem(startNode, new List<Edge>()));
while (queue.Count > 0)
{
var currentItem = queue.Dequeue();
foreach (var edge in currentItem.Node.Edges)
{
if (!currentItem.VisitedEdges.Contains(edge))
{
var visitedEdges = new List<Edge>(currentItem.VisitedEdges) { edge };
if (edge.TargetNode == endNode)
{
var path = GetPath(startNode, endNode, visitedEdges);
paths.Add(path);
}
else
{
queue.Enqueue(new QueueItem(edge.TargetNode, visitedEdges));
}
}
}
}
return paths;
}
public Path GetShortestPath(Node startNode, Node endNode)
{
List<Path> paths = GetAllPaths(startNode, endNode);
var shortestPath = new Path();
double shortestPathWeight = double.PositiveInfinity;
if (paths.Count > 0)
{
foreach (var path in paths)
{
if (path.TotalWeight < shortestPathWeight)
{
if (path.TotalWeight < shortestPathWeight)
{
shortestPathWeight = path.TotalWeight;
shortestPath = new Path(startNode, endNode, path.PathRepresentation, path.VisitedCount,
path.TotalWeight);
}
}
}
}
return shortestPath;
}
public List<Path> GetPathsWithMinWeight(Node startNode, Node endNode, double minWeight, bool inclusive)
{
if (inclusive)
return GetAllPaths(startNode, endNode).Where(path => path.TotalWeight >= minWeight).ToList();
return GetAllPaths(startNode, endNode).Where(path => path.TotalWeight > minWeight).ToList();
}
public List<Path> GetPathsWithMaxWeight(Node startNode, Node endNode, double maxWeight, bool inclusive)
{
if(inclusive)
return GetAllPaths(startNode, endNode).Where(path => path.TotalWeight <= maxWeight).ToList();
return GetAllPaths(startNode, endNode).Where(path => path.TotalWeight < maxWeight).ToList();
}
public List<Path> GetPathsWithExactWeight(Node startNode, Node endNode, double weight)
{
return GetAllPaths(startNode, endNode).Where(path => path.TotalWeight.Equals(weight)).ToList();
}
public List<Path> GetPathsWithMinStops(Node startNode, Node endNode, int minStops, bool inclusive)
{
if(inclusive)
return GetAllPaths(startNode, endNode).Where(path => path.VisitedCount >= minStops).ToList();
return GetAllPaths(startNode, endNode).Where(path => path.VisitedCount > minStops).ToList();
}
public List<Path> GetPathsWithMaxStops(Node startNode, Node endNode, int maxStops, bool inclusive)
{
if(inclusive)
return GetAllPaths(startNode, endNode).Where(path => path.VisitedCount <= maxStops).ToList();
return GetAllPaths(startNode, endNode).Where(path => path.VisitedCount < maxStops).ToList();
}
public List<Path> GetPathsWithExactStops(Node startNode, Node endNode, int stops)
{
return GetAllPaths(startNode, endNode).Where(path => path.VisitedCount == stops).ToList();
}
public List<Path> GetAllPaths2(Node startNode, Node endNode)
{
var paths = new List<Path>();
var queue = new Queue<QueueItem>();
queue.Enqueue(new QueueItem(startNode, new List<Edge>()));
while (queue.Count > 0)
{
var currentItem = queue.Dequeue();
foreach (var edge in currentItem.Node.Edges)
{
if (!currentItem.VisitedEdges.Contains(edge))
{
var visitedEdges = new List<Edge>(currentItem.VisitedEdges) { edge };
if (edge.TargetNode == endNode)
{
var path = GetPath(startNode, endNode, visitedEdges);
paths.Add(path);
}
else
{
queue.Enqueue(new QueueItem(edge.TargetNode, visitedEdges));
}
}
}
}
return paths;
}
}
IPathPrinter interface
public interface IPathPrinter
{
void PrintShortestPath(Path path);
void PrintPathsWithMaxWeight(List<Path> paths, Node startNode, Node endNode, double maxWeight, bool inclusive);
void PrintPathDistance(List<Path> paths);
void PrintPathsWithMaxStops(List<Path> paths, Node startNode, Node endNode, int maxStops, bool inclusive);
void PrintPathsWithExactStops(List<Path> paths, Node startNode, Node endNode, int maxStops);
}
PathPrinter class
public class PathPrinter : IPathPrinter
{
public void PrintShortestPath(Path path)
{
Console.WriteLine("The shortest path from '{0}' to '{1} is '{2}' with a distance of {3}",
path.StartNode.Name, path.EndNode.Name, path.PathRepresentation, path.TotalWeight);
}
public void PrintPathsWithMaxWeight(List<Path> paths, Node startNode, Node endNode, double maxWeight, bool inclusive)
{
if (inclusive)
{
Console.WriteLine(
"The number of trips from '{0}' to '{1}' with a distance of less than or equal to {2} is {3}:",
startNode.Name, endNode.Name, maxWeight, paths.Count);
}
else
{
Console.WriteLine(
"The number of trips from '{0}' to '{1}' with a distance of less than {2} is {3}:",
startNode.Name, endNode.Name, maxWeight, paths.Count);
}
foreach (var path in paths)
{
Console.WriteLine("{0} with a distance of {1}", path.PathRepresentation, path.TotalWeight);
}
}
public void PrintPathsWithMaxStops(List<Path> paths, Node startNode, Node endNode, int maxStops, bool inclusive)
{
if (inclusive)
{
Console.WriteLine("The number of trips from '{0}' to '{1}' with a maximum of {2} stops is {3}:", startNode.Name, endNode.Name, maxStops, paths.Count);
}
else
{
Console.WriteLine("The number of trips from '{0}' to '{1}' with a maximum of less than {2} stops is {3}:", startNode.Name, endNode.Name, maxStops, paths.Count);
}
PrintPaths(paths);
}
public void PrintPathsWithExactStops(List<Path> paths, Node startNode, Node endNode, int maxStops)
{
Console.WriteLine("The number of trips from '{0}' to '{1}' with exactly {2} stops is {3}:", startNode.Name, endNode.Name, maxStops, paths.Count);
PrintPaths(paths);
}
public void PrintPathDistance(List<Path> paths)
{
foreach (var path in paths)
{
Console.WriteLine("The distance of the route '{0}' is {1}", path.PathRepresentation, path.TotalWeight);
}
}
private static void PrintPaths(IEnumerable<Path> paths)
{
foreach (var path in paths)
{
Console.WriteLine(path.PathRepresentation);
}
}
}
Program class
public class Program
{
public static void Main(string[] args)
{
IPathFinder pathFinder = new PathFinder();
IPathPrinter pathPrinter = new PathPrinter();
IGraph graph = new Graph().CreateGraph();
var pathsAtoC = pathFinder.GetAllPaths(graph.GetNode("A"), graph.GetNode("C"));
pathPrinter.PrintPathDistance(pathsAtoC.Where(x => x.PathRepresentation.Equals("A->B->C")).ToList());
var pathsAtoD = pathFinder.GetAllPaths(graph.GetNode("A"), graph.GetNode("D"));
pathPrinter.PrintPathDistance(pathsAtoD.Where(x => x.PathRepresentation.Equals("A->D")).ToList());
pathPrinter.PrintPathDistance(pathsAtoC.Where(x => x.PathRepresentation.Equals("A->D->C")).ToList());
pathPrinter.PrintPathDistance(pathsAtoD.Where(x => x.PathRepresentation.Equals("A->E->B->C->D")).ToList());
var validPath = pathsAtoD.Any(x => x.PathRepresentation.Equals("A->E->D"));
if(validPath)
pathPrinter.PrintPathDistance(pathsAtoD.Where(x => x.PathRepresentation.Equals("A->E->D")).ToList());
Console.WriteLine(Environment.NewLine);
var path = pathFinder.GetShortestPath(graph.GetNode("A"), graph.GetNode("C"));
pathPrinter.PrintShortestPath(path);
path = pathFinder.GetShortestPath(graph.GetNode("C"), graph.GetNode("C"));
pathPrinter.PrintShortestPath(path);
Console.WriteLine(Environment.NewLine);
var paths = pathFinder.GetPathsWithMaxStops(graph.GetNode("C"), graph.GetNode("C"),3,true);
pathPrinter.PrintPathsWithMaxStops(paths,graph.GetNode("C"),graph.GetNode("C"),3,true);
Console.WriteLine(Environment.NewLine);
paths = pathFinder.GetPathsWithExactStops(graph.GetNode("A"), graph.GetNode("C"), 4);
pathPrinter.PrintPathsWithExactStops(paths, graph.GetNode("A"), graph.GetNode("C"), 4);
Console.WriteLine(Environment.NewLine);
paths = pathFinder.GetPathsWithMaxWeight(graph.GetNode("C"), graph.GetNode("C"), 30, false);
pathPrinter.PrintPathsWithMaxWeight(paths, graph.GetNode("C"), graph.GetNode("C"), 30,false);
Console.ReadKey();
}
}