使用 python 在我的 pemdas 计算器中忽略用户输入中的一元否定

问题描述 投票:0回答:1

我的 Python 内置 PEMDAS 计算器出现问题。我遇到的问题主要是我找不到一种方法让我的代码识别用户输入开头的“-”号。因此,当用户提交像 -5+5 这样的输入时,代码将完全忽略负责处理存在一元否定的情况的块,并简单地执行正常的加法。

最终会得到错误的 -10 输出,而不是将 -5 添加到正 5,这应该给出 0 作为输出

这是我的代码:

import re
import math
import tkinter as tk

PEMDAS = {'+': 4, '-': 4, '*': 5, '/': 5, '%': 9, '√': 7, "^": 8, '(': 1, ')': 1, "[": 2, "]": 2, "{": 3, "}": 3}


def is_valid_expression(expression):
    return re.match(r'^[-^+*/()%.√\d\s\[\]{}]+$', expression) or re.match(r'^\d+%\s*$', expression)

def tokenize(expression):
    separated_expression = ""

    if expression.startswith("-"):
        valid_components = ["-1"] + re.findall(r'(\d+\.\d+|\d+|\.\d+|%|\D)|[-+\*/\(\)\^]+', expression[1:])
        return valid_components
    elif "." not in expression:
        separated_expression = expression.replace(" ", "")
        valid_components = re.findall(r'(\d+|\+|-|\*|/|\(|\)|\[|\]|\{|\^|√|%)', separated_expression)
        return valid_components
    elif "." in expression and " " in expression:
        separated_expression = expression.replace(" ", "")
        valid_components = re.findall(r'(\d+\.\d+|\d+|%|\D)|[-+\*/\(\)\^]+', separated_expression)
        return valid_components
    elif "." in expression and " " not in expression:
        valid_components = re.findall(r'(\d+\.\d+|\d+|\.\d+|%|\D)|[-+\*/\(\)\^]+', expression)
        return valid_components


def shunting_yard(tokens):
    operators = []  # stack that will hold operators as they are processed
    output = []  # list that will hold the postfix expression.

    # iterates through each token
    for token in tokens:
        # if the token is an operator
        if token in '+-*/^√%':
            # it checks the precedence of each operator using the PEMDAS dictionary
            # and compares it to the precedence of operators in the operators stack.
            while operators and operators[-1] in '+-*/√%' and PEMDAS[token] <= PEMDAS[operators[-1]]:
                output.append(operators.pop())  # It pops operators from the stack and appends them to
                # the output list until the conditions for precedence are satisfied,

            operators.append(token)  # then it pushes the current token onto the operators stack.
        elif token in '([{':
            # If the token is an opening parenthesis, square bracket, or curly brace
            operators.append(token)  # it's pushed into the operators stack.
        elif token in ')]}':
            # If the token is a closing parenthesis, square bracket, or curly brace,
            # it pops operators from the operators stack and appends them to the output
            # list until a matching opening parenthesis, square bracket, or curly brace is encountered.
            while operators and operators[-1] not in '([{':
                output.append(operators.pop())
            if operators and operators[-1] in '([{':
                operators.pop()  # Remove the matching opening parenthesis, square bracket, or curly brace
            else:
                raise ValueError("Mismatched parentheses, brackets, or braces")
        else:
            # if it's not an operator or parenthesis, it's an operand, so it's appended to the output list.
            output.append(token)

    # After processing all tokens,
    # any remaining operators in the operators stack are popped and appended to
    # the output list to complete the conversion.
    while operators:
        output.append(operators.pop())
    return output



class Calculator:
    def __init__(self, root):
        self.root = root
        self.root.title("Calculator")
        #priority from each operator including parenthesis and brackets and % operator
        # Create an entry widget to display and input expressions
        self.expression_entry = tk.Entry(root, justify='right', font=('Arial', 22))
        self.expression_entry.grid(row=0, column=0, columnspan=4, padx=10, pady=10)


        #list of tuples named buttons is defined. 
        # Each tuple contains the text to be displayed on the button, 
        # and its row and column position in the grid layout. #setting up the color as well
        buttons = [
            ('7', 1, 0, '#CCCCCC', 'black'), ('8', 1, 1, '#CCCCCC', 'black'), ('9', 1, 2, '#CCCCCC', 'black'), ('/', 1, 3, '#CCCCCC', 'black'),
            ('4', 2, 0, '#CCCCCC', 'black'), ('5', 2, 1, '#CCCCCC', 'black'), ('6', 2, 2, '#CCCCCC', 'black'), ('*', 2, 3, '#CCCCCC', 'black'),
            ('1', 3, 0, '#CCCCCC', 'black'), ('2', 3, 1, '#CCCCCC', 'black'), ('3', 3, 2, '#CCCCCC', 'black'), ('-', 3, 3, '#CCCCCC', 'black'),
            ('0', 4, 0, '#CCCCCC', 'black'), ('.', 4, 1, '#CCCCCC', 'black'), (']', 4, 2, '#CCCCCC', 'black'), ('+', 4, 3, '#CCCCCC', 'black'),
            ('(', 5, 0, '#CCCCCC', 'black'), (')', 5, 1, '#CCCCCC', 'black'), ('[', 5, 2, '#CCCCCC', 'black'), ('%', 5, 3, '#CCCCCC', 'black'),
            ('{', 6, 0, '#CCCCCC', 'black'), ('}', 6, 1, '#CCCCCC', 'black'), ('√', 6, 2, '#CCCCCC', 'black'), ('^', 6, 3, '#CCCCCC', 'black'),
            ('C', 7, 1, '#CCCCCC', 'red'), ('←', 7, 2, '#CCCCCC', 'red'), ("=", 7, 3, '#CCCCCC', 'black'),
        ]

        for (text, row, col, bg_color, fg_color) in buttons:
            button = tk.Button(root, text=text, font=('Arial', 16), width=5, height=2, bg=bg_color, fg=fg_color, command=lambda t=text: self.button_click(t))
            button.grid(row=row, column=col, padx=5, pady=5)       

    def button_click(self, char):
        # try:
            if char == '=': #If the clicked button is '=', it signifies the user wants to evaluate the expression:
                expression = self.expression_entry.get() #It gets the expression from the expression_entry widget.

                #It checks if the expression ends with a number followed by '%' (e.g., "50%"). 
                # If so, it removes the '%' and adds division by 100 to handle percentages.
                if re.match(r'^\d+%\s*$', expression): #
                    expression = expression[:-1]  # Remove the %
                    expression = f"{expression} / 100"  # Add division by 100


                if is_valid_expression(expression): #It checks if the expression is valid using the is_valid_expression method.
                    tokens = tokenize(expression) #Tokenizes the expression
                    postfix = shunting_yard(tokens) #converts tokenized expression into postfix notation
                    evaluation = self.evaluate_postfix(postfix) #evaluates postfix converted expression
                    # updates the result in the expression_entry widget.
                    self.expression_entry.delete(0, tk.END)
                    self.expression_entry.insert(0, str(evaluation))
                else:
                    #If invalid, it shows "Invalid Input".
                    self.expression_entry.delete(0, tk.END)
                    self.expression_entry.insert(0, "Invalid Input")
        #If the clicked button is 'C', it clears the contents of the expression_entry widget.        
            elif char == 'C':
                self.expression_entry.delete(0, tk.END)
            #If the clicked button is '←', 
            #it removes the last character from the text in the expression_entry widget.
            elif char == '←':
                current_text = self.expression_entry.get()
                self.expression_entry.delete(0, tk.END)
                self.expression_entry.insert(0, current_text[:-1])
            #appends symbols depending on EACH OPERATOR INTRODUCED BY THE USER.
            elif char == '^':
                self.expression_entry.insert(tk.END, '^')
            elif char == '√':
                self.expression_entry.insert(tk.END, '√')
            elif char in '+-':
                if self.expression_entry.get():
                    last_char = self.expression_entry.get()[-1]
                    if last_char in '*/':
                        self.expression_entry.delete(len(self.expression_entry.get()) - 1, tk.END)
                        self.expression_entry.insert(tk.END, f' {last_char} {char} ')
                    elif last_char == '-':
                        # Check if the previous character is also a '-'
                        # If yes, treat the current '-' as a unary negation
                        self.expression_entry.delete(len(self.expression_entry.get()) - 1, tk.END)
                        self.expression_entry.insert(tk.END, ' -1 ')
                    else:
                        self.expression_entry.insert(tk.END, char)
                else:  # Handle unary negation at the start of expression
                    self.expression_entry.insert(tk.END, char)  # This is the part to add
            elif char in '*/%':
                if self.expression_entry.get():
                    last_char = self.expression_entry.get()[-1]
                    if last_char in '+-':
                        self.expression_entry.delete(len(self.expression_entry.get()) - 1, tk.END)
                        self.expression_entry.insert(tk.END, f' {last_char} {char} ')
                    else:
                        self.expression_entry.insert(tk.END, char)
            else:
                self.expression_entry.insert(tk.END, char)
        # except(IndexError, ValueError):
        #     self.expression_entry.delete(0, tk.END)
        #     self.expression_entry.insert(0, "Syntax Error")

    def evaluate_postfix(self, tokens):
        stack = []

        for token in tokens:
            if token in '+-*/^√%':
                if token == '-':
                    if not stack or stack[-1] in '({[':
                        stack.append("-1")  # Push the -1 token as a marker for unary 
                    else:
                        b = float(stack.pop())
                        a = float(stack.pop())
                        stack.append(str(a - b))
                elif token == '+':
                    if stack and stack[-1] == '-1' or stack and stack[-2] == '-2':
                        stack.pop()
                        operand = float(stack.pop())
                        a = float(stack.pop())
                        stack.append(str(-operand))
                    elif stack and stack[-1] == '%':
                        stack.pop()
                        b = float(stack.pop())
                        a = float(stack.pop())
                        stack.append(str(a + (a * b * 0.01)))
                    else:
                        b = float(stack.pop())
                        a = float(stack.pop())
                        stack.append(str(a + b))
                elif token == '*':
                    if stack and stack[-1] == '%':
                        stack.pop()
                        b = float(stack.pop())
                        a = float(stack.pop())
                        stack.append(str(a * (b * 0.01)))
                    else:
                        b = float(stack.pop())
                        a = float(stack.pop())
                        stack.append(str(a * b))
                elif token == '/':
                    if stack and stack[-1] == '%':
                        stack.pop()
                        b = float(stack.pop())
                        a = float(stack.pop())
                        stack.append(str(a / (b * 0.01)))
                    else:
                        b = float(stack.pop())
                        a = float(stack.pop())
                        stack.append(str(a / b))
                elif token == '^':
                    if stack and stack[-1] == '%':
                        stack.pop()  # Remove the %
                        b = 0.01  # Convert percentage to fraction
                    else:
                        b = 1.0  # No percentage, use 1.0 as multiplier

                    b_exponent = float(stack.pop())  # The exponent
                    a_base = float(stack.pop())  # The base
                    result = a_base ** b_exponent * b
                    stack.append(str(result))

                elif token == '√':
                    if stack and stack[-1] == '%':
                        stack.pop()  # Remove the %
                        b = 0.01  # Convert percentage to fraction
                    else:
                        b = 1.0  # No percentage, use 1.0 as multiplier

                    a = float(stack.pop())
                    result = math.sqrt(a) * b
                    stack.append(str(result))

                elif token == '%':
                    stack.append(token)
            elif token == '-1':
                if stack:
                    operand = float(stack.pop())
                    stack.append(str(-operand))  # Apply unary negation to the operand
                else:
                    stack.append(token)
            else:
                stack.append(token)

        result = self.evaluate_grouped_parentheses(stack)
        return result




    def evaluate_grouped_parentheses(self, tokens):
        stack = []
        current_group_value = 1.0
        current_group_operator = None
        unary_negation = False  # Flag to track unary negation

        for token in tokens:
            if token in '({[':
                # Start of a new parentheses group
                if current_group_operator:
                    raise ValueError("Consecutive group operators are not allowed")
                current_group_operator = token
                stack.append(token)
            elif token in ')}]':
                # End of a parentheses group, evaluate the group
                if not current_group_operator:
                    raise ValueError("Unmatched closing group operator")
                if current_group_operator == '{' and token == '}':
                    # Evaluate the contents of the curly braces group
                    group_contents = stack.pop()  # Get the contents of the curly braces 
                    group_tokens = tokenize(group_contents)  # Tokenize the contents
                    group_postfix = shunting_yard(group_tokens)  # Convert to postfix 
                    group_result = self.evaluate_postfix(group_postfix)  # Evaluate the 
                    stack.append(str(group_result))  # Replace the group contents with the 
                elif current_group_operator == '[' and token == ']':
                    # Evaluate the contents of the square brackets group
                    group_contents = stack.pop()  # Get the contents of the square brackets 
                    group_tokens = tokenize(group_contents)  # Tokenize the contents
                    group_postfix = shunting_yard(group_tokens)  # Convert to postfix 
                    group_result = self.evaluate_postfix(group_postfix)  # Evaluate the 
                    stack.append(str(group_result))  # Replace the group contents with the 
                else:
                    raise ValueError("Mismatched group operators")
                current_group_operator = None
            else:
                if current_group_operator:
                    stack.append(token)
                else:
                    if unary_negation:
                        unary_negation = False
                        stack.append(str(-float(token)))
                    else:
                        stack.append(token)

        # Calculate the result by multiplying numeric values left in the stack
        numeric_values = [float(value) for value in stack if value not in '({[}])']
        result = 1.0
        for value in numeric_values:
            result *= value

        return result * current_group_value





def main():
        root = tk.Tk()
        calculator = Calculator(root)
        root.mainloop()
if __name__ == '__main__':
    main()
python calculator postfix-notation shunting-yard
1个回答
0
投票

您需要添加代码以将一元运算符与二元运算符分开处理。 '-' 特别棘手,因为它可以是一元或二元运算符,确定哪个是上下文相关的。

P
表示前缀序列,可以是简单的数字(如 5),也可以是前缀表达式(如 -5 或 √9),或括号表达式(3+4),甚至是具有多个前缀运算符的情况 -√9。

您的表达式的形式为

P
P op P
P op P op P
等,其中
op
是二元运算符。这允许像
4 * -5
.

这样的表达

这里的关键部分是您将遇到的第一件事是前缀序列。所以主循环需要像

def E:
  while more_tokens
    P() // read a prefix sequence

    if next_token in '+-*/%': // just binary ops
       process_binary_operator
    else if token is EOF or closing bracket
       return
    else
       error

def P:
    while next_token in '-√': // prefix ops
       process_unary_op
    if next_token in '([{':
       E() // call main E function recursively 
       check next_token is matching closing brace
    else if next_token is digit
       output.append(token)

我遵循了 Theodore Norvell 的调车场算法,网址为 https://www.engr.mun.ca/~theo/Misc/exp_parsing.htm

© www.soinside.com 2019 - 2024. All rights reserved.