Parser of berki style problems and generator of latex file
Samo Penic
2018-11-15 e8d6264e6cc2cb055dea30f9498fdcf4e771ebbc
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):
71                 if idx1 == idx2:
72                     continue
73                 if not val1.is_float() or not val2.is_float():
74                     if val1.get_formatted_value() == val2.get_formatted_value():
75                         return True
76                 else:
77                     if (
78                         abs(val1.get_formatted_value() - val2.get_formatted_value())
79                         < self.AnsDiff * val1.get_formatted_value()
80                     ):
81                         return True
08b2a2 82         return False
6c2245 83
SP 84
85 class ProblemSource:
7c2a8f 86     def __init__(self, parser=None):
SP 87         self.introduction = None
88         self.subproblems = None
7029ac 89         self.picture= None
7c2a8f 90         self.parsedSolutions = None
SP 91         self.parsedVariables = None
d89217 92         self.variableGenerator = None
SP 93         self.varDict = {}
7c2a8f 94         if parser is not None:
7029ac 95             self.introduction, self.picture, self.subproblems, self.parsedVariables, self.parsedSolutions, self.variableGenerator = (
d89217 96                 parser.get_parsed_sections()
SP 97             )
78d798 98             # self.generateVariables()
7c2a8f 99
SP 100     def generateVariables(self):
101         for key in self.parsedVariables:
ca061f 102             self.varDict[key] = Variable(
SP 103                 next(self.variableGenerator[key]), self.parsedVariables[key]["type"]
104             )
7c2a8f 105
SP 106     def generateSolutions(self):
6c2245 107         # a dirty one but it has to be like this ;)
SP 108         __retsol = []
109
110         # define variables
111         for __varname, __var in self.varDict.items():
112             exec(__varname + "=" + str(__var.get_formatted_value()))
113         for __s in self.parsedSolutions:
114             __ps = {}
115             __ps["correct"] = []
116             __ps["wrong"] = []
117             for __corr in __s["correct"]:
118                 for __corrsplit in __corr.split(";"):
b5b680 119                     __result = None
6c2245 120                     if __corrsplit.find("=") >= 0:
SP 121                         exec(self.substitute_octave(__corrsplit))
122                     else:
123                         __result = eval(self.substitute_octave(__corrsplit))
b5b680 124                 if __result is None:
SP 125                     raise Exceptions.NoResult(
126                         "Result cannot be calculated. Be sure to specify last term without = sign!"
127                     )
6c2245 128                 __ps["correct"].append(Variable(__result, formatting=__s["type"]))
SP 129             for __corr in __s["wrong"]:
130                 for __corrsplit in __corr.split(";"):
993a25 131                     __result = None
6c2245 132                     if __corrsplit.find("=") >= 0:
SP 133                         exec(self.substitute_octave(__corrsplit))
134                     else:
135                         __result = eval(self.substitute_octave(__corrsplit))
b5b680 136                 if __result is None:
SP 137                     raise Exceptions.NoResult(
138                         "Result cannot be calculated. Be sure to specify last term without = sign!"
139                     )
6c2245 140                 __ps["wrong"].append(Variable(__result, formatting=__s["type"]))
78d798 141             __ps["glyph"] = __s["glyph"]
SP 142             __ps["unit"] = __s["unit"]
6c2245 143             __retsol.append(__ps)
SP 144         return __retsol
7c2a8f 145
SP 146     def isMultiProblem(self):
147         if len(self.subproblems) > 0:
148             return True
149         else:
150             return False
ef19a9 151
SP 152     def substitute_variables(self, text):
ca061f 153         for key, var in self.varDict.items():
da1010 154             text = re.sub("\/\*\/" + key + "\/\*\/", var.format_as_tex(dollar=""), text)
ef19a9 155         return text
SP 156
ca061f 157     def substitute_octave(self, text):
SP 158         text = re.sub("\^", "**", text)
159         text = re.sub(";", "\n", text)
160         return text
ef19a9 161
08b2a2 162     def generateProblem(self):
ca061f 163         intro = self.substitute_variables(self.introduction)
SP 164         sp = []
ef19a9 165         for p in self.subproblems:
SP 166             sp.append(self.substitute_variables(p))
6c2245 167         sol = self.generateSolutions()
7029ac 168         return {"introduction": intro, "subproblems": sp, "picture": self.picture, "solutions": sol}