Parser of berki style problems and generator of latex file
Samo Penic
2018-11-13 da1010f84bf6d224cca629d8d44072f144e3bf7e
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
from .Variable import Variable
import re
from . import Exceptions
from math import *
from random import shuffle
 
 
class Problem:
    def __init__(
        self,
        source=None,
        shuffle=True,
        MaxShuffleAttempts=40,
        MaxRegenerateAttempts=100,
        AnsDiff=0.05,
    ):
        self.source = source
        self.MaxShuffleAttempts = MaxShuffleAttempts
        self.MaxRegenerateAttempts = MaxRegenerateAttempts
        self.AnsDiff = AnsDiff
        self.source.generateVariables()
        self.source.generateSolutions()
        self.problem = self.source.generateProblem()
        if shuffle:
            for i in range(0, self.MaxRegenerateAttempts):
                try:
                    self.problem["solutions"] = self.shuffleAnswers(
                        self.problem["solutions"]
                    )
                except Exceptions.AnswerProximityError:
                    self.regenerate()
                    continue
                else:
                    break
            if i == self.MaxRegenerateAttempts - 1:
                raise Exceptions.AnswerProximityError(
                    "Could not shuffle answers and get answers that are not too close together :: "
                    + self.problem["introduction"]
                )
 
    def regenerate(self):
        self.source.generateVariables()
        self.source.generateSolutions()
        self.problem = self.source.generateProblem()
 
    def shuffleAnswers(self, solList):
        shuffList = []
        for cont in solList:
            for i in range(0, self.MaxShuffleAttempts):
                wrong = cont["wrong"]
                shuffle(wrong)
                ans = [
                    *zip(cont["correct"], list("1" * len(cont["correct"]))),
                    *zip(wrong, list("0" * len(wrong))),
                ]
                ans = ans[0:4]
                if self.checkAnsProximity(ans) == False:
                    break
            if i == self.MaxShuffleAttempts - 1:
                raise Exceptions.AnswerProximityError(
                    "Could not shuffle answers and get answers that are not too close together"
                )
            shuffle(ans)
            cont["shuffled"] = ans
            shuffList.append(cont)
        return solList
 
    def checkAnsProximity(self, ans):
        for idx1, (val1, cor) in enumerate(ans):
            for idx2, (val2, cor) in enumerate(ans):
                if idx1 == idx2:
                    continue
                if not val1.is_float() or not val2.is_float():
                    if val1.get_formatted_value() == val2.get_formatted_value():
                        return True
                else:
                    if (
                        abs(val1.get_formatted_value() - val2.get_formatted_value())
                        < self.AnsDiff * val1.get_formatted_value()
                    ):
                        return True
        return False
 
 
class ProblemSource:
    def __init__(self, parser=None):
        self.introduction = None
        self.subproblems = None
        self.picture= None
        self.parsedSolutions = None
        self.parsedVariables = None
        self.variableGenerator = None
        self.varDict = {}
        if parser is not None:
            self.introduction, self.picture, self.subproblems, self.parsedVariables, self.parsedSolutions, self.variableGenerator = (
                parser.get_parsed_sections()
            )
            # self.generateVariables()
 
    def generateVariables(self):
        for key in self.parsedVariables:
            self.varDict[key] = Variable(
                next(self.variableGenerator[key]), self.parsedVariables[key]["type"]
            )
 
    def generateSolutions(self):
        # a dirty one but it has to be like this ;)
        __retsol = []
 
        # define variables
        for __varname, __var in self.varDict.items():
            exec(__varname + "=" + str(__var.get_formatted_value()))
        for __s in self.parsedSolutions:
            __ps = {}
            __ps["correct"] = []
            __ps["wrong"] = []
            for __corr in __s["correct"]:
                for __corrsplit in __corr.split(";"):
                    __result = None
                    if __corrsplit.find("=") >= 0:
                        exec(self.substitute_octave(__corrsplit))
                    else:
                        __result = eval(self.substitute_octave(__corrsplit))
                if __result is None:
                    raise Exceptions.NoResult(
                        "Result cannot be calculated. Be sure to specify last term without = sign!"
                    )
                __ps["correct"].append(Variable(__result, formatting=__s["type"]))
            for __corr in __s["wrong"]:
                for __corrsplit in __corr.split(";"):
                    __result = None
                    if __corrsplit.find("=") >= 0:
                        exec(self.substitute_octave(__corrsplit))
                    else:
                        __result = eval(self.substitute_octave(__corrsplit))
                if __result is None:
                    raise Exceptions.NoResult(
                        "Result cannot be calculated. Be sure to specify last term without = sign!"
                    )
                __ps["wrong"].append(Variable(__result, formatting=__s["type"]))
            __ps["glyph"] = __s["glyph"]
            __ps["unit"] = __s["unit"]
            __retsol.append(__ps)
        return __retsol
 
    def isMultiProblem(self):
        if len(self.subproblems) > 0:
            return True
        else:
            return False
 
    def substitute_variables(self, text):
        for key, var in self.varDict.items():
            text = re.sub("\/\*\/" + key + "\/\*\/", var.format_as_tex(dollar=""), text)
        return text
 
    def substitute_octave(self, text):
        text = re.sub("\^", "**", text)
        text = re.sub(";", "\n", text)
        return text
 
    def generateProblem(self):
        intro = self.substitute_variables(self.introduction)
        sp = []
        for p in self.subproblems:
            sp.append(self.substitute_variables(p))
        sol = self.generateSolutions()
        return {"introduction": intro, "subproblems": sp, "picture": self.picture, "solutions": sol}