-
Notifications
You must be signed in to change notification settings - Fork 2
/
Graph.py
179 lines (136 loc) · 5.57 KB
/
Graph.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# ecmaspeak-py/Graph.py:
# Create a dependency-graph, and find its strongly connected components.
#
# Copyright (C) 2018 J. Michael Dyck <[email protected]>
import sys
from collections import defaultdict
from shared import stderr
class Graph:
def __init__(self):
self.vertices = set()
self.arcs_from_ = defaultdict(set)
def add_vertex(self, vertex):
self.vertices.add(vertex)
def add_arc(self, src_vertex, dst_vertex):
self.add_vertex(src_vertex)
self.add_vertex(dst_vertex)
self.arcs_from_[src_vertex].add(dst_vertex)
def print_arcs(self, file=sys.stdout, indent=''):
for (src_vertex, dst_vertices) in sorted(self.arcs_from_.items()):
for dst_vertex in sorted(list(dst_vertices)):
print('%s%s -> %s' % (indent, src_vertex, dst_vertex), file=file)
def compute_dependency_levels(self):
self.find_strongly_connected_components()
stderr( ' %d SCCs' % len(cluster_) )
stderr( " sorting..." )
for cluster in cluster_:
cluster.members.sort()
# cluster.position = vertex_collater(cluster.members[0])
stderr( " dependencies between SCCs..." )
for cluster in cluster_:
cluster.contains_a_cycle = False
for vertex in cluster.members:
for p in self.arcs_from_[vertex]:
if self.cluster_for_[p] is cluster:
# a "sideways" dependency
cluster.contains_a_cycle = True
else:
if self.cluster_for_[p] not in cluster.direct_prereqs:
cluster.direct_prereqs.append(self.cluster_for_[p])
if len(cluster.members)>1:
assert cluster.contains_a_cycle
# If len(cluster.members) == 1, it still might contain a cycle
levels = establish_dependency_levels()
return levels
# --------
def find_strongly_connected_components(self):
# Find the strongly connected components of the dependency graph.
# See 'Path-based strong component algorithm' in wikipedia
self.preorder_number_of_ = {}
self.cluster_for_ = {}
class G: pass
g = G()
g.S = []
# all the vertices [seen so far] that have not yet been assigned to a SCC,
# in the order in which the depth-first search reaches the vertices
g.P = []
# vertices that have not yet been determined to belong to different SCCs from each other.
g.C = 0
def rec_search(v):
assert v in self.vertices
assert v not in self.preorder_number_of_
# 1. Set the preorder number of v to C, and increment C.
self.preorder_number_of_[v] = g.C
g.C += 1
# 2. Push v onto S and also onto P.
g.S.append(v)
g.P.append(v)
# 3. For each edge from v to a neighboring vertex w:
for w in self.arcs_from_[v]:
# If the preorder number of w has not yet been assigned,
# recursively search w;
if w not in self.preorder_number_of_:
rec_search(w)
# Otherwise, if w has not yet been assigned to
# a strongly connected component:
elif w in g.S:
# Repeatedly pop vertices from P until the top element of P has a
# preorder number less than or equal to the preorder number of w.
while self.preorder_number_of_[g.P[-1]] > self.preorder_number_of_[w]:
g.P.pop()
# 4. If v is the top element of P:
if v is g.P[-1]:
# Pop vertices from S until v has been popped,
# and assign the popped vertices to a new component.
scc = Cluster()
while True:
w = g.S.pop()
# scc.add(w)
assert w not in self.cluster_for_
self.cluster_for_[w] = scc
scc.members.append(w)
if w is v: break
# Pop v from P.
w = g.P.pop()
assert w is v
for v in self.vertices:
if v not in self.preorder_number_of_:
rec_search(v)
# -----------------
cluster_ = []
class Cluster:
def __init__(self):
self.number = len(cluster_)
self.members = []
self.direct_prereqs = []
cluster_.append(self)
# ------------------------------------------------------------------------------
def establish_dependency_levels():
stderr( " establish_dependency_levels ..." )
levels = []
def get_level_of_cluster(cluster):
if hasattr(cluster, 'level'):
return cluster.level
if len(cluster.direct_prereqs) == 0:
# only depends on predefineds
level = 0
else:
level = 1 + max(
get_level_of_cluster(prereq)
for prereq in cluster.direct_prereqs
)
cluster.level = level
if level < len(levels):
pass
elif level == len(levels):
levels.append([])
else:
assert 0
levels[level].append(cluster)
for cluster in cluster_:
get_level_of_cluster(cluster)
stderr( " %d levels" % len(levels) )
for (L, clusters_on_level_L) in enumerate(levels):
clusters_on_level_L.sort(key = lambda cluster: cluster.members[0])
return levels
# vim: sw=4 ts=4 expandtab