Repository: TheAlgorithms/Python

Issue:
Need to support for unary operator in postfix evaluation.
### Feature description

As mentioned in #8724, we have two files for evaluating postfix expressions. But currently both of them only work with binary operators and will raise an exception when an unary operator is given.

example:
```python
>>> res = evaluate_postfix(["2", "-", "3", "+"])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File ".../stacks/evaluate_postfix_notations.py", line 31, in evaluate_postfix
    b, a = stack.pop(), stack.pop()
IndexError: pop from empty list
>>> 
```
`2-3+` is a postfix notation for `-2+3` infix and should result in `1`.

Duplicate solutions for postfix notation evaluation.
### Feature description

Hi,
I noticed there are two files for evaluating postfix notation which are:

1. [...stacks/evaluate_postfix_notations.py](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/evaluate_postfix_notations.py)
2. [...stacks/postfix_evaluation.py](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/postfix_evaluation.py)

The latter has bunch of print statements explaining the steps which is pretty useful for educational purposes but not in production. (It can be changed to have a `verbose=True` parameter for prints for example.)

Is having these two files intentional or we should combine them?


Discussion:
I think there are three main ways that we can implement this, but none of them is a perfect solution:

1. We could distinguish (unary) negation operators from (binary) subtraction operators, perhaps by using different symbols for each. Otherwise, the algorithm would treat all negative signs as subtraction and try to pop two operands off the stack when it should only pop one (which is what it currently does). Of course, it's inconvenient to force users to make this distinction when entering input.
2. We could sidestep negation operators entirely by always having `0 - x` instead of `-x`. Thus, we'd have `02-3+` for `0-2+3`, which would correctly evaluate to `1` using the current algorithm. Of course, this would also be inconvenient to enforce.
3. We could try to disambiguate negation from subtraction using the contents of the stack. For instance, `2-3+` raises an exception because the algorithm thinks that the `-` is subtraction and tries to pop two operands from the stack when there's only one. To fix this, we could have it so that `-` is treated as negation when there's only one operand on the stack. There may be edge cases where this doesn't work, but I'm not sure.

Let me know if you've thought of any other potential solutions.
Can you please say how it would be inconvenient to enforce 2.? We can insert 0 at the beginning of the string if the first character is not a number and continue the postfix conversion, can't we? Or am I wrong? 
> I think there are three main ways that we can implement this, but none of them is a perfect solution:
> 
> 1. We could distinguish (unary) negation operators from (binary) subtraction operators, perhaps by using different symbols for each. Otherwise, the algorithm would treat all negative signs as subtraction and try to pop two operands off the stack when it should only pop one (which is what it currently does). Of course, it's inconvenient to force users to make this distinction when entering input.
> 2. We could sidestep negation operators entirely by always having `0 - x` instead of `-x`. Thus, we'd have `02-3+` for `0-2+3`, which would correctly evaluate to `1` using the current algorithm. Of course, this would also be inconvenient to enforce.
> 3. We could try to disambiguate negation from subtraction using the contents of the stack. For instance, `2-3+` raises an exception because the algorithm thinks that the `-` is subtraction and tries to pop two operands from the stack when there's only one. To fix this, we could have it so that `-` is treated as negation when there's only one operand on the stack. There may be edge cases where this doesn't work, but I'm not sure.
> 
> Let me know if you've thought of any other potential solutions.

For the same reason you mentioned, I'm not satisfied with the first option as we normally do not differentiate between unary minus and binary minus operator in a math expression. I think last option is reasonable and it's what @rohan472000 has suggested in #8758 .
> Can you please say how it would be inconvenient to enforce 2.? We can insert 0 at the beginning of the string if the first character is not a number and continue the postfix conversion, can't we? Or am I wrong?

@niranjank2022 We're not _converting_ the string to postfix notation. Rather, this file evaluates a postfix string, so the input is already in postfix notation, not infix notation. If there's a unary negation operator, it definitely won't be at the start of the string, so it's not immediately obvious where to insert the 0.
> > I think there are three main ways that we can implement this, but none of them is a perfect solution:
> > 
> > 1. We could distinguish (unary) negation operators from (binary) subtraction operators, perhaps by using different symbols for each. Otherwise, the algorithm would treat all negative signs as subtraction and try to pop two operands off the stack when it should only pop one (which is what it currently does). Of course, it's inconvenient to force users to make this distinction when entering input.
> > 2. We could sidestep negation operators entirely by always having `0 - x` instead of `-x`. Thus, we'd have `02-3+` for `0-2+3`, which would correctly evaluate to `1` using the current algorithm. Of course, this would also be inconvenient to enforce.
> > 3. We could try to disambiguate negation from subtraction using the contents of the stack. For instance, `2-3+` raises an exception because the algorithm thinks that the `-` is subtraction and tries to pop two operands from the stack when there's only one. To fix this, we could have it so that `-` is treated as negation when there's only one operand on the stack. There may be edge cases where this doesn't work, but I'm not sure.
> > 
> > Let me know if you've thought of any other potential solutions.
> 
> For the same reason you mentioned, I'm not satisfied with the first option as we normally do not differentiate between unary minus and binary minus operator in a math expression. I think last option is reasonable and it's what @rohan472000 has suggested in #8758 .

@amirsoroush I think option 3 is the most reasonable as well, but I'm not completely sure if this can always be generalized to other unary operators. I believe this method would also work for unary functions such as `sin(x)`, but I'm not sure if this works for, for example, the factorial function `x!`.
I highly doubt that having two files was intentional, especially since their algorithms appear to be basically identical. My guess is that a duplicate implementation was unknowingly approved and merged. Personally, I'm in favor of combining the two implementations into a single file.

Resolving patch:
diff --git a/data_structures/stacks/evaluate_postfix_notations.py b/data_structures/stacks/evaluate_postfix_notations.py
deleted file mode 100644
--- a/data_structures/stacks/evaluate_postfix_notations.py
+++ /dev/null
@@ -1,52 +0,0 @@
-"""
-The Reverse Polish Nation also known as Polish postfix notation
-or simply postfix notation.
-https://en.wikipedia.org/wiki/Reverse_Polish_notation
-Classic examples of simple stack implementations
-Valid operators are +, -, *, /.
-Each operand may be an integer or another expression.
-"""
-from __future__ import annotations
-
-from typing import Any
-
-
-def evaluate_postfix(postfix_notation: list) -> int:
-    """
-    >>> evaluate_postfix(["2", "1", "+", "3", "*"])
-    9
-    >>> evaluate_postfix(["4", "13", "5", "/", "+"])
-    6
-    >>> evaluate_postfix([])
-    0
-    """
-    if not postfix_notation:
-        return 0
-
-    operations = {"+", "-", "*", "/"}
-    stack: list[Any] = []
-
-    for token in postfix_notation:
-        if token in operations:
-            b, a = stack.pop(), stack.pop()
-            if token == "+":
-                stack.append(a + b)
-            elif token == "-":
-                stack.append(a - b)
-            elif token == "*":
-                stack.append(a * b)
-            else:
-                if a * b < 0 and a % b != 0:
-                    stack.append(a // b + 1)
-                else:
-                    stack.append(a // b)
-        else:
-            stack.append(int(token))
-
-    return stack.pop()
-
-
-if __name__ == "__main__":
-    import doctest
-
-    doctest.testmod()
diff --git a/data_structures/stacks/postfix_evaluation.py b/data_structures/stacks/postfix_evaluation.py
--- a/data_structures/stacks/postfix_evaluation.py
+++ b/data_structures/stacks/postfix_evaluation.py
@@ -1,4 +1,11 @@
 """
+Reverse Polish Nation is also known as Polish postfix notation or simply postfix
+notation.
+https://en.wikipedia.org/wiki/Reverse_Polish_notation
+Classic examples of simple stack implementations.
+Valid operators are +, -, *, /.
+Each operand may be an integer or another expression.
+
 Output:
 
 Enter a Postfix Equation (space separated) = 5 6 9 * +
@@ -17,52 +24,177 @@
         Result =  59
 """
 
-import operator as op
+# Defining valid unary operator symbols
+UNARY_OP_SYMBOLS = ("-", "+")
+
+# operators & their respective operation
+OPERATORS = {
+    "^": lambda p, q: p**q,
+    "*": lambda p, q: p * q,
+    "/": lambda p, q: p / q,
+    "+": lambda p, q: p + q,
+    "-": lambda p, q: p - q,
+}
+
+
+def parse_token(token: str | float) -> float | str:
+    """
+    Converts the given data to the appropriate number if it is indeed a number, else
+    returns the data as it is with a False flag. This function also serves as a check
+    of whether the input is a number or not.
+
+    Parameters
+    ----------
+    token: The data that needs to be converted to the appropriate operator or number.
+
+    Returns
+    -------
+    float or str
+        Returns a float if `token` is a number or a str if `token` is an operator
+    """
+    if token in OPERATORS:
+        return token
+    try:
+        return float(token)
+    except ValueError:
+        msg = f"{token} is neither a number nor a valid operator"
+        raise ValueError(msg)
+
+
+def evaluate(post_fix: list[str], verbose: bool = False) -> float:
+    """
+    Evaluate postfix expression using a stack.
+    >>> evaluate(["0"])
+    0.0
+    >>> evaluate(["-0"])
+    -0.0
+    >>> evaluate(["1"])
+    1.0
+    >>> evaluate(["-1"])
+    -1.0
+    >>> evaluate(["-1.1"])
+    -1.1
+    >>> evaluate(["2", "1", "+", "3", "*"])
+    9.0
+    >>> evaluate(["2", "1.9", "+", "3", "*"])
+    11.7
+    >>> evaluate(["2", "-1.9", "+", "3", "*"])
+    0.30000000000000027
+    >>> evaluate(["4", "13", "5", "/", "+"])
+    6.6
+    >>> evaluate(["2", "-", "3", "+"])
+    1.0
+    >>> evaluate(["-4", "5", "*", "6", "-"])
+    -26.0
+    >>> evaluate([])
+    0
+    >>> evaluate(["4", "-", "6", "7", "/", "9", "8"])
+    Traceback (most recent call last):
+    ...
+    ArithmeticError: Input is not a valid postfix expression
+
+    Parameters
+    ----------
+    post_fix:
+        The postfix expression is tokenized into operators and operands and stored
+        as a Python list
 
+    verbose:
+        Display stack contents while evaluating the expression if verbose is True
 
-def solve(post_fix):
+    Returns
+    -------
+    float
+        The evaluated value
+    """
+    if not post_fix:
+        return 0
+    # Checking the list to find out whether the postfix expression is valid
+    valid_expression = [parse_token(token) for token in post_fix]
+    if verbose:
+        # print table header
+        print("Symbol".center(8), "Action".center(12), "Stack", sep=" | ")
+        print("-" * (30 + len(post_fix)))
     stack = []
-    div = lambda x, y: int(x / y)  # noqa: E731 integer division operation
-    opr = {
-        "^": op.pow,
-        "*": op.mul,
-        "/": div,
-        "+": op.add,
-        "-": op.sub,
-    }  # operators & their respective operation
-
-    # print table header
-    print("Symbol".center(8), "Action".center(12), "Stack", sep=" | ")
-    print("-" * (30 + len(post_fix)))
-
-    for x in post_fix:
-        if x.isdigit():  # if x in digit
+    for x in valid_expression:
+        if x not in OPERATORS:
             stack.append(x)  # append x to stack
-            # output in tabular format
-            print(x.rjust(8), ("push(" + x + ")").ljust(12), ",".join(stack), sep=" | ")
-        else:
+            if verbose:
+                # output in tabular format
+                print(
+                    f"{x}".rjust(8),
+                    f"push({x})".ljust(12),
+                    stack,
+                    sep=" | ",
+                )
+            continue
+        # If x is operator
+        # If only 1 value is inside the stack and + or - is encountered
+        # then this is unary + or - case
+        if x in UNARY_OP_SYMBOLS and len(stack) < 2:
             b = stack.pop()  # pop stack
+            if x == "-":
+                b *= -1  # negate b
+            stack.append(b)
+            if verbose:
+                # output in tabular format
+                print(
+                    "".rjust(8),
+                    f"pop({b})".ljust(12),
+                    stack,
+                    sep=" | ",
+                )
+                print(
+                    str(x).rjust(8),
+                    f"push({x}{b})".ljust(12),
+                    stack,
+                    sep=" | ",
+                )
+            continue
+        b = stack.pop()  # pop stack
+        if verbose:
             # output in tabular format
-            print("".rjust(8), ("pop(" + b + ")").ljust(12), ",".join(stack), sep=" | ")
+            print(
+                "".rjust(8),
+                f"pop({b})".ljust(12),
+                stack,
+                sep=" | ",
+            )
 
-            a = stack.pop()  # pop stack
+        a = stack.pop()  # pop stack
+        if verbose:
             # output in tabular format
-            print("".rjust(8), ("pop(" + a + ")").ljust(12), ",".join(stack), sep=" | ")
-
-            stack.append(
-                str(opr[x](int(a), int(b)))
-            )  # evaluate the 2 values popped from stack & push result to stack
+            print(
+                "".rjust(8),
+                f"pop({a})".ljust(12),
+                stack,
+                sep=" | ",
+            )
+        # evaluate the 2 values popped from stack & push result to stack
+        stack.append(OPERATORS[x](a, b))  # type: ignore[index]
+        if verbose:
             # output in tabular format
             print(
-                x.rjust(8),
-                ("push(" + a + x + b + ")").ljust(12),
-                ",".join(stack),
+                f"{x}".rjust(8),
+                f"push({a}{x}{b})".ljust(12),
+                stack,
                 sep=" | ",
             )
-
-    return int(stack[0])
+    # If everything is executed correctly, the stack will contain
+    # only one element which is the result
+    if len(stack) != 1:
+        raise ArithmeticError("Input is not a valid postfix expression")
+    return float(stack[0])
 
 
 if __name__ == "__main__":
-    Postfix = input("\n\nEnter a Postfix Equation (space separated) = ").split(" ")
-    print("\n\tResult = ", solve(Postfix))
+    # Create a loop so that the user can evaluate postfix expressions multiple times
+    while True:
+        expression = input("Enter a Postfix Expression (space separated): ").split(" ")
+        prompt = "Do you want to see stack contents while evaluating? [y/N]: "
+        verbose = input(prompt).strip().lower() == "y"
+        output = evaluate(expression, verbose)
+        print("Result = ", output)
+        prompt = "Do you want to enter another expression? [y/N]: "
+        if input(prompt).strip().lower() != "y":
+            break


Test patch: