IQueueItem interface:
public interface IQueueItem
{
INode Node { get; }
IEnumerable<IEdge> VisitedEdges { get; }
}
IGraph interface:
public interface IGraph
{
void AddNode(string name);
INode GetNode(string name);
}
Node class:
and Edge class:
I'll update more here momentarily on the other classes following this paradigm.
QueueItem class:
public sealed class QueueItem : IQueueItem
{
private readonly INode node;
private readonly IEnumerable<IEdge> visitedEdges;
private QueueItem(INode node, IEnumerable<IEdge> visitedEdges)
{
this.node = node;
this.visitedEdges = visitedEdges;
}
public INode Node
{
get
{
return this.node;
}
}
public IEnumerable<IEdge> VisitedEdges
{
get
{
return this.visitedEdges;
}
}
public static IQueueItem Create(INode node, IEnumerable<IEdge> visitedEdges)
{
return new QueueItem(node, visitedEdges);
}
}
Path struct:
public struct Path
{
private readonly INode startNode;
private readonly INode endNode;
private readonly string pathRepresentation;
private readonly int visitedCount;
private readonly double totalWeight;
public Path(INode startNode, INode endNode, string pathRepresentation, int visitedCount, double totalWeight)
{
this.startNode = startNode;
this.endNode = endNode;
this.pathRepresentation = pathRepresentation;
this.visitedCount = visitedCount;
this.totalWeight = totalWeight;
}
public INode StartNode
{
get
{
return this.startNode;
}
}
public INode EndNode
{
get
{
return this.endNode;
}
}
public string PathRepresentation
{
get
{
return this.pathRepresentation;
}
}
public int VisitedCount
{
get
{
return this.visitedCount;
}
}
public double TotalWeight
{
get
{
return this.totalWeight;
}
}
}
Graph class:
public sealed class Graph : IGraph
{
private readonly IDictionary<string, INode> nodes = new Dictionary<string, INode>();
private Graph()
{
}
private enum NodeIndex
{
Start = 0,
End = 1,
Edge = 2
}
public IDictionary<string, INode> Nodes
{
get
{
return this.nodes;
}
}
public static IGraph Create(IEnumerable<string> graphNodes)
{
IGraph graph = new Graph();
foreach (var node in graphNodes.Select(n => n.Trim()))
{
if (graph.GetNode(node[(int)NodeIndex.Start].ToString()) == null)
{
graph.AddNode(node[(int)NodeIndex.Start].ToString());
}
if (graph.GetNode(node[(int)NodeIndex.End].ToString()) == null)
{
graph.AddNode(node[(int)NodeIndex.End].ToString());
}
graph.GetNode(node[(int)NodeIndex.Start].ToString()).AddEdge(
graph.GetNode(node[(int)NodeIndex.End].ToString()),
Convert.ToInt32(node[(int)NodeIndex.Edge].ToString()));
}
return graph;
}
public void AddNode(string name)
{
this.nodes.Add(name, Node.Create(name));
}
public INode GetNode(string name)
{
return this.nodes.ContainsKey(name) ? this.Nodes[name] : null;
}
}
PathFinder class (I removed IPathFinder interface as the class has no state):
public static class PathFinder
{
public static string GetPathRepresentation(INode startNode, INode endNode, IEnumerable<IEdge> 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 static Path GetPath(INode startNode, INode endNode, IEnumerable<IEdge> visitedEdges)
{
var visitedPaths = visitedEdges as IList<IEdge> ?? visitedEdges.ToList();
var pathRepresentation = GetPathRepresentation(startNode, endNode, visitedPaths);
var totalWeight = visitedPaths.Aggregate(
0.0D,
(current, visitedEdge) => current + visitedEdge.Weight);
return new Path(startNode, endNode, pathRepresentation, visitedPaths.Count(), totalWeight);
}
public static IEnumerable<Path> GetAllPaths(INode startNode, INode endNode)
{
var paths = new List<Path>();
var queue = new Queue<IQueueItem>();
queue.Enqueue(QueueItem.Create(startNode, new List<IEdge>()));
while (queue.Count > 0)
{
var currentItem = queue.Dequeue();
foreach (var edge in currentItem.Node.Edges)
{
if (currentItem.VisitedEdges.Contains(edge))
{
continue;
}
var visitedEdges = new List<IEdge>(currentItem.VisitedEdges) { edge };
if (edge.TargetNode == endNode)
{
var path = GetPath(startNode, endNode, visitedEdges);
paths.Add(path);
}
else
{
queue.Enqueue(QueueItem.Create(edge.TargetNode, visitedEdges));
}
}
}
return paths;
}
public static Path GetShortestPath(INode startNode, INode endNode)
{
var paths = GetAllPaths(startNode, endNode);
var shortestPath = new Path();
double[] shortestPathWeight = { double.PositiveInfinity };
foreach (var path in paths.Where(path => path.TotalWeight < shortestPathWeight[0]).Where(path => path.TotalWeight < shortestPathWeight[0]))
{
shortestPathWeight[0] = path.TotalWeight;
shortestPath = new Path(
startNode,
endNode,
path.PathRepresentation,
path.VisitedCount,
path.TotalWeight);
}
return shortestPath;
}
public static IEnumerable<Path> GetPathsWithMinWeight(INode startNode, INode endNode, double minWeight, bool inclusive)
{
return inclusive
? GetAllPaths(startNode, endNode).Where(path => path.TotalWeight >= minWeight)
: GetAllPaths(startNode, endNode).Where(path => path.TotalWeight > minWeight);
}
public static IEnumerable<Path> GetPathsWithMaxWeight(INode startNode, INode endNode, double maxWeight, bool inclusive)
{
return inclusive
? GetAllPaths(startNode, endNode).Where(path => path.TotalWeight <= maxWeight)
: GetAllPaths(startNode, endNode).Where(path => path.TotalWeight < maxWeight);
}
public static IEnumerable<Path> GetPathsWithExactWeight(INode startNode, INode endNode, double weight)
{
return GetAllPaths(startNode, endNode).Where(path => path.TotalWeight.Equals(weight));
}
public static IEnumerable<Path> GetPathsWithMinStops(INode startNode, INode endNode, int minStops, bool inclusive)
{
return inclusive
? GetAllPaths(startNode, endNode).Where(path => path.VisitedCount >= minStops)
: GetAllPaths(startNode, endNode).Where(path => path.VisitedCount > minStops);
}
public static IEnumerable<Path> GetPathsWithMaxStops(INode startNode, INode endNode, int maxStops, bool inclusive)
{
return inclusive
? GetAllPaths(startNode, endNode).Where(path => path.VisitedCount <= maxStops)
: GetAllPaths(startNode, endNode).Where(path => path.VisitedCount < maxStops);
}
public static IEnumerable<Path> GetPathsWithExactStops(INode startNode, INode endNode, int stops)
{
return GetAllPaths(startNode, endNode).Where(path => path.VisitedCount == stops);
}
public static IEnumerable<Path> GetAllPaths2(INode startNode, INode endNode)
{
var paths = new List<Path>();
var queue = new Queue<IQueueItem>();
queue.Enqueue(QueueItem.Create(startNode, new List<IEdge>()));
while (queue.Count > 0)
{
var currentItem = queue.Dequeue();
foreach (var edge in currentItem.Node.Edges)
{
if (currentItem.VisitedEdges.Contains(edge))
{
continue;
}
var visitedEdges = new List<IEdge>(currentItem.VisitedEdges) { edge };
if (edge.TargetNode == endNode)
{
var path = GetPath(startNode, endNode, visitedEdges);
paths.Add(path);
}
else
{
queue.Enqueue(QueueItem.Create(edge.TargetNode, visitedEdges));
}
}
}
return paths;
}
}
PathPrinter class (I removed IPathPrinter interface as the class has no state):
public static class PathPrinter
{
public static 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 static void PrintPathsWithMaxWeight(IEnumerable<Path> paths, INode startNode, INode endNode, double maxWeight, bool inclusive)
{
Console.WriteLine(
inclusive
? "The number of trips from '{0}' to '{1}' with a distance of less than or equal to {2} is {3}:"
: "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 static void PrintPathsWithMaxStops(IEnumerable<Path> paths, INode startNode, INode endNode, int maxStops, bool inclusive)
{
Console.WriteLine(
inclusive
? "The number of trips from '{0}' to '{1}' with a maximum of {2} stops is {3}:"
: "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 static void PrintPathsWithExactStops(IEnumerable<Path> paths, INode startNode, INode 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 static void PrintPathDistance(IEnumerable<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:
internal static class Program
{
private static void Main()
{
var graph = Graph.Create(File.ReadAllText("graph.csv").Split(','));
var pathsAtoC = PathFinder.GetAllPaths(graph.GetNode("A"), graph.GetNode("C"));
PathPrinter.PrintPathDistance(pathsAtoC.Where(thePath => thePath.PathRepresentation.Equals("A->B->C")).ToList());
var pathsAtoD = PathFinder.GetAllPaths(graph.GetNode("A"), graph.GetNode("D"));
PathPrinter.PrintPathDistance(pathsAtoD.Where(thePath => thePath.PathRepresentation.Equals("A->D")).ToList());
PathPrinter.PrintPathDistance(pathsAtoC.Where(thePath => thePath.PathRepresentation.Equals("A->D->C")).ToList());
PathPrinter.PrintPathDistance(pathsAtoD.Where(thePath => thePath.PathRepresentation.Equals("A->E->B->C->D")).ToList());
var validPath = pathsAtoD.Any(thePath => thePath.PathRepresentation.Equals("A->E->D"));
if (validPath)
{
PathPrinter.PrintPathDistance(pathsAtoD.Where(thePath => thePath.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();
}
}
Now, I'd say there's more to do. I would not have the Graph class load the file directly. It should be passed in a dependency, like a Stream or even just the string of read data from the method that calls Create(). It breaks Single Responsibility Principle to have it both read the nodes from a file and then parse them. EDIT: I just did this and put it in there.
The PathPrinter class would likely do better to have a parameter on each of the methods that's a TextWriter and you can call writer.WriteLine(...) as necessary. The caller (Program.Main()) can pass in Console.Out to see console output, but it can be exchanged for a file, network stream, etc.