summaryrefslogtreecommitdiff
path: root/lib/git/async/graph.py
diff options
context:
space:
mode:
authorSebastian Thiel <byronimo@gmail.com>2010-06-12 12:41:20 +0200
committerSebastian Thiel <byronimo@gmail.com>2010-06-12 12:41:20 +0200
commitf91495e271597034226f1b9651345091083172c4 (patch)
treee0e2aa63b7dc649083858366eaedb6ac4cc5739b /lib/git/async/graph.py
parent7c1169f6ea406fec1e26e99821e18e66437e65eb (diff)
parent7a0b79ee574999ecbc76696506352e4a5a0d7159 (diff)
downloadgitpython-f91495e271597034226f1b9651345091083172c4.tar.gz
Merge branch 'async'
Diffstat (limited to 'lib/git/async/graph.py')
-rw-r--r--lib/git/async/graph.py126
1 files changed, 126 insertions, 0 deletions
diff --git a/lib/git/async/graph.py b/lib/git/async/graph.py
new file mode 100644
index 00000000..4e14c81e
--- /dev/null
+++ b/lib/git/async/graph.py
@@ -0,0 +1,126 @@
+"""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
+