Skip to main content
added `TextWriter` example.
Source Link
Jesse C. Slicer
  • 14.6k
  • 1
  • 40
  • 54

EDIT here's how to have a TextWriter be used:

private static void Main()
{
    DoSomething(Console.Out); // Outputs to the console.
    DoSomething(TextWriter.Null); // Won't output anything.
}

private static void DoSomething(TextWriter writer)
{
    writer.WriteLine("Hello, World!");
    writer.WriteLine();
}

EDIT here's how to have a TextWriter be used:

private static void Main()
{
    DoSomething(Console.Out); // Outputs to the console.
    DoSomething(TextWriter.Null); // Won't output anything.
}

private static void DoSomething(TextWriter writer)
{
    writer.WriteLine("Hello, World!");
    writer.WriteLine();
}
added tons of stuff. SRP.
Source Link
Jesse C. Slicer
  • 14.6k
  • 1
  • 40
  • 54

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.

Node class:

and Edge class:

I'll update more here momentarily on the other classes following this paradigm.

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:

Edge class:

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.

Source Link
Jesse C. Slicer
  • 14.6k
  • 1
  • 40
  • 54

I'm a fan of developing to interfaces and language-provided immutability, so I've refactored your Node and Edge classes to that end:

INode interface:

public interface INode
{
    string Name { get; }

    IEnumerable<IEdge> Edges { get; }

    void AddEdge(INode targetNode, double weight);
}

IEdge interface:

public interface IEdge
{
    INode TargetNode { get; }

    double Weight { get; }
}

Node class:

public sealed class Node : INode
{
    private readonly string name;

    private readonly ICollection<IEdge> edges = new List<IEdge>();

    private Node(string name)
    {
        this.name = name;
    }

    public string Name
    {
        get
        {
            return this.name;
        }
    }

    public IEnumerable<IEdge> Edges
    {
        get
        {
            return this.edges;
        }
    }

    public static INode Create(string name)
    {
        return new Node(name);
    }

    public void AddEdge(INode targetNode, double weight)
    {
        this.edges.Add(Edge.Create(targetNode, weight));
    }
}

and Edge class:

public sealed class Edge : IEdge
{
    private readonly INode targetNode;

    private readonly double weight;

    private Edge(INode targetNode, double weight)
    {
        this.targetNode = targetNode;
        this.weight = weight;
    }

    public INode TargetNode
    {
        get
        {
            return this.targetNode;
        }
    }

    public double Weight
    {
        get
        {
            return this.weight;
        }
    }

    public static IEdge Create(INode targetNode, double weight)
    {
        return new Edge(targetNode, weight);
    }
}

I'll update more here momentarily on the other classes following this paradigm.