"""Simplistic implementation of a graph""" __all__ = ('Node', 'Graph') class Node(object): """A Node in the graph. They know their neighbours, and have an id which should resolve into a string""" __slots__ = ('in_nodes', 'out_nodes', 'id') def __init__(self, id=None): self.id = id self.in_nodes = list() self.out_nodes = list() def __str__(self): return str(self.id) def __repr__(self): return "%s(%s)" % (type(self).__name__, self.id) class Graph(object): """A simple graph implementation, keeping nodes and providing basic access and editing functions. The performance is only suitable for small graphs of not more than 10 nodes !""" __slots__ = "nodes" def __init__(self): self.nodes = list() def __del__(self): """Deletes bidericational dependencies""" for node in self.nodes: node.in_nodes = None node.out_nodes = None # END cleanup nodes # otherwise the nodes would keep floating around def add_node(self, node): """Add a new node to the graph :return: the newly added node""" self.nodes.append(node) return node def remove_node(self, node): """Delete a node from the graph :return: self""" try: del(self.nodes[self.nodes.index(node)]) except ValueError: return self # END ignore if it doesn't exist # clear connections for outn in node.out_nodes: del(outn.in_nodes[outn.in_nodes.index(node)]) for inn in node.in_nodes: del(inn.out_nodes[inn.out_nodes.index(node)]) node.out_nodes = list() node.in_nodes = list() return self def add_edge(self, u, v): """Add an undirected edge between the given nodes u and v. return: self :raise ValueError: If the new edge would create a cycle""" if u is v: raise ValueError("Cannot connect a node with itself") # are they already connected ? if u in v.in_nodes and v in u.out_nodes or \ v in u.in_nodes and u in v.out_nodes: return self # END handle connection exists # cycle check - if we can reach any of the two by following either ones # history, its a cycle for start, end in ((u, v), (v,u)): if not start.in_nodes: continue nodes = start.in_nodes[:] seen = set() # depth first search - its faster while nodes: n = nodes.pop() if n in seen: continue seen.add(n) if n is end: raise ValueError("Connecting u with v would create a cycle") nodes.extend(n.in_nodes) # END while we are searching # END for each direction to look # connection is valid, set it up u.out_nodes.append(v) v.in_nodes.append(u) return self def input_inclusive_dfirst_reversed(self, node): """Return all input nodes of the given node, depth first, It will return the actual input node last, as it is required like that by the pool""" stack = [node] seen = set() # depth first out = list() while stack: n = stack.pop() if n in seen: continue seen.add(n) out.append(n) # only proceed in that direction if visitor is fine with it stack.extend(n.in_nodes) # END call visitor # END while walking out.reverse() return out