From c8315f6e9be6786ff840ff5fc0ff909225b9fa04 Mon Sep 17 00:00:00 2001 From: ZabuzaW Date: Thu, 7 Nov 2019 23:13:56 +0100 Subject: [PATCH] API improvements. * Added AbortBeforeIfModule * doConsiderEdgeForRelaxiation for DijkstraModules exposes TentativeDistance as additional argument * SimpleGraph and SimpleEdge are not final anymore * Path can now be iterated reversely via reverseIterator() * Added 2 shortestPathReachable methods which return a PathTree for more complex path retrieval --- .../external/algorithms/DijkstraModule.java | 22 ++++- .../maglev/external/algorithms/Path.java | 2 +- .../maglev/external/algorithms/PathTree.java | 59 +++++++++++++ .../algorithms/ReverselyIterable.java | 53 ++++++++++++ .../algorithms/ShortestPathComputation.java | 20 +++++ .../ShortestPathComputationBuilder.java | 21 ++++- .../external/graph/simple/SimpleEdge.java | 3 +- .../external/graph/simple/SimpleGraph.java | 3 +- ...tIfModule.java => AbortAfterIfModule.java} | 10 +-- .../shortestpath/AbortAfterRangeModule.java | 2 +- .../shortestpath/AbortBeforeIfModule.java | 55 ++++++++++++ .../AbstractShortestPathComputation.java | 6 ++ .../algorithms/shortestpath/Dijkstra.java | 51 ++++++++--- .../algorithms/shortestpath/EdgePath.java | 8 ++ .../algorithms/shortestpath/EmptyPath.java | 4 + .../shortestpath/IgnoreEdgeIfModule.java | 4 +- .../shortestpath/ModuleDijkstra.java | 42 +++++++-- .../shortestpath/OnDemandPathTree.java | 86 +++++++++++++++++++ 18 files changed, 416 insertions(+), 35 deletions(-) create mode 100644 de.zabuza.maglev/src/de/zabuza/maglev/external/algorithms/PathTree.java create mode 100644 de.zabuza.maglev/src/de/zabuza/maglev/external/algorithms/ReverselyIterable.java rename de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/{AbortIfModule.java => AbortAfterIfModule.java} (78%) create mode 100644 de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/AbortBeforeIfModule.java create mode 100644 de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/OnDemandPathTree.java diff --git a/de.zabuza.maglev/src/de/zabuza/maglev/external/algorithms/DijkstraModule.java b/de.zabuza.maglev/src/de/zabuza/maglev/external/algorithms/DijkstraModule.java index 29dcd2b..0f0399e 100644 --- a/de.zabuza.maglev/src/de/zabuza/maglev/external/algorithms/DijkstraModule.java +++ b/de.zabuza.maglev/src/de/zabuza/maglev/external/algorithms/DijkstraModule.java @@ -18,14 +18,16 @@ public interface DijkstraModule> { * Whether or not the given edge should be considered for relaxation. The algorithm will ignore the edge and not * follow it if this method returns {@code false}. * - * @param edge The edge in question - * @param pathDestination The destination of the shortest path computation or {@code null} if not present + * @param edge The edge in question + * @param pathDestination The destination of the shortest path computation or {@code null} if not present + * @param tentativeDistance The current tentative distance when relaxing this edge * * @return {@code True} if the edge should be considered, {@code false} otherwise */ @SuppressWarnings({ "SameReturnValue", "MethodReturnAlwaysConstant", "BooleanMethodNameMustStartWithQuestion" }) default boolean doConsiderEdgeForRelaxation(@SuppressWarnings("unused") final E edge, - @SuppressWarnings("unused") final N pathDestination) { + @SuppressWarnings("unused") final N pathDestination, + final TentativeDistance tentativeDistance) { return true; } @@ -61,6 +63,18 @@ default OptionalDouble provideEdgeCost(@SuppressWarnings("unused") final E edge, return OptionalDouble.empty(); } + /** + * Whether or not the algorithm should abort computation of the shortest path. The method is called right before the + * given node will be settled. + * + * @param tentativeDistance The tentative distance wrapper of the node that will be settled next + * + * @return {@code True} if the computation should be aborted, {@code false} if not + */ + default boolean shouldAbortBefore(@SuppressWarnings("unused") final TentativeDistance tentativeDistance) { + return false; + } + /** * Whether or not the algorithm should abort computation of the shortest path. The method is called right after the * given node has been settled. @@ -69,7 +83,7 @@ default OptionalDouble provideEdgeCost(@SuppressWarnings("unused") final E edge, * * @return {@code True} if the computation should be aborted, {@code false} if not */ - default boolean shouldAbort(@SuppressWarnings("unused") final TentativeDistance tentativeDistance) { + default boolean shouldAbortAfter(@SuppressWarnings("unused") final TentativeDistance tentativeDistance) { return false; } } \ No newline at end of file diff --git a/de.zabuza.maglev/src/de/zabuza/maglev/external/algorithms/Path.java b/de.zabuza.maglev/src/de/zabuza/maglev/external/algorithms/Path.java index a66578c..bd4ea47 100644 --- a/de.zabuza.maglev/src/de/zabuza/maglev/external/algorithms/Path.java +++ b/de.zabuza.maglev/src/de/zabuza/maglev/external/algorithms/Path.java @@ -10,7 +10,7 @@ * * @author Daniel Tischner {@literal } */ -public interface Path> extends Iterable> { +public interface Path> extends ReverselyIterable> { /** * Gets the destination node of the path. * diff --git a/de.zabuza.maglev/src/de/zabuza/maglev/external/algorithms/PathTree.java b/de.zabuza.maglev/src/de/zabuza/maglev/external/algorithms/PathTree.java new file mode 100644 index 0000000..38b8fe8 --- /dev/null +++ b/de.zabuza.maglev/src/de/zabuza/maglev/external/algorithms/PathTree.java @@ -0,0 +1,59 @@ +package de.zabuza.maglev.external.algorithms; + +import de.zabuza.maglev.external.graph.Edge; + +import java.util.Collection; +import java.util.Optional; +import java.util.stream.Stream; + +/** + * Interface for a shortest path tree. That is the result of a shortest path exploration from given sources to possibly + * multiple destinations. + *

+ * It provides methods to construct the actual shortest path to a given destination and also to retrieve the leaves, + * i.e. the boundary of the exploration. + * + * @param The type of nodes + * @param The type of edges + * + * @author Daniel Tischner {@literal } + */ +public interface PathTree> { + /** + * The sources of the shortest path exploration, i.e. the roots of the tree. This can be a single but also multiple + * nodes, depending on the type of exploration. + * + * @return The sources of the shortest path exploration, can also only contain a single node. The collection is not + * modifiable. + */ + Collection getSources(); + + /** + * Gets a stream over all nodes that are reachable from the given sources, i.e. all nodes in the tree. This includes + * the sources themselves. + *

+ * Implementations must construct the stream in {@code O(1)} time. + * + * @return All reachable nodes, including the sources + */ + Stream getReachableNodes(); + + /** + * Constructs the shortest path from the closest of the given sources to the given destination, i.e. the path from + * the roots down to the given destination. + * + * @param destination The destination to construct the path to + * + * @return The shortest path from one of the sources to the given destination. Or empty if the destination is not + * reachable. + */ + Optional> getPathTo(N destination); + + /** + * Gets the boundary nodes of the shortest path exploration, i.e. the leaves of the tree. Such nodes are farthest + * away from the given set of sources in their respective direction. + * + * @return The boundary nodes of the exploration, can include source nodes + */ + Collection getLeaves(); +} diff --git a/de.zabuza.maglev/src/de/zabuza/maglev/external/algorithms/ReverselyIterable.java b/de.zabuza.maglev/src/de/zabuza/maglev/external/algorithms/ReverselyIterable.java new file mode 100644 index 0000000..5ddf5f9 --- /dev/null +++ b/de.zabuza.maglev/src/de/zabuza/maglev/external/algorithms/ReverselyIterable.java @@ -0,0 +1,53 @@ +package de.zabuza.maglev.external.algorithms; + +import java.util.Iterator; +import java.util.Objects; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Consumer; + +/** + * Extension of {@link Iterable} for classes offering {@link java.util.Iterator}s that iterate the given data source + * reversely, in comparison to the order defined by the iterator returned by the {@link Iterable} implementation. + * + * @param The type of the elements contained in the data source + * + * @author Daniel Tischner {@literal } + */ +public interface ReverselyIterable extends Iterable { + /** + * Returns an iterator over elements of type {@code T} that iterates the data source reversely. + * + * @return A reverse iterator + */ + Iterator reverseIterator(); + + /** + * Performs the given action for each element of the {@code ReverselyIterable} until all elements have been + * processed or the action throws an exception. Actions are performed in the order of iteration, if that order is + * specified. Exceptions thrown by the action are relayed to the caller. + *

+ * The behavior of this method is unspecified if the action performs side-effects that modify the underlying source + * of elements, unless an overriding class has specified a concurrent modification policy. + * + * @param action The action to be performed for each element + * + * @throws NullPointerException if the specified action is null + */ + default void forEachReversed(final Consumer action) { + Objects.requireNonNull(action); + final Iterator reverseIter = reverseIterator(); + while (reverseIter.hasNext()) { + action.accept(reverseIter.next()); + } + } + + /** + * Creates a {@link Spliterator} over the elements described by this {@code ReverselyIterable}. + * + * @return a {@code Spliterator} over the elements described by this {@code ReverselyIterable}. + */ + default Spliterator spliteratorReversed() { + return Spliterators.spliteratorUnknownSize(reverseIterator(), 0); + } +} diff --git a/de.zabuza.maglev/src/de/zabuza/maglev/external/algorithms/ShortestPathComputation.java b/de.zabuza.maglev/src/de/zabuza/maglev/external/algorithms/ShortestPathComputation.java index b2c6b54..4d3d9ad 100644 --- a/de.zabuza.maglev/src/de/zabuza/maglev/external/algorithms/ShortestPathComputation.java +++ b/de.zabuza.maglev/src/de/zabuza/maglev/external/algorithms/ShortestPathComputation.java @@ -106,4 +106,24 @@ public interface ShortestPathComputation> { * @return A map which connects destination nodes to the costs of their shortest path */ Map shortestPathCostsReachable(N source); + + /** + * Computes all shortest paths from the given sources to all other nodes.
+ *
+ * The shortest path from multiple sources is the minimal shortest path for all source nodes individually. + * + * @param sources The sources to compute the shortest path from + * + * @return A tree containing all computed shortest paths + */ + PathTree shortestPathReachable(Collection sources); + + /** + * Computes the shortest paths from the given source to all other nodes. + * + * @param source The source to compute the shortest path from + * + * @return A tree containing all computed shortest paths + */ + PathTree shortestPathReachable(N source); } \ No newline at end of file diff --git a/de.zabuza.maglev/src/de/zabuza/maglev/external/algorithms/ShortestPathComputationBuilder.java b/de.zabuza.maglev/src/de/zabuza/maglev/external/algorithms/ShortestPathComputationBuilder.java index b21a1d4..4905b4f 100644 --- a/de.zabuza.maglev/src/de/zabuza/maglev/external/algorithms/ShortestPathComputationBuilder.java +++ b/de.zabuza.maglev/src/de/zabuza/maglev/external/algorithms/ShortestPathComputationBuilder.java @@ -18,7 +18,8 @@ * The builder offers highly customizable algorithms based on Dijkstra, called Module-Dijkstra. That is a regular * Dijkstra algorithm which can be extended using extension modules that modify its behavior. Offered modules are: *

    - *
  • {@code AbortIfModule} - Aborts further computation as soon as a node that matches a given predicate has been settled
  • + *
  • {@code AbortAfterIfModule} - Aborts further computation as soon as a node that matches a given predicate has been settled
  • + *
  • {@code AbortBeforeIfModule} - Aborts further computation as soon as a node that matches a given predicate would be settled
  • *
  • {@code AbortAfterRangeModule} - Only explores shortest paths up to the given range
  • *
  • {@code IgnoreEdgeIfModule} - Ignores exploring edges that match the given predicate
  • *
  • {@code AStarModule} - Optimization of the algorithm by utilizing a given heuristic metric
  • @@ -179,6 +180,20 @@ public ShortestPathComputationBuilder addModule(final DijkstraModule return this; } + /** + * Adds a module to be used by {@code Module-Dijkstra} which aborts computation right before a node has been settled + * that matches the given predicate. + * + * @param predicate The predicate to test the node against, not null + * + * @return This builder instance + */ + public ShortestPathComputationBuilder addModuleAbortBeforeIf( + final Predicate> predicate) { + modules.add(AbortBeforeIfModule.of(Objects.requireNonNull(predicate))); + return this; + } + /** * Adds a module to be used by {@code Module-Dijkstra} which aborts computation as soon as a node has been settled * that matches the given predicate. @@ -187,9 +202,9 @@ public ShortestPathComputationBuilder addModule(final DijkstraModule * * @return This builder instance */ - public ShortestPathComputationBuilder addModuleAbortIf( + public ShortestPathComputationBuilder addModuleAbortAfterIf( final Predicate> predicate) { - modules.add(AbortIfModule.of(Objects.requireNonNull(predicate))); + modules.add(AbortAfterIfModule.of(Objects.requireNonNull(predicate))); return this; } diff --git a/de.zabuza.maglev/src/de/zabuza/maglev/external/graph/simple/SimpleEdge.java b/de.zabuza.maglev/src/de/zabuza/maglev/external/graph/simple/SimpleEdge.java index 39e1a5c..02f8daa 100644 --- a/de.zabuza.maglev/src/de/zabuza/maglev/external/graph/simple/SimpleEdge.java +++ b/de.zabuza.maglev/src/de/zabuza/maglev/external/graph/simple/SimpleEdge.java @@ -12,7 +12,8 @@ * * @author Daniel Tischner {@literal } */ -public final class SimpleEdge implements Edge, ReversedConsumer { +@SuppressWarnings("DesignForExtension") +public class SimpleEdge implements Edge, ReversedConsumer { /** * The cost of the edge, i.e. its weight. */ diff --git a/de.zabuza.maglev/src/de/zabuza/maglev/external/graph/simple/SimpleGraph.java b/de.zabuza.maglev/src/de/zabuza/maglev/external/graph/simple/SimpleGraph.java index aff555e..03f135e 100644 --- a/de.zabuza.maglev/src/de/zabuza/maglev/external/graph/simple/SimpleGraph.java +++ b/de.zabuza.maglev/src/de/zabuza/maglev/external/graph/simple/SimpleGraph.java @@ -13,7 +13,8 @@ * * @author Daniel Tischner {@literal } */ -public final class SimpleGraph & ReversedConsumer> extends AbstractGraph +@SuppressWarnings("DesignForExtension") +public class SimpleGraph & ReversedConsumer> extends AbstractGraph implements ReversedProvider { /** * A set with all contained nodes. diff --git a/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/AbortIfModule.java b/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/AbortAfterIfModule.java similarity index 78% rename from de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/AbortIfModule.java rename to de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/AbortAfterIfModule.java index d926aa7..b93f862 100644 --- a/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/AbortIfModule.java +++ b/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/AbortAfterIfModule.java @@ -17,7 +17,7 @@ * * @author Daniel Tischner {@literal } */ -public class AbortIfModule> implements DijkstraModule { +public final class AbortAfterIfModule> implements DijkstraModule { /** * Creates an module which aborts computation after exploring to a node which matches the given predicate. @@ -28,9 +28,9 @@ public class AbortIfModule> implements DijkstraModule * * @return The created module */ - public static > AbortIfModule of( + public static > AbortAfterIfModule of( final Predicate> predicate) { - return new AbortIfModule<>(predicate); + return new AbortAfterIfModule<>(predicate); } /** @@ -43,12 +43,12 @@ public static > AbortIfModule of( * * @param predicate The predicate to test the node against */ - AbortIfModule(final Predicate> predicate) { + private AbortAfterIfModule(final Predicate> predicate) { this.predicate = predicate; } @Override - public final boolean shouldAbort(final TentativeDistance tentativeDistance) { + public boolean shouldAbortAfter(final TentativeDistance tentativeDistance) { return predicate.test(tentativeDistance); } diff --git a/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/AbortAfterRangeModule.java b/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/AbortAfterRangeModule.java index c118839..a6b7f1b 100644 --- a/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/AbortAfterRangeModule.java +++ b/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/AbortAfterRangeModule.java @@ -13,7 +13,7 @@ * * @author Daniel Tischner {@literal } */ -public final class AbortAfterRangeModule> extends AbortIfModule { +public final class AbortAfterRangeModule> extends AbortBeforeIfModule { /** * Creates an module which aborts computation after exploring to the given range. diff --git a/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/AbortBeforeIfModule.java b/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/AbortBeforeIfModule.java new file mode 100644 index 0000000..c41e25d --- /dev/null +++ b/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/AbortBeforeIfModule.java @@ -0,0 +1,55 @@ +package de.zabuza.maglev.internal.algorithms.shortestpath; + +import de.zabuza.maglev.external.algorithms.DijkstraModule; +import de.zabuza.maglev.external.algorithms.TentativeDistance; +import de.zabuza.maglev.external.graph.Edge; + +import java.util.function.Predicate; + +/** + * Module for a {@link ModuleDijkstra} that aborts computation of the shortest path before exploring a node which + * matches the given predicate.
    + *
    + * The factory method {@link #of(Predicate)} can be used for convenient instance creation. + * + * @param Type of the nodes + * @param Type of the edges + * + * @author Daniel Tischner {@literal } + */ +public class AbortBeforeIfModule> implements DijkstraModule { + + /** + * Creates an module which aborts computation before exploring a node which matches the given predicate. + * + * @param Type of the nodes + * @param Type of the edges + * @param predicate The predicate to test the node against. + * + * @return The created module + */ + public static > AbortBeforeIfModule of( + final Predicate> predicate) { + return new AbortBeforeIfModule<>(predicate); + } + + /** + * The predicate to test the node against. + */ + private final Predicate> predicate; + + /** + * Creates an module which aborts computation before exploring a node which matches the given predicate. + * + * @param predicate The predicate to test the node against + */ + AbortBeforeIfModule(final Predicate> predicate) { + this.predicate = predicate; + } + + @Override + public final boolean shouldAbortBefore(final TentativeDistance tentativeDistance) { + return predicate.test(tentativeDistance); + } + +} \ No newline at end of file diff --git a/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/AbstractShortestPathComputation.java b/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/AbstractShortestPathComputation.java index 3449241..44e1031 100644 --- a/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/AbstractShortestPathComputation.java +++ b/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/AbstractShortestPathComputation.java @@ -2,6 +2,7 @@ import de.zabuza.maglev.external.algorithms.HasPathCost; import de.zabuza.maglev.external.algorithms.Path; +import de.zabuza.maglev.external.algorithms.PathTree; import de.zabuza.maglev.external.algorithms.ShortestPathComputation; import de.zabuza.maglev.external.graph.Edge; @@ -42,4 +43,9 @@ public Optional shortestPathCost(final N source, final N destination) { return shortestPathCostsReachable(Collections.singletonList(source)); } + @Override + public PathTree shortestPathReachable(final N source) { + return shortestPathReachable(Collections.singletonList(source)); + } + } \ No newline at end of file diff --git a/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/Dijkstra.java b/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/Dijkstra.java index 9284563..ab2177e 100644 --- a/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/Dijkstra.java +++ b/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/Dijkstra.java @@ -2,6 +2,7 @@ import de.zabuza.maglev.external.algorithms.HasPathCost; import de.zabuza.maglev.external.algorithms.Path; +import de.zabuza.maglev.external.algorithms.PathTree; import de.zabuza.maglev.external.algorithms.TentativeDistance; import de.zabuza.maglev.external.graph.Edge; import de.zabuza.maglev.external.graph.Graph; @@ -15,8 +16,9 @@ * Dijkstra has no sense of goal direction. When finishing the computation of the shortest path it has also computed all * shortest paths to nodes less far away, so the search space is rather big.
    *
    - * Subclasses can override {@link #doConsiderEdgeForRelaxation(Edge, Object)} and {@link #getEstimatedDistance(Object, - * Object)} to speedup the algorithm by giving it a sense of goal direction or exploiting precomputed knowledge. + * Subclasses can override {@link #doConsiderEdgeForRelaxation(Edge, Object, TentativeDistance)} and {@link + * #getEstimatedDistance(Object, Object)} to speedup the algorithm by giving it a sense of goal direction or exploiting + * precomputed knowledge. * * @param Type of the node * @param Type of the edge @@ -93,6 +95,11 @@ public Optional shortestPathCost(final Collection sources, return computeShortestPathCostHelper(sources, null); } + @Override + public PathTree shortestPathReachable(final Collection sources) { + return new OnDemandPathTree<>(sources, computeShortestPathCostHelper(sources, null)); + } + /** * Computes the shortest path from the given sources to the given destination and to all other nodes that were * visited in the mean time.
    @@ -135,20 +142,25 @@ protected Map> computeShortestPathCostHelper(final Co continue; } + // End the algorithm if a subclass implementation demands it + if (shouldAbortBefore(distance)) { + break; + } + // Settle the current node nodeToSettledDistance.put(node, distance); // End the algorithm if destination was settled or a subclass // implementation demands it //noinspection PointlessNullCheck - if ((pathDestination != null && node.equals(pathDestination)) || shouldAbort(distance)) { + if ((pathDestination != null && node.equals(pathDestination)) || shouldAbortAfter(distance)) { break; } // Relax all outgoing edges provideEdgesToRelax(distance).forEach(edge -> { // Skip the edge if it should not be considered - if (!doConsiderEdgeForRelaxation(edge, pathDestination)) { + if (!doConsiderEdgeForRelaxation(edge, pathDestination, distance)) { return; } @@ -197,13 +209,15 @@ protected Map> computeShortestPathCostHelper(final Co * Whether or not the given edge should be considered for relaxation. The algorithm will ignore the edge and not * follow it if this method returns {@code false}. * - * @param edge The edge in question - * @param pathDestination The destination of the shortest path computation or {@code null} if not present + * @param edge The edge in question + * @param pathDestination The destination of the shortest path computation or {@code null} if not present + * @param tentativeDistance The current tentative distance when relaxing this edge * * @return {@code True} if the edge should be considered, {@code false} otherwise */ @SuppressWarnings({ "unused", "WeakerAccess", "BooleanMethodNameMustStartWithQuestion" }) - protected boolean doConsiderEdgeForRelaxation(final E edge, final N pathDestination) { + protected boolean doConsiderEdgeForRelaxation(final E edge, final N pathDestination, + final TentativeDistance tentativeDistance) { // Dijkstras algorithm considers every outgoing edge. // This method may be used by extending classes to improve performance. return true; @@ -248,8 +262,8 @@ protected double provideEdgeCost(final E edge, @SuppressWarnings("unused") final * Generates a stream of edges to process for relaxation.
    *
    * The base are all outgoing edges of the given node. Implementations are allowed to override this method in order - * to further filter the stream. Additionally, the method {@link #doConsiderEdgeForRelaxation(Edge, Object)} will be - * called on each element of this stream. + * to further filter the stream. Additionally, the method {@link #doConsiderEdgeForRelaxation(Edge, Object, + * TentativeDistance)} will be called on each element of this stream. * * @param tentativeDistance The tentative distance wrapper of the node to relax edges of * @@ -260,6 +274,23 @@ protected Stream provideEdgesToRelax(final TentativeDistance return graph.getOutgoingEdges(tentativeDistance.getNode()); } + /** + * Whether or not the algorithm should abort computation of the shortest path. The method is called right before the + * given node will be settled. + * + * @param tentativeDistance The tentative distance wrapper of the node that will be settled next + * + * @return {@code True} if the computation should be aborted, {@code false} if not + */ + @SuppressWarnings("WeakerAccess") + protected boolean shouldAbortBefore(@SuppressWarnings("unused") final TentativeDistance tentativeDistance) { + // Dijkstras algorithm relaxes the whole network, it only aborts if the + // target was settled. However, the method can be used by subclasses to + // abort computation earlier, for example after exploring to a fixed + // distance. + return false; + } + /** * Whether or not the algorithm should abort computation of the shortest path. The method is called right after the * given node has been settled. @@ -269,7 +300,7 @@ protected Stream provideEdgesToRelax(final TentativeDistance * @return {@code True} if the computation should be aborted, {@code false} if not */ @SuppressWarnings("WeakerAccess") - protected boolean shouldAbort(@SuppressWarnings("unused") final TentativeDistance tentativeDistance) { + protected boolean shouldAbortAfter(@SuppressWarnings("unused") final TentativeDistance tentativeDistance) { // Dijkstras algorithm relaxes the whole network, it only aborts if the // target was settled. However, the method can be used by subclasses to // abort computation earlier, for example after exploring to a fixed diff --git a/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/EdgePath.java b/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/EdgePath.java index 13301f2..dab9c42 100644 --- a/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/EdgePath.java +++ b/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/EdgePath.java @@ -141,6 +141,14 @@ public int length() { return edges.size(); } + @Override + public Iterator> reverseIterator() { + if (constructionDirection == ConstructionDirection.BACKWARD) { + return edges.iterator(); + } + return new ReverseIterator<>(edges); + } + /** * Direction to construct an edge in. */ diff --git a/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/EmptyPath.java b/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/EmptyPath.java index 23b06a4..1ae553f 100644 --- a/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/EmptyPath.java +++ b/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/EmptyPath.java @@ -57,4 +57,8 @@ public int length() { return 0; } + @Override + public Iterator> reverseIterator() { + return Collections.emptyListIterator(); + } } \ No newline at end of file diff --git a/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/IgnoreEdgeIfModule.java b/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/IgnoreEdgeIfModule.java index 6550470..369440a 100644 --- a/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/IgnoreEdgeIfModule.java +++ b/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/IgnoreEdgeIfModule.java @@ -1,6 +1,7 @@ package de.zabuza.maglev.internal.algorithms.shortestpath; import de.zabuza.maglev.external.algorithms.DijkstraModule; +import de.zabuza.maglev.external.algorithms.TentativeDistance; import de.zabuza.maglev.external.graph.Edge; import java.util.function.Predicate; @@ -45,7 +46,8 @@ private IgnoreEdgeIfModule(final Predicate predicate) { } @Override - public boolean doConsiderEdgeForRelaxation(final E edge, final N pathDestination) { + public boolean doConsiderEdgeForRelaxation(final E edge, final N pathDestination, + final TentativeDistance tentativeDistance) { return considerEdgePredicate.test(edge); } diff --git a/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/ModuleDijkstra.java b/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/ModuleDijkstra.java index 8d27ca3..91319fe 100644 --- a/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/ModuleDijkstra.java +++ b/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/ModuleDijkstra.java @@ -80,15 +80,16 @@ public void removeModule(final DijkstraModule module) { * Whether or not the given edge should be considered for relaxation. The algorithm will ignore the edge and not * follow it if this method returns {@code false}.
    *
    - * This will be the case if any modules {@link DijkstraModule#doConsiderEdgeForRelaxation(Edge, Object)} method - * returns {@code false}. + * This will be the case if any modules {@link DijkstraModule#doConsiderEdgeForRelaxation(Edge, Object, + * TentativeDistance)} method returns {@code false}. */ @Override - protected boolean doConsiderEdgeForRelaxation(final E edge, final N pathDestination) { + protected boolean doConsiderEdgeForRelaxation(final E edge, final N pathDestination, + final TentativeDistance tentativeDistance) { // Ignore the base, it always considers all edges // Ask all modules and accumulate with logical and for (final DijkstraModule module : modules) { - final boolean doNotConsider = !module.doConsiderEdgeForRelaxation(edge, pathDestination); + final boolean doNotConsider = !module.doConsiderEdgeForRelaxation(edge, pathDestination, tentativeDistance); if (doNotConsider) { return false; } @@ -142,23 +143,48 @@ protected double provideEdgeCost(final E edge, final double tentativeDistance) { return super.provideEdgeCost(edge, tentativeDistance); } + /** + * Whether or not the algorithm should abort computation of the shortest path. The method is called right before the + * given node will be settled.
    + *
    + * This will be the case if any modules {@link DijkstraModule#shouldAbortBefore(TentativeDistance)} method returns + * {@code true}. + * + * @param tentativeDistance The tentative distance wrapper of the node that will be settled next + * + * @return {@code True} if the computation should be aborted, {@code false} if not + */ + @Override + protected boolean shouldAbortBefore(final TentativeDistance tentativeDistance) { + // Ignore the base, it never aborts computation + // Ask all modules and accumulate with logical or + for (final DijkstraModule module : modules) { + final boolean abort = module.shouldAbortBefore(tentativeDistance); + if (abort) { + return true; + } + } + + return false; + } + /** * Whether or not the algorithm should abort computation of the shortest path. The method is called right after the * given node has been settled.
    *
    - * This will be the case if any modules {@link DijkstraModule#shouldAbort(TentativeDistance)} method returns {@code - * true}. + * This will be the case if any modules {@link DijkstraModule#shouldAbortAfter(TentativeDistance)} method returns + * {@code true}. * * @param tentativeDistance The tentative distance wrapper of the node that was settled * * @return {@code True} if the computation should be aborted, {@code false} if not */ @Override - protected boolean shouldAbort(final TentativeDistance tentativeDistance) { + protected boolean shouldAbortAfter(final TentativeDistance tentativeDistance) { // Ignore the base, it never aborts computation // Ask all modules and accumulate with logical or for (final DijkstraModule module : modules) { - final boolean abort = module.shouldAbort(tentativeDistance); + final boolean abort = module.shouldAbortAfter(tentativeDistance); if (abort) { return true; } diff --git a/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/OnDemandPathTree.java b/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/OnDemandPathTree.java new file mode 100644 index 0000000..6a628ea --- /dev/null +++ b/de.zabuza.maglev/src/de/zabuza/maglev/internal/algorithms/shortestpath/OnDemandPathTree.java @@ -0,0 +1,86 @@ +package de.zabuza.maglev.internal.algorithms.shortestpath; + +import de.zabuza.maglev.external.algorithms.Path; +import de.zabuza.maglev.external.algorithms.PathTree; +import de.zabuza.maglev.external.algorithms.TentativeDistance; +import de.zabuza.maglev.external.graph.Edge; + +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author Daniel Tischner {@literal } + */ +public final class OnDemandPathTree> implements PathTree { + + private final Collection sources; + private final Map> nodeToDistance; + + public OnDemandPathTree(final Collection sources, final Map> nodeToDistance) { + this.sources = Collections.unmodifiableCollection(sources); + this.nodeToDistance = Collections.unmodifiableMap(nodeToDistance); + } + + @Override + public Collection getSources() { + return sources; + } + + @Override + public Stream getReachableNodes() { + return nodeToDistance.values() + .stream() + .map(TentativeDistance::getNode); + } + + @Override + public Optional> getPathTo(final N destination) { + final TentativeDistance destinationDistance = nodeToDistance.get(destination); + + // Destination is not reachable from the given sources + if (destinationDistance == null) { + return Optional.empty(); + } + + final E parentEdge = destinationDistance.getParentEdge(); + // Destination is already a source node + if (parentEdge == null) { + return Optional.of(new EmptyPath<>(destination)); + } + + // Build the path reversely by following the pointers from the destination + // to one of the sources + final EdgePath path = new EdgePath<>(EdgePath.ConstructionDirection.BACKWARD); + TentativeDistance currentDistanceContainer = destinationDistance; + E currentEdge = parentEdge; + while (currentEdge != null) { + // Add the edge + final double distance = currentDistanceContainer.getTentativeDistance(); + final N parent = currentEdge.getSource(); + final TentativeDistance parentDistanceContainer = nodeToDistance.get(parent); + final double parentDistance = parentDistanceContainer.getTentativeDistance(); + + path.addEdge(currentEdge, distance - parentDistance); + + // Prepare next round + currentEdge = parentDistanceContainer.getParentEdge(); + currentDistanceContainer = parentDistanceContainer; + } + return Optional.of(path); + } + + @Override + public Collection getLeaves() { + final Set reachableNodes = getReachableNodes().collect(Collectors.toSet()); + // Remove all nodes that appear as source of a parent-pointer + nodeToDistance.values() + .stream() + .map(TentativeDistance::getParentEdge) + .filter(Predicate.not(Objects::isNull)) + .map(E::getSource) + .forEach(reachableNodes::remove); + return reachableNodes; + } +}