You need more functions.
I found this code hard to understand as I didn't know what it was doing.
Without functions all I know is there are a lot of possibly independent calls in a bunch of for loops.
This makes understanding the code very hard.
The creation of all the following should be in their own function:
visited
,
all_nodes_reverse
and the mutation of node_index
.
- The display of the reversed graph.
- The creation of 'strongly connected component's.
After doing this to your code it became much easier to understand.
A word explanation of your algorithm is:
- Create graph.
- Find
visit_time
.
- Display nodes with their
visit_time
.
- Invert graph.
The graph a -> b
becomes b -> a
.
- Print inverted graph's edges.
- Find strongly connected components from inverted graph.
- Display strongly connected components.
And so we should aim to remove (4).
To do this is we can add a list that adds the inverse connections.
To then make the addition to both the connections
and the inverse connections,
we can add the function append
to GraphNode
.
This can simply be:
def append(self, node):
self.connections.append(node)
node.connections_inv.append(self)
After this I aimed to simplify DFS
, this is as it's a core part of the algorithm.
I mostly dislike the addition of visit_time_counter
to make it work.
Instead you can append to visited
where you assign to self.visit_time
.
This utilizes the ordered aspect of visited
to hold the visit_time
.
This however means you'll need to pass two lists to DFS
.
And so I'd add a public interface to the current, to be, private function.
To also make DFS
more efficient you can change visited
to a set.
This allows you to have \$O(1)\$ look up on the in
operator rather than \$O(n)\$.
And I'd also change DFS
to return nodes rather than node names.
This can result in:
def _DFS(self, to_add, visited, inverse=False):
if self in visited:
return
visited.add(self)
for node in self.connections_inv if inverse else self.connections:
if node not in visited:
node._DFS(to_add, visited)
to_add.append(self)
def DFS(self, visited, inverse=False):
to_add = []
self._DFS(to_add, set(visited), inverse=inverse)
visited.extend(to_add)
return to_add
Usage is pretty much the same as it was before, but we can get a list of new additions.
And we get nodes back rather than strings in visited
.
After this we can change strongly_connected
.
As we know that the input list is already sorted we can just flat out reverse it.
I'd also make the function a generator, just as it makes the code simpler.
I'd change the algorithm to remove from the nodes when you've visited them.
This allows you to remove the pre_visited
as those nodes won't be in the list any more.
I'd also change sc
to be created by a set comprehension,
as the way you're doing it now is quite long-winded.
And so I'd write:
def strongly_connected(nodes, inverse=True):
nodes_ = set(nodes)
visited = []
for node in reversed(nodes):
if node not in visited:
to_remove = node.DFS(visited, inverse=inverse)
yield {node for node in nodes_ if node in visited}
for item in to_remove:
nodes_.remove(item)
After this I'd make the functions:
all_DFS
which creates visited
which is just the for loop. And
display_connections
which is the for loop to display the edges of the graph.
This leaves us with just the code that inverts the graph.
As we can and have added a keyword to use the inversed connections,
we can now just remove the code that creates a new inverted graph.
I'd also make this code Python 3 compatible by using __future__.print_function
.
And I'd change the class to a new-style class by inheriting from object
,
so that you don't have any problems with the old-style class.
If you add more to it.
I'd also recommend that you use lower_case_with_underscores
rather than mixedCase
for your variables and functions.
And for you to follow other things highlighted in PEP8.
This is as it makes your code more consistent by following it, and makes your code easier to read.
As for your questions, you should focus more on writing better and cleaner code.
Speed isn't everything, but if you really need speed go use C, C++, C#, JAVA, etc.
My final code was:
from __future__ import print_function
class GraphNode(object):
def __init__(self, name):
self.name = name
self.connections = []
self.connections_inv = []
def __repr__(self):
return 'GraphNode({!r})'.format(self.name)
def __hash__(self):
return hash(self.name)
def append(self, node):
self.connections.append(node)
node.connections_inv.append(self)
def _DFS(self, to_add, visited, inverse=False):
if self in visited:
return
visited.add(self)
for node in self.connections_inv if inverse else self.connections:
if node not in visited:
node._DFS(to_add, visited)
to_add.append(self)
def DFS(self, visited, inverse=False):
to_add = []
self._DFS(to_add, set(visited), inverse=inverse)
visited.extend(to_add)
return to_add
def all_DFS(nodes, inverse=False):
visited = []
for node in nodes:
if node not in visited:
node.DFS(visited, inverse=inverse)
return visited
def strongly_connected(nodes, inverse=True):
nodes_ = set(nodes)
visited = []
for node in reversed(nodes):
if node not in visited:
to_remove = node.DFS(visited, inverse=inverse)
yield {node for node in nodes_ if node in visited}
for item in to_remove:
nodes_.remove(item)
def display_connections(nodes, inverse=False):
for i in nodes:
for j in i.connections_inv if inverse else i.connections:
print(i.name, '=>', j.name)
if __name__ == "__main__":
node_a = GraphNode('A')
node_b = GraphNode('B')
node_c = GraphNode('C')
node_d = GraphNode('D')
all_nodes = [node_a, node_b, node_c, node_d]
node_a.append(node_b)
node_a.append(node_c)
node_b.append(node_a)
node_c.append(node_d)
node_d.append(node_c)
visited = all_DFS(all_nodes)
for node in all_nodes:
print(node.name, visited.index(node) + 1)
display_connections(all_nodes, inverse=True)
for sc in strongly_connected(visited):
print('strongly connected component: ' + ' '.join(i.name for i in sc))
if __name__ == "__main__"
part are not formatting properly, tried but cannot fix, anyone could help to fix the formatting? Thanks. \$\endgroup\$<!-- -->
comment to end the list. \$\endgroup\$