-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathalgorithm.py
More file actions
328 lines (261 loc) · 16.2 KB
/
algorithm.py
File metadata and controls
328 lines (261 loc) · 16.2 KB
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
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
import contextlib
import itertools
import json
import utils
import random
import time
def _solution_exists(result_map: dict[list, list], equation: str) -> bool:
"""
Function checks if the solution already exists in the result dictionary
Args:
result_map (dict): Stores all solutions and mutations
equation (string): The new equation to check for
Returns:
bool: Whether the equation exists in the result_map
"""
return any(solution["new_equation"] == equation for solution in result_map["solutions"])
def _get_equation_matchstick_count(equation: str) -> int:
"""
Gets the total matchstick count to check if stick was removed
without being placed or placed without being removed
Args:
equation (string): The equation to check the matchstick count
Returns:
int: The amount of ones/sticks in the equation
"""
equation_matchstick_count = 0
# Loops over all chars in the equation and check the
# amount of matchsticks that every char is built with
for char in equation:
with contextlib.suppress(Exception):
equation_matchstick_count += utils.get_match_sticks_count(utils.encode_num(char))
return equation_matchstick_count
def solve(equation: str) -> dict[list, list]:
"""
Solves the matchstick equation
Args:
equation (str): The equation to solve
Returns:
dict[list, list]: The results
"""
# ---------- SETUP ----------#
equation_matchstick_count = _get_equation_matchstick_count(equation)
if utils.evaluate_eq(equation):
return {"solutions": [{"new_equation": equation, "original_equation": equation, "explanation": "Expression was already true"}], "mutations": []}
result_map = {"solutions": [], "mutations": []}
transform_data = None
solutions = []
mutations = []
# Opens the matchstick_transform_data file that contains
# the numbers each number can become by removing/getting/moving one stick
with open("./matchstick_transform_data.json") as f:
transform_data = json.load(f)
for index, num in enumerate(equation):
# If the number exists then continue,
# it will filter '=' or any other invalid char
if transform_data.get(num):
# Loops over all the options for the numbers:
# - "to" (numbers you get when removing a stick from the number)
# - "from" (numbers you get when getting a stick from a number)
# - "morph" (numbers you get when moving a stick within the number)
for opt in transform_data[num]:
for num_opt in transform_data[num][opt]:
# If the option is "to" which means the number after a stick was removed
# then it will check what number can get a stick and create a valid equation
if opt == "to":
# Now look through all numbers in equation to check if can add stick to them
for index2, num2 in enumerate(equation):
# Again, check if the number is valid
if transform_data.get(num2):
# Loop through the current number's options after getting another stick
for from_num in transform_data[num2]["from"]:
# Test the equation's validity with the new numbers
# (the one that a stick was removed from and the one a stick was added to)
new_eq = list(equation)
new_eq[index2] = from_num
new_eq[index] = num_opt
new_eq = "".join(new_eq)
solve = utils.create_eq(new_eq)
# There are multiple checks that need to be verified:
# 1. Check if the equation is true - it means that 1+1=4 wont go through
# 2. Check if the solution wasn't added already - because numbers lead to each other, so there can be duplicates
# 4. Check if there weren't extra/less sticks than the original one - So it wont remove a stick without placing it back somewhere
if solve[0] and not _solution_exists(result_map, solve[1]) and _get_equation_matchstick_count(new_eq) == equation_matchstick_count:
result_map["solutions"].append(
{
"new_equation": solve[1],
"original_equation": equation,
"explanation": utils.explanation_generator(
utils.encode_num(num), utils.encode_num(num_opt), utils.encode_num(num2), utils.encode_num(from_num), new_eq, equation
),
}
)
else:
mutations.append(
{
"new_equation": solve[1],
"original_equation": equation,
}
)
# If the option is "from" which means the number after a stick was added
# then it will check what number can remove a stick and create a valid equation
elif opt == "from":
continue
# Now look through all numbers in equation to check if can add stick to them
for index2, num2 in enumerate(equation):
# Again, check if the number is valid
if transform_data.get(num2):
# Loop through the current number's options after removing another stick
for from_num in transform_data[num2]["to"]:
# Test the equation's validity with the new numbers
# (the one that a stick was removed from and the one a stick was added to)
new_eq = list(equation)
new_eq[index2] = from_num
new_eq[index] = num_opt
new_eq = "".join(new_eq)
solve = utils.create_eq(new_eq)
# There are multiple checks that need to be verified:
# 1. Check if the equation is true - it means that 1+1=4 wont go through
# 2. Check if the solution wasn't added already - because numbers lead to each other, so there can be duplicates
# 4. Check if there weren't extra/less sticks than the original one - So it wont remove a stick without placing it back somewhere
if solve[0] and not _solution_exists(result_map, solve[1]) and _get_equation_matchstick_count(new_eq) == equation_matchstick_count:
result_map["solutions"].append(
{
"new_equation": solve[1],
"original_equation": equation,
"explanation": utils.explanation_generator(
utils.encode_num(num), utils.encode_num(num_opt), utils.encode_num(num2), utils.encode_num(from_num), new_eq, equation
),
}
)
else:
mutations.append(
{
"new_equation": solve[1],
"original_equation": equation,
}
)
# If the option is "morph" which means the number after moving a stick within it self,
# For example 6 to 0
else:
# Test the equation's validity with the new number
new_eq = list(equation)
original = new_eq[index]
new_eq[index] = num_opt
new_eq = "".join(new_eq)
solve = utils.create_eq(new_eq)
# There are multiple checks that need to be verified:
# 1. Check if the equation is true - it means that 1+1=4 wont go through
# 2. Check if the solution wasn't added already - because numbers lead to each other, so there can be duplicates
# 4. Check if there weren't extra/less sticks than the original one - So it wont remove a stick without placing it back somewhere
if solve[0] and not _solution_exists(result_map, solve[1]) and _get_equation_matchstick_count(new_eq) == equation_matchstick_count:
result_map["solutions"].append(
{
"new_equation": solve[1],
"original_equation": equation,
"explanation": utils.explanation_generator(utils.encode_num(list(equation)[index]), None, utils.encode_num(num_opt), None, new_eq, equation, morph=True),
}
)
else:
mutations.append({"new_equation": solve[1], "original_equation": equation})
result_map["mutations"] = mutations
return result_map
def create_equation(answer: int = None, min_num: int = 1, max_num: int = 10, divide: bool = False, multiply: bool = False) -> dict[str, list]:
"""
Creates a random valid equation by choosing random numbers and with them
creating the equation
Args:
answer (int, optional): Set's the answer so the original's result would be the answer, example: (answer=4) 6+4=4. Defaults to None.
min_num (int, optional): The minimum number that can show up in the equation. Defaults to 1.
max_num (int, optional): The maximum number that can show up in the equation. Defaults to 10.
divide (int, optional): Wether the function should generate an equation with the / sign. Defaults to False.
multiply (int, optional): Wether the function should generate an equation with the * sign. Defaults to False.
Returns:
dict[str, list]: The random equation with it's answers
"""
# Create a new equation until results in a valid one according to the equation's parameters
while True:
# Create the random equation
random_num1 = random.randint(min_num, max_num)
random_num2 = random.randint(min_num, max_num)
random_num3 = random.randint(min_num, max_num)
operation = random.choice("+-" + ("/" if divide else "") + ("*" if multiply else ""))
eq = f"{random_num1}{operation}{random_num2}={answer or random_num3}"
try:
result = solve(eq)
except Exception:
continue
if len(result["solutions"]) and not utils.evaluate_eq(eq):
return {"equation": eq, "solutions": result["solutions"]}
def get_problems(eq: str) -> list:
"""
Gets all valid matchstick problems that eq (desired answer) is in their solutions list.
It does that by implementing the solve algorithm but backwards
So it will allow all not true equations
Returns:
list: A list of all found equations that `eq` is a part of their solutions list
"""
equations = []
eq_strings = []
with open("./matchstick_transform_data.json") as f:
transform_data = json.load(f)
for index, char in enumerate(eq):
if transform_data.get(char):
# Loop over all numbers that can become from removing/adding/morphing a matchstick from `char`
# then loop over all chars in eq that can become a number when a matchstick
# is removed/added/morphed to them and then it checks if the equation is a valid one and is not true
# (because these equations cant be equal)
for give_to_num, (index2, char2) in itertools.product(transform_data[char]["to"], enumerate(eq)):
if transform_data.get(char2):
for get_from_num in transform_data[char2]["from"]:
tmp_eq = list(eq)
tmp_eq[index] = give_to_num
tmp_eq[index2] = get_from_num
eval_result = utils.evaluate_eq("".join(tmp_eq))
if not eval_result and eval_result != None:
if "".join(tmp_eq) not in eq_strings:
solve_data = solve("".join(tmp_eq))
in_solutions = False
for solution in solve_data["solutions"]:
if solution["new_equation"] == eq:
in_solutions = True
break
if in_solutions:
eq_strings.append("".join(tmp_eq))
equations.append({"eq": "".join(tmp_eq), "data": solve_data})
if transform_data.get(char):
for get_from_num, (index2, char2) in itertools.product(transform_data[char]["from"], enumerate(eq)):
if transform_data.get(char2):
for give_to_num in transform_data[char2]["to"]:
tmp_eq = list(eq)
tmp_eq[index] = give_to_num
tmp_eq[index2] = get_from_num
eval_result = utils.evaluate_eq("".join(tmp_eq))
if not eval_result and eval_result != None:
if "".join(tmp_eq) not in eq_strings:
solve_data = solve("".join(tmp_eq))
in_solutions = False
for solution in solve_data["solutions"]:
if solution["new_equation"] == eq:
in_solutions = True
break
if in_solutions:
eq_strings.append("".join(tmp_eq))
equations.append({"eq": "".join(tmp_eq), "data": solve_data})
if transform_data.get(char):
for morph_num in transform_data[char]["morph"]:
tmp_eq = list(eq)
tmp_eq[index] = morph_num
eval_result = utils.evaluate_eq("".join(tmp_eq))
if not eval_result and eval_result != None:
if "".join(tmp_eq) not in eq_strings:
solve_data = solve("".join(tmp_eq))
in_solutions = False
for solution in solve_data["solutions"]:
if solution["new_equation"] == eq:
in_solutions = True
break
if in_solutions:
eq_strings.append("".join(tmp_eq))
equations.append({"eq": "".join(tmp_eq), "data": solve_data})
return equations