From b74146bdf7be2b56d1fb47732c8e029a52775d5b Mon Sep 17 00:00:00 2001 From: Thomas Cauwelier Date: Fri, 8 Mar 2024 12:34:58 +0100 Subject: [PATCH] Modify YenShortestPathsAlgorithm to support generic Edge type --- .../ShortestPath/YenShortestPathsAlgorithm.cs | 53 +++++------ .../YenShortestPathsAlgorithmTests.cs | 94 +++++++++---------- 2 files changed, 72 insertions(+), 75 deletions(-) diff --git a/src/QuikGraph/Algorithms/ShortestPath/YenShortestPathsAlgorithm.cs b/src/QuikGraph/Algorithms/ShortestPath/YenShortestPathsAlgorithm.cs index b2c30b51f..dbff2129f 100644 --- a/src/QuikGraph/Algorithms/ShortestPath/YenShortestPathsAlgorithm.cs +++ b/src/QuikGraph/Algorithms/ShortestPath/YenShortestPathsAlgorithm.cs @@ -14,20 +14,21 @@ namespace QuikGraph.Algorithms.ShortestPath /// with non negative edge cost. /// /// Vertex type. - public class YenShortestPathsAlgorithm + /// Edge type. + public class YenShortestPathsAlgorithm where TEdge : IEdge { /// /// Class representing a sorted path. /// - public struct SortedPath : IEnumerable>, IEquatable + public struct SortedPath : IEnumerable, IEquatable { [NotNull, ItemNotNull] - private readonly List> _edges; + private readonly List _edges; /// /// Initializes a new instance of the struct. /// - public SortedPath([NotNull, ItemNotNull] IEnumerable> edges) + public SortedPath([NotNull, ItemNotNull] IEnumerable edges) { _edges = edges.ToList(); } @@ -48,7 +49,7 @@ internal TVertex GetVertex(int i) [Pure] [NotNull] - internal EquatableTaggedEdge GetEdge(int i) + internal TEdge GetEdge(int i) { Debug.Assert(i >= 0 && i < _edges.Count); @@ -57,7 +58,7 @@ internal EquatableTaggedEdge GetEdge(int i) [Pure] [NotNull, ItemNotNull] - internal EquatableTaggedEdge[] GetEdges(int count) + internal TEdge[] GetEdges(int count) { if (count > _edges.Count) { @@ -88,7 +89,7 @@ public override int GetHashCode() } /// - public IEnumerator> GetEnumerator() + public IEnumerator GetEnumerator() { return _edges.GetEnumerator(); } @@ -104,7 +105,7 @@ IEnumerator IEnumerable.GetEnumerator() private readonly TVertex _targetVertex; [NotNull] - private readonly Func, double> _weights; + private readonly Func _weights; [NotNull] private readonly Func, IEnumerable> _filter; @@ -113,10 +114,10 @@ IEnumerator IEnumerable.GetEnumerator() private readonly int _k; [NotNull] - private readonly IMutableVertexAndEdgeListGraph> _graph; + private readonly IMutableVertexAndEdgeListGraph _graph; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// for tag type (edge) which comes from Dijkstra’s algorithm, which is used to get one shortest path. @@ -134,11 +135,11 @@ IEnumerator IEnumerable.GetEnumerator() /// is not part of . /// is lower than 1. public YenShortestPathsAlgorithm( - [NotNull] AdjacencyGraph> graph, + [NotNull] AdjacencyGraph graph, [NotNull] TVertex source, [NotNull] TVertex target, int k, - [CanBeNull] Func, double> edgeWeights = null, + [CanBeNull] Func edgeWeights, [CanBeNull] Func, IEnumerable> filter = null) { if (graph is null) @@ -153,12 +154,14 @@ public YenShortestPathsAlgorithm( throw new ArgumentException("Target must be in the graph", nameof(source)); if (k < 1) throw new ArgumentOutOfRangeException(nameof(k), "Value must be positive."); + if (edgeWeights == null) + throw new ArgumentNullException(nameof(edgeWeights)); _sourceVertex = source; _targetVertex = target; _k = k; _graph = graph.Clone(); - _weights = edgeWeights ?? DefaultGetWeights; + _weights = edgeWeights; _filter = filter ?? DefaultFilter; } @@ -169,12 +172,6 @@ private static IEnumerable DefaultFilter([NotNull] IEnumerable edge) - { - return edge.Tag; - } - [Pure] private double GetPathDistance([ItemNotNull] SortedPath edges) { @@ -196,7 +193,7 @@ private SortedPath GetInitialShortestPath() [Pure] [CanBeNull] private SortedPath? GetShortestPathInGraph( - [NotNull] IVertexListGraph> graph, + [NotNull] IVertexListGraph graph, [NotNull] TVertex source, [NotNull] TVertex target) { @@ -205,8 +202,8 @@ private SortedPath GetInitialShortestPath() Debug.Assert(target != null); // Compute distances between the start vertex and other - var algorithm = new DijkstraShortestPathAlgorithm>(graph, _weights); - var recorder = new VertexPredecessorRecorderObserver>(); + var algorithm = new DijkstraShortestPathAlgorithm(graph, _weights); + var recorder = new VertexPredecessorRecorderObserver(); using (recorder.Attach(algorithm)) { @@ -214,7 +211,7 @@ private SortedPath GetInitialShortestPath() } // Get shortest path from start (source) vertex to target - return recorder.TryGetPath(target, out IEnumerable> path) + return recorder.TryGetPath(target, out IEnumerable path) ? new SortedPath(path) : (SortedPath?)null; } @@ -258,12 +255,12 @@ private bool SearchAndAddKthShortestPath( TVertex spurVertex = previousPath.GetVertex(i); // The sequence of nodes from the source to the spur node of the previous k-shortest path - EquatableTaggedEdge[] rootPath = previousPath.GetEdges(i); + TEdge[] rootPath = previousPath.GetEdges(i); foreach (SortedPath path in shortestPaths.Where(path => rootPath.SequenceEqual(path.GetEdges(i)))) { // Remove the links that are part of the previous shortest paths which share the same root path - EquatableTaggedEdge edgeToRemove = path.GetEdge(i); + TEdge edgeToRemove = path.GetEdge(i); _edgesToRestore.Add(edgeToRemove); _graph.RemoveEdge(edgeToRemove); } @@ -343,10 +340,10 @@ public IEnumerable Execute() } [NotNull, ItemNotNull] - private readonly List> _edgesToRestore = - new List>(); + private readonly List _edgesToRestore = + new List(); - private void OnGraphEdgeRemoved([NotNull] EquatableTaggedEdge edge) + private void OnGraphEdgeRemoved([NotNull] TEdge edge) { _edgesToRestore.Add(edge); } diff --git a/tests/QuikGraph.Tests/Algorithms/ShortestPath/YenShortestPathsAlgorithmTests.cs b/tests/QuikGraph.Tests/Algorithms/ShortestPath/YenShortestPathsAlgorithmTests.cs index 895df5296..4c18f2731 100644 --- a/tests/QuikGraph.Tests/Algorithms/ShortestPath/YenShortestPathsAlgorithmTests.cs +++ b/tests/QuikGraph.Tests/Algorithms/ShortestPath/YenShortestPathsAlgorithmTests.cs @@ -6,7 +6,7 @@ namespace QuikGraph.Tests.Algorithms.ShortestPath { /// - /// Tests for . + /// Tests for . /// [TestFixture] internal sealed class YenShortestPathsAlgorithmTests @@ -14,21 +14,23 @@ internal sealed class YenShortestPathsAlgorithmTests [Test] public void Constructor() { - Func, double> Weights = _ => 1.0; + Func, double> weights = edge => edge.Tag; var graph = new AdjacencyGraph>(); graph.AddVertexRange(new[] { 1, 2 }); // ReSharper disable ObjectCreationAsStatement - Assert.DoesNotThrow(() => new YenShortestPathsAlgorithm(graph, 1, 2, int.MaxValue)); - Assert.DoesNotThrow(() => new YenShortestPathsAlgorithm(graph, 1, 2, 10)); + Assert.DoesNotThrow(() => new YenShortestPathsAlgorithm>(graph, 1, 2, int.MaxValue, weights)); + Assert.DoesNotThrow(() => new YenShortestPathsAlgorithm>(graph, 1, 2, 10, weights)); - Assert.DoesNotThrow(() => new YenShortestPathsAlgorithm(graph, 1, 2, int.MaxValue, Weights, paths => paths.Where(path => path.Count() > 2))); + Assert.DoesNotThrow(() => new YenShortestPathsAlgorithm>(graph, 1, 2, int.MaxValue, weights, paths => paths.Where(path => path.Count() > 2))); // ReSharper restore ObjectCreationAsStatement } [Test] public void Constructor_Throws() { + Func, double> weights = edge => edge.Tag; + // ReSharper disable ObjectCreationAsStatement // ReSharper disable AssignNullToNotNullAttribute var vertex1 = new TestVertex("1"); @@ -36,41 +38,41 @@ public void Constructor_Throws() var graph = new AdjacencyGraph>(); Assert.Throws( - () => new YenShortestPathsAlgorithm(graph, vertex1, vertex2, int.MaxValue)); + () => new YenShortestPathsAlgorithm>(graph, vertex1, vertex2, int.MaxValue, weights)); graph = new AdjacencyGraph>(); graph.AddVertex(vertex1); Assert.Throws( - () => new YenShortestPathsAlgorithm(graph, vertex1, vertex2, int.MaxValue)); + () => new YenShortestPathsAlgorithm>(graph, vertex1, vertex2, int.MaxValue, weights)); graph = new AdjacencyGraph>(); graph.AddVertex(vertex2); Assert.Throws( - () => new YenShortestPathsAlgorithm(graph, vertex1, vertex2, int.MaxValue)); + () => new YenShortestPathsAlgorithm>(graph, vertex1, vertex2, int.MaxValue, weights)); graph = new AdjacencyGraph>(); graph.AddVertexRange(new[] { vertex1, vertex2 }); Assert.Throws( - () => new YenShortestPathsAlgorithm(null, vertex1, vertex2, int.MaxValue)); + () => new YenShortestPathsAlgorithm>(null, vertex1, vertex2, int.MaxValue, weights)); Assert.Throws( - () => new YenShortestPathsAlgorithm(graph, null, vertex2, int.MaxValue)); + () => new YenShortestPathsAlgorithm>(graph, null, vertex2, int.MaxValue, weights)); Assert.Throws( - () => new YenShortestPathsAlgorithm(graph, vertex1, null, int.MaxValue)); + () => new YenShortestPathsAlgorithm>(graph, vertex1, null, int.MaxValue, weights)); Assert.Throws( - () => new YenShortestPathsAlgorithm(null, null, vertex2, int.MaxValue)); + () => new YenShortestPathsAlgorithm>(null, null, vertex2, int.MaxValue, weights)); Assert.Throws( - () => new YenShortestPathsAlgorithm(null, vertex1, null, int.MaxValue)); + () => new YenShortestPathsAlgorithm>(null, vertex1, null, int.MaxValue, weights)); Assert.Throws( - () => new YenShortestPathsAlgorithm(graph, null, null, int.MaxValue)); + () => new YenShortestPathsAlgorithm>(graph, null, null, int.MaxValue, weights)); Assert.Throws( - () => new YenShortestPathsAlgorithm(null, null, null, int.MaxValue)); + () => new YenShortestPathsAlgorithm>(null, null, null, int.MaxValue, weights)); // ReSharper restore AssignNullToNotNullAttribute Assert.Throws( - () => new YenShortestPathsAlgorithm(graph, vertex1, vertex2, 0)); + () => new YenShortestPathsAlgorithm>(graph, vertex1, vertex2, 0, weights)); Assert.Throws( - () => new YenShortestPathsAlgorithm(graph, vertex1, vertex2, -1)); + () => new YenShortestPathsAlgorithm>(graph, vertex1, vertex2, -1, weights)); // ReSharper restore ObjectCreationAsStatement } @@ -85,11 +87,12 @@ public void SimpleNoPathGraph() graph.AddVertex('1'); // ReSharper disable ReturnValueOfPureMethodIsNotUsed - var algorithm = new YenShortestPathsAlgorithm(graph, '1', '1', 10); + Func, double> weights = edge => edge.Tag; + var algorithm = new YenShortestPathsAlgorithm>(graph, '1', '1', 10, weights); Assert.Throws(() => algorithm.Execute()); graph.AddVertex('2'); - algorithm = new YenShortestPathsAlgorithm(graph, '1', '2', 10); + algorithm = new YenShortestPathsAlgorithm>(graph, '1', '2', 10, weights); Assert.Throws(() => algorithm.Execute()); // ReSharper restore ReturnValueOfPureMethodIsNotUsed } @@ -104,7 +107,8 @@ public void LoopGraph() var graph = new AdjacencyGraph>(true); graph.AddVertexRange("1"); - var algorithm = new YenShortestPathsAlgorithm(graph, '1', '1', 10); + Func, double> weights = edge => edge.Tag; + var algorithm = new YenShortestPathsAlgorithm>(graph, '1', '1', 10, weights); graph.AddEdge(new EquatableTaggedEdge('1', '1', 7)); // ReSharper disable once ReturnValueOfPureMethodIsNotUsed Assert.Throws(() => algorithm.Execute()); @@ -127,8 +131,9 @@ public void GraphWithCycle() }; graph.AddEdgeRange(edges); - var algorithm = new YenShortestPathsAlgorithm(graph, '1', '5', 10); - YenShortestPathsAlgorithm.SortedPath[] paths = algorithm.Execute().ToArray(); + Func, double> weights = edge => edge.Tag; + var algorithm = new YenShortestPathsAlgorithm>(graph, '1', '5', 10, weights); + YenShortestPathsAlgorithm>.SortedPath[] paths = algorithm.Execute().ToArray(); // Expecting to get 2 paths: // 1 => 1-2-3-5 @@ -161,8 +166,9 @@ public void GraphWithMultiplePaths() }; graph.AddEdgeRange(edges); - var algorithm = new YenShortestPathsAlgorithm(graph, "A", "D", 5); - YenShortestPathsAlgorithm.SortedPath[] paths = algorithm.Execute().ToArray(); + Func, double> weights = edge => edge.Tag; + var algorithm = new YenShortestPathsAlgorithm>(graph, "A", "D", 5, weights); + YenShortestPathsAlgorithm>.SortedPath[] paths = algorithm.Execute().ToArray(); // Expecting to get 3 paths: // 1 => A-B-D @@ -205,8 +211,9 @@ public void GraphWithMultiplePaths_KShortest() graph.AddEdgeRange(edges); // K = 5 - var algorithmK5 = new YenShortestPathsAlgorithm(graph, 'C', 'H', 5); - YenShortestPathsAlgorithm.SortedPath[] paths = algorithmK5.Execute().ToArray(); + Func, double> weights = edge => edge.Tag; + var algorithmK5 = new YenShortestPathsAlgorithm>(graph, 'C', 'H', 5, weights); + YenShortestPathsAlgorithm>.SortedPath[] paths = algorithmK5.Execute().ToArray(); // Expecting to get 5 paths: // 1 => C-E-F-H @@ -220,7 +227,7 @@ public void GraphWithMultiplePaths_KShortest() // K = 50 - var algorithmK50 = new YenShortestPathsAlgorithm(graph, 'C', 'H', 50); + var algorithmK50 = new YenShortestPathsAlgorithm>(graph, 'C', 'H', 50, weights); paths = algorithmK50.Execute().ToArray(); // Expecting to get 7 paths: @@ -250,7 +257,7 @@ public void GraphWithMultiplePaths_KShortest() #region Local function - void CheckFiveFirstPaths(YenShortestPathsAlgorithm.SortedPath[] ps) + void CheckFiveFirstPaths(YenShortestPathsAlgorithm>.SortedPath[] ps) { // 1 EquatableTaggedEdge[] path0 = ps[0].ToArray(); @@ -287,23 +294,16 @@ void CheckFiveFirstPaths(YenShortestPathsAlgorithm.SortedPath[] ps) [Test] public void MultipleRunMethods() { + Func, double> weights = edge => edge.Tag; AdjacencyGraph> graph = GenerateGraph( out EquatableTaggedEdge[] graphEdges); - // Default weight function and default filter function case - var algorithm = new YenShortestPathsAlgorithm(graph, '1', '5', 10); - RunYenAndCheck(algorithm); - - // Custom weight function and default filter function case - algorithm = new YenShortestPathsAlgorithm(graph, '1', '5', 10, e => e.Tag); - RunYenAndCheck(algorithm); - - // Default weight function and custom filter function case - algorithm = new YenShortestPathsAlgorithm(graph, '1', '5', 10, null, e => e); + // Default filter function case + var algorithm = new YenShortestPathsAlgorithm>(graph, '1', '5', 10, weights); RunYenAndCheck(algorithm); - // Custom weight function and custom filter function case - algorithm = new YenShortestPathsAlgorithm(graph, '1', '5', 10, e => e.Tag, e => e); + // Custom filter function case + algorithm = new YenShortestPathsAlgorithm>(graph, '1', '5', 10, weights, e => e); RunYenAndCheck(algorithm); #region Local functions @@ -331,12 +331,12 @@ AdjacencyGraph> GenerateGraph( return g; } - void RunYenAndCheck(YenShortestPathsAlgorithm yen) + void RunYenAndCheck(YenShortestPathsAlgorithm> yen) { // Generate simple graph // like this https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm // but with directed edges input graph - YenShortestPathsAlgorithm.SortedPath[] paths = yen.Execute().ToArray(); + YenShortestPathsAlgorithm>.SortedPath[] paths = yen.Execute().ToArray(); // Expecting to get 3 paths: // 1 => 1-3-4-5 @@ -374,10 +374,10 @@ public void SortedPathHashCode() new EquatableTaggedEdge(2, 3, 1.0), new EquatableTaggedEdge(3, 4, 1.0) }; - var path1 = new YenShortestPathsAlgorithm.SortedPath(edges); - var path2 = new YenShortestPathsAlgorithm.SortedPath(edges); + var path1 = new YenShortestPathsAlgorithm>.SortedPath(edges); + var path2 = new YenShortestPathsAlgorithm>.SortedPath(edges); - var path3 = new YenShortestPathsAlgorithm.SortedPath(new[] + var path3 = new YenShortestPathsAlgorithm>.SortedPath(new[] { new EquatableTaggedEdge(1, 2, 1.0), new EquatableTaggedEdge(2, 3, 1.0), @@ -400,11 +400,11 @@ public void SortedPathEnumeration() new EquatableTaggedEdge(3, 4, 1.0) }; - var path = new YenShortestPathsAlgorithm.SortedPath(edges); + var path = new YenShortestPathsAlgorithm>.SortedPath(edges); CollectionAssert.AreEqual(edges, path); CollectionAssert.IsEmpty( - new YenShortestPathsAlgorithm.SortedPath( + new YenShortestPathsAlgorithm>.SortedPath( Enumerable.Empty>())); } }