QuizGenerator 0.4.2__py3-none-any.whl
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.
- QuizGenerator/README.md +5 -0
- QuizGenerator/__init__.py +27 -0
- QuizGenerator/__main__.py +7 -0
- QuizGenerator/canvas/__init__.py +13 -0
- QuizGenerator/canvas/canvas_interface.py +627 -0
- QuizGenerator/canvas/classes.py +235 -0
- QuizGenerator/constants.py +149 -0
- QuizGenerator/contentast.py +1955 -0
- QuizGenerator/generate.py +253 -0
- QuizGenerator/logging.yaml +55 -0
- QuizGenerator/misc.py +579 -0
- QuizGenerator/mixins.py +548 -0
- QuizGenerator/performance.py +202 -0
- QuizGenerator/premade_questions/__init__.py +0 -0
- QuizGenerator/premade_questions/basic.py +103 -0
- QuizGenerator/premade_questions/cst334/__init__.py +1 -0
- QuizGenerator/premade_questions/cst334/languages.py +391 -0
- QuizGenerator/premade_questions/cst334/math_questions.py +297 -0
- QuizGenerator/premade_questions/cst334/memory_questions.py +1400 -0
- QuizGenerator/premade_questions/cst334/ostep13_vsfs.py +572 -0
- QuizGenerator/premade_questions/cst334/persistence_questions.py +451 -0
- QuizGenerator/premade_questions/cst334/process.py +648 -0
- QuizGenerator/premade_questions/cst463/__init__.py +0 -0
- QuizGenerator/premade_questions/cst463/gradient_descent/__init__.py +3 -0
- QuizGenerator/premade_questions/cst463/gradient_descent/gradient_calculation.py +369 -0
- QuizGenerator/premade_questions/cst463/gradient_descent/gradient_descent_questions.py +305 -0
- QuizGenerator/premade_questions/cst463/gradient_descent/loss_calculations.py +650 -0
- QuizGenerator/premade_questions/cst463/gradient_descent/misc.py +73 -0
- QuizGenerator/premade_questions/cst463/math_and_data/__init__.py +2 -0
- QuizGenerator/premade_questions/cst463/math_and_data/matrix_questions.py +631 -0
- QuizGenerator/premade_questions/cst463/math_and_data/vector_questions.py +534 -0
- QuizGenerator/premade_questions/cst463/models/__init__.py +0 -0
- QuizGenerator/premade_questions/cst463/models/attention.py +192 -0
- QuizGenerator/premade_questions/cst463/models/cnns.py +186 -0
- QuizGenerator/premade_questions/cst463/models/matrices.py +24 -0
- QuizGenerator/premade_questions/cst463/models/rnns.py +202 -0
- QuizGenerator/premade_questions/cst463/models/text.py +203 -0
- QuizGenerator/premade_questions/cst463/models/weight_counting.py +227 -0
- QuizGenerator/premade_questions/cst463/neural-network-basics/__init__.py +6 -0
- QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py +1314 -0
- QuizGenerator/premade_questions/cst463/tensorflow-intro/__init__.py +6 -0
- QuizGenerator/premade_questions/cst463/tensorflow-intro/tensorflow_questions.py +936 -0
- QuizGenerator/qrcode_generator.py +293 -0
- QuizGenerator/question.py +715 -0
- QuizGenerator/quiz.py +467 -0
- QuizGenerator/regenerate.py +472 -0
- QuizGenerator/typst_utils.py +113 -0
- quizgenerator-0.4.2.dist-info/METADATA +265 -0
- quizgenerator-0.4.2.dist-info/RECORD +52 -0
- quizgenerator-0.4.2.dist-info/WHEEL +4 -0
- quizgenerator-0.4.2.dist-info/entry_points.txt +3 -0
- quizgenerator-0.4.2.dist-info/licenses/LICENSE +674 -0
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
#!env python
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import abc
|
|
5
|
+
import difflib
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
from QuizGenerator.question import Question, Answer, QuestionRegistry
|
|
9
|
+
from QuizGenerator.contentast import ContentAST
|
|
10
|
+
from QuizGenerator.mixins import TableQuestionMixin, BodyTemplatesMixin
|
|
11
|
+
|
|
12
|
+
log = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class IOQuestion(Question, abc.ABC):
|
|
16
|
+
def __init__(self, *args, **kwargs):
|
|
17
|
+
kwargs["topic"] = kwargs.get("topic", Question.Topic.IO)
|
|
18
|
+
super().__init__(*args, **kwargs)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@QuestionRegistry.register()
|
|
22
|
+
class HardDriveAccessTime(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
23
|
+
|
|
24
|
+
def refresh(self, *args, **kwargs):
|
|
25
|
+
super().refresh(*args, **kwargs)
|
|
26
|
+
|
|
27
|
+
self.hard_drive_rotation_speed = 100 * self.rng.randint(36, 150) # e.g. 3600rpm to 15000rpm
|
|
28
|
+
self.seek_delay = float(round(self.rng.randrange(3, 20), 2))
|
|
29
|
+
self.transfer_rate = self.rng.randint(50, 300)
|
|
30
|
+
self.number_of_reads = self.rng.randint(1, 20)
|
|
31
|
+
self.size_of_reads = self.rng.randint(1, 10)
|
|
32
|
+
|
|
33
|
+
self.rotational_delay = (1 / self.hard_drive_rotation_speed) * (60 / 1) * (1000 / 1) * (1/2)
|
|
34
|
+
self.access_delay = self.rotational_delay + self.seek_delay
|
|
35
|
+
self.transfer_delay = 1000 * (self.size_of_reads * self.number_of_reads) / 1024 / self.transfer_rate
|
|
36
|
+
self.disk_access_delay = self.access_delay * self.number_of_reads + self.transfer_delay
|
|
37
|
+
|
|
38
|
+
self.answers.update({
|
|
39
|
+
"answer__rotational_delay": Answer.float_value(
|
|
40
|
+
"answer__rotational_delay",
|
|
41
|
+
self.rotational_delay
|
|
42
|
+
),
|
|
43
|
+
"answer__access_delay": Answer.float_value(
|
|
44
|
+
"answer__access_delay",
|
|
45
|
+
self.access_delay
|
|
46
|
+
),
|
|
47
|
+
"answer__transfer_delay": Answer.float_value(
|
|
48
|
+
"answer__transfer_delay",
|
|
49
|
+
self.transfer_delay
|
|
50
|
+
),
|
|
51
|
+
"answer__disk_access_delay": Answer.float_value(
|
|
52
|
+
"answer__disk_access_delay",
|
|
53
|
+
self.disk_access_delay
|
|
54
|
+
),
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
def get_body(self, *args, **kwargs) -> ContentAST.Section:
|
|
58
|
+
# Create parameter info table using mixin
|
|
59
|
+
parameter_info = {
|
|
60
|
+
"Hard Drive Rotation Speed": f"{self.hard_drive_rotation_speed}RPM",
|
|
61
|
+
"Seek Delay": f"{self.seek_delay}ms",
|
|
62
|
+
"Transfer Rate": f"{self.transfer_rate}MB/s",
|
|
63
|
+
"Number of Reads": f"{self.number_of_reads}",
|
|
64
|
+
"Size of Reads": f"{self.size_of_reads}KB"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
parameter_table = self.create_info_table(parameter_info)
|
|
68
|
+
|
|
69
|
+
# Create answer table with multiple rows using mixin
|
|
70
|
+
answer_rows = [
|
|
71
|
+
{"Variable": "Rotational Delay", "Value": "answer__rotational_delay"},
|
|
72
|
+
{"Variable": "Access Delay", "Value": "answer__access_delay"},
|
|
73
|
+
{"Variable": "Transfer Delay", "Value": "answer__transfer_delay"},
|
|
74
|
+
{"Variable": "Total Disk Access Delay", "Value": "answer__disk_access_delay"}
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
answer_table = self.create_answer_table(
|
|
78
|
+
headers=["Variable", "Value"],
|
|
79
|
+
data_rows=answer_rows,
|
|
80
|
+
answer_columns=["Value"]
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Use mixin to create complete body with both tables
|
|
84
|
+
intro_text = "Given the information below, please calculate the following values."
|
|
85
|
+
|
|
86
|
+
instructions = (
|
|
87
|
+
f"Make sure your answers are rounded to {Answer.DEFAULT_ROUNDING_DIGITS} decimal points "
|
|
88
|
+
f"(even if they are whole numbers), and do so after you finish all your calculations! "
|
|
89
|
+
f"(i.e. don't use your rounded answers to calculate your overall answer)"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
body = self.create_parameter_calculation_body(
|
|
93
|
+
intro_text=intro_text,
|
|
94
|
+
parameter_table=parameter_table,
|
|
95
|
+
answer_table=answer_table,
|
|
96
|
+
additional_instructions=instructions
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
return body
|
|
100
|
+
|
|
101
|
+
def get_explanation(self) -> ContentAST.Section:
|
|
102
|
+
explanation = ContentAST.Section()
|
|
103
|
+
|
|
104
|
+
explanation.add_element(
|
|
105
|
+
ContentAST.Paragraph([
|
|
106
|
+
"To calculate the total disk access time (or \"delay\"), "
|
|
107
|
+
"we should first calculate each of the individual parts.",
|
|
108
|
+
r"Since we know that $t_{total} = (\text{# of reads}) \cdot t_{access} + t_{transfer}$"
|
|
109
|
+
r"we therefore need to calculate $t_{access}$ and $t_{transfer}$, where "
|
|
110
|
+
r"$t_{access} = t_{rotation} + t_{seek}$.",
|
|
111
|
+
])
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
explanation.add_elements([
|
|
115
|
+
ContentAST.Paragraph(["Starting with the rotation delay, we calculate:"]),
|
|
116
|
+
ContentAST.Equation(
|
|
117
|
+
"t_{rotation} = "
|
|
118
|
+
+ f"\\frac{{1 minute}}{{{self.hard_drive_rotation_speed}revolutions}}"
|
|
119
|
+
+ r"\cdot \frac{60 seconds}{1 minute} \cdot \frac{1000 ms}{1 second} \cdot \frac{1 revolution}{2} = "
|
|
120
|
+
+ f"{self.rotational_delay:0.2f}ms",
|
|
121
|
+
)
|
|
122
|
+
])
|
|
123
|
+
|
|
124
|
+
explanation.add_elements([
|
|
125
|
+
ContentAST.Paragraph([
|
|
126
|
+
"Now we can calculate:",
|
|
127
|
+
]),
|
|
128
|
+
ContentAST.Equation(
|
|
129
|
+
f"t_{{access}} "
|
|
130
|
+
f"= t_{{rotation}} + t_{{seek}} "
|
|
131
|
+
f"= {self.rotational_delay:0.2f}ms + {self.seek_delay:0.2f}ms = {self.access_delay:0.2f}ms"
|
|
132
|
+
)
|
|
133
|
+
])
|
|
134
|
+
|
|
135
|
+
explanation.add_elements([
|
|
136
|
+
ContentAST.Paragraph([r"Next we need to calculate our transfer delay, $t_{transfer}$, which we do as:"]),
|
|
137
|
+
ContentAST.Equation(
|
|
138
|
+
f"t_{{transfer}} "
|
|
139
|
+
f"= \\frac{{{self.number_of_reads} \\cdot {self.size_of_reads}KB}}{{1}} \\cdot \\frac{{1MB}}{{1024KB}} "
|
|
140
|
+
f"\\cdot \\frac{{1 second}}{{{self.transfer_rate}MB}} \\cdot \\frac{{1000ms}}{{1second}} "
|
|
141
|
+
f"= {self.transfer_delay:0.2}ms"
|
|
142
|
+
)
|
|
143
|
+
])
|
|
144
|
+
|
|
145
|
+
explanation.add_elements([
|
|
146
|
+
ContentAST.Paragraph(["Putting these together we get:"]),
|
|
147
|
+
ContentAST.Equation(
|
|
148
|
+
f"t_{{total}} "
|
|
149
|
+
f"= \\text{{(# reads)}} \\cdot t_{{access}} + t_{{transfer}} "
|
|
150
|
+
f"= {self.number_of_reads} \\cdot {self.access_delay:0.2f} + {self.transfer_delay:0.2f} "
|
|
151
|
+
f"= {self.disk_access_delay:0.2f}ms")
|
|
152
|
+
])
|
|
153
|
+
return explanation
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@QuestionRegistry.register()
|
|
157
|
+
class INodeAccesses(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
158
|
+
|
|
159
|
+
def refresh(self, *args, **kwargs):
|
|
160
|
+
super().refresh(*args, **kwargs)
|
|
161
|
+
|
|
162
|
+
# Calculating this first to use blocksize as an even multiple of it
|
|
163
|
+
self.inode_size = 2**self.rng.randint(6, 10)
|
|
164
|
+
|
|
165
|
+
self.block_size = self.inode_size * self.rng.randint(8, 20)
|
|
166
|
+
self.inode_number = self.rng.randint(0, 256)
|
|
167
|
+
self.inode_start_location = self.block_size * self.rng.randint(2, 5)
|
|
168
|
+
|
|
169
|
+
self.inode_address = self.inode_start_location + self.inode_number * self.inode_size
|
|
170
|
+
self.inode_block = self.inode_address // self.block_size
|
|
171
|
+
self.inode_address_in_block = self.inode_address % self.block_size
|
|
172
|
+
self.inode_index_in_block = int(self.inode_address_in_block / self.inode_size)
|
|
173
|
+
|
|
174
|
+
self.answers.update({
|
|
175
|
+
"answer__inode_address": Answer.integer("answer__inode_address", self.inode_address),
|
|
176
|
+
"answer__inode_block": Answer.integer("answer__inode_block", self.inode_block),
|
|
177
|
+
"answer__inode_address_in_block": Answer.integer("answer__inode_address_in_block", self.inode_address_in_block),
|
|
178
|
+
"answer__inode_index_in_block": Answer.integer("answer__inode_index_in_block", self.inode_index_in_block),
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
def get_body(self) -> ContentAST.Section:
|
|
182
|
+
# Create parameter info table using mixin
|
|
183
|
+
parameter_info = {
|
|
184
|
+
"Block Size": f"{self.block_size} Bytes",
|
|
185
|
+
"Inode Number": f"{self.inode_number}",
|
|
186
|
+
"Inode Start Location": f"{self.inode_start_location} Bytes",
|
|
187
|
+
"Inode size": f"{self.inode_size} Bytes"
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
parameter_table = self.create_info_table(parameter_info)
|
|
191
|
+
|
|
192
|
+
# Create answer table with multiple rows using mixin
|
|
193
|
+
answer_rows = [
|
|
194
|
+
{"Variable": "Inode address", "Value": "answer__inode_address"},
|
|
195
|
+
{"Variable": "Block containing inode", "Value": "answer__inode_block"},
|
|
196
|
+
{"Variable": "Inode address (offset) within block", "Value": "answer__inode_address_in_block"},
|
|
197
|
+
{"Variable": "Inode index within block", "Value": "answer__inode_index_in_block"}
|
|
198
|
+
]
|
|
199
|
+
|
|
200
|
+
answer_table = self.create_answer_table(
|
|
201
|
+
headers=["Variable", "Value"],
|
|
202
|
+
data_rows=answer_rows,
|
|
203
|
+
answer_columns=["Value"]
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# Use mixin to create complete body with both tables
|
|
207
|
+
intro_text = "Given the information below, please calculate the following values."
|
|
208
|
+
|
|
209
|
+
instructions = (
|
|
210
|
+
"(hint: they should all be round numbers). "
|
|
211
|
+
"Remember, demonstrating you know the equations and what goes into them is generally sufficient."
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
body = self.create_parameter_calculation_body(
|
|
215
|
+
intro_text=intro_text,
|
|
216
|
+
parameter_table=parameter_table,
|
|
217
|
+
answer_table=answer_table,
|
|
218
|
+
additional_instructions=instructions
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
return body
|
|
222
|
+
|
|
223
|
+
def get_explanation(self) -> ContentAST.Section:
|
|
224
|
+
explanation = ContentAST.Section()
|
|
225
|
+
|
|
226
|
+
explanation.add_element(
|
|
227
|
+
ContentAST.Paragraph([
|
|
228
|
+
"If we are given an inode number, there are a few steps that we need to take to load the actual inode. "
|
|
229
|
+
"These consist of determining the address of the inode, which block would contain it, "
|
|
230
|
+
"and then its address within the block.",
|
|
231
|
+
"To find the inode address, we calculate:",
|
|
232
|
+
])
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
explanation.add_element(
|
|
236
|
+
ContentAST.Equation.make_block_equation__multiline_equals(
|
|
237
|
+
r"(\text{Inode address})",
|
|
238
|
+
[
|
|
239
|
+
r"(\text{Inode Start Location}) + (\text{inode #}) \cdot (\text{inode size})",
|
|
240
|
+
f"{self.inode_start_location} + {self.inode_number} \\cdot {self.inode_size}",
|
|
241
|
+
f"{self.inode_address}"
|
|
242
|
+
])
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
explanation.add_element(
|
|
246
|
+
ContentAST.Paragraph([
|
|
247
|
+
"Next, we us this to figure out what block the inode is in. "
|
|
248
|
+
"We do this directly so we know what block to load, "
|
|
249
|
+
"thus minimizing the number of loads we have to make.",
|
|
250
|
+
])
|
|
251
|
+
)
|
|
252
|
+
explanation.add_element(ContentAST.Equation.make_block_equation__multiline_equals(
|
|
253
|
+
r"\text{Block containing inode}",
|
|
254
|
+
[
|
|
255
|
+
r"(\text{Inode address}) \mathbin{//} (\text{block size})",
|
|
256
|
+
f"{self.inode_address} \\mathbin{{//}} {self.block_size}",
|
|
257
|
+
f"{self.inode_block}"
|
|
258
|
+
]
|
|
259
|
+
))
|
|
260
|
+
|
|
261
|
+
explanation.add_element(
|
|
262
|
+
ContentAST.Paragraph([
|
|
263
|
+
"When we load this block, we now have in our system memory "
|
|
264
|
+
"(remember, blocks on the hard drive are effectively useless to us until they're in main memory!), "
|
|
265
|
+
"the inode, so next we need to figure out where it is within that block."
|
|
266
|
+
"This means that we'll need to find the offset into this block. "
|
|
267
|
+
"We'll calculate this both as the offset in bytes, and also in number of inodes, "
|
|
268
|
+
"since we can use array indexing.",
|
|
269
|
+
])
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
explanation.add_element(ContentAST.Equation.make_block_equation__multiline_equals(
|
|
273
|
+
r"\text{offset within block}",
|
|
274
|
+
[
|
|
275
|
+
r"(\text{Inode address}) \bmod (\text{block size})",
|
|
276
|
+
f"{self.inode_address} \\bmod {self.block_size}",
|
|
277
|
+
f"{self.inode_address_in_block}"
|
|
278
|
+
]
|
|
279
|
+
))
|
|
280
|
+
|
|
281
|
+
explanation.add_element(
|
|
282
|
+
ContentAST.Text("Remember that `mod` is the same as `%`, the modulo operation.")
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
explanation.add_element(ContentAST.Paragraph(["and"]))
|
|
286
|
+
|
|
287
|
+
explanation.add_element(ContentAST.Equation.make_block_equation__multiline_equals(
|
|
288
|
+
r"\text{index within block}",
|
|
289
|
+
[
|
|
290
|
+
r"\dfrac{\text{offset within block}}{\text{inode size}}",
|
|
291
|
+
f"\\dfrac{{{self.inode_address_in_block}}}{{{self.inode_size}}}",
|
|
292
|
+
f"{self.inode_index_in_block}"
|
|
293
|
+
]
|
|
294
|
+
))
|
|
295
|
+
|
|
296
|
+
return explanation
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
@QuestionRegistry.register()
|
|
300
|
+
class VSFS_states(IOQuestion):
|
|
301
|
+
|
|
302
|
+
from .ostep13_vsfs import fs as vsfs
|
|
303
|
+
|
|
304
|
+
def __init__(self, *args, **kwargs):
|
|
305
|
+
super().__init__(*args, **kwargs)
|
|
306
|
+
self.answer_kind = Answer.AnswerKind.MULTIPLE_DROPDOWN
|
|
307
|
+
|
|
308
|
+
self.num_steps = kwargs.get("num_steps", 10)
|
|
309
|
+
|
|
310
|
+
def refresh(self, *args, **kwargs):
|
|
311
|
+
super().refresh(*args, **kwargs)
|
|
312
|
+
|
|
313
|
+
fs = self.vsfs(4, 4, self.rng)
|
|
314
|
+
operations = fs.run_for_steps(self.num_steps)
|
|
315
|
+
|
|
316
|
+
self.start_state = operations[-1]["start_state"]
|
|
317
|
+
self.end_state = operations[-1]["end_state"]
|
|
318
|
+
|
|
319
|
+
wrong_answers = list(filter(
|
|
320
|
+
lambda o: o != operations[-1]["cmd"],
|
|
321
|
+
map(
|
|
322
|
+
lambda o: o["cmd"],
|
|
323
|
+
operations
|
|
324
|
+
)
|
|
325
|
+
))
|
|
326
|
+
self.rng.shuffle(wrong_answers)
|
|
327
|
+
|
|
328
|
+
self.answers["answer__cmd"] = Answer(
|
|
329
|
+
"answer__cmd",
|
|
330
|
+
f"{operations[-1]['cmd']}",
|
|
331
|
+
kind=Answer.AnswerKind.MULTIPLE_DROPDOWN,
|
|
332
|
+
correct=True,
|
|
333
|
+
baffles=list(set([op['cmd'] for op in operations[:-1] if op != operations[-1]['cmd']]))
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
def get_body(self) -> ContentAST.Section:
|
|
337
|
+
body = ContentAST.Section()
|
|
338
|
+
|
|
339
|
+
body.add_element(ContentAST.Paragraph(["What operation happens between these two states?"]))
|
|
340
|
+
|
|
341
|
+
body.add_element(
|
|
342
|
+
ContentAST.Code(
|
|
343
|
+
self.start_state,
|
|
344
|
+
make_small=True
|
|
345
|
+
)
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
body.add_element(
|
|
349
|
+
ContentAST.AnswerBlock(
|
|
350
|
+
ContentAST.Answer(
|
|
351
|
+
self.answers["answer__cmd"],
|
|
352
|
+
label="Command"
|
|
353
|
+
)
|
|
354
|
+
)
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
body.add_element(
|
|
358
|
+
ContentAST.Code(
|
|
359
|
+
self.end_state,
|
|
360
|
+
make_small=True
|
|
361
|
+
)
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
return body
|
|
365
|
+
|
|
366
|
+
def get_explanation(self) -> ContentAST.Section:
|
|
367
|
+
explanation = ContentAST.Section()
|
|
368
|
+
|
|
369
|
+
log.debug(f"self.start_state: {self.start_state}")
|
|
370
|
+
log.debug(f"self.end_state: {self.end_state}")
|
|
371
|
+
|
|
372
|
+
explanation.add_elements([
|
|
373
|
+
ContentAST.Paragraph([
|
|
374
|
+
"The key thing to pay attention to when solving these problems is where there are differences between the start state and the end state.",
|
|
375
|
+
"In this particular problem, we can see that these lines are different:"
|
|
376
|
+
])
|
|
377
|
+
])
|
|
378
|
+
|
|
379
|
+
chunk_to_add = []
|
|
380
|
+
lines_that_changed = []
|
|
381
|
+
for start_line, end_line in zip(self.start_state.split('\n'), self.end_state.split('\n')):
|
|
382
|
+
if start_line == end_line:
|
|
383
|
+
continue
|
|
384
|
+
lines_that_changed.append((start_line, end_line))
|
|
385
|
+
chunk_to_add.append(
|
|
386
|
+
f" - `{start_line}` -> `{end_line}`"
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
explanation.add_element(
|
|
390
|
+
ContentAST.Paragraph(chunk_to_add)
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
chunk_to_add = [
|
|
394
|
+
"A great place to start is to check to see if the bitmaps have changed as this can quickly tell us a lot of information"
|
|
395
|
+
]
|
|
396
|
+
|
|
397
|
+
inode_bitmap_lines = list(filter(lambda s: "inode bitmap" in s[0], lines_that_changed))
|
|
398
|
+
data_bitmap_lines = list(filter(lambda s: "data bitmap" in s[0], lines_that_changed))
|
|
399
|
+
|
|
400
|
+
def get_bitmap(line: str) -> str:
|
|
401
|
+
log.debug(f"line: {line}")
|
|
402
|
+
return line.split()[-1]
|
|
403
|
+
|
|
404
|
+
def highlight_changes(a: str, b: str) -> str:
|
|
405
|
+
matcher = difflib.SequenceMatcher(None, a, b)
|
|
406
|
+
result = []
|
|
407
|
+
|
|
408
|
+
for tag, i1, i2, j1, j2 in matcher.get_opcodes():
|
|
409
|
+
if tag == "equal":
|
|
410
|
+
result.append(b[j1:j2])
|
|
411
|
+
elif tag in ("insert", "replace"):
|
|
412
|
+
result.append(f"***{b[j1:j2]}***")
|
|
413
|
+
# for "delete", do nothing since text is removed
|
|
414
|
+
|
|
415
|
+
return "".join(result)
|
|
416
|
+
|
|
417
|
+
if len(inode_bitmap_lines) > 0:
|
|
418
|
+
inode_bitmap_lines = inode_bitmap_lines[0]
|
|
419
|
+
chunk_to_add.append(f"The inode bitmap lines have changed from {get_bitmap(inode_bitmap_lines[0])} to {get_bitmap(inode_bitmap_lines[1])}.")
|
|
420
|
+
if get_bitmap(inode_bitmap_lines[0]).count('1') < get_bitmap(inode_bitmap_lines[1]).count('1'):
|
|
421
|
+
chunk_to_add.append("We can see that we have added an inode, so we have either called `creat` or `mkdir`.")
|
|
422
|
+
else:
|
|
423
|
+
chunk_to_add.append("We can see that we have removed an inode, so we have called `unlink`.")
|
|
424
|
+
|
|
425
|
+
if len(data_bitmap_lines) > 0:
|
|
426
|
+
data_bitmap_lines = data_bitmap_lines[0]
|
|
427
|
+
chunk_to_add.append(f"The inode bitmap lines have changed from {get_bitmap(data_bitmap_lines[0])} to {get_bitmap(data_bitmap_lines[1])}.")
|
|
428
|
+
if get_bitmap(data_bitmap_lines[0]).count('1') < get_bitmap(data_bitmap_lines[1]).count('1'):
|
|
429
|
+
chunk_to_add.append("We can see that we have added a data block, so we have either called `mkdir` or `write`.")
|
|
430
|
+
else:
|
|
431
|
+
chunk_to_add.append("We can see that we have removed a data block, so we have `unlink`ed a file.")
|
|
432
|
+
|
|
433
|
+
if len(data_bitmap_lines) == 0 and len(inode_bitmap_lines) == 0:
|
|
434
|
+
chunk_to_add.append("If they have not changed, then we know we must have eithered called `link` or `unlink` and must check the references.")
|
|
435
|
+
|
|
436
|
+
explanation.add_element(
|
|
437
|
+
ContentAST.Paragraph(chunk_to_add)
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
explanation.add_elements([
|
|
441
|
+
ContentAST.Paragraph(["The overall changes are highlighted with `*` symbols below"])
|
|
442
|
+
])
|
|
443
|
+
|
|
444
|
+
explanation.add_element(
|
|
445
|
+
ContentAST.Code(
|
|
446
|
+
highlight_changes(self.start_state, self.end_state)
|
|
447
|
+
)
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
return explanation
|
|
451
|
+
|