-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgraph.py
340 lines (278 loc) · 9.65 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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
import graph_utils
class GraphError(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return self.value
class Node(object):
""" Represents a node that has a label. This label can be any object """
def __init__(self, label):
self.label = label
def __str__(self):
return "".join(["<node ", str(self.label), ">"])
class Edge(object):
""" Represents a weighted edge that connects two Node objects """
def __init__(self, u, v, weight=0):
self.u = u
self.v = v
self.weight = weight
def __str__(self):
return "".join(["<edge ", str(self.u), ", ", str(self.v),
str(self.weight), ">"])
class Graph(object):
""" Represent undirected graphs built of nodes and edges """
def __init__(self):
self.nodes = {}
self.edges = {}
def __str__(self):
slist = [str(i) for i in self.get_edges()]
slist.append(" >")
return '<graph ' + "".join(slist)
def __iter__(self):
""" Iterate over the nodes: 'for i in Graph' """
return self.nodes.iterkeys()
def __len__(self):
""" The number of nodes """
return len(self.nodes)
def __contains__(self, node):
""" Return whether the graph contains the node or not """
try:
return node in self.nodes
except TypeError:
return False
def __getitem__(self, node):
""" Return the node's neighbors """
try:
return self.nodes[node]
except KeyError:
return []
def copy (self):
""" Return a shallow copy of the graph """
g = Graph()
map(lambda e: g.add_edge(e), self.get_nodes())
return g
def get_nodes(self):
""" Give the graph nodes list """
return self.nodes.keys()
def neighbors(self, node):
""" Equal to Graph[node], here just for convenience """
return self[node]
def get_edges(self):
""" Give the graph edges. Because the graph is undirected, (u, v) is
the same as (v, u) and is returned once """
return {}.fromkeys(self.edges.values()).keys()
def get_node(self, label):
""" Return the node given its label """
try:
return [i for i in self.nodes if i.label == label][0]
except IndexError:
return None
def get_edge(self, u, v):
""" Return the edge given the nodes it connects """
try:
return self.edges[(u, v)]
except KeyError:
return None
def has_edge(self, u, v):
""" Return whether an edge between u and v exists or not """
return self.edges.has_key((u, v)) and self.edges.has_key((v, u))
def add_node(self, node):
""" Add the node """
if node not in self.nodes:
self.nodes[node] = []
return True
return False
def add_edge(self, edge):
""" Add the edges to the graph. If the edge is connected to some node
that doesnt belong to the graph, the node is added also """
try:
if edge.u in self.nodes[edge.v] and edge.v in self.nodes[edge.u] \
and edge == self.edges[(edge.u, edge.v)] and \
edge == self.edges[(edge.v, edge.u)]:
return False
elif edge.u in self.nodes[edge.v] and edge.v not in \
self.nodes[edge.u]:
return False
elif edge.u not in self.nodes[edge.v] and edge.v in \
self.nodes[edge.u]:
return False
except KeyError:
pass
self.add_node(edge.u)
self.add_node(edge.v)
self.nodes[edge.u].append(edge.v)
self.nodes[edge.v].append(edge.u)
self.edges[(edge.u, edge.v)] = edge
self.edges[(edge.v, edge.u)] = edge
return True
def del_node(self, node):
# Delete all edges adjacent to this node
try:
neighbors = self[node]
except KeyError:
raise GraphError('Node %s not in graph' %str(node))
map(lambda v: self.del_edge(self.get_edge(v, node)), neighbors)
try:
self.nodes.pop(node)
except KeyError:
pass
def del_edge(self, edge):
""" Remove the given edge. Notice this method doesnt handle graph
disconection and all nodes with no incident edge will be removed """
try:
if edge.v not in self[edge.u] or edge.u not in self[edge.v]:
raise GraphError('Bad edge %s on the graph' %str(edge))
self.edges.pop((edge.u, edge.v))
self.edges.pop((edge.v, edge.u))
self[edge.u].remove(edge.v)
self[edge.v].remove(edge.u)
# Delete isolated nodes
if self.order(edge.u) == 0:
self.nodes.pop(edge.u)
if self.order(edge.v) == 0:
self.nodes.pop(edge.v)
except KeyError:
raise GraphError('Edge not found')
except:
raise
def order(self, node):
""" Give the node order """
return len(self[node])
def get_mst_kruskal(self):
""" Give the edges of the minimum spanning tree using Kruskal algorithm
"""
edges = self.get_edges()
edges.sort(key=lambda e: e.weight)
forest = graph_utils.UnionFind()
for e in edges:
if forest[e.u] != forest[e.v]:
yield e
forest.union(e.u, e.v)
del forest
@staticmethod
def read(filename):
""" Read a graph object from a file """
import pickle
f = open(filename, 'r')
obj = pickle.load(f)
if type(obj) is Graph:
return obj
else:
raise GraphError('wtf r u trying to read?')
f.close()
return obj
def write(self, filename):
""" Write a graph to a file """
import pickle
f = open(filename, 'w')
pickle.dump(self, f)
f.close()
def draw(self, imagefile):
import gvgen
import pygraphviz as pgv
G = gvgen.GvGen()
n = {}
for node in self:
n[node] = G.newItem('%s' %node.label)
for e in self.edges:
edge = self.edges[e]
ge = G.newLink(n[edge.u], n[edge.v])
G.propertyAppend(ge, "arrowhead", "none")
import StringIO
fd = StringIO.StringIO()
G.dot(fd)
dottext = fd.getvalue()
G = pgv.AGraph()
G.from_string(dottext)
G.layout()
G.draw(imagefile)
class Tree(Graph):
def __init__(self):
Graph.__init__(self)
def del_node(self, node):
""" Just leaves can be deleted """
if self.is_leaf(node) or self.order(node) == 0:
Graph.del_node(self, node)
else:
raise GraphError('Cant delete %s. Just allowed deletion of leaves.'
%str(node))
def get_leaves(self):
""" Return a iterator with all tree's leaves """
for node in self:
if self.is_leaf(node):
yield node
def is_leaf(self, node):
return self.order(node) == 1
class SteinerTree(Tree):
""" The SteinerTree class is a tree that must have some nodes (the
terminal nodes) and its total cost is supposed to be minimum.
All non terminal nodes belonging to the tree are called steiner nodes """
def __init__(self):
Tree.__init__(self)
self.terminals = {}
self.cost = 0
def copy(self):
st = SteinerTree()
map(lambda e: st.add_edge(e), self.get_edges())
map(lambda t: st.add_terminal(t), self.terminals)
st.cost = self.cost
return st
def get_terminals(self):
return self.terminals.keys()
def add_terminal(self, node):
if node not in self:
self.add_node(node)
self.terminals[node] = None
def add_edge(self, e):
if Tree.add_edge(self, e):
self.cost += e.weight
return True
return False
def del_terminal(self, node):
try:
self.terminals.pop(node)
except KeyError:
raise GraphError('%s is not a terminal node' %str(node))
def del_node(self, node):
if node not in self.terminals:
Tree.del_node(self, node)
else:
raise GraphError('Deletion of terminal %s doesnt allowed'
%str(node))
def del_edge(self, edge):
Tree.del_edge(self, edge)
self.cost -= edge.weight
def del_useless_edges(self):
nodes = self.get_nodes()
nodes.sort(key=lambda o: len(self[o]))
for node in nodes:
if self.is_leaf(node) and node not in self.terminals:
self.del_node(node)
def is_terminal(self, node):
return node in self.terminals
def get_cost(self):
return self.cost
def draw(self, imagefile):
import gvgen
import pygraphviz as pgv
G = gvgen.GvGen()
G.styleAppend("terminal", "color", "red")
G.styleAppend("terminal", "style", "filled")
G.styleAppend("terminal", "fontcolor", "white")
n = {}
for node in self:
n[node] = G.newItem('%s' %str(node.label))
if node in self.terminals:
G.styleApply("terminal", n[node])
for e in self.edges:
edge = self.edges[e]
ge = G.newLink(n[edge.u], n[edge.v])
G.propertyAppend(ge, "arrowhead", "none")
import StringIO
fd = StringIO.StringIO()
G.dot(fd)
dottext = fd.getvalue()
G = pgv.AGraph()
G.from_string(dottext)
G.layout()
G.draw(imagefile)