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 diff --git a/boolean/boolean.py b/boolean/boolean.py index 369f074..22e0543 100644 --- a/boolean/boolean.py +++ b/boolean/boolean.py @@ -26,12 +26,21 @@ import inspect import itertools +from operator import and_ as and_operator +from operator import or_ as or_operator + try: basestring # Python 2 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 @@ -849,6 +858,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 +1065,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 +1424,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 +1455,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)) diff --git a/boolean/test_boolean.py b/boolean/test_boolean.py index fa9fbf8..5001fe9 100644 --- a/boolean/test_boolean.py +++ b/boolean/test_boolean.py @@ -1073,5 +1073,51 @@ 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()