mathjson-solver 1.7.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mathjson-solver
3
- Version: 1.7.0
3
+ Version: 1.8.0
4
4
  Summary: Utilities for MathJSON evaluation
5
5
  Home-page: https://github.com/LongenesisLtd/mathjson-solver
6
6
  Author: Martins Mednis
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = mathjson-solver
3
- version = 1.7.0
3
+ version = 1.8.0
4
4
  author = Martins Mednis
5
5
  author_email = mrt@mednis.info
6
6
  description = Utilities for MathJSON evaluation
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
5
5
 
6
6
  setuptools.setup(
7
7
  name="mathjson-solver",
8
- version="1.7.0",
8
+ version="1.8.0",
9
9
  author="Martins Mednis",
10
10
  author_email="mrt@mednis.info",
11
11
  description="Utilities for MathJSON evaluation",
@@ -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,4 +1,5 @@
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
@@ -60,36 +61,52 @@ def create_mathjson_solver(solver_parameters):
60
61
  def Arr(s):
61
62
  return s
62
63
 
63
- @requires_array
64
+ # @requires_array
64
65
  def Max(s):
65
- return max([f(x, c) for x in s[1][1:]])
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))])
66
70
 
67
- @requires_array
71
+ # @requires_array
68
72
  def Min(s):
69
- return min([f(x, c) for x in s[1][1:]])
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))])
70
77
 
71
- @requires_array
72
78
  def Average(s):
73
- s_ = [float(f(x, c)) for x in s[1][1:] if is_numeric(f(x, c))]
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))]
74
84
  # print(f"{s_} {sum(s_)}/{len(s_)}")
75
85
  try:
76
86
  return sum(s_) / len(s_)
77
87
  except ZeroDivisionError:
78
88
  return None
79
89
 
80
- @requires_array
90
+ # @requires_array
81
91
  def Median(s):
82
- return median([f(x, c) for x in s[1][1:]])
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))])
83
96
 
84
- @requires_array
97
+ # @requires_array
85
98
  def Length(s):
86
- return len([f(x, c) for x in s[1][1:]])
99
+ if isinstance(s[1], str):
100
+
101
+ return len([x for x in f(s[1], c)][1:])
102
+ else:
103
+ return len([x for x in s[1][1:]])
87
104
 
88
- @requires_array
105
+ # @requires_array
89
106
  def Any(s):
90
107
  return any([f(x, c) for x in s[1][1:]])
91
108
 
92
- @requires_array
109
+ # @requires_array
93
110
  def All(s):
94
111
  return all([f(x, c) for x in s[1][1:]])
95
112
 
@@ -112,7 +129,7 @@ def create_mathjson_solver(solver_parameters):
112
129
  for x in s[3:]:
113
130
  if len(x) != 2:
114
131
  raise ValueError(
115
- f"Case of 'Switch' should have exactly two parameters"
132
+ "Case of 'Switch' should have exactly two parameters"
116
133
  )
117
134
  if expression == f(x[0], c):
118
135
  return f(x[1], c)
@@ -121,20 +138,20 @@ def create_mathjson_solver(solver_parameters):
121
138
 
122
139
  def If(s):
123
140
  if len(s) < 3:
124
- raise ValueError(f"Wrong parameters for 'If'")
141
+ raise ValueError("Wrong parameters for 'If'")
125
142
  for x in s[1:-1]:
126
143
  if len(x) != 2:
127
- raise ValueError(f"Wrong if or elif in 'If'")
144
+ raise ValueError("Wrong if or elif in 'If'")
128
145
  try:
129
146
  if f(x[0], c):
130
147
  try:
131
148
  return f(x[1], c)
132
- except MathJSONException as e:
149
+ except MathJSONException:
133
150
  logging.error(
134
151
  "MathJSONException: %s", traceback.format_exc()
135
152
  )
136
153
  continue
137
- except MathJSONException as e:
154
+ except MathJSONException:
138
155
  logging.error("MathJSONException: %s", traceback.format_exc())
139
156
  return f(s[-1], c) # return default value (else)
140
157
 
@@ -142,29 +159,29 @@ def create_mathjson_solver(solver_parameters):
142
159
 
143
160
  def In(s):
144
161
  if len(s) != 3:
145
- raise ValueError(f"Wrong parameters for 'In'")
146
- if type(s[2]) == list and s[2][0] == "Array":
162
+ raise ValueError("Wrong parameters for 'In'")
163
+ if isinstance(s[2], list) and s[2][0] == "Array":
147
164
  return f(s[1], c) in [f(x, c) for x in s[2][1:]]
148
165
 
149
- elif type(s[2]) == str:
166
+ elif isinstance(s[2], str):
150
167
  return f(s[1], c) in f(s[2], c)
151
168
  else:
152
169
  raise ValueError(
153
- f"Wrong parameters for 'In'. Parameter 2 must be a list."
170
+ "Wrong parameters for 'In'. Parameter 2 must be a list."
154
171
  )
155
172
 
156
173
  def Not_in(s):
157
174
  return not In(s)
158
175
 
159
176
  def Contains_any_of(s):
160
- if type(s[1]) == list and s[1][0] == "Array":
177
+ if isinstance(s[1], list) and s[1][0] == "Array":
161
178
  list1 = [f(x, c) for x in s[1][1:]]
162
- elif type(s[1]) == str:
179
+ elif isinstance(s[1], str):
163
180
  list1 = f(s[1], c)
164
181
 
165
- if type(s[2]) == list and s[2][0] == "Array":
182
+ if isinstance(s[2], list) and s[2][0] == "Array":
166
183
  list2 = [f(x, c) for x in s[2][1:]]
167
- elif type(s[2]) == str:
184
+ elif isinstance(s[2], str):
168
185
  list2 = f(s[2], c)
169
186
 
170
187
  if any(x in list1 for x in list2):
@@ -172,14 +189,14 @@ def create_mathjson_solver(solver_parameters):
172
189
  return False
173
190
 
174
191
  def Contains_all_of(s):
175
- if type(s[1]) == list and s[1][0] == "Array":
192
+ if isinstance(s[1], list) and s[1][0] == "Array":
176
193
  list1 = [f(x, c) for x in s[1][1:]]
177
- elif type(s[1]) == str:
194
+ elif isinstance(s[1], str):
178
195
  list1 = f(s[1], c)
179
196
 
180
- if type(s[2]) == list and s[2][0] == "Array":
197
+ if isinstance(s[2], list) and s[2][0] == "Array":
181
198
  list2 = [f(x, c) for x in s[2][1:]]
182
- elif type(s[2]) == str:
199
+ elif isinstance(s[2], str):
183
200
  list2 = f(s[2], c)
184
201
 
185
202
  if all(x in list1 for x in list2):
@@ -191,7 +208,7 @@ def create_mathjson_solver(solver_parameters):
191
208
 
192
209
  def Str(s):
193
210
  if len(s) < 2:
194
- raise ValueError(f"Wrong parameters for 'Str'")
211
+ raise ValueError("Wrong parameters for 'Str'")
195
212
  return f"{f(s[1])}"
196
213
 
197
214
  def Not(s):
@@ -226,9 +243,11 @@ def create_mathjson_solver(solver_parameters):
226
243
  "LessEqual": lambda s: f(s[1], c) <= f(s[2], c),
227
244
  "NotEqual": lambda s: f(s[1], c) != f(s[2], c),
228
245
  "Abs": lambda s: abs(f(s[1], c)),
229
- "Round": lambda s: round(f(s[1], c), f(s[2], c))
230
- if len(s) == 3
231
- else int(round(f(s[1], c))),
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
+ ),
232
251
  "Max": Max,
233
252
  "Min": Min,
234
253
  "Average": Average,
@@ -271,3 +290,73 @@ def create_mathjson_solver(solver_parameters):
271
290
  return s
272
291
 
273
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mathjson-solver
3
- Version: 1.7.0
3
+ Version: 1.8.0
4
4
  Summary: Utilities for MathJSON evaluation
5
5
  Home-page: https://github.com/LongenesisLtd/mathjson-solver
6
6
  Author: Martins Mednis
@@ -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(
@@ -52,7 +52,11 @@ from mathjson_solver import create_solver, MathJSONException
52
52
  ({}, ["Array", 1, 2, 3, 5, 2], ["Array", 1, 2, 3, 5, 2]),
53
53
  ({}, ["Max", ["Array", 1, 2, 3, 5, 2]], 5),
54
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),
55
58
  ({}, ["Median", ["Array", 1, 2, 3, 5, 2]], 2),
59
+ ({"a": ["Array", 1, 2, 3, 5, 2]}, ["Median", "a"], 2),
56
60
  ({}, ["Average", ["Array", 1, 2, 3, 5, 2]], 2.6),
57
61
  (
58
62
  {},
@@ -80,8 +84,10 @@ from mathjson_solver import create_solver, MathJSONException
80
84
  5.0,
81
85
  ),
82
86
  ({}, ["Average", ["Array"]], None),
87
+ ({"a": ["Array", 2, 8]}, ["Average", "a"], 5),
83
88
  ({"a": 10, "b": 20}, ["Average", ["Array", "a", "b"]], 15),
84
89
  ({}, ["Length", ["Array", 1, 2, 3, 5, 2, 9]], 6),
90
+ ({"a": ["Array", 1, 2, 3, 5, 2, 9]}, ["Length", "a"], 6),
85
91
  ({}, ["Length", ["Array"]], 0),
86
92
  ({}, ["Int", "12"], 12),
87
93
  ({}, ["Int", "12.2"], 12),
@@ -206,3 +212,26 @@ def test_handle_exception():
206
212
  assert True
207
213
  else:
208
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