-
Notifications
You must be signed in to change notification settings - Fork 0
/
arithmetics.py
233 lines (170 loc) · 6.71 KB
/
arithmetics.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
"""
Used for the creation of "questions" for the user,
along with the expected answer (that is, the result of the operation)
Some examples of "questions" and their "answers":
"+2-4" , "-2"
"(-2)(+7)", "-14"
"-1.60-2.04", "-3.64"
The program should be aimed specifically at teaching
the very basics of addition or multiplication of integers or decimals,
in a scaling difficulty.
Exercises that deviate from those specific concepts should be avoided.
Examples of what should NOT be implemented:
"2-4", # Deviates (slightly) since it also teaches that 2 == +2
"-2(+7)", # Deviates (same as example above)
"+2-4(-5)", # Deviates since it combines addition and multiplication
"""
import decimal
import random
import languages
# ----------------------------------------------------------------------------------------------------------------------
DIFFICULTY_TO_TERMS_COUNT_AND_TYPE_MAP = {
'1': {'terms_count': 2, 'terms_type': 'int', 'user_str': 'easy'},
'2': {'terms_count': 3, 'terms_type': 'int', 'user_str': 'medium'},
'3': {'terms_count': 2, 'terms_type': 'float', 'user_str': 'hard'}
}
TOTAL_DIFFICULTY_LVLS = len(DIFFICULTY_TO_TERMS_COUNT_AND_TYPE_MAP)
# ----------------------------------------------------------------------------------------------------------------------
class Terms(object):
MIN_TERMS_COUNT = 2
MAX_TERMS_COUNT = 3
TERMS_TYPES = {'int', 'float'}
MAX_ABS_VALUE = 10
def __init__(self, difficulty_lvl):
self.terms_count = DIFFICULTY_TO_TERMS_COUNT_AND_TYPE_MAP[difficulty_lvl]['terms_count']
self.terms_type = DIFFICULTY_TO_TERMS_COUNT_AND_TYPE_MAP[difficulty_lvl]['terms_type']
@staticmethod
def _int_term(max_val=MAX_ABS_VALUE):
return random.randint(0, max_val)
@staticmethod
def _float_term(max_val=MAX_ABS_VALUE):
return random.random() * max_val
@staticmethod
def final_term(term_without_sign):
"""
Creates a single term.
:param term_without_sign:
:return: (num)
"""
sign = random.choice(['-', '+'])
if sign == '-':
final_term = -1 * term_without_sign
else:
final_term = term_without_sign
return final_term
def all_terms(self):
lst = []
if self.terms_type == 'int':
func = self._int_term
elif self.terms_type == 'float':
func = self._float_term
else:
raise NotImplemented('{}'.format(self.terms_type))
for _ in range(self.terms_count):
term_without_sign = func()
final_term = self.final_term(term_without_sign=term_without_sign)
lst.append(final_term)
return lst
class QuestionAndAnswer(object):
"""
Based on difficulty and operation type,
creates terms (either float or ints) which are then rounded to allow
specific number of decimals.
"""
OPERATIONS_TYPES = ('addition', 'multiplication')
def __init__(self, difficulty_lvl, op_type):
self.terms_as_numbers = Terms(difficulty_lvl=difficulty_lvl).all_terms()
self.op_type = op_type
@staticmethod
def round_single_term_1_decimals(given_float):
num = decimal.Decimal(str(given_float))
return num.quantize(decimal.Decimal('.1'), decimal.ROUND_HALF_UP)
def terms_to_rounded_numbers(self):
"""
Ensures all terms have an appropriate number of decimals.
:return: (list)
"""
lst = []
for t in self.terms_as_numbers:
if not isinstance(t, int):
t = self.round_single_term_1_decimals(given_float=t)
lst.append(t)
return lst
def terms_as_strings(self):
"""
Converts terms to strings.
0 gets a random sign explicitly instead of using `{:+f}` for all terms' formatting
since the latter can't achieve that.
:return: (list)
"""
lst = []
for t in self.terms_to_rounded_numbers():
if t > 0:
lst.append('+{}'.format(t))
elif t == 0:
random_sign = random.choice(['+', '-'])
# using abs() since sometimes a -0.0 can be given
lst.append('{sign}{term}'.format(sign=random_sign, term=abs(t)))
else:
lst.append(str(t))
return lst
def operation_str(self):
"""
Creates the operation string presented to the user.
:return: (str)
"""
terms_as_strings = self.terms_as_strings()
if self.op_type == 'addition':
# (whitespace between terms for better visibility)
op_str = ' '.join(terms_as_strings)
elif self.op_type == 'multiplication':
op_str = ''
for t in terms_as_strings:
# (whitespace between terms for better visibility)
if op_str:
op_str += ' '
op_str += '({})'.format(t)
else:
raise NotImplemented('{}'.format(self.op_type))
return op_str
def expected_answer(self):
if self.op_type == 'addition':
result = 0
for t in self.terms_to_rounded_numbers():
result += t
elif self.op_type == 'multiplication':
result = 1
for t in self.terms_to_rounded_numbers():
result *= t
else:
raise NotImplemented('{}'.format(self.op_type))
return result
if __name__ == '__main__':
# VISUAL TESTS
if 1:
# --------------------------------------------------
# all_terms()
print('\n'+'-'*80)
print('TERMS')
def print_all_terms(difficulty_lvl):
print('\nDifficulty {}'.format(difficulty_lvl))
# (terms' lists printed for each difficulty lvl)
terms_lsts_count = 3
for _ in range(terms_lsts_count):
print(Terms(difficulty_lvl=difficulty_lvl).all_terms())
# (tests all difficulties)
for d in range(1, TOTAL_DIFFICULTY_LVLS + 1):
print_all_terms(difficulty_lvl=d)
# --------------------------------------------------
# operation_str() and answer
print('\n'+'-'*80)
print('OPERATION STRING')
for d in range(1, TOTAL_DIFFICULTY_LVLS + 1):
print('\nDifficulty: {}'.format(d))
for operation in QuestionAndAnswer.OPERATIONS_TYPES:
for _ in range(3):
inst = QuestionAndAnswer(difficulty_lvl=d, op_type=operation)
question = inst.operation_str()
answer = inst.expected_answer()
msg = '{q} = {a}'.format(q=question, a=answer)
print(msg)