diff --git a/ppp_libmodule/simplification.py b/ppp_libmodule/simplification.py new file mode 100644 index 0000000..f6b0045 --- /dev/null +++ b/ppp_libmodule/simplification.py @@ -0,0 +1,69 @@ +import operator +import itertools + +from ppp_datamodel.nodes import Resource +from ppp_datamodel.nodes.list_operators import * + +__all__ = ['simplify'] + +def partition(pred, list_): + # Partition the items in lists / non-lists + list_ = [x for x in list_ if x] + lists = filter(pred, list_) + not_lists = itertools.filterfalse(pred, list_) + return (lists, not_lists) + +def simplify_union(tree): + # Trivial cases + if len(tree.list) == 0: + return List([]) + elif len(tree.list) == 1: + return tree.list[0] + + (lists, non_lists) = partition(lambda x:isinstance(x, List), tree.list) + # Make union of lists + lists = map(operator.attrgetter('list'), lists) + lists = list(set(itertools.chain(*lists))) + + non_lists = list(non_lists) + if non_lists: # If there are non-lists (eg. triples) + all_ = non_lists + all_.append(List(lists)) + return Union(all_) + else: # If there are only lists + return List(lists) + +def simplify_intersection(tree): + # Trivial cases + if len(tree.list) == 0: + return tree + elif len(tree.list) == 1: + return tree.list[0] + + (lists, non_lists) = partition(lambda x:isinstance(x, List), tree.list) + # Make intersection of lists + lists = list(map(set, map(operator.attrgetter('list'), lists))) + lists = list(lists[0].intersection(*lists[1:])) + + non_lists = list(non_lists) + if non_lists: # If there are non-lists (eg. triples) + all_ = non_lists + all_.append(List(lists)) + return Intersection(all_) + else: # If there are only lists + return List(lists) + + +predicates = { + Union: simplify_union, + Intersection: simplify_intersection, + } + +def predicate(tree): + for (cls, f) in predicates.items(): + if isinstance(tree, cls): + return f(tree) + return tree + +def simplify(tree): + return tree.traverse(predicate) diff --git a/setup.py b/setup.py index d860652..77f1be6 100755 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ 'Topic :: Software Development :: Libraries', ], install_requires=[ - 'ppp_datamodel>=0.5.2,<0.6', + 'ppp_datamodel>=0.5.22,<0.6', ], packages=[ 'ppp_libmodule', diff --git a/tests/test_simplification.py b/tests/test_simplification.py new file mode 100644 index 0000000..be18ebf --- /dev/null +++ b/tests/test_simplification.py @@ -0,0 +1,42 @@ +import unittest + +from ppp_datamodel.nodes import Triple as T +from ppp_datamodel.nodes import Missing as M +from ppp_datamodel.nodes import Resource as R +from ppp_datamodel.nodes.list_operators import * +from ppp_libmodule.simplification import simplify + + +class SimplificationTestCase(unittest.TestCase): + def testUnionTrivial(self): + self.assertEqual(simplify(Union([])), List([])) + self.assertEqual(simplify(Union([List([R('a')])])), + List([R('a')])) + def testUnionResourceLists(self): + t = Union([List([R('a'), R('b')]), List([R('c')])]) + t = simplify(t) + self.assertIsInstance(t, List) + self.assertEqual(set(t.list), {R('a'), R('b'), R('c')}) + def testUnionMixed(self): + t = Union([List([R('a'), R('b')]), T(M(), M(), M())]) + t = simplify(t) + self.assertIsInstance(t, Union) + self.assertEqual(t.list[0], T(M(), M(), M())) + self.assertIsInstance(t.list[1], List) + self.assertEqual(set(t.list[1].list), {R('a'), R('b')}) + + def testIntersectionTrivial(self): + self.assertEqual(simplify(Intersection([])), Intersection([])) + self.assertEqual(simplify(Intersection([List([R('a')])])), + List([R('a')])) + def testIntersectionResourceLists(self): + t = Intersection([List([R('a'), R('b')]), List([R('c'), R('a')])]) + t = simplify(t) + self.assertEqual(t, List([R('a')])) + def testIntersectionMixed(self): + t = Intersection([List([R('a'), R('b')]), List([R('c'), R('a')]), + T(M(), M(), M())]) + t = simplify(t) + self.assertIsInstance(t, Intersection) + self.assertEqual(t.list[0], T(M(), M(), M())) + self.assertEqual(t.list[1], List([R('a')]))