--- /dev/null
+v0.2.1, Wed Sep 07 15:15:14 EST 2011 -- Fix naive bug in bracket matching. Oops.
+
+v0.2.0, Mon Aug 22 01:04:14 EST 2011 -- Build predicates from strings.
+
+v0.1.0, Sun Aug 14 22:32:03 EST 2011 -- Initial release.
--- /dev/null
+Copyright (c) 2011, David Basden
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the copyright holder nor the names of the contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--- /dev/null
+Metadata-Version: 1.0
+Name: digraphtools
+Version: 0.2.1
+Summary: Some tools for working with digraphs, partial orders and topological sorting with Python
+Home-page: https://github.com/dbasden/python-digraphtools
+Author: David Basden
+Author-email: davidb-python@rcpt.to
+License: UNKNOWN
+Description:
+ ============
+ digraphtools
+ ============
+
+ Some tools for working with directed acyclic graphs, partial orders and
+ topological sorting with Python
+
+ digraphtools was written as a lightweight way of using DAGs and partial
+ ordering to represent, sort and traverse dependency trees in a lightweight
+ way.
+
+ The code is hosted on github at https://github.com/dbasden/python-digraphtools
+
+ Graph Representation
+ ====================
+
+ Graphs
+ ------
+
+ A graph is represented as a dict which maps a node to a list
+ nodes connected via the outgoing edges of that node.
+
+ e.g.
+
+ graph = { 1: [2,3],
+ 2: [3],
+ 3: [] }
+
+ is a DAG represented by the edges (1,2) (1,3) (2,3)
+ where the edge 2tuple is in the form of (from,to)
+
+ There are helper methods in deptools to generate graphs from a
+ list of edges, and vice versa
+
+ Binary relations
+ ----------------
+
+ If a DAG represents dependencies, e.g. the edge (1,2) is taken
+ to mean "1 depends on 2", this is backwards from a binary relation.
+ (1,2) would be the relation 2P1
+
+
+ Topological Sorting
+ ===================
+
+ There are two ways of generating linear extensions / topological sorts of
+ dependencies (i.e. orders items must be processed in to satisfy dependency
+ requirements):
+
+ deptools.dfs_topsort_traversal
+ ------------------------------
+
+ deptools.dfs_topsort_traversal will take a graph and iterate over a
+ single valid topologicaly sorted order
+
+ deptools.topsort.vr_topsort
+ ---------------------------
+
+ deptools.topsort.vr_topsort will generate all valid linear extensions /
+ topological orderings given an initial 'seed' linear extension (such as
+ the one generated by deptools.dfs_topsort_traversal).
+
+ The method does not take the graph format as used by deptools as input,
+ but it does have a helper method to generate it's input matrix from a
+ partial order set (which can be generated from a graph using helpers in
+ deptools).
+
+ See the examples in topsort.py and test/test_topsort.py for how to do this.
+
+
+ Authors
+ =======
+
+ digraphtools was initially written by David Basden
+
+
+ Thanks
+ ======
+
+ Thanks to Yaakov L. Varol and Doron Rotem for the design of the algorithm
+ in topsort.py
+
+Platform: any
+Classifier: Intended Audience :: Developers
+Classifier: Development Status :: 3 - Alpha
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
--- /dev/null
+
+============
+digraphtools
+============
+
+Some tools for working with directed acyclic graphs, partial orders and
+topological sorting with Python
+
+digraphtools was written as a lightweight way of using DAGs and partial
+ordering to represent, sort and traverse dependency trees in a lightweight
+way.
+
+The code is hosted on github at https://github.com/dbasden/python-digraphtools
+
+Graph Representation
+====================
+
+Graphs
+------
+
+A graph is represented as a dict which maps a node to a list
+nodes connected via the outgoing edges of that node.
+
+e.g.
+
+ graph = { 1: [2,3],
+ 2: [3],
+ 3: [] }
+
+is a DAG represented by the edges (1,2) (1,3) (2,3)
+where the edge 2tuple is in the form of (from,to)
+
+There are helper methods in deptools to generate graphs from a
+list of edges, and vice versa
+
+Binary relations
+----------------
+
+If a DAG represents dependencies, e.g. the edge (1,2) is taken
+to mean "1 depends on 2", this is backwards from a binary relation.
+(1,2) would be the relation 2P1
+
+
+Topological Sorting
+===================
+
+There are two ways of generating linear extensions / topological sorts of
+dependencies (i.e. orders items must be processed in to satisfy dependency
+requirements):
+
+deptools.dfs_topsort_traversal
+------------------------------
+
+deptools.dfs_topsort_traversal will take a graph and iterate over a
+single valid topologicaly sorted order
+
+deptools.topsort.vr_topsort
+---------------------------
+
+deptools.topsort.vr_topsort will generate all valid linear extensions /
+topological orderings given an initial 'seed' linear extension (such as
+the one generated by deptools.dfs_topsort_traversal).
+
+The method does not take the graph format as used by deptools as input,
+but it does have a helper method to generate it's input matrix from a
+partial order set (which can be generated from a graph using helpers in
+deptools).
+
+See the examples in topsort.py and test/test_topsort.py for how to do this.
+
+
+Authors
+=======
+
+digraphtools was initially written by David Basden
+
+
+Thanks
+======
+
+Thanks to Yaakov L. Varol and Doron Rotem for the design of the algorithm
+in topsort.py
--- /dev/null
+from digraphtools import *
--- /dev/null
+#! /usr/bin/env python
+
+'''a collection of tools for working with directed acyclic graphs
+
+A graph is represented as a dict which maps a node to a list
+nodes connected via the outgoing edges of that node.
+
+e.g.
+
+ graph = { 1: [2,3],
+ 2: [3],
+ 3: [] }
+
+is a DAG represented by the edges (1,2) (1,3) (2,3)
+where the edge 2tuple is in the form of (from,to)
+
+Note: If a DAG represents dependencies, e.g. the edge (1,2) is taken
+ to mean "1 depends on 2", this is backwards from a binary relation.
+ (1,2) would be the relation 2P1
+'''
+from collections import defaultdict,deque
+
+def graph_from_edges(edges):
+ '''return a graph from a set of edges
+ edges are a 2tuple in the form of (from_node, to_node)
+ '''
+ graph = defaultdict(set)
+ for a,b in edges:
+ graph[a].add(b)
+ graph[b]
+ return dict((a,list(b)) for a,b in graph.items())
+
+def copy_graph(graph):
+ '''return a copy of a graph'''
+ return dict((a,list(b)) for a,b in graph.items())
+
+def iter_edges(graph):
+ '''return an iterator over every edge in a graph in the form (from,to)'''
+ return ((a,b) for a in graph.iterkeys() for b in graph[a])
+
+def iter_partial_order(graph):
+ return ((a,b) for b,a in iter_edges(graph))
+
+def from_partial_order(edges): return [(a,b) for (b,a) in edges]
+to_partial_order = from_partial_order
+
+class OrderViolationException(Exception): pass
+
+def verify_partial_order(partial_order, items):
+ '''verify that the order of items supplied satisfies the dependency graph
+ raises OrderViolationException unless a<b for every aPb in the partial order
+ (i.e. if a task is inserted before any of it's dependencies)
+ '''
+ itempos = dict((item,pos) for pos,item in enumerate(items))
+ for a,b in partial_order:
+ if itempos[a] >= itempos[b]:
+ raise OrderViolationException("item '%s' is in the order before it's dependency '%s'" %( repr(b),repr(a)),items)
+
+def postorder_traversal(graph, root):
+ '''traverse a graph post-order and yield a list of nodes'''
+ for n in graph[root]:
+ for traversed in postorder_traversal(graph,n):
+ yield traversed
+ yield root
+
+def dfs_topsort_traversal(graph, root):
+ '''traverse a graph postorder while not traversing already seen nodes'''
+ seen = set()
+ for n in postorder_traversal(graph, root):
+ if n not in seen:
+ yield n
+ seen.add(n)
+
+def dfs_traverse_iter_path(graph, root, path=None):
+ '''depth first traversal of graph generating every path seen'''
+ if path == None: path = []
+ if root in path: return
+ path.append(root)
+ yield path
+ for n in graph[root]:
+ for traversed in dfs_traverse_iter_path(graph, n, path):
+ yield traversed
+ path.pop()
+
+def dfs_iter_edges(graph, root):
+ '''traverse a graph depth-first and yield edges as they are traversed'''
+ for n in graph[root]:
+ yield root,n
+ for edge in dfs_iter_edges(graph,n):
+ yield edge
+
+def get_connected_subgraph(graph, root):
+ '''return the connected subgraph visible from supplied root node'''
+ return graph_from_edges( dfs_iter_edges(graph,root) )
--- /dev/null
+#! /usr/bin/env python
+
+'''predicates that can be chained together with boolean expressions before evaluation
+
+e.g.
+ a = predicate(lambda s: 'a' in s)
+ b = predicate(lambda s: 'b' in s)
+ c = predicate(lambda s: 'c' in s)
+
+ anyof = a | b | c
+ allof = a & b & c
+ not_anyof = anyof != True
+ assert anyof('--a--')
+ assert allof('-abc-')
+ assert not_anyof('12345')
+
+Also, generate predicates such as above from strings
+
+ pf = PredicateContainsFactory()
+ anyof2 = pf.predicate_from_string('a | b | c')
+ pallof2 = pf.predicate_from_string('a & b & c')
+ not_anyof2 = pf.predicate_from_string('!(a & b & c)')
+ assert anyof2('--a--')
+ assert allof2('-abc-')
+ assert not_anyof2('12345')
+
+These can be very useful for filtering of dependency graphs
+'''
+
+import operator
+import re
+
+def defer(origfunc,*argfs,**argfd):
+ '''defer execution of the arguments of a function
+ given origfunc return a function such that the code
+ f = defer(origfunc, arga, key=argb)
+ f(*newargs, **newargd)
+ is equivalent to:
+ origfunc(arga(*newargs,**newargd), key=argb(*newargs,**newargd))
+ '''
+ def wrapper(*args, **argd):
+ newargs = [argf(*args, **argd) for argf in argfs]
+ newargd = dict((k,argf(*args, **argd)) for k,argf in argfd.items())
+ return origfunc(*newargs, **newargd)
+ wrapper.origfunc = origfunc
+ wrapper.argfs = argfs
+ wrapper.argfd = argfd
+ wrapper.__repr__ = lambda s: "defer <%s>( *(%s) **(%s)) " % (repr(origfunc),repr(argfs),repr(argfd))
+ return wrapper
+
+def always(val):
+ '''returns a function that always returns val regardless of inputs'''
+ def alwaysf(*args, **argd): return val
+ alwaysf.val = val
+ return alwaysf
+
+class predicate(object):
+ '''chainable predicates
+ e.g.
+ a = predicate(lambda s: 'a' in s)
+ b = predicate(lambda s: 'b' in s)
+ c = predicate(lambda s: 'c' in s)
+
+ anyof = a | b | c
+ allof = a & b & c
+ not_anyof = anyof != True
+ assert anyof('--a--')
+ assert allof('-abc-')
+ assert not_anyof('12345')
+ '''
+
+ def __init__(self, func):
+ self.func = func
+ def __call__(self, arg):
+ return self.func(arg)
+ def __and__(self,other):
+ return self.__defer_infix__(other,operator.__and__)
+ def __or__(self,other):
+ return self.__defer_infix__(other,operator.__or__)
+ def __ne__(self,other):
+ return self.__defer_infix__(other,operator.__ne__)
+ def __defer_infix__(self,other,op):
+ if isinstance(other, bool):
+ other = always(other)
+ elif not isinstance(other, predicate):
+ return NotImplemented
+ return self.__class__(defer(op, self, other))
+ def __repr__(self):
+ return 'pred( '+repr(self.func)+' )'
+
+class notp(predicate):
+ '''exactly the same as a predicate but inverts it's __call__ output'''
+ def __call__(self, *args, **argd):
+ return not predicate.__call__(self, *args, **argd)
+
+
+
+def partition_list(items, partition):
+ '''works like str.partition but for lists
+ e.g. partition(['aa','bb','cd','ee'],'cd') == ['aa','bb'],'cd',['ee']
+ partition(['aa','bb','cd','ee'],'ff') == ['aa','bb','cd','ee'],None,[]
+ '''
+ for i,obj in enumerate(items):
+ if obj == partition:
+ return items[:i],obj,items[i+1:]
+ return items,None,[]
+
+class ParseSyntaxError(Exception): pass
+class LexParse(object):
+ '''very simple lexer/parser'''
+ class _leaf(object):
+ def __init__(self, data): self.data = data
+ def __repr__(self): return '_leaf(%s)' %(repr(self.data))
+
+ valid_tokens = ['(',')','!','&','|']
+
+ def _match_bracket(self, tokens, i, bopen='(',bclose=')'):
+ '''find the closing bracket that matches an open bracket
+ return None if there is no matching bracket
+ otherwise the index into tokens of the close bracket that matches the opening bracket at position i
+ '''
+ assert i < len(tokens)
+ assert tokens[i] == bopen
+ depth = 0
+ for i in xrange(i,len(tokens)):
+ tok = tokens[i]
+ if tok == bopen:
+ depth += 1
+ elif tok == bclose:
+ depth -= 1
+ if depth < 0: return None
+ if depth == 0: return i
+ return None
+
+ def lex(self, s):
+ '''returns a list of tokens from a string
+ tokens returned are anything inside self.valid_tokens or
+ any other string not containing tokens, stripped
+ of leading and trailing whitespace
+ '''
+ s = s.strip()
+ if s == '': return []
+ for tok in self.valid_tokens:
+ l,t,r = s.partition(tok)
+ if t==tok: return self.lex(l)+[tok]+self.lex(r)
+ return [self._leaf(s)]
+
+ def parse(self, tokens):
+ '''parse a list of tokens in order of predicence and return the output'''
+ if len(tokens) == 0:
+ raise ParseSyntaxError('Cannot parse empty subexpression')
+ # Brackets
+ l,part,r = partition_list(tokens, '(')
+ if part != None:
+ if ')' in l: raise ParseSyntaxError('unmatched ) near',tokens)
+ r.insert(0,'(')
+ rindex = self._match_bracket(r, 0)
+ if rindex is None: raise ParseSyntaxError('unmatched ( near',tokens)
+ assert r[rindex] == ')'
+ inner = r[1:rindex]
+ r = r[rindex+1:]
+ inner = self.brackets(self.parse(inner))
+ return self.parse(l+[inner]+r)
+
+ # unary not
+ if tokens[0] == '!':
+ if len(tokens) < 2: raise ParseSyntaxError('syntax error near',tokens)
+ # this only works without other unary operators
+ if tokens[1] in self.valid_tokens: raise ParseSyntaxError('syntax error near', tokens)
+ argument = self.parse([ tokens[1] ])
+ inv = self.notx(argument)
+ return self.parse([inv]+tokens[2:])
+
+ # and
+ l,part,r = partition_list(tokens, '&')
+ if part != None:
+ if not len(l) or not len(r):
+ raise ParseSyntaxError('syntax error near', tokens)
+ l,r = self.parse(l), self.parse(r)
+ return self.andx(l,r)
+
+ # or
+ l,part,r = partition_list(tokens, '|')
+ if part != None:
+ if not len(l) or not len(r):
+ raise ParseSyntaxError('syntax error near', tokens)
+ l,r = self.parse(l), self.parse(r)
+ return self.orx(l,r)
+
+ if len(tokens) == 1:
+ if isinstance(tokens[0], self._leaf):
+ return self.data(tokens[0].data) # base case
+ elif tokens[0] in self.valid_tokens:
+ raise ParseSyntaxError('syntax error near',tokens)
+ return tokens[0] # Already parsed
+
+ # Nothing else is sane
+ print repr(tokens)
+ raise ParseSyntaxError('syntax error near', tokens)
+
+ def brackets(self, expr):
+ '''You almost never want to override this'''
+ return expr
+
+ def notx(self, expr): pass
+ def andx(self, expr_l, expr_r): pass
+ def orx(self, expr_l, expr_r): pass
+ def data(self, data): pass
+
+class BoolParse(LexParse):
+ '''example parser implementation
+ bp = BoolParse()
+ assert False or (False and not (True or False)) == False
+ inp = 'False | (False & ! (True | False))'
+ assert bp.parse(bp.lex(inp)) is False
+ '''
+ notx = lambda s,expr: not expr
+ andx = lambda s,l,r: l and r
+ orx = lambda s,l,r: l or r
+ def data(self,data):
+ return not(data.lower() == 'false' or data == '0')
+
+
+class PredicateContainsFactory(LexParse):
+ '''create predicates that act on the contents of a container passed to them'''
+ def predicate_from_string(self, definition):
+ tokens = self.lex(definition)
+ return self.parse(tokens)
+ def notx(self, pred):
+ return notp(pred)
+ def andx(self, pred_l, pred_r):
+ return pred_l & pred_r
+ def orx(self, pred_l, pred_r):
+ return pred_l | pred_r
+ def data(self, data):
+ return predicate(lambda container: data in container)
+
+if __name__ == "__main__":
+ def defer_sample():
+ def a(arga, moo=None, argb=None):
+ return arga+argb
+ def b(arga, moo=None, argb=None):
+ return arga^argb
+ def c(arga, moo=None, argb=None):
+ return arga,argb
+ ooer = defer(c, a,argb=b)
+ result = ooer(1234,argb=4312)
+ assert result == (5546, 5130)
+
+ def predicate_sample():
+ a = predicate(lambda s: 'a' in s)
+ b = predicate(lambda s: 'b' in s)
+ c = predicate(lambda s: 'c' in s)
+ d = predicate(lambda s: 'd' in s)
+
+ anyof = a | b | c
+ allof = a & b & c
+
+ not_anyof = anyof != True
+ not_allof = allof != True
+
+
+ assert anyof('asdf')
+ assert allof('abc')
+ assert not anyof('1234')
+ assert not allof('ab')
+ assert not_anyof('1234')
+ assert not_allof('1234')
+
+ nottest = a & b & notp( c | d )
+ assert nottest('ab')
+ assert not nottest('abc')
+ assert not nottest('b')
+ assert not nottest('d')
+ assert not nottest('bd')
+ assert not nottest('abd')
+
+ e = predicate(lambda n: n%2==0)
+ t = predicate(lambda n: n%3==0)
+ eset = set(filter(e, range(1000)))
+ tset = set(filter(t, range(1000)))
+ eutset = set(filter(e|t, range(1000)))
+ eitset = set(filter(e&t, range(1000)))
+ assert eutset == eset.union(tset)
+ assert eitset == eset.intersection(tset)
+
+ def parser_internal_test():
+ lp = LexParse()
+ #lp._match_bracket(self, tokens, i, bopen='(',bclose=')'):
+ assert lp._match_bracket('()',0) == 1
+ assert lp._match_bracket('(',0) == None
+ assert lp._match_bracket(')))))()))))',5) == 6
+ assert lp._match_bracket('((()(()))(()((()(()()))())()))',0) == 29
+ assert lp._match_bracket('((()(()))(()((()(()()))())()))',2) == 3
+ assert lp._match_bracket('((()(()))(()((()(()()))())()))',12) == 25
+ assert lp._match_bracket('((()(()))(()((()(()()))())()))',23) == 24
+ assert lp._match_bracket('((()(()))(()((()(()()))())()))',23) == 24
+ assert lp._match_bracket('((()(()))(()((()(()()))())()))',26) == 27
+ assert lp._match_bracket('((()(()))(()((()(()()))())()))',9) == 28
+ assert lp._match_bracket('((()(()))(()((()(()()))())()))',10) == 11
+ assert lp._match_bracket('((()(()))(()((()(()()))())()))',16) == 21
+
+ def parser_sample():
+ bp = BoolParse()
+ assert False or (False and not (True or False)) == False
+ inp = 'False | (False & ! (True | False))'
+ assert bp.parse(bp.lex(inp)) is False
+ assert bp.parse(bp.lex('true & !false'))
+
+ def predicate_factory_sample():
+ pf = PredicateContainsFactory()
+ pred = pf.predicate_from_string('fish & !cow')
+ assert pred(['fish', 'bat', 'pidgeon'])
+ assert not pred( ['fish', 'cow', 'bat'] )
+ assert not pred( [] )
+ assert not pred( ['cow'] )
+ assert not pred( ['bat','pig'] )
+
+ a = predicate(lambda s: 'a' in s)
+ b = predicate(lambda s: 'b' in s)
+ c = predicate(lambda s: 'c' in s)
+ anyof2 = pf.predicate_from_string('a | b | c')
+ allof2 = pf.predicate_from_string('a & b & c')
+ not_anyof2 = pf.predicate_from_string('!(a & b & c)')
+ assert anyof2('--a--')
+ assert allof2('-abc-')
+ assert not_anyof2('12345')
+
+ pred = pf.predicate_from_string('( a | b | c ) & ( c | e | d )')
+ assert not pred('b')
+ assert pred('c')
+ assert pred('cd')
+ assert pred('acd')
+ assert not pred('ab')
+ assert not pred('a')
+
+ parser_internal_test()
+ defer_sample()
+ predicate_sample()
+ parser_sample()
+ predicate_factory_sample()
--- /dev/null
+#! /usr/bin/env python
+
+'''unit tests for digraphtools'''
+
+import unittest2 as unittest
+import digraphtools
+
+class DigraphTests(unittest.TestCase):
+ def test_graph_from_edges(self):
+ g = digraphtools.graph_from_edges([(1,2),(1,3),(2,3)])
+ self.assertEqual(set(g[1]),set([2,3]))
+ self.assertEqual(set(g[2]),set([3]))
+ self.assertEqual(set(g[3]),set())
+ def test_verify_partial_order(self):
+ g = digraphtools.graph_from_edges([(1,2),(1,3),(2,3)])
+ # Will raise an exception if incorrect
+ digraphtools.verify_partial_order(digraphtools.iter_partial_order(g), [3,2,1])
+ self.assertRaises(digraphtools.OrderViolationException,digraphtools.verify_partial_order, digraphtools.iter_partial_order(g), [3,1,2])
+ def test_iter_edges(self):
+ edges = set([(1,2),(1,3),(2,3)])
+ g = digraphtools.graph_from_edges(edges)
+ gedges = set(digraphtools.iter_edges(g))
+ self.assertEqual(edges,gedges)
+ def test_copy_graph(self):
+ edges = set([(1,2),(1,3),(2,3)])
+ g = digraphtools.graph_from_edges(edges)
+ gg = digraphtools.copy_graph(g)
+ self.assertEqual(g,gg)
+ gedges = set(digraphtools.iter_edges(g))
+ ggedges = set(digraphtools.iter_edges(gg))
+ self.assertEqual(gedges,ggedges)
+ gg[2].remove(3)
+ self.assertNotEqual(g,gg)
+ gedges = set(digraphtools.iter_edges(g))
+ ggedges = set(digraphtools.iter_edges(gg))
+ self.assertNotEqual(gedges,ggedges)
+ def test_postorder_traversal(self):
+ edges = set([(1,2),(1,3),(2,3)])
+ g = digraphtools.graph_from_edges(edges)
+ po = list(digraphtools.postorder_traversal(g,1))
+ self.assertEqual([3,2,3,1],po)
+ def test_dfs_topsort_traversal(self):
+ edges = set([(1,2),(1,3),(2,3)])
+ g = digraphtools.graph_from_edges(edges)
+ po = list(digraphtools.dfs_topsort_traversal(g,1))
+ self.assertEqual([3,2,1],po)
+ def test_dfs_iter_edges(self):
+ g = digraphtools.graph_from_edges([(1,2),(1,3),(2,3)])
+ edgeiter = digraphtools.dfs_iter_edges(g,1)
+ self.assertEqual([(1,2),(2,3),(1,3)],list(edgeiter))
+ def test_get_connected_subgraph(self):
+ g = digraphtools.graph_from_edges([(1,2),(1,3),(2,3)])
+ self.assertEqual(g, digraphtools.get_connected_subgraph(g,1))
+ sg = digraphtools.graph_from_edges([(2,3)])
+ self.assertEqual(sg, digraphtools.get_connected_subgraph(g,2))
+
+if __name__ == '__main__':
+ unittest.main()
--- /dev/null
+#! /usr/bin/env python
+
+'''unit tests for digraphtools'''
+
+import unittest2 as unittest
+import digraphtools
+import digraphtools.topsort as topsort
+
+class TopsortTests(unittest.TestCase):
+ def test_vr_topsort(self):
+ n = 5
+ partial_order = [(1,2), (2,3), (1,5)]
+ g = digraphtools.graph_from_edges(digraphtools.from_partial_order(partial_order))
+ grid = topsort.partial_order_to_grid(partial_order,n)
+ for le in topsort.vr_topsort(n,grid):
+ digraphtools.verify_partial_order(digraphtools.iter_partial_order(g), le)
+
+if __name__ == '__main__':
+ unittest.main()
--- /dev/null
+#! /usr/bin/env python
+
+def partial_order_to_grid(poset, n):
+ '''poset is given in 2tuples of indexes into a list containing a linear extension
+ as the indicies are into a list containing a valid topological sort,
+ i must be < j for every (i,j) in the poset'''
+ grid = [[False for j in xrange(n+1)]+[True] for i in xrange(n+2)]
+ for i,j in poset:
+ assert i<j
+ grid[i][j] = True
+ grid[j][i] = True
+ return grid
+
+def vr_topsort(n, m):
+ '''Python implementation of Varol and Rotem (1979)
+ generate all linear extensions of a poset based on an initial valid linear extension
+ as described in
+
+ Yaakov L. Varol and Doron Rotem, An Algorithm to Generate All Topological Sorting Arrangements.
+ Computer J., 24 (1981) pp. 83-84.
+
+ the 'seed' topological sort is mapped to integers from [1..n]
+ e.g. given nodes {a,b,c,d}, with the partial order of {(c,a),(b,a),(a,d)}
+ one valid topological ordering is c,b,a,d. We then map 1=c 2=b 3=a 4=d
+ such that cbad is represented as 1234. The partial order is now
+ {(1,3),(2,3),(3,4)} (which can be passed to partial_order_to_grid to generate
+ the incidence matrix 'm')
+
+ n is the number of nodes in the set the partial order is across
+ m is the incidence matrix of the binary relations after mapping to integers
+ '''
+ # n is the number of nodes in the DAG
+ # m is a table (2d array) of bools giving the adjacency matrix
+ # (n rows of n+1, indexed from 1).
+ # There is also a terminating 'True' at the end of each row
+
+ # The algorithm was written with list indicies starting at 1,
+ # so this implementation does the same. http://xkcd.com/163/
+ loc = range(n+1)
+ p = range(n+2)
+ yield p[1:n+1]
+ i = 1
+ k = 1
+ while i < n:
+ k = loc[i]
+ kk = k + 1
+ if m[i][p[kk]]:
+ p[i:k+1] = [p[k]]+p[i:k] # roll-right a[i:k+1]
+ loc[i] = i
+ i += 1
+ else:
+ p[k],p[kk] = p[kk],p[k]
+ loc[i] = kk
+ i = 1
+ yield p[1:n+1]
+
+
+if __name__ == "__main__":
+ n = 5
+ poset = [(1,2), (2,3), (1,5)]
+ grid = partial_order_to_grid(poset,n)
+ for le in vr_topsort(n,grid):
+ print le
--- /dev/null
+from distutils.core import setup
+
+setup(
+ name='digraphtools',
+ version='0.2.1',
+ author='David Basden',
+ author_email='davidb-python@rcpt.to',
+ packages=['digraphtools','digraphtools.test'],
+ url='https://github.com/dbasden/python-digraphtools',
+ description='Some tools for working with digraphs, partial orders and topological sorting with Python',
+ long_description=open('README.txt').read(),
+ platforms = ['any'],
+ classifiers = [
+ 'Intended Audience :: Developers',
+ 'Development Status :: 3 - Alpha',
+ 'License :: OSI Approved :: BSD License',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ ],
+)