diff --git a/setup.py b/setup.py index 3431ab5..1d97a1e 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ author_email='trey@treymorris.com', description='auto generate truth tables', long_description=open('README.rst').read(), - install_requires=['prettytable'], + install_requires=['prettytable', 'pyparsing'], classifiers=['Development Status :: 5 - Production/Stable', 'License :: OSI Approved :: Apache Software License'], keywords=['truth', 'table', 'truth table', 'truthtable', 'logic'], diff --git a/truths/truths.py b/truths/truths.py index e55ba88..6e8605f 100644 --- a/truths/truths.py +++ b/truths/truths.py @@ -18,50 +18,148 @@ import itertools from prettytable import PrettyTable import re +import pyparsing +# dict of boolean operations +operations = { + 'not': (lambda x: not x), + '-': (lambda x: not x), + '~': (lambda x: not x), -class Gob(object): - pass + 'or': (lambda x, y: x or y), + 'nor': (lambda x, y: not (x or y)), + 'xor': (lambda x, y: x != y), + + 'and': (lambda x, y: x and y), + 'nand': (lambda x, y: not (x and y)), + 'xand': (lambda x, y: not (x and y)), + + '=>': (lambda x, y: (not x) or y), + '->': (lambda x, y: (not x) or y), + 'implies': (lambda x, y: (not x) or y), + + '=': (lambda x, y: x == y), + '==': (lambda x, y: x == y), + '!=': (lambda x, y: x != y), +} + + +def recursive_map(func, data): + """Recursively applies a map function to a list and all sublists.""" + if isinstance(data, list): + return [recursive_map(func, elem) for elem in data] + else: + return func(data) + + +def string_to_bool(s): + """Converts a string to boolean if string is either 'True' or 'False' + otherwise returns it unchanged. + """ + if s == 'True': + return True + elif s == 'False': + return False + return s + + +def solve_phrase(p): + """Recursively evaluates a logical phrase that has been grouped into + sublists where each list is one operation. + """ + if isinstance(p, bool): + return p + if isinstance(p, list): + # list with just a list in it + if len(p) == 1: + return solve_phrase(p[0]) + # single operand operation + if len(p) == 2: + return operations[p[0]](solve_phrase(p[1])) + # double operand operation + else: + return operations[p[1]](solve_phrase(p[0]), solve_phrase([p[2]])) + + +def group_operations(p): + """Recursively groups logical operations into seperate lists based on + the order of operations such that each list is one operation. + + Order of operations is: + not, and, or, implication + """ + if isinstance(p, list): + if len(p) == 1: + return p + for x in ['not', '~', '-']: + while x in p: + index = p.index(x) + p[index] = [x, group_operations(p[index+1])] + p.pop(index+1) + for x in ['and', 'nand', 'xand']: + while x in p: + index = p.index(x) + p[index] = [group_operations(p[index-1]), x, group_operations(p[index+1])] + p.pop(index+1) + p.pop(index-1) + for x in ['or', 'nor', 'xor']: + while x in p: + index = p.index(x) + p[index] = [group_operations(p[index-1]), x, group_operations(p[index+1])] + p.pop(index+1) + p.pop(index-1) + return p class Truths(object): - def __init__(self, base=None, phrases=None, ints=True): - if not base: + def __init__(self, bases=None, phrases=None, ints=True): + if not bases: raise Exception('Base items are required') - self.base = base + self.bases = bases self.phrases = phrases or [] self.ints = ints # generate the sets of booleans for the bases self.base_conditions = list(itertools.product([False, True], - repeat=len(base))) + repeat=len(bases))) # regex to match whole words defined in self.bases # used to add object context to variables in self.phrases - self.p = re.compile(r'(?