|
1 | 1 | """
|
| 2 | +Reverse Polish Nation is also known as Polish postfix notation or simply postfix |
| 3 | +notation. |
| 4 | +https://en.wikipedia.org/wiki/Reverse_Polish_notation |
| 5 | +Classic examples of simple stack implementations. |
| 6 | +Valid operators are +, -, *, /. |
| 7 | +Each operand may be an integer or another expression. |
| 8 | +
|
2 | 9 | Output:
|
3 | 10 |
|
4 | 11 | Enter a Postfix Equation (space separated) = 5 6 9 * +
|
|
17 | 24 | Result = 59
|
18 | 25 | """
|
19 | 26 |
|
20 |
| -import operator as op |
| 27 | +# Defining valid unary operator symbols |
| 28 | +UNARY_OP_SYMBOLS = ("-", "+") |
| 29 | + |
| 30 | +# operators & their respective operation |
| 31 | +OPERATORS = { |
| 32 | + "^": lambda p, q: p**q, |
| 33 | + "*": lambda p, q: p * q, |
| 34 | + "/": lambda p, q: p / q, |
| 35 | + "+": lambda p, q: p + q, |
| 36 | + "-": lambda p, q: p - q, |
| 37 | +} |
| 38 | + |
| 39 | + |
| 40 | +def parse_token(token: str | float) -> float | str: |
| 41 | + """ |
| 42 | + Converts the given data to the appropriate number if it is indeed a number, else |
| 43 | + returns the data as it is with a False flag. This function also serves as a check |
| 44 | + of whether the input is a number or not. |
| 45 | +
|
| 46 | + Parameters |
| 47 | + ---------- |
| 48 | + token: The data that needs to be converted to the appropriate operator or number. |
| 49 | +
|
| 50 | + Returns |
| 51 | + ------- |
| 52 | + float or str |
| 53 | + Returns a float if `token` is a number or a str if `token` is an operator |
| 54 | + """ |
| 55 | + if token in OPERATORS: |
| 56 | + return token |
| 57 | + try: |
| 58 | + return float(token) |
| 59 | + except ValueError: |
| 60 | + msg = f"{token} is neither a number nor a valid operator" |
| 61 | + raise ValueError(msg) |
| 62 | + |
| 63 | + |
| 64 | +def evaluate(post_fix: list[str], verbose: bool = False) -> float: |
| 65 | + """ |
| 66 | + Evaluate postfix expression using a stack. |
| 67 | + >>> evaluate(["0"]) |
| 68 | + 0.0 |
| 69 | + >>> evaluate(["-0"]) |
| 70 | + -0.0 |
| 71 | + >>> evaluate(["1"]) |
| 72 | + 1.0 |
| 73 | + >>> evaluate(["-1"]) |
| 74 | + -1.0 |
| 75 | + >>> evaluate(["-1.1"]) |
| 76 | + -1.1 |
| 77 | + >>> evaluate(["2", "1", "+", "3", "*"]) |
| 78 | + 9.0 |
| 79 | + >>> evaluate(["2", "1.9", "+", "3", "*"]) |
| 80 | + 11.7 |
| 81 | + >>> evaluate(["2", "-1.9", "+", "3", "*"]) |
| 82 | + 0.30000000000000027 |
| 83 | + >>> evaluate(["4", "13", "5", "/", "+"]) |
| 84 | + 6.6 |
| 85 | + >>> evaluate(["2", "-", "3", "+"]) |
| 86 | + 1.0 |
| 87 | + >>> evaluate(["-4", "5", "*", "6", "-"]) |
| 88 | + -26.0 |
| 89 | + >>> evaluate([]) |
| 90 | + 0 |
| 91 | + >>> evaluate(["4", "-", "6", "7", "/", "9", "8"]) |
| 92 | + Traceback (most recent call last): |
| 93 | + ... |
| 94 | + ArithmeticError: Input is not a valid postfix expression |
| 95 | +
|
| 96 | + Parameters |
| 97 | + ---------- |
| 98 | + post_fix: |
| 99 | + The postfix expression is tokenized into operators and operands and stored |
| 100 | + as a Python list |
21 | 101 |
|
| 102 | + verbose: |
| 103 | + Display stack contents while evaluating the expression if verbose is True |
22 | 104 |
|
23 |
| -def solve(post_fix): |
| 105 | + Returns |
| 106 | + ------- |
| 107 | + float |
| 108 | + The evaluated value |
| 109 | + """ |
| 110 | + if not post_fix: |
| 111 | + return 0 |
| 112 | + # Checking the list to find out whether the postfix expression is valid |
| 113 | + valid_expression = [parse_token(token) for token in post_fix] |
| 114 | + if verbose: |
| 115 | + # print table header |
| 116 | + print("Symbol".center(8), "Action".center(12), "Stack", sep=" | ") |
| 117 | + print("-" * (30 + len(post_fix))) |
24 | 118 | stack = []
|
25 |
| - div = lambda x, y: int(x / y) # noqa: E731 integer division operation |
26 |
| - opr = { |
27 |
| - "^": op.pow, |
28 |
| - "*": op.mul, |
29 |
| - "/": div, |
30 |
| - "+": op.add, |
31 |
| - "-": op.sub, |
32 |
| - } # operators & their respective operation |
33 |
| - |
34 |
| - # print table header |
35 |
| - print("Symbol".center(8), "Action".center(12), "Stack", sep=" | ") |
36 |
| - print("-" * (30 + len(post_fix))) |
37 |
| - |
38 |
| - for x in post_fix: |
39 |
| - if x.isdigit(): # if x in digit |
| 119 | + for x in valid_expression: |
| 120 | + if x not in OPERATORS: |
40 | 121 | stack.append(x) # append x to stack
|
41 |
| - # output in tabular format |
42 |
| - print(x.rjust(8), ("push(" + x + ")").ljust(12), ",".join(stack), sep=" | ") |
43 |
| - else: |
| 122 | + if verbose: |
| 123 | + # output in tabular format |
| 124 | + print( |
| 125 | + f"{x}".rjust(8), |
| 126 | + f"push({x})".ljust(12), |
| 127 | + stack, |
| 128 | + sep=" | ", |
| 129 | + ) |
| 130 | + continue |
| 131 | + # If x is operator |
| 132 | + # If only 1 value is inside the stack and + or - is encountered |
| 133 | + # then this is unary + or - case |
| 134 | + if x in UNARY_OP_SYMBOLS and len(stack) < 2: |
44 | 135 | b = stack.pop() # pop stack
|
| 136 | + if x == "-": |
| 137 | + b *= -1 # negate b |
| 138 | + stack.append(b) |
| 139 | + if verbose: |
| 140 | + # output in tabular format |
| 141 | + print( |
| 142 | + "".rjust(8), |
| 143 | + f"pop({b})".ljust(12), |
| 144 | + stack, |
| 145 | + sep=" | ", |
| 146 | + ) |
| 147 | + print( |
| 148 | + str(x).rjust(8), |
| 149 | + f"push({x}{b})".ljust(12), |
| 150 | + stack, |
| 151 | + sep=" | ", |
| 152 | + ) |
| 153 | + continue |
| 154 | + b = stack.pop() # pop stack |
| 155 | + if verbose: |
45 | 156 | # output in tabular format
|
46 |
| - print("".rjust(8), ("pop(" + b + ")").ljust(12), ",".join(stack), sep=" | ") |
| 157 | + print( |
| 158 | + "".rjust(8), |
| 159 | + f"pop({b})".ljust(12), |
| 160 | + stack, |
| 161 | + sep=" | ", |
| 162 | + ) |
47 | 163 |
|
48 |
| - a = stack.pop() # pop stack |
| 164 | + a = stack.pop() # pop stack |
| 165 | + if verbose: |
49 | 166 | # output in tabular format
|
50 |
| - print("".rjust(8), ("pop(" + a + ")").ljust(12), ",".join(stack), sep=" | ") |
51 |
| - |
52 |
| - stack.append( |
53 |
| - str(opr[x](int(a), int(b))) |
54 |
| - ) # evaluate the 2 values popped from stack & push result to stack |
| 167 | + print( |
| 168 | + "".rjust(8), |
| 169 | + f"pop({a})".ljust(12), |
| 170 | + stack, |
| 171 | + sep=" | ", |
| 172 | + ) |
| 173 | + # evaluate the 2 values popped from stack & push result to stack |
| 174 | + stack.append(OPERATORS[x](a, b)) # type: ignore[index] |
| 175 | + if verbose: |
55 | 176 | # output in tabular format
|
56 | 177 | print(
|
57 |
| - x.rjust(8), |
58 |
| - ("push(" + a + x + b + ")").ljust(12), |
59 |
| - ",".join(stack), |
| 178 | + f"{x}".rjust(8), |
| 179 | + f"push({a}{x}{b})".ljust(12), |
| 180 | + stack, |
60 | 181 | sep=" | ",
|
61 | 182 | )
|
62 |
| - |
63 |
| - return int(stack[0]) |
| 183 | + # If everything is executed correctly, the stack will contain |
| 184 | + # only one element which is the result |
| 185 | + if len(stack) != 1: |
| 186 | + raise ArithmeticError("Input is not a valid postfix expression") |
| 187 | + return float(stack[0]) |
64 | 188 |
|
65 | 189 |
|
66 | 190 | if __name__ == "__main__":
|
67 |
| - Postfix = input("\n\nEnter a Postfix Equation (space separated) = ").split(" ") |
68 |
| - print("\n\tResult = ", solve(Postfix)) |
| 191 | + # Create a loop so that the user can evaluate postfix expressions multiple times |
| 192 | + while True: |
| 193 | + expression = input("Enter a Postfix Expression (space separated): ").split(" ") |
| 194 | + prompt = "Do you want to see stack contents while evaluating? [y/N]: " |
| 195 | + verbose = input(prompt).strip().lower() == "y" |
| 196 | + output = evaluate(expression, verbose) |
| 197 | + print("Result = ", output) |
| 198 | + prompt = "Do you want to enter another expression? [y/N]: " |
| 199 | + if input(prompt).strip().lower() != "y": |
| 200 | + break |
0 commit comments