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