mathjson-solver 1.6.1__tar.gz → 1.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mathjson-solver-1.6.1/src/mathjson_solver.egg-info → mathjson_solver-1.8.0}/PKG-INFO +1 -1
- {mathjson-solver-1.6.1 → mathjson_solver-1.8.0}/setup.cfg +1 -1
- {mathjson-solver-1.6.1 → mathjson_solver-1.8.0}/setup.py +1 -1
- {mathjson-solver-1.6.1 → mathjson_solver-1.8.0}/src/mathjson_solver/__init__.py +1 -1
- {mathjson-solver-1.6.1 → mathjson_solver-1.8.0}/src/mathjson_solver/__main__.py +137 -33
- {mathjson-solver-1.6.1 → mathjson_solver-1.8.0/src/mathjson_solver.egg-info}/PKG-INFO +1 -1
- {mathjson-solver-1.6.1 → mathjson_solver-1.8.0}/tests/test_with_pytest.py +34 -1
- {mathjson-solver-1.6.1 → mathjson_solver-1.8.0}/LICENSE +0 -0
- {mathjson-solver-1.6.1 → mathjson_solver-1.8.0}/README.md +0 -0
- {mathjson-solver-1.6.1 → mathjson_solver-1.8.0}/pyproject.toml +0 -0
- {mathjson-solver-1.6.1 → mathjson_solver-1.8.0}/src/mathjson_solver.egg-info/SOURCES.txt +0 -0
- {mathjson-solver-1.6.1 → mathjson_solver-1.8.0}/src/mathjson_solver.egg-info/dependency_links.txt +0 -0
- {mathjson-solver-1.6.1 → mathjson_solver-1.8.0}/src/mathjson_solver.egg-info/top_level.txt +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
from .__main__ import create_mathjson_solver as create_solver
|
|
2
|
-
from .__main__ import MathJSONException
|
|
2
|
+
from .__main__ import MathJSONException, extract_variables
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import numbers
|
|
2
|
+
from typing import Union
|
|
2
3
|
from functools import reduce
|
|
3
4
|
import math
|
|
4
5
|
from copy import deepcopy
|
|
5
6
|
from statistics import median
|
|
7
|
+
import logging
|
|
8
|
+
import traceback
|
|
6
9
|
|
|
7
10
|
|
|
8
11
|
class MathJSONException(Exception):
|
|
@@ -58,36 +61,52 @@ def create_mathjson_solver(solver_parameters):
|
|
|
58
61
|
def Arr(s):
|
|
59
62
|
return s
|
|
60
63
|
|
|
61
|
-
@requires_array
|
|
64
|
+
# @requires_array
|
|
62
65
|
def Max(s):
|
|
63
|
-
|
|
66
|
+
if isinstance(s[1], str):
|
|
67
|
+
return max([f(x, c) for x in f(s[1], c) if is_numeric(f(x, c))])
|
|
68
|
+
else:
|
|
69
|
+
return max([f(x, c) for x in s[1][1:] if is_numeric(f(x, c))])
|
|
64
70
|
|
|
65
|
-
@requires_array
|
|
71
|
+
# @requires_array
|
|
66
72
|
def Min(s):
|
|
67
|
-
|
|
73
|
+
if isinstance(s[1], str):
|
|
74
|
+
return min([f(x, c) for x in f(s[1], c) if is_numeric(f(x, c))])
|
|
75
|
+
else:
|
|
76
|
+
return min([f(x, c) for x in s[1][1:] if is_numeric(f(x, c))])
|
|
68
77
|
|
|
69
|
-
@requires_array
|
|
70
78
|
def Average(s):
|
|
71
|
-
|
|
79
|
+
if isinstance(s[1], str):
|
|
80
|
+
# A reference to "answer" has been passed
|
|
81
|
+
s_ = [float(f(x, c)) for x in f(s[1], c) if is_numeric(f(x, c))]
|
|
82
|
+
else:
|
|
83
|
+
s_ = [float(f(x, c)) for x in s[1][1:] if is_numeric(f(x, c))]
|
|
72
84
|
# print(f"{s_} {sum(s_)}/{len(s_)}")
|
|
73
85
|
try:
|
|
74
86
|
return sum(s_) / len(s_)
|
|
75
87
|
except ZeroDivisionError:
|
|
76
88
|
return None
|
|
77
89
|
|
|
78
|
-
@requires_array
|
|
90
|
+
# @requires_array
|
|
79
91
|
def Median(s):
|
|
80
|
-
|
|
92
|
+
if isinstance(s[1], str):
|
|
93
|
+
return median([f(x, c) for x in f(s[1], c) if is_numeric(f(x, c))])
|
|
94
|
+
else:
|
|
95
|
+
return median([f(x, c) for x in s[1][1:] if is_numeric(f(x, c))])
|
|
81
96
|
|
|
82
|
-
@requires_array
|
|
97
|
+
# @requires_array
|
|
83
98
|
def Length(s):
|
|
84
|
-
|
|
99
|
+
if isinstance(s[1], str):
|
|
85
100
|
|
|
86
|
-
|
|
101
|
+
return len([x for x in f(s[1], c)][1:])
|
|
102
|
+
else:
|
|
103
|
+
return len([x for x in s[1][1:]])
|
|
104
|
+
|
|
105
|
+
# @requires_array
|
|
87
106
|
def Any(s):
|
|
88
107
|
return any([f(x, c) for x in s[1][1:]])
|
|
89
108
|
|
|
90
|
-
@requires_array
|
|
109
|
+
# @requires_array
|
|
91
110
|
def All(s):
|
|
92
111
|
return all([f(x, c) for x in s[1][1:]])
|
|
93
112
|
|
|
@@ -110,7 +129,7 @@ def create_mathjson_solver(solver_parameters):
|
|
|
110
129
|
for x in s[3:]:
|
|
111
130
|
if len(x) != 2:
|
|
112
131
|
raise ValueError(
|
|
113
|
-
|
|
132
|
+
"Case of 'Switch' should have exactly two parameters"
|
|
114
133
|
)
|
|
115
134
|
if expression == f(x[0], c):
|
|
116
135
|
return f(x[1], c)
|
|
@@ -119,39 +138,50 @@ def create_mathjson_solver(solver_parameters):
|
|
|
119
138
|
|
|
120
139
|
def If(s):
|
|
121
140
|
if len(s) < 3:
|
|
122
|
-
raise ValueError(
|
|
141
|
+
raise ValueError("Wrong parameters for 'If'")
|
|
123
142
|
for x in s[1:-1]:
|
|
124
143
|
if len(x) != 2:
|
|
125
|
-
raise ValueError(
|
|
126
|
-
|
|
127
|
-
|
|
144
|
+
raise ValueError("Wrong if or elif in 'If'")
|
|
145
|
+
try:
|
|
146
|
+
if f(x[0], c):
|
|
147
|
+
try:
|
|
148
|
+
return f(x[1], c)
|
|
149
|
+
except MathJSONException:
|
|
150
|
+
logging.error(
|
|
151
|
+
"MathJSONException: %s", traceback.format_exc()
|
|
152
|
+
)
|
|
153
|
+
continue
|
|
154
|
+
except MathJSONException:
|
|
155
|
+
logging.error("MathJSONException: %s", traceback.format_exc())
|
|
156
|
+
return f(s[-1], c) # return default value (else)
|
|
157
|
+
|
|
128
158
|
return f(s[-1], c)
|
|
129
159
|
|
|
130
160
|
def In(s):
|
|
131
161
|
if len(s) != 3:
|
|
132
|
-
raise ValueError(
|
|
133
|
-
if
|
|
162
|
+
raise ValueError("Wrong parameters for 'In'")
|
|
163
|
+
if isinstance(s[2], list) and s[2][0] == "Array":
|
|
134
164
|
return f(s[1], c) in [f(x, c) for x in s[2][1:]]
|
|
135
165
|
|
|
136
|
-
elif
|
|
166
|
+
elif isinstance(s[2], str):
|
|
137
167
|
return f(s[1], c) in f(s[2], c)
|
|
138
168
|
else:
|
|
139
169
|
raise ValueError(
|
|
140
|
-
|
|
170
|
+
"Wrong parameters for 'In'. Parameter 2 must be a list."
|
|
141
171
|
)
|
|
142
172
|
|
|
143
173
|
def Not_in(s):
|
|
144
174
|
return not In(s)
|
|
145
175
|
|
|
146
176
|
def Contains_any_of(s):
|
|
147
|
-
if
|
|
177
|
+
if isinstance(s[1], list) and s[1][0] == "Array":
|
|
148
178
|
list1 = [f(x, c) for x in s[1][1:]]
|
|
149
|
-
elif
|
|
179
|
+
elif isinstance(s[1], str):
|
|
150
180
|
list1 = f(s[1], c)
|
|
151
181
|
|
|
152
|
-
if
|
|
182
|
+
if isinstance(s[2], list) and s[2][0] == "Array":
|
|
153
183
|
list2 = [f(x, c) for x in s[2][1:]]
|
|
154
|
-
elif
|
|
184
|
+
elif isinstance(s[2], str):
|
|
155
185
|
list2 = f(s[2], c)
|
|
156
186
|
|
|
157
187
|
if any(x in list1 for x in list2):
|
|
@@ -159,14 +189,14 @@ def create_mathjson_solver(solver_parameters):
|
|
|
159
189
|
return False
|
|
160
190
|
|
|
161
191
|
def Contains_all_of(s):
|
|
162
|
-
if
|
|
192
|
+
if isinstance(s[1], list) and s[1][0] == "Array":
|
|
163
193
|
list1 = [f(x, c) for x in s[1][1:]]
|
|
164
|
-
elif
|
|
194
|
+
elif isinstance(s[1], str):
|
|
165
195
|
list1 = f(s[1], c)
|
|
166
196
|
|
|
167
|
-
if
|
|
197
|
+
if isinstance(s[2], list) and s[2][0] == "Array":
|
|
168
198
|
list2 = [f(x, c) for x in s[2][1:]]
|
|
169
|
-
elif
|
|
199
|
+
elif isinstance(s[2], str):
|
|
170
200
|
list2 = f(s[2], c)
|
|
171
201
|
|
|
172
202
|
if all(x in list1 for x in list2):
|
|
@@ -178,7 +208,7 @@ def create_mathjson_solver(solver_parameters):
|
|
|
178
208
|
|
|
179
209
|
def Str(s):
|
|
180
210
|
if len(s) < 2:
|
|
181
|
-
raise ValueError(
|
|
211
|
+
raise ValueError("Wrong parameters for 'Str'")
|
|
182
212
|
return f"{f(s[1])}"
|
|
183
213
|
|
|
184
214
|
def Not(s):
|
|
@@ -213,9 +243,11 @@ def create_mathjson_solver(solver_parameters):
|
|
|
213
243
|
"LessEqual": lambda s: f(s[1], c) <= f(s[2], c),
|
|
214
244
|
"NotEqual": lambda s: f(s[1], c) != f(s[2], c),
|
|
215
245
|
"Abs": lambda s: abs(f(s[1], c)),
|
|
216
|
-
"Round": lambda s:
|
|
217
|
-
|
|
218
|
-
|
|
246
|
+
"Round": lambda s: (
|
|
247
|
+
round(f(s[1], c), f(s[2], c))
|
|
248
|
+
if len(s) == 3
|
|
249
|
+
else int(round(f(s[1], c)))
|
|
250
|
+
),
|
|
219
251
|
"Max": Max,
|
|
220
252
|
"Min": Min,
|
|
221
253
|
"Average": Average,
|
|
@@ -237,6 +269,7 @@ def create_mathjson_solver(solver_parameters):
|
|
|
237
269
|
"Float": Float,
|
|
238
270
|
"Str": Str,
|
|
239
271
|
"Not": Not,
|
|
272
|
+
"IsDefined": lambda s: s[1] in c,
|
|
240
273
|
}
|
|
241
274
|
if s[0] in constructs:
|
|
242
275
|
try:
|
|
@@ -253,6 +286,77 @@ def create_mathjson_solver(solver_parameters):
|
|
|
253
286
|
elif s in c:
|
|
254
287
|
return f(c[s], c)
|
|
255
288
|
else:
|
|
289
|
+
# raise KeyError(f"Parameter '{s}' is not defined")
|
|
256
290
|
return s
|
|
257
291
|
|
|
258
292
|
return f
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def extract_variables(s: Union[list, int, float, str], li: set, ignore_list: set):
|
|
296
|
+
constructs = [
|
|
297
|
+
"Add",
|
|
298
|
+
"Sum",
|
|
299
|
+
"Subtract",
|
|
300
|
+
"Constants",
|
|
301
|
+
"Switch",
|
|
302
|
+
"If",
|
|
303
|
+
"Multiply",
|
|
304
|
+
"Divide",
|
|
305
|
+
"Negate",
|
|
306
|
+
"Power",
|
|
307
|
+
"Root",
|
|
308
|
+
"Sqrt",
|
|
309
|
+
"Square",
|
|
310
|
+
"Exp",
|
|
311
|
+
"Log",
|
|
312
|
+
"Log2",
|
|
313
|
+
"Log10",
|
|
314
|
+
"Equal",
|
|
315
|
+
"Greater",
|
|
316
|
+
"GreaterEqual",
|
|
317
|
+
"Less",
|
|
318
|
+
"LessEqual",
|
|
319
|
+
"NotEqual",
|
|
320
|
+
"Abs",
|
|
321
|
+
"Round",
|
|
322
|
+
"Max",
|
|
323
|
+
"Min",
|
|
324
|
+
"Average",
|
|
325
|
+
"Median",
|
|
326
|
+
"Length",
|
|
327
|
+
"Any",
|
|
328
|
+
"All",
|
|
329
|
+
"Array",
|
|
330
|
+
"In",
|
|
331
|
+
"Not_in",
|
|
332
|
+
"Contains_any_of",
|
|
333
|
+
"Contains_all_of",
|
|
334
|
+
"Contains_none_of",
|
|
335
|
+
"NotIn",
|
|
336
|
+
"ContainsAnyOf",
|
|
337
|
+
"ContainsAllOf",
|
|
338
|
+
"ContainsNoneOf",
|
|
339
|
+
"Int",
|
|
340
|
+
"Float",
|
|
341
|
+
"Str",
|
|
342
|
+
"Not",
|
|
343
|
+
"IsDefined",
|
|
344
|
+
]
|
|
345
|
+
if isinstance(s, str):
|
|
346
|
+
if s in ignore_list:
|
|
347
|
+
return li
|
|
348
|
+
if s not in constructs:
|
|
349
|
+
li.add(s)
|
|
350
|
+
return li
|
|
351
|
+
elif isinstance(s, list):
|
|
352
|
+
if s[0] == "Constants":
|
|
353
|
+
for x in s[1:-1]:
|
|
354
|
+
ignore_list.add(x[0])
|
|
355
|
+
li.update(extract_variables(x[1], li, ignore_list))
|
|
356
|
+
li.update(extract_variables(x[-1], li, ignore_list))
|
|
357
|
+
else:
|
|
358
|
+
for x in s[1:]:
|
|
359
|
+
li.update(extract_variables(x, li, ignore_list))
|
|
360
|
+
return li
|
|
361
|
+
else:
|
|
362
|
+
return li
|
|
@@ -5,7 +5,7 @@ import re
|
|
|
5
5
|
|
|
6
6
|
sys.path.append(os.path.join(os.path.dirname(__file__), "../src/"))
|
|
7
7
|
|
|
8
|
-
from mathjson_solver import create_solver, MathJSONException
|
|
8
|
+
from mathjson_solver import create_solver, MathJSONException, extract_variables
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
@pytest.mark.parametrize(
|
|
@@ -45,10 +45,18 @@ from mathjson_solver import create_solver, MathJSONException
|
|
|
45
45
|
({"color": "green"}, ["Switch", "color", 0, ["blue", 10], ["red", 30]], 0),
|
|
46
46
|
({"color": "green"}, ["Switch", "undefined", 0, ["blue", 10], ["red", 30]], 0),
|
|
47
47
|
({}, ["If", [["Equal", 1, 0], 10], [["Equal", 2, 2], 20], 9000], 20),
|
|
48
|
+
({"a": 10, "b": 10}, ["If", [["Equal", "a", "b"], 10], 9000], 10),
|
|
49
|
+
({"a": 10, "b": 20}, ["If", [["Equal", "a", "b"], 10], 9000], 9000),
|
|
50
|
+
({"a": 10}, ["If", [["Equal", "a", "b"], 10], 9000], 9000),
|
|
51
|
+
({"a": 10}, ["If", [["Equal", "a", ["Sum", 1, "b"]], 10], 9000], 9000),
|
|
48
52
|
({}, ["Array", 1, 2, 3, 5, 2], ["Array", 1, 2, 3, 5, 2]),
|
|
49
53
|
({}, ["Max", ["Array", 1, 2, 3, 5, 2]], 5),
|
|
50
54
|
({}, ["Max", ["Array", 1, 2, ["Sum", 2, 4, 3], 5, 2]], 9),
|
|
55
|
+
({"a": ["Array", 1, 2, 3, 5, 2]}, ["Max", "a"], 5),
|
|
56
|
+
({}, ["Min", ["Array", 1, 2, 3, 5, 2]], 1),
|
|
57
|
+
({"a": ["Array", 2, 1, 3, 5, 2]}, ["Min", "a"], 1),
|
|
51
58
|
({}, ["Median", ["Array", 1, 2, 3, 5, 2]], 2),
|
|
59
|
+
({"a": ["Array", 1, 2, 3, 5, 2]}, ["Median", "a"], 2),
|
|
52
60
|
({}, ["Average", ["Array", 1, 2, 3, 5, 2]], 2.6),
|
|
53
61
|
(
|
|
54
62
|
{},
|
|
@@ -76,8 +84,10 @@ from mathjson_solver import create_solver, MathJSONException
|
|
|
76
84
|
5.0,
|
|
77
85
|
),
|
|
78
86
|
({}, ["Average", ["Array"]], None),
|
|
87
|
+
({"a": ["Array", 2, 8]}, ["Average", "a"], 5),
|
|
79
88
|
({"a": 10, "b": 20}, ["Average", ["Array", "a", "b"]], 15),
|
|
80
89
|
({}, ["Length", ["Array", 1, 2, 3, 5, 2, 9]], 6),
|
|
90
|
+
({"a": ["Array", 1, 2, 3, 5, 2, 9]}, ["Length", "a"], 6),
|
|
81
91
|
({}, ["Length", ["Array"]], 0),
|
|
82
92
|
({}, ["Int", "12"], 12),
|
|
83
93
|
({}, ["Int", "12.2"], 12),
|
|
@@ -202,3 +212,26 @@ def test_handle_exception():
|
|
|
202
212
|
assert True
|
|
203
213
|
else:
|
|
204
214
|
assert False
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@pytest.mark.parametrize(
|
|
218
|
+
"parameters, expression, expected_result",
|
|
219
|
+
[
|
|
220
|
+
({}, ["Add", 2, 4, 3], set()),
|
|
221
|
+
({}, ["Sum", 2, "a", 3], set(["a"])),
|
|
222
|
+
({"a": 5}, ["Sum", 2, "a", 3], set()),
|
|
223
|
+
({"a": 5, "b": 6}, ["Sum", 2, "a", "c"], set(["c"])),
|
|
224
|
+
({}, ["Constants", ["c", 1], ["d", ["Add", 2, "a"]], "d"], set(["a"])),
|
|
225
|
+
# Now with "ugly" variables that mimic the ones used for deep referencing.
|
|
226
|
+
(
|
|
227
|
+
{},
|
|
228
|
+
["Add", "[slug1][0][question1]", "y", 4, "[slug1][-3:-1][question1]"],
|
|
229
|
+
set(["[slug1][0][question1]", "y", "[slug1][-3:-1][question1]"]),
|
|
230
|
+
),
|
|
231
|
+
],
|
|
232
|
+
)
|
|
233
|
+
def test_extract_variables(parameters, expression, expected_result):
|
|
234
|
+
assert (
|
|
235
|
+
extract_variables(expression, set(), set([x for x in parameters.keys()]))
|
|
236
|
+
== expected_result
|
|
237
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mathjson-solver-1.6.1 → mathjson_solver-1.8.0}/src/mathjson_solver.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|