diff --git a/src/QuikGraph/Algorithms/ShortestPath/YenShortestPathsAlgorithm.cs b/src/QuikGraph/Algorithms/ShortestPath/YenShortestPathsAlgorithm.cs
index b2c30b51f..651178112 100644
--- a/src/QuikGraph/Algorithms/ShortestPath/YenShortestPathsAlgorithm.cs
+++ b/src/QuikGraph/Algorithms/ShortestPath/YenShortestPathsAlgorithm.cs
@@ -16,6 +16,95 @@ namespace QuikGraph.Algorithms.ShortestPath
/// Vertex type.
public class YenShortestPathsAlgorithm
{
+ ///
+ /// Struct provides the parameters used for the path inspection.
+ ///
+ public struct InspectPathParameters
+ {
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ public InspectPathParameters(SortedPath path, IEnumerable shortestPaths, IEnumerable acceptedPaths)
+ {
+ Path = path;
+ ShortestPaths = shortestPaths;
+ AcceptedPaths = acceptedPaths;
+ }
+
+ ///
+ /// The current found shortest path.
+ ///
+ public SortedPath Path { get; }
+
+ ///
+ /// All shortest paths found so far. They might were accepted or not. Includes the current found path.
+ ///
+ public IEnumerable ShortestPaths { get; }
+
+ ///
+ /// All shortest paths that were accepted so far.
+ ///
+ public IEnumerable AcceptedPaths { get; }
+ }
+
+ ///
+ /// Struct provides the result of the path inspection.
+ ///
+ public struct InspectPathResult
+ {
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ public InspectPathResult(PathAcceptance pathAcceptance, SearchContinuation searchContinuation)
+ {
+ PathAcceptance = pathAcceptance;
+ SearchContinuation = searchContinuation;
+ }
+
+ ///
+ /// Decides whether to add the found path to the result set.
+ ///
+ public PathAcceptance PathAcceptance { get; }
+
+ ///
+ /// Decide whether to stop searching for more paths.
+ ///
+ public SearchContinuation SearchContinuation { get; }
+ }
+
+ ///
+ /// After path inspection, decide whether to add the found path to the result set.
+ ///
+ public enum PathAcceptance
+ {
+ ///
+ /// The found shortest path will be added to the result list.
+ ///
+ Accept,
+
+ ///
+ /// The found shortest path will not be added to the result list.
+ ///
+ Reject
+ }
+
+ ///
+ /// After path inspection, decide whether to stop searching for more paths.
+ ///
+ [Flags]
+ public enum SearchContinuation
+ {
+ ///
+ /// Continue the search until the k shortest paths are found
+ ///
+ Continue,
+
+ ///
+ /// The the search and return the found paths even if not k iterations have been done.
+ ///
+ StopSearch
+ }
+
///
/// Class representing a sorted path.
///
@@ -109,6 +198,9 @@ IEnumerator IEnumerable.GetEnumerator()
[NotNull]
private readonly Func, IEnumerable> _filter;
+ [NotNull]
+ private readonly Func _inspectPath;
+
// Limit for the amount of paths
private readonly int _k;
@@ -127,6 +219,7 @@ IEnumerator IEnumerable.GetEnumerator()
/// Maximum number of path to search.
/// Optional function that computes the weight for a given edge.
/// Optional filter of found paths.
+ /// Optional function to inspect a found shortest path right after it was found.
/// is .
/// is .
/// is .
@@ -139,7 +232,8 @@ public YenShortestPathsAlgorithm(
[NotNull] TVertex target,
int k,
[CanBeNull] Func, double> edgeWeights = null,
- [CanBeNull] Func, IEnumerable> filter = null)
+ [CanBeNull] Func, IEnumerable> filter = null,
+ [CanBeNull] Func inspectPath = null)
{
if (graph is null)
throw new ArgumentNullException(nameof(graph));
@@ -160,6 +254,7 @@ public YenShortestPathsAlgorithm(
_graph = graph.Clone();
_weights = edgeWeights ?? DefaultGetWeights;
_filter = filter ?? DefaultFilter;
+ _inspectPath = inspectPath ?? DefaultInspectPath;
}
[Pure]
@@ -175,6 +270,12 @@ private static double DefaultGetWeights([NotNull] EquatableTaggedEdge Execute()
{
SortedPath initialPath = GetInitialShortestPath();
var shortestPaths = new List { initialPath };
+ var acceptedPaths = new List();
+
+ InspectPathResult inspectPathResult = _inspectPath(new InspectPathParameters(initialPath, shortestPaths, acceptedPaths));
+
+ if (inspectPathResult.PathAcceptance == PathAcceptance.Accept)
+ {
+ acceptedPaths.Add(initialPath);
+ }
+
+ if (inspectPathResult.SearchContinuation == SearchContinuation.StopSearch)
+ {
+ return _filter(acceptedPaths);
+ }
// Initialize the set to store the potential k-th shortest path
var shortestPathCandidates = new BinaryQueue(GetPathDistance);
+ var previousPath = initialPath;
for (int k = 1; k < _k; ++k)
{
- SortedPath previousPath = shortestPaths[k - 1];
-
if (!SearchAndAddKthShortestPath(previousPath, shortestPaths, shortestPathCandidates))
+ {
break;
+ }
+
+ SortedPath newPath = shortestPaths[k];
+ inspectPathResult = _inspectPath(new InspectPathParameters(newPath, shortestPaths, acceptedPaths));
+
+ if (inspectPathResult.PathAcceptance == PathAcceptance.Accept)
+ {
+ acceptedPaths.Add(newPath);
+ }
+
+ if (inspectPathResult.SearchContinuation == SearchContinuation.StopSearch)
+ {
+ break;
+ }
+
+ previousPath = newPath;
}
- return _filter(shortestPaths);
+ return _filter(acceptedPaths);
}
[NotNull, ItemNotNull]
diff --git a/tests/QuikGraph.Tests/Algorithms/ShortestPath/YenShortestPathsAlgorithmTests.cs b/tests/QuikGraph.Tests/Algorithms/ShortestPath/YenShortestPathsAlgorithmTests.cs
index 895df5296..e55a74fd1 100644
--- a/tests/QuikGraph.Tests/Algorithms/ShortestPath/YenShortestPathsAlgorithmTests.cs
+++ b/tests/QuikGraph.Tests/Algorithms/ShortestPath/YenShortestPathsAlgorithmTests.cs
@@ -365,6 +365,142 @@ void RunYenAndCheck(YenShortestPathsAlgorithm yen)
#endregion
}
+ [Test]
+ public void InspectPath_RejectSomePaths()
+ {
+ var graph = new AdjacencyGraph>(false);
+ graph.AddVertexRange(new[] { "A", "B", "C", "D" });
+ var edges = new[]
+ {
+ new EquatableTaggedEdge("A", "B", 5),
+ new EquatableTaggedEdge("A", "C", 6),
+ new EquatableTaggedEdge("B", "C", 7),
+ new EquatableTaggedEdge("B", "D", 8),
+ new EquatableTaggedEdge("C", "D", 9)
+ };
+ graph.AddEdgeRange(edges);
+
+ // ignore all paths where "B" comes in the 2nd position
+ var algorithm = new YenShortestPathsAlgorithm(graph, "A", "D", 5, inspectPath: InspectPath);
+ YenShortestPathsAlgorithm.SortedPath[] paths = algorithm.Execute().ToArray();
+
+ // Expecting to find 3 paths:
+ // * => A-B-D (gets ignored)
+ // 1 => A-C-D
+ // * => A-B-C-D (gets ignored)
+ // Consistently checking the result
+ Assert.AreEqual(1, paths.Length);
+ // 1
+ EquatableTaggedEdge[] path0 = paths[0].ToArray();
+ Assert.AreEqual(path0[0], edges[1]);
+ Assert.AreEqual(path0[1], edges[4]);
+
+ #region Local functions
+
+ YenShortestPathsAlgorithm.InspectPathResult InspectPath(YenShortestPathsAlgorithm.InspectPathParameters inspectPathParameters)
+ {
+ var pathAcceptance = inspectPathParameters.Path.GetVertex(1) == "B"
+ ? YenShortestPathsAlgorithm.PathAcceptance.Reject
+ : YenShortestPathsAlgorithm.PathAcceptance.Accept;
+
+ return new YenShortestPathsAlgorithm.InspectPathResult(pathAcceptance, YenShortestPathsAlgorithm.SearchContinuation.Continue);
+ }
+
+ #endregion
+ }
+
+ [Test]
+ public void InspectPath_StopsSearchWhenConditionIsMet_ForInitialShortestPath()
+ {
+ var graph = new AdjacencyGraph>(false);
+ graph.AddVertexRange(new[] { "A", "B", "C", "D" });
+ var edges = new[]
+ {
+ new EquatableTaggedEdge("A", "B", 5),
+ new EquatableTaggedEdge("A", "C", 6),
+ new EquatableTaggedEdge("B", "C", 7),
+ new EquatableTaggedEdge("B", "D", 8),
+ new EquatableTaggedEdge("C", "D", 9)
+ };
+ graph.AddEdgeRange(edges);
+
+ // stop when we have found a shortest path that exceeds some costs
+ var algorithm = new YenShortestPathsAlgorithm(graph, "A", "D", 10, inspectPath: InspectPath);
+ YenShortestPathsAlgorithm.SortedPath[] paths = algorithm.Execute().ToArray();
+
+ // Expecting to get 3 paths:
+ // 1 => A-B-D (cost = 13)
+ // * => A-C-D (path limit reached)
+ // * => A-B-C-D (path limit reached)
+ // Consistently checking the result
+ Assert.AreEqual(1, paths.Length);
+ // 1
+ EquatableTaggedEdge[] path0 = paths[0].ToArray();
+ Assert.AreEqual(path0[0], edges[0]);
+ Assert.AreEqual(path0[1], edges[3]);
+
+ #region Local functions
+
+ YenShortestPathsAlgorithm.InspectPathResult InspectPath(YenShortestPathsAlgorithm.InspectPathParameters inspectPathParameters)
+ {
+ var searchContinuation = inspectPathParameters.Path.Sum(x => x.Tag) >= 10
+ ? YenShortestPathsAlgorithm.SearchContinuation.StopSearch
+ : YenShortestPathsAlgorithm.SearchContinuation.Continue;
+
+ return new YenShortestPathsAlgorithm.InspectPathResult(YenShortestPathsAlgorithm.PathAcceptance.Accept, searchContinuation);
+ }
+
+ #endregion
+ }
+
+ [Test]
+ public void InspectPath_StopsSearchWhenConditionIsMet_ForNthShortestPath()
+ {
+ var graph = new AdjacencyGraph>(false);
+ graph.AddVertexRange(new[] { "A", "B", "C", "D" });
+ var edges = new[]
+ {
+ new EquatableTaggedEdge("A", "B", 5),
+ new EquatableTaggedEdge("A", "C", 6),
+ new EquatableTaggedEdge("B", "C", 7),
+ new EquatableTaggedEdge("B", "D", 8),
+ new EquatableTaggedEdge("C", "D", 9)
+ };
+ graph.AddEdgeRange(edges);
+
+ // stop when we have found a shortest path that exceeds some costs
+ var algorithm = new YenShortestPathsAlgorithm(graph, "A", "D", 10, inspectPath: InspectPath);
+ YenShortestPathsAlgorithm.SortedPath[] paths = algorithm.Execute().ToArray();
+
+ // Expecting to get 3 paths:
+ // 1 => A-B-D (cost = 13)
+ // 2 => A-C-D (cost = 15)
+ // * => A-B-C-D (path limit reached)
+ // Consistently checking the result
+ Assert.AreEqual(2, paths.Length);
+ // 1
+ EquatableTaggedEdge[] path0 = paths[0].ToArray();
+ Assert.AreEqual(path0[0], edges[0]);
+ Assert.AreEqual(path0[1], edges[3]);
+ // 2
+ EquatableTaggedEdge[] path1 = paths[1].ToArray();
+ Assert.AreEqual(path1[0], edges[1]);
+ Assert.AreEqual(path1[1], edges[4]);
+
+ #region Local functions
+
+ YenShortestPathsAlgorithm.InspectPathResult InspectPath(YenShortestPathsAlgorithm.InspectPathParameters inspectPathParameters)
+ {
+ var searchContinuation = inspectPathParameters.Path.Sum(x => x.Tag) >= 15
+ ? YenShortestPathsAlgorithm.SearchContinuation.StopSearch
+ : YenShortestPathsAlgorithm.SearchContinuation.Continue;
+
+ return new YenShortestPathsAlgorithm.InspectPathResult(YenShortestPathsAlgorithm.PathAcceptance.Accept, searchContinuation);
+ }
+
+ #endregion
+ }
+
[Test]
public void SortedPathHashCode()
{