-
Notifications
You must be signed in to change notification settings - Fork 0
/
calculator_services.py
246 lines (229 loc) · 11.6 KB
/
calculator_services.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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# ================================================
# Calculator Services
# ================================================
from typing import Optional, Tuple, Union, Dict, Callable
from calculator_domain import (NonZeroDigit, PendingOp, CalculatorMathOp, Number,
DigitAccumulator, ZeroStateData, AccumulatorStateData, ComputedStateData,
ErrorStateData, MathOperationError, MathOperationResult, CalculatorInput)
import math
class CalculatorServices:
def append_to_accumulator(self, max_len: int, accumulator: DigitAccumulator, append_ch: str) -> DigitAccumulator:
"""
Appends a character to the accumulator if it doesn't exceed the maximum length.
"""
if len(accumulator.strip()) >= max_len:
print("Max length reached; ignoring new input.")
return accumulator.strip() # Ignore new input if length exceeds max
return accumulator.strip() + append_ch
def accumulate_non_zero_digit(self, max_len: int):
"""
Returns a function that appends a non-zero digit to the accumulator.
"""
def inner(digit: NonZeroDigit, accumulator: DigitAccumulator) -> DigitAccumulator:
append_ch = str(digit)
new_accumulator = self.append_to_accumulator(max_len, accumulator.strip(), append_ch)
return new_accumulator
return inner
def accumulate_zero(self, max_len: int):
"""
Returns a function that appends a zero to the accumulator.
"""
def inner(accumulator: DigitAccumulator) -> DigitAccumulator:
return self.append_to_accumulator(max_len, accumulator.strip(), "0")
return inner
def accumulate_separator(self, max_len: int):
"""
Returns a function that appends a decimal separator to the accumulator.
"""
def inner(accumulator: DigitAccumulator) -> DigitAccumulator:
if '.' in accumulator:
return accumulator
else:
append_ch = "0." if accumulator.strip() == "" else "."
new_accumulator = self.append_to_accumulator(max_len, accumulator.strip(), append_ch)
return new_accumulator
return inner
def get_number_from_accumulator(self, accumulator_state_data) -> Number:
"""
Converts the digits in the accumulator to a float number.
"""
try:
return float(accumulator_state_data.digits)
except ValueError:
return 0.0
def do_math_operation(self, op: CalculatorMathOp, f1: Number, f2: Number, memory: DigitAccumulator) -> MathOperationResult:
"""
Performs a mathematical operation based on the provided operator.
"""
if op == CalculatorMathOp.PERCENT:
return MathOperationResult(success=f1/100)
elif op == CalculatorMathOp.ROOT:
try: d = math.sqrt(f1)
except ValueError: d = None
if d is not None:
return MathOperationResult(success=d)
else:
return MathOperationResult(failure=MathOperationError.MATHDOMAINERROR)
elif op == CalculatorMathOp.INVERSE:
if f1 == 0:
return MathOperationResult(failure=MathOperationError.DIVIDEBYZERO)
return MathOperationResult(success=f2 / f1)
elif op == CalculatorMathOp.ADD:
return MathOperationResult(success=f1 + f2)
elif op == CalculatorMathOp.SUBTRACT:
return MathOperationResult(success=f1 - f2)
elif op == CalculatorMathOp.MULTIPLY:
return MathOperationResult(success=f1 * f2)
elif op == CalculatorMathOp.DIVIDE:
if f2 == 0:
return MathOperationResult(failure=MathOperationError.DIVIDEBYZERO)
return MathOperationResult(success=f1 / f2)
elif op == CalculatorMathOp.CHANGESIGN:
return MathOperationResult(success=f1 * -1)
elif op == CalculatorMathOp.MEMORYADD:
return MathOperationResult(success=f1 + f2)
elif op == op == CalculatorMathOp.MEMORYSUBTRACT:
return MathOperationResult(success=f2 - f1)
def get_display_from_state(self, error_msg: str):
"""
Returns the display strings based on the current state of the calculator.
"""
def inner(calculator_state) -> str:
if isinstance(calculator_state, ZeroStateData):
return "0"
elif isinstance(calculator_state, AccumulatorStateData):
accu_str = calculator_state.digits
number_str = str(self.get_number_from_accumulator(calculator_state))
if '.' in number_str and '.' in accu_str:
return accu_str
else:
return number_str.rstrip('0').rstrip('.')
return number_str
elif isinstance(calculator_state, ComputedStateData):
number_str = f"{calculator_state.display_number:.10g}" # Format to limit significant digits
if '.' in number_str:
return number_str.rstrip('0') #.rstrip('.')
return number_str
elif isinstance(calculator_state, ErrorStateData):
return error_msg + calculator_state.math_error.value
return inner
def get_pending_op_from_state(self):
"""
Returns the pending operation string based on the current state of the calculator.
"""
def op_to_string(op: CalculatorMathOp) -> str:
return {
CalculatorMathOp.ADD: "+",
CalculatorMathOp.SUBTRACT: "-",
CalculatorMathOp.MULTIPLY: "*",
CalculatorMathOp.DIVIDE: "/",
CalculatorMathOp.CHANGESIGN: "(change sign)",
CalculatorMathOp.INVERSE: "(inverse)"
}.get(op, "")
def display_string_for_pending_op(pending_op: Optional[PendingOp]) -> str:
if pending_op:
op, number = pending_op
formatted_number = str(number).rstrip('0').rstrip('.') if '.' in str(number) else str(number)
return f"{formatted_number} {op_to_string(op)}"
return ""
def inner(calculator_state) -> str:
if calculator_state is None or isinstance(calculator_state, ErrorStateData) or calculator_state.pending_op is None:
return ""
return display_string_for_pending_op(calculator_state.pending_op)
return inner
def get_memo_from_state(self):
"""
Returns the memory indicator string based on the current state of the calculator.
"""
def get_memo_from(state_data: DigitAccumulator) -> str:
return "M" if state_data.strip() != "" else " "
def inner(calculator_state) -> str:
if calculator_state is None or calculator_state.memory is None:
return " "
return get_memo_from(calculator_state.memory)
return inner
"""
Returns the initial state of the calculator.
"""
initial_state = ZeroStateData(pending_op=None, memory=" ")
"""
Returns a dictionary of charachter mappings to calculator inputs
"""
input_mapping = {
'0': (CalculatorInput.ZERO, None),
'1': (CalculatorInput.DIGIT, NonZeroDigit.ONE),
'2': (CalculatorInput.DIGIT, NonZeroDigit.TWO),
'3': (CalculatorInput.DIGIT, NonZeroDigit.THREE),
'4': (CalculatorInput.DIGIT, NonZeroDigit.FOUR),
'5': (CalculatorInput.DIGIT, NonZeroDigit.FIVE),
'6': (CalculatorInput.DIGIT, NonZeroDigit.SIX),
'7': (CalculatorInput.DIGIT, NonZeroDigit.SEVEN),
'8': (CalculatorInput.DIGIT, NonZeroDigit.EIGHT),
'9': (CalculatorInput.DIGIT, NonZeroDigit.NINE),
'.': (CalculatorInput.DECIMALSEPARATOR, None),
'+': (CalculatorInput.MATHOP, CalculatorMathOp.ADD),
'-': (CalculatorInput.MATHOP, CalculatorMathOp.SUBTRACT),
'*': (CalculatorInput.MATHOP, CalculatorMathOp.MULTIPLY),
'/': (CalculatorInput.MATHOP, CalculatorMathOp.DIVIDE),
'=': (CalculatorInput.EQUALS, None),
'√': (CalculatorInput.MATHOP, CalculatorMathOp.ROOT),
'±': (CalculatorInput.MATHOP, CalculatorMathOp.CHANGESIGN),
'1/x': (CalculatorInput.MATHOP, CalculatorMathOp.INVERSE),
'%': (CalculatorInput.MATHOP, CalculatorMathOp.PERCENT),
'←': (CalculatorInput.BACK, None),
'C': (CalculatorInput.CLEAR, None),
'CE': (CalculatorInput.CLEARENTRY, None),
'MC': (CalculatorInput.MEMORYCLEAR, None),
'MR': (CalculatorInput.MEMORYRECALL, None),
'MS': (CalculatorInput.MEMORYSTORE, None),
'M+': (CalculatorInput.MATHOP, CalculatorMathOp.MEMORYADD),
'M-': (CalculatorInput.MATHOP, CalculatorMathOp.MEMORYSUBTRACT)
}
ten_key_input_mapping = {
'0': (CalculatorInput.ZERO, None),
'1': (CalculatorInput.DIGIT, NonZeroDigit.ONE),
'2': (CalculatorInput.DIGIT, NonZeroDigit.TWO),
'3': (CalculatorInput.DIGIT, NonZeroDigit.THREE),
'4': (CalculatorInput.DIGIT, NonZeroDigit.FOUR),
'5': (CalculatorInput.DIGIT, NonZeroDigit.FIVE),
'6': (CalculatorInput.DIGIT, NonZeroDigit.SIX),
'7': (CalculatorInput.DIGIT, NonZeroDigit.SEVEN),
'8': (CalculatorInput.DIGIT, NonZeroDigit.EIGHT),
'9': (CalculatorInput.DIGIT, NonZeroDigit.NINE),
'.': (CalculatorInput.DECIMALSEPARATOR, None),
'←': (CalculatorInput.BACK, None),
'CE': (CalculatorInput.CLEARENTRY, None),
'MR': (CalculatorInput.MEMORYRECALL, None)
}
"""
Returns a dictionary of charachter mappings to calculator inputs
"""
@staticmethod
def create_services() -> Dict[str, Callable]:
"""
Creates and returns a dictionary of calculator service functions.
Each service function is mapped to a corresponding key, allowing for
easy access and invocation of various calculator operations such as
digit accumulation, mathematical operations, and state retrieval.
Returns:
dict: A dictionary with the following keys and corresponding service functions:
- "accumulate_non_zero_digit": Function to accumulate non-zero digits.
- "accumulate_zero": Function to accumulate zeros.
- "accumulate_separator": Function to accumulate decimal separators.
- "do_math_operation": Function to perform mathematical operations.
- "get_number_from_accumulator": Function to convert accumulator to a number.
- "get_display_from_state": Function to retrieve display string from calculator state.
- "get_pending_op_from_state": Function to retrieve pending operation from calculator state.
- "get_memo_from_state": Function to retrieve memory state.
"""
services = CalculatorServices()
return {
"accumulate_non_zero_digit": services.accumulate_non_zero_digit(15),
"accumulate_zero": services.accumulate_zero(15),
"accumulate_separator": services.accumulate_separator(15),
"do_math_operation": services.do_math_operation,
"get_number_from_accumulator": services.get_number_from_accumulator,
"get_display_from_state": services.get_display_from_state("ERROR:"),
"get_pending_op_from_state": services.get_pending_op_from_state(),
"get_memo_from_state": services.get_memo_from_state()
}