From 420db8471503846a7f978689cce458d0eccf6872 Mon Sep 17 00:00:00 2001 From: gemerden Date: Fri, 14 Jul 2017 11:26:07 +0200 Subject: [PATCH 1/5] ignore pycharm project files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 3f8a666..fce3abb 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ /dist/ /tmp/ /.cache/ +/.idea/ \ No newline at end of file From 8a45453e9c20f656ae8b018db720c44597fee2bc Mon Sep 17 00:00:00 2001 From: gemerden Date: Fri, 14 Jul 2017 11:35:28 +0200 Subject: [PATCH 2/5] adds __call__methods to enable boolean evaluation __call__methods are added to Symbol, NOT, AND, OR. In short: __call__methods are recursively called on NOT, AND, OR classes, following the logic tree via self.args. When a Symbol instance is found, the corrsponding value (e.g. True/False) is looked up in the kwargs argument and returned to the caller. --- boolean/boolean.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/boolean/boolean.py b/boolean/boolean.py index 369f074..5c2a5c5 100644 --- a/boolean/boolean.py +++ b/boolean/boolean.py @@ -26,6 +26,8 @@ import inspect import itertools +from operator import and_ as and_operator, or_ as or_operator + try: basestring # Python 2 @@ -849,6 +851,10 @@ def __init__(self, obj): self.iscanonical = True self.isliteral = True + def __call__(self, **kwargs): + """ returns the value for this symbol from kwargs """ + return kwargs[self.obj] + def __hash__(self): if self.obj is None: # Anonymous Symbol. return id(self) @@ -1052,6 +1058,10 @@ def demorgan(self): op = expr.args[0] return op.dual(*(self.__class__(arg).cancel() for arg in op.args)) + def __call__(self, **kwargs): + """ negates the value returned from self.args[0].__call__ """ + return not self.args[0](**kwargs) + def __lt__(self, other): return self.args[0] < other @@ -1407,6 +1417,14 @@ def __init__(self, arg1, arg2, *args): self.dual = self.OR self.operator = '&' + def __call__(self, **kwargs): + """ + Calls arg.__call__ for each arg in self.args and applies python 'and' operator. + + reduce is used as in e.g. AND(a, b, c, d) == AND(a, AND(b, AND(c, d))) + """ + return reduce(and_operator, (a(**kwargs) for a in self.args)) + class OR(DualBase): """ @@ -1430,3 +1448,11 @@ def __init__(self, arg1, arg2, *args): self.annihilator = self.TRUE self.dual = self.AND self.operator = '|' + + def __call__(self, **kwargs): + """ + Calls arg.__call__ for each arg in self.args and applies python 'or' operator. + + reduce is used as in e.g. OR(a, b, c, d) == OR(a, OR(b, OR(c, d))) + """ + return reduce(or_operator, (a(**kwargs) for a in self.args)) From 6577a47b2991b0db18d0df75e60ab13ef764f0ea Mon Sep 17 00:00:00 2001 From: gemerden Date: Fri, 14 Jul 2017 11:35:56 +0200 Subject: [PATCH 3/5] adds tests for the __call__ evaluation methods. --- boolean/test_boolean.py | 47 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/boolean/test_boolean.py b/boolean/test_boolean.py index fa9fbf8..2b07100 100644 --- a/boolean/test_boolean.py +++ b/boolean/test_boolean.py @@ -1073,5 +1073,52 @@ def __init__(self, name, value='value'): self.fail(e) +class CallabilityTestCase(unittest.TestCase): + + def test_and(self): + algebra = BooleanAlgebra() + exp = algebra.parse("a&b&c") + for a in [True, False]: + for b in [True, False]: + for c in [True, False]: + self.assertEqual(exp(a=a, b=b, c=c), a and b and c) + + def test_or(self): + algebra = BooleanAlgebra() + exp = algebra.parse("a|b|c") + for a in [True, False]: + for b in [True, False]: + for c in [True, False]: + self.assertEqual(exp(a=a, b=b, c=c), a or b or c) + + def test_not(self): + algebra = BooleanAlgebra() + exp = algebra.parse("!a") + for a in [True, False]: + self.assertEqual(exp(a=a), not a) + + def test_symbol(self): + algebra = BooleanAlgebra() + exp = algebra.parse("a") + for a in [True, False]: + self.assertEqual(exp(a=a), a) + + def test_composite(self): + algebra = BooleanAlgebra() + exp = algebra.parse("!(a|b&(a|!c))") + for a in [True, False]: + for b in [True, False]: + for c in [True, False]: + self.assertEqual(exp(a=a, b=b, c=c), not(a or b and (a or not c))) + + def test_negate_A_or_B(self): + algebra = BooleanAlgebra() + exp = algebra.parse("!(a|b)") + for a in [True, False]: + for b in [True, False]: + self.assertEqual(exp(a=a, b=b), not(a or b)) + + + if __name__ == '__main__': unittest.main() From fb86c366c437b1faff03a7a3d735c3628779a9b5 Mon Sep 17 00:00:00 2001 From: gemerden Date: Fri, 14 Jul 2017 11:42:38 +0200 Subject: [PATCH 4/5] removes whiteline --- boolean/test_boolean.py | 1 - 1 file changed, 1 deletion(-) diff --git a/boolean/test_boolean.py b/boolean/test_boolean.py index 2b07100..5001fe9 100644 --- a/boolean/test_boolean.py +++ b/boolean/test_boolean.py @@ -1119,6 +1119,5 @@ def test_negate_A_or_B(self): self.assertEqual(exp(a=a, b=b), not(a or b)) - if __name__ == '__main__': unittest.main() From 54aa1f959532af28b2e0fe354faea8023fac3560 Mon Sep 17 00:00:00 2001 From: gemerden Date: Wed, 19 Jul 2017 14:56:42 +0200 Subject: [PATCH 5/5] splits import, fixed reduce NameError for python3 --- boolean/boolean.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/boolean/boolean.py b/boolean/boolean.py index 5c2a5c5..22e0543 100644 --- a/boolean/boolean.py +++ b/boolean/boolean.py @@ -26,7 +26,8 @@ import inspect import itertools -from operator import and_ as and_operator, or_ as or_operator +from operator import and_ as and_operator +from operator import or_ as or_operator try: @@ -34,6 +35,12 @@ except NameError: basestring = str # Python 3 +try: + reduce # Python 2 +except NameError: + from functools import reduce # Python 3 + + # Set to True to enable tracing for parsing TRACE_PARSE = False