1*179860b2SJed Brownfrom __future__ import generators 2*179860b2SJed Brown 3*179860b2SJed Brownclass DirectedGraph(object): 4*179860b2SJed Brown '''This class is for directed graphs with vertices of arbitrary type''' 5*179860b2SJed Brown def __init__(self, vertices = []): 6*179860b2SJed Brown '''Create a graph''' 7*179860b2SJed Brown self.vertices = [] 8*179860b2SJed Brown self.inEdges = {} 9*179860b2SJed Brown self.outEdges = {} 10*179860b2SJed Brown map(self.addVertex, vertices) 11*179860b2SJed Brown return 12*179860b2SJed Brown 13*179860b2SJed Brown def __len__(self): 14*179860b2SJed Brown return len(self.vertices) 15*179860b2SJed Brown 16*179860b2SJed Brown def __str__(self): 17*179860b2SJed 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' 18*179860b2SJed Brown 19*179860b2SJed Brown def addVertex(self, vertex): 20*179860b2SJed Brown '''Add a vertex if it does not already exist in the vertex list 21*179860b2SJed Brown - Should be able to use Set in Python 2.3''' 22*179860b2SJed Brown if vertex is None: return 23*179860b2SJed Brown if not vertex in self.vertices: 24*179860b2SJed Brown self.vertices.append(vertex) 25*179860b2SJed Brown self.clearEdges(vertex) 26*179860b2SJed Brown return 27*179860b2SJed Brown 28*179860b2SJed Brown def addEdges(self, vertex, inputs = [], outputs = []): 29*179860b2SJed Brown '''Define the in and out edges for a vertex by listing the other vertices defining the edges 30*179860b2SJed Brown - If any vertex does not exist in the graph, it is created''' 31*179860b2SJed Brown self.addVertex(vertex) 32*179860b2SJed Brown for input in inputs: 33*179860b2SJed Brown self.addVertex(input) 34*179860b2SJed Brown if not vertex is None and not input is None: 35*179860b2SJed Brown if not input in self.inEdges[vertex]: self.inEdges[vertex].append(input) 36*179860b2SJed Brown if not vertex in self.outEdges[input]: self.outEdges[input].append(vertex) 37*179860b2SJed Brown for output in outputs: 38*179860b2SJed Brown self.addVertex(output) 39*179860b2SJed Brown if not vertex is None and not output is None: 40*179860b2SJed Brown if not vertex in self.inEdges[output]: self.inEdges[output].append(vertex) 41*179860b2SJed Brown if not output in self.outEdges[vertex]: self.outEdges[vertex].append(output) 42*179860b2SJed Brown return 43*179860b2SJed Brown 44*179860b2SJed Brown def getEdges(self, vertex): 45*179860b2SJed Brown return (self.inEdges[vertex], self.outEdges[vertex]) 46*179860b2SJed Brown 47*179860b2SJed Brown def clearEdges(self, vertex, inOnly = 0, outOnly = 0): 48*179860b2SJed Brown if inOnly and outOnly: 49*179860b2SJed Brown raise RuntimeError('Inconsistent arguments') 50*179860b2SJed Brown if not outOnly: 51*179860b2SJed Brown self.inEdges[vertex] = [] 52*179860b2SJed Brown if not inOnly: 53*179860b2SJed Brown self.outEdges[vertex] = [] 54*179860b2SJed Brown return 55*179860b2SJed Brown 56*179860b2SJed Brown def removeVertex(self, vertex): 57*179860b2SJed Brown '''Remove a vertex if it already exists in the vertex list 58*179860b2SJed Brown - Also removes all associated edges''' 59*179860b2SJed Brown if vertex is None: return 60*179860b2SJed Brown if vertex in self.vertices: 61*179860b2SJed Brown self.vertices.remove(vertex) 62*179860b2SJed Brown del self.inEdges[vertex] 63*179860b2SJed Brown del self.outEdges[vertex] 64*179860b2SJed Brown for v in self.vertices: 65*179860b2SJed Brown if vertex in self.inEdges[v]: self.inEdges[v].remove(vertex) 66*179860b2SJed Brown if vertex in self.outEdges[v]: self.outEdges[v].remove(vertex) 67*179860b2SJed Brown return 68*179860b2SJed Brown 69*179860b2SJed Brown def replaceVertex(self, vertex, newVertex): 70*179860b2SJed Brown '''Replace a vertex with newVertex if it already exists in the vertex list 71*179860b2SJed Brown - Also transfers all associated edges''' 72*179860b2SJed Brown if vertex is None or newVertex is None: return 73*179860b2SJed Brown self.addEdges(newVertex, self.inEdges[vertex], self.outEdges[vertex]) 74*179860b2SJed Brown self.removeVertex(vertex) 75*179860b2SJed Brown return 76*179860b2SJed Brown 77*179860b2SJed Brown def addSubgraph(self, graph): 78*179860b2SJed Brown '''Add the vertices and edges of another graph into this one''' 79*179860b2SJed Brown map(self.addVertex, graph.vertices) 80*179860b2SJed Brown map(lambda v: apply(self.addEdges, (v,)+graph.getEdges(v)), graph.vertices) 81*179860b2SJed Brown return 82*179860b2SJed Brown 83*179860b2SJed Brown def removeSubgraph(self, graph): 84*179860b2SJed Brown '''Remove the vertices and edges of a subgraph, and all the edges connected to it''' 85*179860b2SJed Brown map(self.removeVertex, graph.vertices) 86*179860b2SJed Brown return 87*179860b2SJed Brown 88*179860b2SJed Brown def printIndent(self, indent): 89*179860b2SJed Brown import sys 90*179860b2SJed Brown for i in range(indent): sys.stdout.write(' ') 91*179860b2SJed Brown 92*179860b2SJed Brown def display(self): 93*179860b2SJed Brown print 'I am a DirectedGraph with '+str(len(self.vertices))+' vertices' 94*179860b2SJed Brown for vertex in DirectedGraph.breadthFirstSearch(self): 95*179860b2SJed Brown self.printIndent(vertex.__level) 96*179860b2SJed 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])) 97*179860b2SJed Brown return 98*179860b2SJed Brown 99*179860b2SJed Brown def appendGraph(self, graph): 100*179860b2SJed Brown '''Join every leaf of this graph to every root of the input graph, leaving the result in this graph''' 101*179860b2SJed Brown leaves = DirectedGraph.getLeaves(self) 102*179860b2SJed Brown self.addSubgraph(graph) 103*179860b2SJed Brown map(lambda v: self.addEdges(v, outputs = DirectedGraph.getRoots(graph)), leaves) 104*179860b2SJed Brown return self 105*179860b2SJed Brown 106*179860b2SJed Brown def prependGraph(self, graph): 107*179860b2SJed Brown '''Join every leaf of the input graph to every root of this graph, leaving the result in this graph''' 108*179860b2SJed Brown roots = DirectedGraph.getRoots(self) 109*179860b2SJed Brown self.addSubgraph(graph) 110*179860b2SJed Brown map(lambda v: self.addEdges(v, outputs = roots), DirectedGraph.getLeaves(graph)) 111*179860b2SJed Brown return self 112*179860b2SJed Brown 113*179860b2SJed Brown def getRoots(graph): 114*179860b2SJed Brown '''Return all the sources in the graph (nodes without entering edges)''' 115*179860b2SJed Brown return filter(lambda v: not len(graph.getEdges(v)[0]), graph.vertices) 116*179860b2SJed Brown getRoots = staticmethod(getRoots) 117*179860b2SJed Brown 118*179860b2SJed Brown def getLeaves(graph): 119*179860b2SJed Brown '''Return all the sinks in the graph (nodes without exiting edges)''' 120*179860b2SJed Brown return filter(lambda v: not len(graph.getEdges(v)[1]), graph.vertices) 121*179860b2SJed Brown getLeaves = staticmethod(getLeaves) 122*179860b2SJed Brown 123*179860b2SJed Brown def depthFirstVisit(graph, vertex, seen = None, returnFinished = 0, outEdges = 1): 124*179860b2SJed Brown '''This is a generator returning vertices in a depth-first traversal only for the subtree rooted at vertex 125*179860b2SJed Brown - If returnFinished is True, return a vertex when it finishes 126*179860b2SJed Brown - Otherwise, return a vertex when it is first seen 127*179860b2SJed Brown - If outEdges is True, proceed along these, otherwise use inEdges''' 128*179860b2SJed Brown if seen is None: seen = [] 129*179860b2SJed Brown seen.append(vertex) 130*179860b2SJed Brown if not returnFinished: 131*179860b2SJed Brown yield vertex 132*179860b2SJed Brown # Cute trick since outEdges is index 1, and inEdges is index 0 133*179860b2SJed Brown for v in graph.getEdges(vertex)[outEdges]: 134*179860b2SJed Brown if not v in seen: 135*179860b2SJed Brown try: 136*179860b2SJed Brown for v2 in DirectedGraph.depthFirstVisit(graph, v, seen, returnFinished, outEdges): 137*179860b2SJed Brown yield v2 138*179860b2SJed Brown except StopIteration: 139*179860b2SJed Brown pass 140*179860b2SJed Brown if returnFinished: 141*179860b2SJed Brown yield vertex 142*179860b2SJed Brown return 143*179860b2SJed Brown depthFirstVisit = staticmethod(depthFirstVisit) 144*179860b2SJed Brown 145*179860b2SJed Brown def depthFirstSearch(graph, returnFinished = 0, outEdges = 1): 146*179860b2SJed Brown '''This is a generator returning vertices in a depth-first traversal 147*179860b2SJed Brown - If returnFinished is True, return a vertex when it finishes 148*179860b2SJed Brown - Otherwise, return a vertex when it is first seen 149*179860b2SJed Brown - If outEdges is True, proceed along these, otherwise use inEdges''' 150*179860b2SJed Brown seen = [] 151*179860b2SJed Brown for vertex in graph.vertices: 152*179860b2SJed Brown if not vertex in seen: 153*179860b2SJed Brown try: 154*179860b2SJed Brown for v in DirectedGraph.depthFirstVisit(graph, vertex, seen, returnFinished, outEdges): 155*179860b2SJed Brown yield v 156*179860b2SJed Brown except StopIteration: 157*179860b2SJed Brown pass 158*179860b2SJed Brown return 159*179860b2SJed Brown depthFirstSearch = staticmethod(depthFirstSearch) 160*179860b2SJed Brown 161*179860b2SJed Brown def breadthFirstSearch(graph, returnFinished = 0): 162*179860b2SJed Brown '''This is a generator returning vertices in a breadth-first traversal 163*179860b2SJed Brown - If returnFinished is True, return a vertex when it finishes 164*179860b2SJed Brown - Otherwise, return a vertex when it is first seen''' 165*179860b2SJed Brown queue = DirectedGraph.getRoots(graph)[0:1] 166*179860b2SJed Brown if not len(queue): return 167*179860b2SJed Brown seen = [queue[0]] 168*179860b2SJed Brown if not returnFinished: 169*179860b2SJed Brown queue[0].__level = 0 170*179860b2SJed Brown yield queue[0] 171*179860b2SJed Brown while len(queue): 172*179860b2SJed Brown vertex = queue[-1] 173*179860b2SJed Brown for v in graph.getEdges(vertex)[1]: 174*179860b2SJed Brown if not v in seen: 175*179860b2SJed Brown seen.append(v) 176*179860b2SJed Brown v.__level = vertex.__level + 1 177*179860b2SJed Brown queue.insert(0, v) 178*179860b2SJed Brown if not returnFinished: 179*179860b2SJed Brown yield v 180*179860b2SJed Brown vertex = queue.pop() 181*179860b2SJed Brown if returnFinished: 182*179860b2SJed Brown yield vertex 183*179860b2SJed Brown return 184*179860b2SJed Brown 185*179860b2SJed Brown def topologicalSort(graph, start = None, outEdges = 1): 186*179860b2SJed Brown '''Reorder the vertices using topological sort''' 187*179860b2SJed Brown if start is None: 188*179860b2SJed Brown vertices = [vertex for vertex in DirectedGraph.depthFirstSearch(graph, returnFinished = 1, outEdges = outEdges)] 189*179860b2SJed Brown else: 190*179860b2SJed Brown vertices = [vertex for vertex in DirectedGraph.depthFirstVisit(graph, start, returnFinished = 1, outEdges = outEdges)] 191*179860b2SJed Brown vertices.reverse() 192*179860b2SJed Brown for vertex in vertices: 193*179860b2SJed Brown yield vertex 194*179860b2SJed Brown return 195*179860b2SJed Brown topologicalSort = staticmethod(topologicalSort) 196