-6

I'd like to create an algorithm that takes as input a propositional logic expression without parentheses and outputs the same expression enclosed in parentheses in all possible ways depending on the logical connectives present. For example, if I have "p and q or r implies not s" as input I should obtain ((p and (q or r)) implies (not s): here's my code:

def parenthesize(expression):
    # Define a list of logical connectives
    connectives = ["and", "or", "not", "implies", "if and only if"]

    # Base case: if the expression is a single variable, return it enclosed in parentheses
    if expression not in connectives and len(expression.split()) == 1:
        return "(" + expression + ")"

    # Recursive case: find the outermost connective and apply the algorithm to each sub-expression
    nesting_levels = [0] * len(expression)
    stack = []
    for i, char in enumerate(expression):
        if char == "(":
            stack.append(i)
        elif char == ")":
            j = stack.pop()
            nesting_levels[j:i+1] = [len(stack)] * (i-j+1)

    max_nesting_level = max(nesting_levels)
    outermost_connectives = [connectives[i] for i, level in enumerate(nesting_levels) if level == max_nesting_level]

    if len(outermost_connectives) == 1:
        connective = outermost_connectives[0]
        subexpressions = expression.split(connective)
        parenthesized_subexpressions = [parenthesize(subexpression) for subexpression in subexpressions]
        return "(" + connective.join(parenthesized_subexpressions) + ")"
    else:
        # If there are multiple outermost connectives, choose the first one
        connective = outermost_connectives[0]
        index = expression.index(connective)
        left_expression = expression[:index].strip()
        right_expression = expression[index+len(connective):].strip()
        parenthesized_left_expression = parenthesize(left_expression)
        parenthesized_right_expression = parenthesize(right_expression)
        return "(" + parenthesized_left_expression + " " + connective + " " + parenthesized_right_expression + ")"

# Example usage
expression = "p and q or r implies not s"
parenthesized_expression = parenthesize(expression)
print(parenthesized_expression)

Problem is my output is wrong: it's "(p and q or r implies not s)", could someone lead me to a solution? Thanks

Mark
  • 13
  • 7
  • `if expression not in connectives` does not test *if the expression is a single variable*. – Scott Hunter Jun 12 '23 at 12:45
  • @ScottHunter I added a checker: ` if expression not in connectives and len(expression.split()) == 1` but the output is still wrong, this time is: ((p )and(( q )or((( r )implies( ))not( s))))" – Mark Jun 12 '23 at 12:48
  • Your code assumes all connectives are binary; they are *not*. – Scott Hunter Jun 12 '23 at 12:49
  • These sorts of problems are best solved once you have exactly determined the grammar of your input string. Consider familiarising yourself with EBNF https://condor.depaul.edu/ichu/csc447/notes/wk3/BNF.pdf. It should not be too difficult to write a grammar for your specific case. Once you have done that, parsing should be much simpler as you will know exactly what terms you have to test for when parsing the string. – Marcel Ferrari Jun 12 '23 at 12:58
  • @ScottHunter unfortunately it's not that easy as to modify my code and handle connectives of any arity, as instead of splitting the expression at the position of each connective, even if I find the outermost connective and split the expression at that position (by keeping track of the nesting level of each connective and selecting the connective with the highest nesting level as the outermost connective) I obtain the following error: "IndexError: list index out of range". I updated my algorithm appling these changes anyway – Mark Jun 12 '23 at 13:02
  • @MarcelFerrari I tried to implement a few functions following your suggested approach (I posted an answer reporting those changes) but the solution seems still far... – Mark Jun 12 '23 at 13:12

1 Answers1

0

I wrote the following EBNF to define a very basic version of your inputs.

All operators are left associative, which is not usually the case, but I think that it serves well as a basic example.

<letter> ::= 'a' | 'b' | ... | 'z'
<term> ::= <letter>+

<not_operator> ::= 'not'
<and_operator> ::= 'and'
<or_operator> ::= 'or'
<implies_operator> ::= 'implies'
<iff_operator> ::= 'iff'

<unary_expression> ::= <not_operator> <term> | <term>
<and_expression> ::= <unary_expression> | <and_expression> <and_operator> <unary_expression>
<or_expression> ::= <and_expression> | <or_expression> <or_operator> <and_expression>
<implies_expression> ::= <or_expression> | <implies_expression> <implies_operator> <or_expression>
<expression> ::= <implies_expression> | <expression> <iff_operator> <implies_expression>  

There might be more compact and concise ways of defining the same thing, but this is what I was able to come up with.

The order of precedence is:

1. Unary not
2. Binary and
3. Binary or
4. Binary implies
5. Binary iff

Here is a very basic implementation of a recursive descent parser for this grammar I generated with ChatGPT. It seems to work fine however you should double check and tweak it to your needs.

class Parser:
    def __init__(self, input):
        self.tokens = input.split()
        self.current_token = None
        self.next()

    def next(self):
        if len(self.tokens) == 0:
            self.current_token = None
        else:
            self.current_token = self.tokens.pop(0)

    def parse_expression(self):
        result = self.parse_implies_expression()
        while self.current_token == 'iff':
            self.next()
            result = f"({result} iff {self.parse_implies_expression()})"
        return result

    def parse_implies_expression(self):
        result = self.parse_or_expression()
        while self.current_token == 'implies':
            self.next()
            result = f"({result} implies {self.parse_or_expression()})"
        return result

    def parse_or_expression(self):
        result = self.parse_and_expression()
        while self.current_token == 'or':
            self.next()
            result = f"({result} or {self.parse_and_expression()})"
        return result

    def parse_and_expression(self):
        result = self.parse_unary_expression()
        while self.current_token == 'and':
            self.next()
            result = f"({result} and {self.parse_unary_expression()})"
        return result

    def parse_unary_expression(self):
        if self.current_token == 'not':
            self.next()
            return f"(not {self.parse_term()})"
        else:
            return self.parse_term()

    def parse_term(self):
        term = self.current_token
        self.next()
        return term

def parse_string(input_str):
    parser = Parser(input_str)
    return parser.parse_expression()

input_str = "a implies b iff a and b"
print(parse_string(input_str))

Marcel Ferrari
  • 321
  • 1
  • 8