QuizGenerator 0.7.0__py3-none-any.whl → 0.8.0__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/contentast.py +6 -6
- QuizGenerator/generate.py +2 -1
- QuizGenerator/mixins.py +14 -100
- QuizGenerator/premade_questions/basic.py +24 -29
- QuizGenerator/premade_questions/cst334/languages.py +100 -99
- QuizGenerator/premade_questions/cst334/math_questions.py +112 -122
- QuizGenerator/premade_questions/cst334/memory_questions.py +621 -621
- QuizGenerator/premade_questions/cst334/persistence_questions.py +137 -163
- QuizGenerator/premade_questions/cst334/process.py +312 -322
- QuizGenerator/premade_questions/cst463/gradient_descent/gradient_calculation.py +34 -35
- QuizGenerator/premade_questions/cst463/gradient_descent/gradient_descent_questions.py +41 -36
- QuizGenerator/premade_questions/cst463/gradient_descent/loss_calculations.py +48 -41
- QuizGenerator/premade_questions/cst463/math_and_data/matrix_questions.py +285 -520
- QuizGenerator/premade_questions/cst463/math_and_data/vector_questions.py +149 -126
- QuizGenerator/premade_questions/cst463/models/attention.py +44 -50
- QuizGenerator/premade_questions/cst463/models/cnns.py +43 -47
- QuizGenerator/premade_questions/cst463/models/matrices.py +61 -11
- QuizGenerator/premade_questions/cst463/models/rnns.py +48 -50
- QuizGenerator/premade_questions/cst463/models/text.py +65 -67
- QuizGenerator/premade_questions/cst463/models/weight_counting.py +47 -46
- QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py +100 -156
- QuizGenerator/premade_questions/cst463/tensorflow-intro/tensorflow_questions.py +93 -141
- QuizGenerator/question.py +273 -202
- QuizGenerator/quiz.py +8 -5
- QuizGenerator/regenerate.py +128 -19
- {quizgenerator-0.7.0.dist-info → quizgenerator-0.8.0.dist-info}/METADATA +30 -2
- {quizgenerator-0.7.0.dist-info → quizgenerator-0.8.0.dist-info}/RECORD +30 -30
- {quizgenerator-0.7.0.dist-info → quizgenerator-0.8.0.dist-info}/WHEEL +0 -0
- {quizgenerator-0.7.0.dist-info → quizgenerator-0.8.0.dist-info}/entry_points.txt +0 -0
- {quizgenerator-0.7.0.dist-info → quizgenerator-0.8.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
import abc
|
|
5
5
|
import difflib
|
|
6
6
|
import logging
|
|
7
|
+
import random
|
|
7
8
|
|
|
8
9
|
from QuizGenerator.question import Question, QuestionRegistry
|
|
9
10
|
import QuizGenerator.contentast as ca
|
|
@@ -21,56 +22,60 @@ class IOQuestion(Question, abc.ABC):
|
|
|
21
22
|
@QuestionRegistry.register()
|
|
22
23
|
class HardDriveAccessTime(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
25
|
+
@classmethod
|
|
26
|
+
def _build_context(cls, *, rng_seed=None, **kwargs):
|
|
27
|
+
rng = random.Random(rng_seed)
|
|
28
|
+
hard_drive_rotation_speed = 100 * rng.randint(36, 150)
|
|
29
|
+
seek_delay = float(round(rng.randrange(3, 20), 2))
|
|
30
|
+
transfer_rate = rng.randint(50, 300)
|
|
31
|
+
number_of_reads = rng.randint(1, 20)
|
|
32
|
+
size_of_reads = rng.randint(1, 10)
|
|
33
|
+
|
|
34
|
+
rotational_delay = (1 / hard_drive_rotation_speed) * (60 / 1) * (1000 / 1) * (1/2)
|
|
35
|
+
access_delay = rotational_delay + seek_delay
|
|
36
|
+
transfer_delay = 1000 * (size_of_reads * number_of_reads) / 1024 / transfer_rate
|
|
37
|
+
disk_access_delay = access_delay * number_of_reads + transfer_delay
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
"hard_drive_rotation_speed": hard_drive_rotation_speed,
|
|
41
|
+
"seek_delay": seek_delay,
|
|
42
|
+
"transfer_rate": transfer_rate,
|
|
43
|
+
"number_of_reads": number_of_reads,
|
|
44
|
+
"size_of_reads": size_of_reads,
|
|
45
|
+
"rotational_delay": rotational_delay,
|
|
46
|
+
"access_delay": access_delay,
|
|
47
|
+
"transfer_delay": transfer_delay,
|
|
48
|
+
"disk_access_delay": disk_access_delay,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def _build_body(cls, context):
|
|
46
53
|
"""Build question body and collect answers."""
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
self.answers["answer__disk_access_delay"],
|
|
52
|
-
]
|
|
54
|
+
rotational_answer = ca.AnswerTypes.Float(context["rotational_delay"])
|
|
55
|
+
access_answer = ca.AnswerTypes.Float(context["access_delay"])
|
|
56
|
+
transfer_answer = ca.AnswerTypes.Float(context["transfer_delay"])
|
|
57
|
+
disk_access_answer = ca.AnswerTypes.Float(context["disk_access_delay"])
|
|
53
58
|
|
|
54
59
|
# Create parameter info table using mixin
|
|
55
60
|
parameter_info = {
|
|
56
|
-
"Hard Drive Rotation Speed": f"{
|
|
57
|
-
"Seek Delay": f"{
|
|
58
|
-
"Transfer Rate": f"{
|
|
59
|
-
"Number of Reads": f"{
|
|
60
|
-
"Size of Reads": f"{
|
|
61
|
+
"Hard Drive Rotation Speed": f"{context['hard_drive_rotation_speed']}RPM",
|
|
62
|
+
"Seek Delay": f"{context['seek_delay']}ms",
|
|
63
|
+
"Transfer Rate": f"{context['transfer_rate']}MB/s",
|
|
64
|
+
"Number of Reads": f"{context['number_of_reads']}",
|
|
65
|
+
"Size of Reads": f"{context['size_of_reads']}KB"
|
|
61
66
|
}
|
|
62
67
|
|
|
63
|
-
parameter_table =
|
|
68
|
+
parameter_table = cls.create_info_table(parameter_info)
|
|
64
69
|
|
|
65
70
|
# Create answer table with multiple rows using mixin
|
|
66
71
|
answer_rows = [
|
|
67
|
-
{"Variable": "Rotational Delay", "Value":
|
|
68
|
-
{"Variable": "Access Delay", "Value":
|
|
69
|
-
{"Variable": "Transfer Delay", "Value":
|
|
70
|
-
{"Variable": "Total Disk Access Delay", "Value":
|
|
72
|
+
{"Variable": "Rotational Delay", "Value": rotational_answer},
|
|
73
|
+
{"Variable": "Access Delay", "Value": access_answer},
|
|
74
|
+
{"Variable": "Transfer Delay", "Value": transfer_answer},
|
|
75
|
+
{"Variable": "Total Disk Access Delay", "Value": disk_access_answer}
|
|
71
76
|
]
|
|
72
77
|
|
|
73
|
-
answer_table =
|
|
78
|
+
answer_table = cls.create_answer_table(
|
|
74
79
|
headers=["Variable", "Value"],
|
|
75
80
|
data_rows=answer_rows,
|
|
76
81
|
answer_columns=["Value"]
|
|
@@ -85,21 +90,17 @@ class HardDriveAccessTime(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
|
85
90
|
f"(i.e. don't use your rounded answers to calculate your overall answer)"
|
|
86
91
|
)
|
|
87
92
|
|
|
88
|
-
body =
|
|
93
|
+
body = cls.create_parameter_calculation_body(
|
|
89
94
|
intro_text=intro_text,
|
|
90
95
|
parameter_table=parameter_table,
|
|
91
96
|
answer_table=answer_table,
|
|
92
97
|
additional_instructions=instructions
|
|
93
98
|
)
|
|
94
99
|
|
|
95
|
-
return body, answers
|
|
96
|
-
|
|
97
|
-
def get_body(self, *args, **kwargs) -> ca.Section:
|
|
98
|
-
"""Build question body (backward compatible interface)."""
|
|
99
|
-
body, _ = self._get_body(*args, **kwargs)
|
|
100
100
|
return body
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
@classmethod
|
|
103
|
+
def _build_explanation(cls, context):
|
|
103
104
|
explanation = ca.Section()
|
|
104
105
|
|
|
105
106
|
explanation.add_element(
|
|
@@ -116,9 +117,9 @@ class HardDriveAccessTime(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
|
116
117
|
ca.Paragraph(["Starting with the rotation delay, we calculate:"]),
|
|
117
118
|
ca.Equation(
|
|
118
119
|
"t_{rotation} = "
|
|
119
|
-
+ f"\\frac{{1 minute}}{{{
|
|
120
|
+
+ f"\\frac{{1 minute}}{{{context['hard_drive_rotation_speed']}revolutions}}"
|
|
120
121
|
+ r"\cdot \frac{60 seconds}{1 minute} \cdot \frac{1000 ms}{1 second} \cdot \frac{1 revolution}{2} = "
|
|
121
|
-
+ f"{
|
|
122
|
+
+ f"{context['rotational_delay']:0.2f}ms",
|
|
122
123
|
)
|
|
123
124
|
])
|
|
124
125
|
|
|
@@ -129,7 +130,7 @@ class HardDriveAccessTime(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
|
129
130
|
ca.Equation(
|
|
130
131
|
f"t_{{access}} "
|
|
131
132
|
f"= t_{{rotation}} + t_{{seek}} "
|
|
132
|
-
f"= {
|
|
133
|
+
f"= {context['rotational_delay']:0.2f}ms + {context['seek_delay']:0.2f}ms = {context['access_delay']:0.2f}ms"
|
|
133
134
|
)
|
|
134
135
|
])
|
|
135
136
|
|
|
@@ -137,9 +138,9 @@ class HardDriveAccessTime(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
|
137
138
|
ca.Paragraph([r"Next we need to calculate our transfer delay, $t_{transfer}$, which we do as:"]),
|
|
138
139
|
ca.Equation(
|
|
139
140
|
f"t_{{transfer}} "
|
|
140
|
-
f"= \\frac{{{
|
|
141
|
-
f"\\cdot \\frac{{1 second}}{{{
|
|
142
|
-
f"= {
|
|
141
|
+
f"= \\frac{{{context['number_of_reads']} \\cdot {context['size_of_reads']}KB}}{{1}} \\cdot \\frac{{1MB}}{{1024KB}} "
|
|
142
|
+
f"\\cdot \\frac{{1 second}}{{{context['transfer_rate']}MB}} \\cdot \\frac{{1000ms}}{{1second}} "
|
|
143
|
+
f"= {context['transfer_delay']:0.2}ms"
|
|
143
144
|
)
|
|
144
145
|
])
|
|
145
146
|
|
|
@@ -148,70 +149,66 @@ class HardDriveAccessTime(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
|
148
149
|
ca.Equation(
|
|
149
150
|
f"t_{{total}} "
|
|
150
151
|
f"= \\text{{(# reads)}} \\cdot t_{{access}} + t_{{transfer}} "
|
|
151
|
-
f"= {
|
|
152
|
-
f"= {
|
|
152
|
+
f"= {context['number_of_reads']} \\cdot {context['access_delay']:0.2f} + {context['transfer_delay']:0.2f} "
|
|
153
|
+
f"= {context['disk_access_delay']:0.2f}ms")
|
|
153
154
|
])
|
|
154
|
-
return explanation, []
|
|
155
|
-
|
|
156
|
-
def get_explanation(self) -> ca.Section:
|
|
157
|
-
"""Build question explanation (backward compatible interface)."""
|
|
158
|
-
explanation, _ = self._get_explanation()
|
|
159
155
|
return explanation
|
|
160
156
|
|
|
161
157
|
|
|
162
158
|
@QuestionRegistry.register()
|
|
163
159
|
class INodeAccesses(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
164
160
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
"
|
|
182
|
-
"
|
|
183
|
-
"
|
|
184
|
-
"
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
161
|
+
@classmethod
|
|
162
|
+
def _build_context(cls, *, rng_seed=None, **kwargs):
|
|
163
|
+
rng = random.Random(rng_seed)
|
|
164
|
+
inode_size = 2**rng.randint(6, 10)
|
|
165
|
+
block_size = inode_size * rng.randint(8, 20)
|
|
166
|
+
inode_number = rng.randint(0, 256)
|
|
167
|
+
inode_start_location = block_size * rng.randint(2, 5)
|
|
168
|
+
|
|
169
|
+
inode_address = inode_start_location + inode_number * inode_size
|
|
170
|
+
inode_block = inode_address // block_size
|
|
171
|
+
inode_address_in_block = inode_address % block_size
|
|
172
|
+
inode_index_in_block = int(inode_address_in_block / inode_size)
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
"inode_size": inode_size,
|
|
176
|
+
"block_size": block_size,
|
|
177
|
+
"inode_number": inode_number,
|
|
178
|
+
"inode_start_location": inode_start_location,
|
|
179
|
+
"inode_address": inode_address,
|
|
180
|
+
"inode_block": inode_block,
|
|
181
|
+
"inode_address_in_block": inode_address_in_block,
|
|
182
|
+
"inode_index_in_block": inode_index_in_block,
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
@classmethod
|
|
186
|
+
def _build_body(cls, context):
|
|
188
187
|
"""Build question body and collect answers."""
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
self.answers["answer__inode_index_in_block"],
|
|
194
|
-
]
|
|
188
|
+
inode_address_answer = ca.AnswerTypes.Int(context["inode_address"])
|
|
189
|
+
inode_block_answer = ca.AnswerTypes.Int(context["inode_block"])
|
|
190
|
+
inode_address_in_block_answer = ca.AnswerTypes.Int(context["inode_address_in_block"])
|
|
191
|
+
inode_index_in_block_answer = ca.AnswerTypes.Int(context["inode_index_in_block"])
|
|
195
192
|
|
|
196
193
|
# Create parameter info table using mixin
|
|
197
194
|
parameter_info = {
|
|
198
|
-
"Block Size": f"{
|
|
199
|
-
"Inode Number": f"{
|
|
200
|
-
"Inode Start Location": f"{
|
|
201
|
-
"Inode size": f"{
|
|
195
|
+
"Block Size": f"{context['block_size']} Bytes",
|
|
196
|
+
"Inode Number": f"{context['inode_number']}",
|
|
197
|
+
"Inode Start Location": f"{context['inode_start_location']} Bytes",
|
|
198
|
+
"Inode size": f"{context['inode_size']} Bytes"
|
|
202
199
|
}
|
|
203
200
|
|
|
204
|
-
parameter_table =
|
|
201
|
+
parameter_table = cls.create_info_table(parameter_info)
|
|
205
202
|
|
|
206
203
|
# Create answer table with multiple rows using mixin
|
|
207
204
|
answer_rows = [
|
|
208
|
-
{"Variable": "Inode address", "Value":
|
|
209
|
-
{"Variable": "Block containing inode", "Value":
|
|
210
|
-
{"Variable": "Inode address (offset) within block", "Value":
|
|
211
|
-
{"Variable": "Inode index within block", "Value":
|
|
205
|
+
{"Variable": "Inode address", "Value": inode_address_answer},
|
|
206
|
+
{"Variable": "Block containing inode", "Value": inode_block_answer},
|
|
207
|
+
{"Variable": "Inode address (offset) within block", "Value": inode_address_in_block_answer},
|
|
208
|
+
{"Variable": "Inode index within block", "Value": inode_index_in_block_answer}
|
|
212
209
|
]
|
|
213
210
|
|
|
214
|
-
answer_table =
|
|
211
|
+
answer_table = cls.create_answer_table(
|
|
215
212
|
headers=["Variable", "Value"],
|
|
216
213
|
data_rows=answer_rows,
|
|
217
214
|
answer_columns=["Value"]
|
|
@@ -220,21 +217,17 @@ class INodeAccesses(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
|
220
217
|
# Use mixin to create complete body with both tables
|
|
221
218
|
intro_text = "Given the information below, please calculate the following values."
|
|
222
219
|
|
|
223
|
-
body =
|
|
220
|
+
body = cls.create_parameter_calculation_body(
|
|
224
221
|
intro_text=intro_text,
|
|
225
222
|
parameter_table=parameter_table,
|
|
226
223
|
answer_table=answer_table,
|
|
227
224
|
# additional_instructions=instructions
|
|
228
225
|
)
|
|
229
226
|
|
|
230
|
-
return body, answers
|
|
231
|
-
|
|
232
|
-
def get_body(self) -> ca.Section:
|
|
233
|
-
"""Build question body (backward compatible interface)."""
|
|
234
|
-
body, _ = self._get_body()
|
|
235
227
|
return body
|
|
236
228
|
|
|
237
|
-
|
|
229
|
+
@classmethod
|
|
230
|
+
def _build_explanation(cls, context):
|
|
238
231
|
explanation = ca.Section()
|
|
239
232
|
|
|
240
233
|
explanation.add_element(
|
|
@@ -251,8 +244,8 @@ class INodeAccesses(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
|
251
244
|
r"(\text{Inode address})",
|
|
252
245
|
[
|
|
253
246
|
r"(\text{Inode Start Location}) + (\text{inode #}) \cdot (\text{inode size})",
|
|
254
|
-
f"{
|
|
255
|
-
f"{
|
|
247
|
+
f"{context['inode_start_location']} + {context['inode_number']} \\cdot {context['inode_size']}",
|
|
248
|
+
f"{context['inode_address']}"
|
|
256
249
|
])
|
|
257
250
|
)
|
|
258
251
|
|
|
@@ -267,8 +260,8 @@ class INodeAccesses(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
|
267
260
|
r"\text{Block containing inode}",
|
|
268
261
|
[
|
|
269
262
|
r"(\text{Inode address}) \mathbin{//} (\text{block size})",
|
|
270
|
-
f"{
|
|
271
|
-
f"{
|
|
263
|
+
f"{context['inode_address']} \\mathbin{{//}} {context['block_size']}",
|
|
264
|
+
f"{context['inode_block']}"
|
|
272
265
|
]
|
|
273
266
|
))
|
|
274
267
|
|
|
@@ -287,8 +280,8 @@ class INodeAccesses(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
|
287
280
|
r"\text{offset within block}",
|
|
288
281
|
[
|
|
289
282
|
r"(\text{Inode address}) \bmod (\text{block size})",
|
|
290
|
-
f"{
|
|
291
|
-
f"{
|
|
283
|
+
f"{context['inode_address']} \\bmod {context['block_size']}",
|
|
284
|
+
f"{context['inode_address_in_block']}"
|
|
292
285
|
]
|
|
293
286
|
))
|
|
294
287
|
|
|
@@ -302,16 +295,11 @@ class INodeAccesses(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
|
302
295
|
r"\text{index within block}",
|
|
303
296
|
[
|
|
304
297
|
r"\dfrac{\text{offset within block}}{\text{inode size}}",
|
|
305
|
-
f"\\dfrac{{{
|
|
306
|
-
f"{
|
|
298
|
+
f"\\dfrac{{{context['inode_address_in_block']}}}{{{context['inode_size']}}}",
|
|
299
|
+
f"{context['inode_index_in_block']}"
|
|
307
300
|
]
|
|
308
301
|
))
|
|
309
302
|
|
|
310
|
-
return explanation, []
|
|
311
|
-
|
|
312
|
-
def get_explanation(self) -> ca.Section:
|
|
313
|
-
"""Build question explanation (backward compatible interface)."""
|
|
314
|
-
explanation, _ = self._get_explanation()
|
|
315
303
|
return explanation
|
|
316
304
|
|
|
317
305
|
|
|
@@ -323,69 +311,61 @@ class VSFS_states(IOQuestion):
|
|
|
323
311
|
def __init__(self, *args, **kwargs):
|
|
324
312
|
super().__init__(*args, **kwargs)
|
|
325
313
|
self.answer_kind = ca.Answer.CanvasAnswerKind.MULTIPLE_DROPDOWN
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
fs =
|
|
333
|
-
operations = fs.run_for_steps(
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
lambda o: o != operations[-1]["cmd"],
|
|
340
|
-
map(
|
|
341
|
-
lambda o: o["cmd"],
|
|
342
|
-
operations
|
|
343
|
-
)
|
|
344
|
-
))
|
|
345
|
-
self.rng.shuffle(wrong_answers)
|
|
346
|
-
|
|
347
|
-
self.answers["answer__cmd"] = ca.Answer.dropdown(
|
|
314
|
+
|
|
315
|
+
@classmethod
|
|
316
|
+
def _build_context(cls, *, rng_seed=None, **kwargs):
|
|
317
|
+
rng = random.Random(rng_seed)
|
|
318
|
+
num_steps = kwargs.get("num_steps", 10)
|
|
319
|
+
|
|
320
|
+
fs = cls.vsfs(4, 4, rng)
|
|
321
|
+
operations = fs.run_for_steps(num_steps)
|
|
322
|
+
|
|
323
|
+
start_state = operations[-1]["start_state"]
|
|
324
|
+
end_state = operations[-1]["end_state"]
|
|
325
|
+
|
|
326
|
+
command_answer = ca.Answer.dropdown(
|
|
348
327
|
f"{operations[-1]['cmd']}",
|
|
349
328
|
baffles=list(set([op['cmd'] for op in operations[:-1] if op != operations[-1]['cmd']])),
|
|
350
329
|
label="Command"
|
|
351
330
|
)
|
|
352
|
-
|
|
353
|
-
def _get_body(self):
|
|
354
|
-
"""Build question body and collect answers."""
|
|
355
|
-
answers = [self.answers["answer__cmd"]]
|
|
356
331
|
|
|
332
|
+
return {
|
|
333
|
+
"start_state": start_state,
|
|
334
|
+
"end_state": end_state,
|
|
335
|
+
"command_answer": command_answer,
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
@classmethod
|
|
339
|
+
def _build_body(cls, context):
|
|
340
|
+
"""Build question body and collect answers."""
|
|
357
341
|
body = ca.Section()
|
|
358
342
|
|
|
359
343
|
body.add_element(ca.Paragraph(["What operation happens between these two states?"]))
|
|
360
344
|
|
|
361
345
|
body.add_element(
|
|
362
346
|
ca.Code(
|
|
363
|
-
|
|
347
|
+
context["start_state"],
|
|
364
348
|
make_small=True
|
|
365
349
|
)
|
|
366
350
|
)
|
|
367
351
|
|
|
368
|
-
body.add_element(ca.AnswerBlock(
|
|
352
|
+
body.add_element(ca.AnswerBlock(context["command_answer"]))
|
|
369
353
|
|
|
370
354
|
body.add_element(
|
|
371
355
|
ca.Code(
|
|
372
|
-
|
|
356
|
+
context["end_state"],
|
|
373
357
|
make_small=True
|
|
374
358
|
)
|
|
375
359
|
)
|
|
376
360
|
|
|
377
|
-
return body, answers
|
|
378
|
-
|
|
379
|
-
def get_body(self) -> ca.Section:
|
|
380
|
-
"""Build question body (backward compatible interface)."""
|
|
381
|
-
body, _ = self._get_body()
|
|
382
361
|
return body
|
|
383
362
|
|
|
384
|
-
|
|
363
|
+
@classmethod
|
|
364
|
+
def _build_explanation(cls, context):
|
|
385
365
|
explanation = ca.Section()
|
|
386
366
|
|
|
387
|
-
log.debug(f"
|
|
388
|
-
log.debug(f"
|
|
367
|
+
log.debug(f"start_state: {context['start_state']}")
|
|
368
|
+
log.debug(f"end_state: {context['end_state']}")
|
|
389
369
|
|
|
390
370
|
explanation.add_elements([
|
|
391
371
|
ca.Paragraph([
|
|
@@ -396,7 +376,7 @@ class VSFS_states(IOQuestion):
|
|
|
396
376
|
|
|
397
377
|
chunk_to_add = []
|
|
398
378
|
lines_that_changed = []
|
|
399
|
-
for start_line, end_line in zip(
|
|
379
|
+
for start_line, end_line in zip(context["start_state"].split('\n'), context["end_state"].split('\n')):
|
|
400
380
|
if start_line == end_line:
|
|
401
381
|
continue
|
|
402
382
|
lines_that_changed.append((start_line, end_line))
|
|
@@ -461,14 +441,8 @@ class VSFS_states(IOQuestion):
|
|
|
461
441
|
|
|
462
442
|
explanation.add_element(
|
|
463
443
|
ca.Code(
|
|
464
|
-
highlight_changes(
|
|
444
|
+
highlight_changes(context["start_state"], context["end_state"])
|
|
465
445
|
)
|
|
466
446
|
)
|
|
467
447
|
|
|
468
|
-
return explanation, []
|
|
469
|
-
|
|
470
|
-
def get_explanation(self) -> ca.Section:
|
|
471
|
-
"""Build question explanation (backward compatible interface)."""
|
|
472
|
-
explanation, _ = self._get_explanation()
|
|
473
448
|
return explanation
|
|
474
|
-
|