-
Notifications
You must be signed in to change notification settings - Fork 0
/
calculator.py
157 lines (114 loc) · 4.73 KB
/
calculator.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import re
from tokens import Operand, Operator
from exception import ExpressionError, ParenthesesError, OperatorError, OperandError
class LexicalAnalyzer:
"""Find wrong expression and change expression to postfix"""
def __init__(self, expression=None):
"""Initialize LexicalAnalyzer
Args:
expression (str): expression to calculate
"""
self.expression = expression if expression else self.get_expression()
self.check_expression()
def get_expression(self):
"""get expression from user"""
self.expression = input("Enter an expression: ").replace(" ", "")
return self.expression
def check_expression(self) -> None:
"""check expression is valid or not
Raises:
ExpressionError: When expression is not valid
OperatorError: When operators are overlapped
ParenthesesError: When Parentheses are not matched
OperandError: When points are overlapped
"""
expression = self.expression.replace(" ", "")
pattern = re.compile("[^\d.\/\+\*\-\(\)]")
pattern2 = re.compile(r"([\+\-\*\/]){2}")
if pattern.search(expression):
raise ExpressionError("Unexpected character")
elif pattern2.search(expression):
raise OperatorError("Overlap operator")
elif re.search(r"[\+\-\*\/\(\)].*(-)[0-9]", expression):
raise OperandError("Negative number")
elif expression.startswith(("*", "/", "+", "-", ")")) or expression.endswith(("*", "/", "+", "-", "(")):
raise ExpressionError("Wrong expression")
elif expression.count("(") != expression.count(")"):
raise ParenthesesError("Wrong parentheses match")
elif expression.find("(") + expression.find(")") >= 0:
x = 0
for i in expression:
if i == "(":
x += 1
elif i == ")":
x -= 1
if x < 0:
raise ParenthesesError("Wrong Parentheses")
elif expression.find("..") >= 0:
raise OperandError("Double dot")
def to_postfix(self) -> list:
"""make expression to postfix
Returns:
list: postfix expression list contains operand and operator objects
"""
stack = []
postfix = []
expression = re.findall(r"[0-9.]+|[\+\-\*\/\(\)]", self.expression)
for token in expression:
# 피연산자 일 때에는 postfix에 추가
if Operand.is_valid(token):
postfix.append(Operand(token))
# "("를 만나면 stack에 추가
elif token == "(":
stack.append(token)
# ")"를 만나면 stack에서 "("를 만날 때까지 pop
elif token == ")":
while stack and stack[-1] != "(":
postfix.append(stack.pop())
# "(" stack에서 제거
stack.pop()
# 연산자 일 때에는 stack에 추가
else:
while stack and stack[-1]!='('and Operator.check_priority(token) <= Operator.check_priority(stack[-1]):
postfix.append(stack.pop())
stack.append(Operator(token))
while stack:
postfix.append(stack.pop())
return postfix
def calculate(expression) -> Operand:
"""Calculate postfix expression
Returns:
Returns:
Returns:
Operand: result of expression
"""
stack = []
for token in expression:
if type(token) == Operand:
stack.append(token)
continue
if len(stack) < 2:
stack.insert(0, Operand(0))
op1, op2 = stack.pop(), stack.pop()
if token.value == '+':
stack.append(op2 + op1)
elif token.value == '-':
stack.append(op2 - op1)
elif token.value == '*':
stack.append(op2 * op1)
elif token.value == '/':
if op1.value == 0:
raise ZeroDivisionError("Zero Division Error")
stack.append(op2 / op1)
else:
stack.append(token)
return stack.pop()
# test code 작성하기
# input spec에서 정의한 것들을 테스트케이스로 보여줘야 함
# input spec에 정의한 것들은 모두 실행할 수 있어야 하고
# test case 를 꼼꼼하게 작성!! => 예외처리하는 것에 대한 통찰을 기를 수 있음
if __name__ == "__main__":
calc = LexicalAnalyzer()
post_expression = calc.to_postfix()
print(f"Postfix expression : {post_expression}")
print(f"result : {calculate(post_expression)}")