-
Notifications
You must be signed in to change notification settings - Fork 46
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
Add DigraphShortestPath method #148
Conversation
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.
Thanks @MTWhyte this looks really good for a first PR!! I've made a number of suggestions, please revise the PR and we can then move forward with it.
doc/oper.xml
Outdated
<Returns>A pair of lists, or <K>fail</K>.</Returns> | ||
<Description> | ||
If there exists a non-trivial directed path (or a non-trivial cycle, in the | ||
case that <A>u</A> <C>=</C> <A>v</A>) from vertex <A>u</A> to vertex |
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.
This is a minor quibble, but I think it'd be better if there were no parentheses in this sentence. Better possibly as two sentences:
Returns the shortest directed path in the digraph digraph from the vertex u to the vertex v, if such a path exists. If u = v, then the shortest cycle of length greater than 1 is returned, again, if it exists.
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.
In reply to @james-d-mitchell: this should be either shortest cycle of length greater than 0
, or shortest non-trivial cycle
, as discussed below.
gap/oper.gi
Outdated
distance := []; | ||
|
||
if u = v and v in nbs[v] then # Considers the trivial path from v to v | ||
return [[v, v], [Position(nbs[v], v)]]; |
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.
Hmm, does this agree with the documentation? Isn't this condition true
if u = v
and there is a loop at u
? Or, is it that the loop has to be of length > 0, not > 1 as I somehow assumed before??
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.
In reply to @james-d-mitchell: the definition of trivial in the package documentation:
The length of a directed walk (v1,e1,v2,e2,...,en−1,vn) is equal to n−1, the number of edges it contains. A directed walk of zero length, i.e. a sequence (v) for some vertex v, is called trivial. A trivial directed walk is considered to be both a circuit and a cycle, as is the empty directed walk ().
By the original documentation in this PR:
(or a non-trivial cycle, in the case that <A>u</A> <C>=</C> <A>v</A>)
a loop therefore qualifies as the shortest path, since a loop is a non-trivial cycle.
However, I therefore suggest adjusting the misleading comment in the line above.
gap/oper.gi
Outdated
# Setting up objects useful in the function. | ||
for i in verts do | ||
Add(distance, -1); | ||
od; |
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.
You could replace this entire for-loop with: distance := ListWithIdenticalEntries(Length(verts), -1);
gap/oper.gi
Outdated
for i in verts do | ||
Add(next, false); | ||
Add(falselist, false); | ||
od; |
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.
Similar, to the previous comment, this could be next := BlistList([1 .. Length(verts)], []);
and falselist := BlistList([1 .. Length(verts)], []);
gap/oper.gi
Outdated
Add(falselist, false); | ||
od; | ||
|
||
n := 0; # Counts the loops |
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.
I suggest either removing or improving the comment here. "The loops" could be misconstrued as meaning loops in the digraph, which is not what you mean I think.
gap/oper.gi
Outdated
# Finds the path | ||
for i in [1 .. n] do | ||
Add(path[1], b); | ||
Add(path[2], Position(nbs[parent[b]], b)); |
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.
The Position(nbs[parent[b]], b)
lookup is potentially time consuming (possible linear search in nbs[parent[b]]
). You could avoid this altogether by having another list edge
, replacing the for-loop for b in nbs[a] do
by for j in [1 .. Length(nbs[a])] do b := nbs[a][i];
and after parent[b] := a
writing edge[b] := i
, and the replacing Position(nbs[parent[b]], b)
by Add(path[2], edge[b])
. I hope that this makes sense.
gap/oper.gi
Outdated
InstallMethod(DigraphShortestPath, "for a digraph and two pos ints", | ||
[IsDigraph, IsPosInt, IsPosInt], | ||
function(digraph, u, v) | ||
local current, next, parent, distance, falselist, verts, nbs, n, a, b, i, path; |
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.
You should add some basic checks at the start of this function too. For example, the start of DigraphPath
is what's written below. I think you should probably have the same thing at the start of this method.
verts := DigraphVertices(digraph);
if not (u in verts and v in verts) then
ErrorNoReturn("Digraphs: DigraphPath: usage,\n",
"the second and third arguments <u> and <v> must be\n",
"vertices of the first argument <digraph>,");
fi;
if IsDigraphEdge(digraph, [u, v]) then
return [[u, v], [Position(OutNeighboursOfVertex(digraph, u), v)]];
elif HasIsTransitiveDigraph(digraph) and IsTransitiveDigraph(digraph) then
# If it's a known transitive digraph, just check whether the edge exists
return fail;
# Glean information from WCC if we have it
elif HasDigraphConnectedComponents(digraph)
and DigraphConnectedComponents(digraph).id[u] <>
DigraphConnectedComponents(digraph).id[v] then
return fail;
fi;
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.
I've added these tests to the start of the function. I believe the
if IsDigraphEdge(digraph, [u, v]) then
return [[u, v], [Position(OutNeighboursOfVertex(digraph, u), v)]];
does much the same thing as the existing
if u = v and v in nbs[v] then # Considers the trivial path from v to v
return [[v, v], [Position(nbs[v], v)]];
which was the subject of a previous comment. I shall just remove the latter for simplicity.
gap/oper.gi
Outdated
Add(path[1], u); # Adds the starting vertex to the list of vertices. | ||
return [Reversed(path[1]), Reversed(path[2])]; | ||
fi; | ||
|
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.
Please remove this blank line.
gap/oper.gi
Outdated
|
||
od; | ||
od; | ||
|
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.
Please remove the two blank lines here too.
gap/oper.gi
Outdated
od; | ||
|
||
current := ListBlist(verts, next); | ||
next := IntersectionBlist(next, falselist); |
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.
I think (not 100%) that you'd be better off doing IntersectBlist(next, falselist);
. The reason being that IntersectionBlist
produces a new list, whereas IntersectBlist
changes next
in-place.
gap/oper.gi
Outdated
|
||
n := 0; # Counts the loops | ||
while current <> [] do | ||
n := n + 1; |
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.
Weird spacing before the :=
here.
gap/oper.gi
Outdated
next := IntersectionBlist(next, falselist); | ||
|
||
od; | ||
return fail; |
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.
Bad indentation.
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.
Thank you for this! I've added some comments in relation to James's ones.
doc/oper.xml
Outdated
<Returns>A pair of lists, or <K>fail</K>.</Returns> | ||
<Description> | ||
If there exists a non-trivial directed path (or a non-trivial cycle, in the | ||
case that <A>u</A> <C>=</C> <A>v</A>) from vertex <A>u</A> to vertex |
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.
In reply to @james-d-mitchell: this should be either shortest cycle of length greater than 0
, or shortest non-trivial cycle
, as discussed below.
gap/oper.gi
Outdated
distance := []; | ||
|
||
if u = v and v in nbs[v] then # Considers the trivial path from v to v | ||
return [[v, v], [Position(nbs[v], v)]]; |
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.
In reply to @james-d-mitchell: the definition of trivial in the package documentation:
The length of a directed walk (v1,e1,v2,e2,...,en−1,vn) is equal to n−1, the number of edges it contains. A directed walk of zero length, i.e. a sequence (v) for some vertex v, is called trivial. A trivial directed walk is considered to be both a circuit and a cycle, as is the empty directed walk ().
By the original documentation in this PR:
(or a non-trivial cycle, in the case that <A>u</A> <C>=</C> <A>v</A>)
a loop therefore qualifies as the shortest path, since a loop is a non-trivial cycle.
However, I therefore suggest adjusting the misleading comment in the line above.
gap/oper.gi
Outdated
nbs := OutNeighbors(digraph); | ||
distance := []; | ||
|
||
if u = v and v in nbs[v] then # Considers the trivial path from v to v |
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.
A loop is not a trivial path. Therefore please change this comment, to something like # is u = v, and there is a loop at v?
I'm happy for this to be merged, but it needs to be rebased on the current |
06ed6ef
to
e712e7e
Compare
This pull request implements a
DigraphShortestPath
method, which takes a digraphs and two positive integers, and returns a list in the same format asDigraphPath
.The shortest path is found using a breadth-first search.