Parser of berki style problems and generator of latex file
Samo Penic
2018-11-21 7ec3a648501d965ce8f6cc342595abc289f85134
commit | author | age
6c2245 1 from .Variable import Variable
ef19a9 2 import re
993a25 3 from . import Exceptions
ef19a9 4 from math import *
08b2a2 5 from random import shuffle
d89217 6
b5b680 7
7c2a8f 8 class Problem:
b5b680 9     def __init__(
SP 10         self,
11         source=None,
12         shuffle=True,
13         MaxShuffleAttempts=40,
14         MaxRegenerateAttempts=100,
15         AnsDiff=0.05,
16     ):
78d798 17         self.source = source
b5b680 18         self.MaxShuffleAttempts = MaxShuffleAttempts
SP 19         self.MaxRegenerateAttempts = MaxRegenerateAttempts
08b2a2 20         self.AnsDiff = AnsDiff
6c2245 21         self.source.generateVariables()
SP 22         self.source.generateSolutions()
78d798 23         self.problem = self.source.generateProblem()
08b2a2 24         if shuffle:
b5b680 25             for i in range(0, self.MaxRegenerateAttempts):
SP 26                 try:
27                     self.problem["solutions"] = self.shuffleAnswers(
28                         self.problem["solutions"]
29                     )
30                 except Exceptions.AnswerProximityError:
7224af 31                     self.regenerate()
b5b680 32                     continue
SP 33                 else:
34                     break
35             if i == self.MaxRegenerateAttempts - 1:
36                 raise Exceptions.AnswerProximityError(
37                     "Could not shuffle answers and get answers that are not too close together :: "
38                     + self.problem["introduction"]
39                 )
6c2245 40
7224af 41     def regenerate(self):
SP 42         self.source.generateVariables()
43         self.source.generateSolutions()
44         self.problem = self.source.generateProblem()
45
b5b680 46     def shuffleAnswers(self, solList):
SP 47         shuffList = []
08b2a2 48         for cont in solList:
SP 49             for i in range(0, self.MaxShuffleAttempts):
b5b680 50                 wrong = cont["wrong"]
08b2a2 51                 shuffle(wrong)
b5b680 52                 ans = [
SP 53                     *zip(cont["correct"], list("1" * len(cont["correct"]))),
54                     *zip(wrong, list("0" * len(wrong))),
55                 ]
56                 ans = ans[0:4]
57                 if self.checkAnsProximity(ans) == False:
08b2a2 58                     break
b5b680 59             if i == self.MaxShuffleAttempts - 1:
SP 60                 raise Exceptions.AnswerProximityError(
61                     "Could not shuffle answers and get answers that are not too close together"
62                 )
08b2a2 63             shuffle(ans)
b5b680 64             cont["shuffled"] = ans
08b2a2 65             shuffList.append(cont)
SP 66         return solList
67
68     def checkAnsProximity(self, ans):
b5b680 69         for idx1, (val1, cor) in enumerate(ans):
SP 70             for idx2, (val2, cor) in enumerate(ans):
839d16 71
b5b680 72                 if idx1 == idx2:
SP 73                     continue
839d16 74
7ec3a6 75                 if (val1.get_formatted_value()==val2.get_formatted_value()): #solves two 0.0 problem!
839d16 76                     return True
b5b680 77                 if not val1.is_float() or not val2.is_float():
SP 78                     if val1.get_formatted_value() == val2.get_formatted_value():
79                         return True
80                 else:
81                     if (
82                         abs(val1.get_formatted_value() - val2.get_formatted_value())
83                         < self.AnsDiff * val1.get_formatted_value()
84                     ):
85                         return True
08b2a2 86         return False
6c2245 87
SP 88
89 class ProblemSource:
7c2a8f 90     def __init__(self, parser=None):
SP 91         self.introduction = None
92         self.subproblems = None
7029ac 93         self.picture= None
7c2a8f 94         self.parsedSolutions = None
SP 95         self.parsedVariables = None
d89217 96         self.variableGenerator = None
SP 97         self.varDict = {}
7c2a8f 98         if parser is not None:
7029ac 99             self.introduction, self.picture, self.subproblems, self.parsedVariables, self.parsedSolutions, self.variableGenerator = (
d89217 100                 parser.get_parsed_sections()
SP 101             )
78d798 102             # self.generateVariables()
7c2a8f 103
SP 104     def generateVariables(self):
105         for key in self.parsedVariables:
ca061f 106             self.varDict[key] = Variable(
SP 107                 next(self.variableGenerator[key]), self.parsedVariables[key]["type"]
108             )
7c2a8f 109
SP 110     def generateSolutions(self):
6c2245 111         # a dirty one but it has to be like this ;)
SP 112         __retsol = []
113
114         # define variables
115         for __varname, __var in self.varDict.items():
116             exec(__varname + "=" + str(__var.get_formatted_value()))
117         for __s in self.parsedSolutions:
118             __ps = {}
119             __ps["correct"] = []
120             __ps["wrong"] = []
121             for __corr in __s["correct"]:
122                 for __corrsplit in __corr.split(";"):
b5b680 123                     __result = None
6c2245 124                     if __corrsplit.find("=") >= 0:
839d16 125                         try:
SP 126                             exec(self.substitute_octave(__corrsplit))
127                         except:
128                             print("Error while evaluating {}".format(__corrsplit))
6c2245 129                     else:
SP 130                         __result = eval(self.substitute_octave(__corrsplit))
b5b680 131                 if __result is None:
SP 132                     raise Exceptions.NoResult(
133                         "Result cannot be calculated. Be sure to specify last term without = sign!"
134                     )
6c2245 135                 __ps["correct"].append(Variable(__result, formatting=__s["type"]))
SP 136             for __corr in __s["wrong"]:
137                 for __corrsplit in __corr.split(";"):
993a25 138                     __result = None
6c2245 139                     if __corrsplit.find("=") >= 0:
SP 140                         exec(self.substitute_octave(__corrsplit))
141                     else:
142                         __result = eval(self.substitute_octave(__corrsplit))
b5b680 143                 if __result is None:
SP 144                     raise Exceptions.NoResult(
145                         "Result cannot be calculated. Be sure to specify last term without = sign!"
146                     )
6c2245 147                 __ps["wrong"].append(Variable(__result, formatting=__s["type"]))
78d798 148             __ps["glyph"] = __s["glyph"]
SP 149             __ps["unit"] = __s["unit"]
6c2245 150             __retsol.append(__ps)
SP 151         return __retsol
7c2a8f 152
SP 153     def isMultiProblem(self):
154         if len(self.subproblems) > 0:
155             return True
156         else:
157             return False
ef19a9 158
SP 159     def substitute_variables(self, text):
ca061f 160         for key, var in self.varDict.items():
da1010 161             text = re.sub("\/\*\/" + key + "\/\*\/", var.format_as_tex(dollar=""), text)
ef19a9 162         return text
SP 163
ca061f 164     def substitute_octave(self, text):
SP 165         text = re.sub("\^", "**", text)
166         text = re.sub(";", "\n", text)
167         return text
ef19a9 168
08b2a2 169     def generateProblem(self):
ca061f 170         intro = self.substitute_variables(self.introduction)
SP 171         sp = []
ef19a9 172         for p in self.subproblems:
SP 173             sp.append(self.substitute_variables(p))
6c2245 174         sol = self.generateSolutions()
7029ac 175         return {"introduction": intro, "subproblems": sp, "picture": self.picture, "solutions": sol}