31
34
node identifiers can be any hashable object, and are typically strings.
33
parents = {} # node -> list of parents
34
children = {} # node -> list of children
35
for node, node_parents in graph:
36
assert node not in parents, \
37
('node %r repeated in graph' % node)
38
parents[node] = set(node_parents)
39
if node not in children:
40
children[node] = set()
41
for parent in node_parents:
42
if parent in children:
43
children[parent].add(node)
45
children[parent] = set([node])
48
# find nodes with no parents, and take them now
49
no_parents = [n for n in parents if len(parents[n]) == 0]
52
raise GraphCycleError(parents)
55
for child in children[n]:
56
assert n in parents[child]
57
parents[child].remove(n)
36
return TopoSorter(graph).sorted()
39
class TopoSorter(object):
41
def __init__(self, graph):
42
"""Topological sorting of a graph.
44
:param graph: sequence of pairs of node_name->parent_names_list.
45
i.e. [('C', ['B']), ('B', ['A']), ('A', [])]
46
For this input the output from the sort or
47
iter_topo_order routines will be:
50
node identifiers can be any hashable object, and are typically strings.
52
The graph is sorted lazily: until you iterate or sort the input is
53
not processed other than to create an internal representation.
55
iteration or sortign may raise GraphCycleError if a cycle is present
58
# a dict of the graph.
59
self._graph = dict(graph)
61
# self._original_graph = dict(graph)
63
# this is a stack storing the depth first search into the graph.
64
self._node_name_stack = []
65
# at each level of 'recursion' we have to check each parent. This
66
# stack stores the parents we have not yet checked for the node at the
67
# matching depth in _node_name_stack
68
self._pending_parents_stack = []
69
# this is a set of the completed nodes for fast checking whether a
70
# parent in a node we are processing on the stack has already been
71
# emitted and thus can be skipped.
72
self._completed_node_names = set()
75
"""Sort the graph and return as a list.
77
After calling this the sorter is empty and you must create a new one.
79
return list(self.iter_topo_order())
81
### Useful if fiddling with this code.
83
### sorted_names = list(self.iter_topo_order())
84
### for index in range(len(sorted_names)):
85
### rev = sorted_names[index]
86
### for left_index in range(index):
87
### if rev in self.original_graph[sorted_names[left_index]]:
88
### print "revision in parent list of earlier revision"
89
### import pdb;pdb.set_trace()
91
def iter_topo_order(self):
92
"""Yield the nodes of a graph in a topological order.
94
:param graph: sequence of pairs of node_name->parent_names_list.
95
i.e. [('C', ['B']), ('B', ['A']), ('A', [])]
96
For this input the following would be yielded:
99
node identifiers can be any hashable object, and are typically strings.
101
This may raise GraphCycleError if a cycle is present in the graph.
103
After finishing iteration the sorter is empty and you cannot continue
107
# now pick a random node in the source graph, and transfer it to the
108
# top of the depth first search stack.
109
node_name, parents = self._graph.popitem()
110
self._push_node(node_name, parents)
111
while self._node_name_stack:
112
# loop until this call completes.
113
parents_to_visit = self._pending_parents_stack[-1]
114
# if all parents are done, the revision is done
115
if not parents_to_visit:
116
# append the revision to the topo sorted list
117
# all the nodes parents have been added to the output, now
118
# we can add it to the output.
119
yield self._pop_node()
121
while self._pending_parents_stack[-1]:
122
# recurse depth first into a single parent
123
next_node_name = self._pending_parents_stack[-1].pop()
124
if next_node_name in self._completed_node_names:
125
# this parent was completed by a child on the
126
# call stack. skip it.
128
# otherwise transfer it from the source graph into the
129
# top of the current depth first search stack.
131
parents = self._graph.pop(next_node_name)
133
# if the next node is not in the source graph it has
134
# already been popped from it and placed into the
135
# current search stack (but not completed or we would
136
# have hit the continue 4 lines up.
137
# this indicates a cycle.
138
raise errors.GraphCycleError(self._node_name_stack)
139
self._push_node(next_node_name, parents)
140
# and do not continue processing parents until this 'call'
144
def _push_node(self, node_name, parents):
145
"""Add node_name to the pending node stack.
147
Names in this stack will get emitted into the output as they are popped
151
self._node_name_stack.append(node_name)
152
self._pending_parents_stack.append(list(parents))
155
"""Pop the top node off the stack
157
The node is appended to the sorted output.
159
# we are returning from the flattened call frame:
160
# pop off the local variables
161
node_name = self._node_name_stack.pop()
162
self._pending_parents_stack.pop()
164
self._completed_node_names.add(node_name)