I implemented the Shunting-yard algorithm in Python, I have experience in C# but I'm looking for comments making this more pythonic. I'm also be interested if these methods would be better as classmethods instead of staticmethods.
from collections import deque
import re
class ShuntingYardParser:
"""Converts an infix expression to a postfix expression
using the Shunting-yard algorithm"""
_operatorPrecedence = {"*": 3,
"/": 3,
"+": 2,
"-": 2}
@staticmethod
def _tokenize(expression):
tokenizer = re.compile(r'\s*([()+*/-]|\d+)')
index = 0
tokens = []
while index < len(expression):
match = tokenizer.match(expression, index)
if match is None:
raise ValueError("Syntax error.")
tokens.append(match.group(1))
index = match.end()
return tokens
@staticmethod
def _is_operand(token):
try:
int(token)
return True
except ValueError:
return False
@staticmethod
def _is_operator(token):
return token in ShuntingYardParser._operatorPrecedence
@staticmethod
def parse(infix_expression):
"""Parses the infix expression and returns the postfix expression"""
if not isinstance(infix_expression, str):
raise TypeError("Input is not of type string")
output = list()
operators = deque()
tokens = ShuntingYardParser._tokenize(infix_expression)
for token in tokens:
if ShuntingYardParser._is_operand(token):
output.append(token)
elif ShuntingYardParser._is_operator(token):
current_operator_precedence = ShuntingYardParser._operatorPrecedence[token]
while (operators
and operators[-1] != "("
and ShuntingYardParser._operatorPrecedence[operators[-1]] <= current_operator_precedence):
output.append(operators.pop())
operators.append(token)
elif token == "(":
operators.append(token)
elif token == ")":
# Pop all the operators in front of the "(".
while operators and operators[-1] != "(":
output.append(operators.pop())
# The previous operation would have removed all the operators
# because there is no matching opening parenthesises.
if not operators:
raise ValueError("Non matching parenthesises in the input.")
# Remove matching parenthesis.
operators.pop()
for operator in operators:
# If there are still opening parenthesises in the stack this means
# we haven't found a matching closing one.
if operator == "(":
raise ValueError("Non matching parenthesises in the input.")
output.append(operator)
return output
if __name__ == "__main__":
while True:
action = input("Enter infix expression:")
try:
postfix_notation = ShuntingYardParser.parse(action)
print(postfix_notation)
except ValueError as e:
print(e.args)
Unit tests:
from unittest import TestCase
from ShuntingYardParser import ShuntingYardParser
class TestShuntingYardParser(TestCase):
def test_single_number(self):
# arrange & act
output = ShuntingYardParser.parse("5")
# assert
self.assertEqual(output, list("5"))
def test_non_string_input(self):
# arrange & act & assert
self.assertRaises(TypeError, ShuntingYardParser.parse, 5)
def test_matching_parenthesises(self):
# arrange & act
output = ShuntingYardParser.parse("(5+2)")
# assert
self.assertEqual(output, ["5", "2", "+"])
def test_non_matching_parenthesises_start(self):
# arrange & act
self.assertRaises(ValueError, ShuntingYardParser.parse, "((5+2)")
def test_non_matching_parenthesises_end(self):
# arrange & act
self.assertRaises(ValueError, ShuntingYardParser.parse, "(5+2))")