1179860b2SJed Brownfrom __future__ import generators 2179860b2SJed Brown 3179860b2SJed Brownclass DirectedGraph(object): 4179860b2SJed Brown '''This class is for directed graphs with vertices of arbitrary type''' 5179860b2SJed Brown def __init__(self, vertices = []): 6179860b2SJed Brown '''Create a graph''' 7179860b2SJed Brown self.vertices = [] 8179860b2SJed Brown self.inEdges = {} 9179860b2SJed Brown self.outEdges = {} 10179860b2SJed Brown map(self.addVertex, vertices) 11179860b2SJed Brown return 12179860b2SJed Brown 13179860b2SJed Brown def __len__(self): 14179860b2SJed Brown return len(self.vertices) 15179860b2SJed Brown 16179860b2SJed Brown def __str__(self): 17179860b2SJed Brown return 'DirectedGraph with '+str(len(self.vertices))+' vertices and '+str(reduce(lambda k,l: k+l, [len(edgeList) for edgeList in self.inEdges.values()], 0))+' edges' 18179860b2SJed Brown 19179860b2SJed Brown def addVertex(self, vertex): 20179860b2SJed Brown '''Add a vertex if it does not already exist in the vertex list 21179860b2SJed Brown - Should be able to use Set in Python 2.3''' 22179860b2SJed Brown if vertex is None: return 23179860b2SJed Brown if not vertex in self.vertices: 24179860b2SJed Brown self.vertices.append(vertex) 25179860b2SJed Brown self.clearEdges(vertex) 26179860b2SJed Brown return 27179860b2SJed Brown 28179860b2SJed Brown def addEdges(self, vertex, inputs = [], outputs = []): 29179860b2SJed Brown '''Define the in and out edges for a vertex by listing the other vertices defining the edges 30179860b2SJed Brown - If any vertex does not exist in the graph, it is created''' 31179860b2SJed Brown self.addVertex(vertex) 32179860b2SJed Brown for input in inputs: 33179860b2SJed Brown self.addVertex(input) 34179860b2SJed Brown if not vertex is None and not input is None: 35179860b2SJed Brown if not input in self.inEdges[vertex]: self.inEdges[vertex].append(input) 36179860b2SJed Brown if not vertex in self.outEdges[input]: self.outEdges[input].append(vertex) 37179860b2SJed Brown for output in outputs: 38179860b2SJed Brown self.addVertex(output) 39179860b2SJed Brown if not vertex is None and not output is None: 40179860b2SJed Brown if not vertex in self.inEdges[output]: self.inEdges[output].append(vertex) 41179860b2SJed Brown if not output in self.outEdges[vertex]: self.outEdges[vertex].append(output) 42179860b2SJed Brown return 43179860b2SJed Brown 44179860b2SJed Brown def getEdges(self, vertex): 45179860b2SJed Brown return (self.inEdges[vertex], self.outEdges[vertex]) 46179860b2SJed Brown 47179860b2SJed Brown def clearEdges(self, vertex, inOnly = 0, outOnly = 0): 48179860b2SJed Brown if inOnly and outOnly: 49179860b2SJed Brown raise RuntimeError('Inconsistent arguments') 50179860b2SJed Brown if not outOnly: 51179860b2SJed Brown self.inEdges[vertex] = [] 52179860b2SJed Brown if not inOnly: 53179860b2SJed Brown self.outEdges[vertex] = [] 54179860b2SJed Brown return 55179860b2SJed Brown 56179860b2SJed Brown def removeVertex(self, vertex): 57179860b2SJed Brown '''Remove a vertex if it already exists in the vertex list 58179860b2SJed Brown - Also removes all associated edges''' 59179860b2SJed Brown if vertex is None: return 60179860b2SJed Brown if vertex in self.vertices: 61179860b2SJed Brown self.vertices.remove(vertex) 62179860b2SJed Brown del self.inEdges[vertex] 63179860b2SJed Brown del self.outEdges[vertex] 64179860b2SJed Brown for v in self.vertices: 65179860b2SJed Brown if vertex in self.inEdges[v]: self.inEdges[v].remove(vertex) 66179860b2SJed Brown if vertex in self.outEdges[v]: self.outEdges[v].remove(vertex) 67179860b2SJed Brown return 68179860b2SJed Brown 69179860b2SJed Brown def replaceVertex(self, vertex, newVertex): 70179860b2SJed Brown '''Replace a vertex with newVertex if it already exists in the vertex list 71179860b2SJed Brown - Also transfers all associated edges''' 72179860b2SJed Brown if vertex is None or newVertex is None: return 73179860b2SJed Brown self.addEdges(newVertex, self.inEdges[vertex], self.outEdges[vertex]) 74179860b2SJed Brown self.removeVertex(vertex) 75179860b2SJed Brown return 76179860b2SJed Brown 77179860b2SJed Brown def addSubgraph(self, graph): 78179860b2SJed Brown '''Add the vertices and edges of another graph into this one''' 79179860b2SJed Brown map(self.addVertex, graph.vertices) 80*e76427acSJed Brown map(lambda v: self.addEdges(v, *graph.getEdges(v)), graph.vertices) 81179860b2SJed Brown return 82179860b2SJed Brown 83179860b2SJed Brown def removeSubgraph(self, graph): 84179860b2SJed Brown '''Remove the vertices and edges of a subgraph, and all the edges connected to it''' 85179860b2SJed Brown map(self.removeVertex, graph.vertices) 86179860b2SJed Brown return 87179860b2SJed Brown 88179860b2SJed Brown def printIndent(self, indent): 89179860b2SJed Brown import sys 90179860b2SJed Brown for i in range(indent): sys.stdout.write(' ') 91179860b2SJed Brown 92179860b2SJed Brown def display(self): 93179860b2SJed Brown print 'I am a DirectedGraph with '+str(len(self.vertices))+' vertices' 94179860b2SJed Brown for vertex in DirectedGraph.breadthFirstSearch(self): 95179860b2SJed Brown self.printIndent(vertex.__level) 96179860b2SJed Brown print '('+str(self.vertices.index(vertex))+') '+str(vertex)+' in: '+str(map(self.vertices.index, self.inEdges[vertex]))+' out: '+str(map(self.vertices.index, self.outEdges[vertex])) 97179860b2SJed Brown return 98179860b2SJed Brown 99179860b2SJed Brown def appendGraph(self, graph): 100179860b2SJed Brown '''Join every leaf of this graph to every root of the input graph, leaving the result in this graph''' 101179860b2SJed Brown leaves = DirectedGraph.getLeaves(self) 102179860b2SJed Brown self.addSubgraph(graph) 103179860b2SJed Brown map(lambda v: self.addEdges(v, outputs = DirectedGraph.getRoots(graph)), leaves) 104179860b2SJed Brown return self 105179860b2SJed Brown 106179860b2SJed Brown def prependGraph(self, graph): 107179860b2SJed Brown '''Join every leaf of the input graph to every root of this graph, leaving the result in this graph''' 108179860b2SJed Brown roots = DirectedGraph.getRoots(self) 109179860b2SJed Brown self.addSubgraph(graph) 110179860b2SJed Brown map(lambda v: self.addEdges(v, outputs = roots), DirectedGraph.getLeaves(graph)) 111179860b2SJed Brown return self 112179860b2SJed Brown 113179860b2SJed Brown def getRoots(graph): 114179860b2SJed Brown '''Return all the sources in the graph (nodes without entering edges)''' 115179860b2SJed Brown return filter(lambda v: not len(graph.getEdges(v)[0]), graph.vertices) 116179860b2SJed Brown getRoots = staticmethod(getRoots) 117179860b2SJed Brown 118179860b2SJed Brown def getLeaves(graph): 119179860b2SJed Brown '''Return all the sinks in the graph (nodes without exiting edges)''' 120179860b2SJed Brown return filter(lambda v: not len(graph.getEdges(v)[1]), graph.vertices) 121179860b2SJed Brown getLeaves = staticmethod(getLeaves) 122179860b2SJed Brown 123179860b2SJed Brown def depthFirstVisit(graph, vertex, seen = None, returnFinished = 0, outEdges = 1): 124179860b2SJed Brown '''This is a generator returning vertices in a depth-first traversal only for the subtree rooted at vertex 125179860b2SJed Brown - If returnFinished is True, return a vertex when it finishes 126179860b2SJed Brown - Otherwise, return a vertex when it is first seen 127179860b2SJed Brown - If outEdges is True, proceed along these, otherwise use inEdges''' 128179860b2SJed Brown if seen is None: seen = [] 129179860b2SJed Brown seen.append(vertex) 130179860b2SJed Brown if not returnFinished: 131179860b2SJed Brown yield vertex 132179860b2SJed Brown # Cute trick since outEdges is index 1, and inEdges is index 0 133179860b2SJed Brown for v in graph.getEdges(vertex)[outEdges]: 134179860b2SJed Brown if not v in seen: 135179860b2SJed Brown try: 136179860b2SJed Brown for v2 in DirectedGraph.depthFirstVisit(graph, v, seen, returnFinished, outEdges): 137179860b2SJed Brown yield v2 138179860b2SJed Brown except StopIteration: 139179860b2SJed Brown pass 140179860b2SJed Brown if returnFinished: 141179860b2SJed Brown yield vertex 142179860b2SJed Brown return 143179860b2SJed Brown depthFirstVisit = staticmethod(depthFirstVisit) 144179860b2SJed Brown 145179860b2SJed Brown def depthFirstSearch(graph, returnFinished = 0, outEdges = 1): 146179860b2SJed Brown '''This is a generator returning vertices in a depth-first traversal 147179860b2SJed Brown - If returnFinished is True, return a vertex when it finishes 148179860b2SJed Brown - Otherwise, return a vertex when it is first seen 149179860b2SJed Brown - If outEdges is True, proceed along these, otherwise use inEdges''' 150179860b2SJed Brown seen = [] 151179860b2SJed Brown for vertex in graph.vertices: 152179860b2SJed Brown if not vertex in seen: 153179860b2SJed Brown try: 154179860b2SJed Brown for v in DirectedGraph.depthFirstVisit(graph, vertex, seen, returnFinished, outEdges): 155179860b2SJed Brown yield v 156179860b2SJed Brown except StopIteration: 157179860b2SJed Brown pass 158179860b2SJed Brown return 159179860b2SJed Brown depthFirstSearch = staticmethod(depthFirstSearch) 160179860b2SJed Brown 161179860b2SJed Brown def breadthFirstSearch(graph, returnFinished = 0): 162179860b2SJed Brown '''This is a generator returning vertices in a breadth-first traversal 163179860b2SJed Brown - If returnFinished is True, return a vertex when it finishes 164179860b2SJed Brown - Otherwise, return a vertex when it is first seen''' 165179860b2SJed Brown queue = DirectedGraph.getRoots(graph)[0:1] 166179860b2SJed Brown if not len(queue): return 167179860b2SJed Brown seen = [queue[0]] 168179860b2SJed Brown if not returnFinished: 169179860b2SJed Brown queue[0].__level = 0 170179860b2SJed Brown yield queue[0] 171179860b2SJed Brown while len(queue): 172179860b2SJed Brown vertex = queue[-1] 173179860b2SJed Brown for v in graph.getEdges(vertex)[1]: 174179860b2SJed Brown if not v in seen: 175179860b2SJed Brown seen.append(v) 176179860b2SJed Brown v.__level = vertex.__level + 1 177179860b2SJed Brown queue.insert(0, v) 178179860b2SJed Brown if not returnFinished: 179179860b2SJed Brown yield v 180179860b2SJed Brown vertex = queue.pop() 181179860b2SJed Brown if returnFinished: 182179860b2SJed Brown yield vertex 183179860b2SJed Brown return 184179860b2SJed Brown 185179860b2SJed Brown def topologicalSort(graph, start = None, outEdges = 1): 186179860b2SJed Brown '''Reorder the vertices using topological sort''' 187179860b2SJed Brown if start is None: 188179860b2SJed Brown vertices = [vertex for vertex in DirectedGraph.depthFirstSearch(graph, returnFinished = 1, outEdges = outEdges)] 189179860b2SJed Brown else: 190179860b2SJed Brown vertices = [vertex for vertex in DirectedGraph.depthFirstVisit(graph, start, returnFinished = 1, outEdges = outEdges)] 191179860b2SJed Brown vertices.reverse() 192179860b2SJed Brown for vertex in vertices: 193179860b2SJed Brown yield vertex 194179860b2SJed Brown return 195179860b2SJed Brown topologicalSort = staticmethod(topologicalSort) 196