-
Notifications
You must be signed in to change notification settings - Fork 2.5k
[CALCITE-4049] Improve the implementation of the shortest-path algorithm #2027
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -64,14 +64,21 @@ class DirectedGraphTest { | |
| g.addEdge("B", "D"); | ||
| assertEquals("[A, B, D]", shortestPath(g, "A", "D").toString()); | ||
| assertNull(shortestPath(g, "A", "E"), "There is no path from A to E"); | ||
| assertEquals("[]", shortestPath(g, "D", "D").toString()); | ||
| assertEquals("[D]", shortestPath(g, "D", "D").toString()); | ||
| assertNull(shortestPath(g, "X", "A"), "Node X is not in the graph"); | ||
| assertEquals("[[A, B, C, D], [A, B, D]]", paths(g, "A", "D").toString()); | ||
| assertEquals("[[A, B, D], [A, B, C, D]]", paths(g, "A", "D").toString()); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. where are the tests for getShortestDistance()?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have DirectedGraphTest#testDistance |
||
| } | ||
|
|
||
| private <V> List<V> shortestPath(DirectedGraph<V, DefaultEdge> g, | ||
| V source, V target) { | ||
| return Graphs.makeImmutable(g).getShortestPath(source, target); | ||
| List<List<V>> paths = Graphs.makeImmutable(g).getPaths(source, target); | ||
| return paths.isEmpty() ? null : paths.get(0); | ||
| } | ||
|
|
||
| private <V> List<V> shortestPath(Graphs.FrozenGraph<V, DefaultEdge> g, | ||
| V source, V target) { | ||
| List<List<V>> paths = g.getPaths(source, target); | ||
| return paths.isEmpty() ? null : paths.get(0); | ||
| } | ||
|
|
||
| private <V> List<List<V>> paths(DirectedGraph<V, DefaultEdge> g, | ||
|
|
@@ -217,15 +224,16 @@ private DefaultDirectedGraph<String, DefaultEdge> createDag1() { | |
| final DefaultDirectedGraph<String, DefaultEdge> graph = createDag1(); | ||
| final Graphs.FrozenGraph<String, DefaultEdge> frozenGraph = | ||
| Graphs.makeImmutable(graph); | ||
| assertEquals("[A, B]", frozenGraph.getShortestPath("A", "B").toString()); | ||
| assertEquals("[A, B]", shortestPath(frozenGraph, "A", "B").toString()); | ||
| assertEquals("[[A, B]]", frozenGraph.getPaths("A", "B").toString()); | ||
| assertEquals("[A, D, E]", frozenGraph.getShortestPath("A", "E").toString()); | ||
| assertEquals("[[A, B, C, E], [A, D, E]]", | ||
| assertEquals("[A, D, E]", shortestPath(frozenGraph, "A", "E").toString()); | ||
| assertEquals("[[A, D, E], [A, B, C, E]]", | ||
| frozenGraph.getPaths("A", "E").toString()); | ||
| assertNull(frozenGraph.getShortestPath("B", "A")); | ||
| assertNull(frozenGraph.getShortestPath("D", "C")); | ||
| assertNull(shortestPath(frozenGraph, "B", "A")); | ||
|
|
||
| assertNull(shortestPath(frozenGraph, "D", "C")); | ||
| assertEquals("[[D, E]]", frozenGraph.getPaths("D", "E").toString()); | ||
| assertEquals("[D, E]", frozenGraph.getShortestPath("D", "E").toString()); | ||
| assertEquals("[D, E]", shortestPath(frozenGraph, "D", "E").toString()); | ||
| } | ||
|
|
||
| @Test void testDistances() { | ||
|
|
@@ -355,9 +363,9 @@ private Set<String> getB(DefaultDirectedGraph<String, DefaultEdge> graph, | |
| g.addEdge("B", "D", 1); | ||
| assertEquals("[A, B, D]", shortestPath(g, "A", "D").toString()); | ||
| assertNull(shortestPath(g, "A", "E"), "There is no path from A to E"); | ||
| assertEquals("[]", shortestPath(g, "D", "D").toString()); | ||
| assertEquals("[D]", shortestPath(g, "D", "D").toString()); | ||
| assertNull(shortestPath(g, "X", "A"), "Node X is not in the graph"); | ||
| assertEquals("[[A, B, C, D], [A, B, D]]", paths(g, "A", "D").toString()); | ||
| assertEquals("[[A, B, D], [A, B, C, D]]", paths(g, "A", "D").toString()); | ||
| assertThat(g.addVertex("B"), is(false)); | ||
|
|
||
| assertThat(Iterables.size(g.getEdges("A", "B")), is(1)); | ||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you also fix the getPaths() to return shortest paths first? This is to make sure we choose the shortest path during convert.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, it would be an overkill to call getPaths() only to get the shortest path.
To support this scenario, I have restored the getShortestPath API, and implement it with BFS. It should be more efficient than the Dijkstra and Floyd algorihtms.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMHO this is not the ideal approach, now we would be re-computing everything time something (shortest path) that was previously computed only once and returned in O(1).
If we take a few steps back, I think it is clear that we require both pieces of information (shortesPath & distance), to be provided ASAP from
FrozenGraph. Then why not just pre-computing both inGraphs#makeImmutableand storing both of them inFrozenGraph, so that we guarantee thatgetShortestPath&getShortestDistanceare executed in O(1)?I think we could keep track of shortestPath and distance in
Graphs#makeImmutable, somehow combining the old approach with the newly proposed approach, either keeping two maps:Or a single map with the combination of both as value:
Then we would pass this information as a parameter for FrozeGraph constructor, and we would have shortesPath & distance pre-computed from the beginning.
I'm not sure if what I say makes sense or it is an overkill. What do you guys think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@liyafan82 I am not saying getPaths() to return shortest paths. What I mean is you should put shortest path at the front of list, so during convert() we always choose the shortest converted path if possible. This only requires a sort after generating all possible path.
@rubenada I don't think we need to cache shortest path. This is only one time used and there's no benefit to cache it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@xndai I have revised the getPaths() method accordingly. Thank you.
@rubenada Thanks a lot for your suggestion. I tend to agree with @xndai. If getting shortest paths is not a frequently used operation, there is no need to store all pairs of shortest paths. Here, we preserve the getShortestPath API, mainly for the sake of backward compatibility.