From 3b9264794a4d28cdad56633f52a8f6fcd1ed7a28 Mon Sep 17 00:00:00 2001 From: Caleb Bassi Date: Wed, 30 May 2018 09:30:13 -0700 Subject: [PATCH 1/5] Rename base to bases --- truths/truths.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/truths/truths.py b/truths/truths.py index e55ba88..43f4058 100644 --- a/truths/truths.py +++ b/truths/truths.py @@ -25,25 +25,25 @@ class Gob(object): 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'(? Date: Wed, 30 May 2018 09:57:46 -0700 Subject: [PATCH 2/5] Replace gob with a dict --- truths/truths.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/truths/truths.py b/truths/truths.py index 43f4058..d7c2420 100644 --- a/truths/truths.py +++ b/truths/truths.py @@ -20,10 +20,6 @@ import re -class Gob(object): - pass - - class Truths(object): def __init__(self, bases=None, phrases=None, ints=True): if not bases: @@ -41,20 +37,16 @@ def __init__(self, bases=None, phrases=None, ints=True): self.p = re.compile(r'(? Date: Wed, 30 May 2018 10:06:01 -0700 Subject: [PATCH 3/5] Replace bases in phrases with literal bool value --- truths/truths.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/truths/truths.py b/truths/truths.py index d7c2420..6115c48 100644 --- a/truths/truths.py +++ b/truths/truths.py @@ -42,7 +42,7 @@ def calculate(self, *args): # each phrase eval_phrases = [] for item in self.phrases: - item = self.p.sub(r"bools['\1']", item) + item = self.p.sub(lambda match: str(bools[match.group(0)]), item) eval_phrases.append(eval(item)) # add the bases and evaluated phrases to create a single row From f27a9f5072c3a05a6f76ab728050ae57d21acbe5 Mon Sep 17 00:00:00 2001 From: Caleb Bassi Date: Wed, 30 May 2018 16:06:43 -0700 Subject: [PATCH 4/5] Rename item to phrase in phrases loop --- truths/truths.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/truths/truths.py b/truths/truths.py index 6115c48..029836a 100644 --- a/truths/truths.py +++ b/truths/truths.py @@ -41,9 +41,9 @@ def calculate(self, *args): # substitute bases with boolean values in self.phrases then evaluate # each phrase eval_phrases = [] - for item in self.phrases: - item = self.p.sub(lambda match: str(bools[match.group(0)]), item) - eval_phrases.append(eval(item)) + for phrase in self.phrases: + phrase = self.p.sub(lambda match: str(bools[match.group(0)]), phrase) + eval_phrases.append(eval(phrase)) # add the bases and evaluated phrases to create a single row row = [val for key, val in bools.items()] + eval_phrases From 6995c6ebd200852a02d0b2ee1554d25fc269ba26 Mon Sep 17 00:00:00 2001 From: Caleb Bassi Date: Wed, 30 May 2018 10:21:52 -0700 Subject: [PATCH 5/5] Add support for more logical operations * use custom process for evaluating phrases instead of using 'eval()' * added support for 'not', 'implies', 'xor', 'xand' etc * adding support for other operations is as simple as defining a lambda expression and adding some logic to the `group_operations` function --- setup.py | 2 +- truths/truths.py | 112 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 110 insertions(+), 4 deletions(-) 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 029836a..6e8605f 100644 --- a/truths/truths.py +++ b/truths/truths.py @@ -18,6 +18,97 @@ 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), + + '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): @@ -36,14 +127,29 @@ def __init__(self, bases=None, phrases=None, ints=True): # used to add object context to variables in self.phrases self.p = re.compile(r'(?