QuizGenerator 0.7.1__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.
Files changed (30) hide show
  1. QuizGenerator/contentast.py +6 -6
  2. QuizGenerator/generate.py +2 -1
  3. QuizGenerator/mixins.py +14 -100
  4. QuizGenerator/premade_questions/basic.py +24 -29
  5. QuizGenerator/premade_questions/cst334/languages.py +100 -99
  6. QuizGenerator/premade_questions/cst334/math_questions.py +112 -122
  7. QuizGenerator/premade_questions/cst334/memory_questions.py +621 -621
  8. QuizGenerator/premade_questions/cst334/persistence_questions.py +137 -163
  9. QuizGenerator/premade_questions/cst334/process.py +312 -322
  10. QuizGenerator/premade_questions/cst463/gradient_descent/gradient_calculation.py +34 -35
  11. QuizGenerator/premade_questions/cst463/gradient_descent/gradient_descent_questions.py +41 -36
  12. QuizGenerator/premade_questions/cst463/gradient_descent/loss_calculations.py +48 -41
  13. QuizGenerator/premade_questions/cst463/math_and_data/matrix_questions.py +285 -520
  14. QuizGenerator/premade_questions/cst463/math_and_data/vector_questions.py +149 -126
  15. QuizGenerator/premade_questions/cst463/models/attention.py +44 -50
  16. QuizGenerator/premade_questions/cst463/models/cnns.py +43 -47
  17. QuizGenerator/premade_questions/cst463/models/matrices.py +61 -11
  18. QuizGenerator/premade_questions/cst463/models/rnns.py +48 -50
  19. QuizGenerator/premade_questions/cst463/models/text.py +65 -67
  20. QuizGenerator/premade_questions/cst463/models/weight_counting.py +47 -46
  21. QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py +100 -156
  22. QuizGenerator/premade_questions/cst463/tensorflow-intro/tensorflow_questions.py +93 -141
  23. QuizGenerator/question.py +273 -202
  24. QuizGenerator/quiz.py +8 -5
  25. QuizGenerator/regenerate.py +14 -6
  26. {quizgenerator-0.7.1.dist-info → quizgenerator-0.8.0.dist-info}/METADATA +30 -2
  27. {quizgenerator-0.7.1.dist-info → quizgenerator-0.8.0.dist-info}/RECORD +30 -30
  28. {quizgenerator-0.7.1.dist-info → quizgenerator-0.8.0.dist-info}/WHEEL +0 -0
  29. {quizgenerator-0.7.1.dist-info → quizgenerator-0.8.0.dist-info}/entry_points.txt +0 -0
  30. {quizgenerator-0.7.1.dist-info → quizgenerator-0.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -3,8 +3,8 @@ from __future__ import annotations
3
3
 
4
4
  import abc
5
5
  import enum
6
- import itertools
7
- from typing import List, Dict, Optional, Tuple, Any
6
+ import random
7
+ from typing import List, Dict, Optional
8
8
 
9
9
  from QuizGenerator.question import QuestionRegistry, Question
10
10
 
@@ -154,27 +154,16 @@ class ValidStringsInLanguageQuestion(LanguageQuestion):
154
154
  kwargs['grammar_str_bad'] = grammar_str_bad
155
155
 
156
156
  super().__init__(*args, **kwargs)
157
+ self.answer_kind = ca.Answer.CanvasAnswerKind.MULTIPLE_ANSWER
157
158
 
158
- # Store whether grammars are fixed (provided) or should be randomized
159
- self.fixed_grammars = grammar_str_good is not None and grammar_str_bad is not None
160
- if self.fixed_grammars:
161
- self.grammar_str_good = grammar_str_good
162
- self.grammar_str_bad = grammar_str_bad
163
- self.include_spaces = kwargs.get("include_spaces", False)
164
- self.MAX_LENGTH = kwargs.get("max_length", 30)
165
- self.grammar_good = BNF.parse_bnf(self.grammar_str_good, self.rng)
166
- self.grammar_bad = BNF.parse_bnf(self.grammar_str_bad, self.rng)
167
-
168
- self.num_answer_options = kwargs.get("num_answer_options", 4)
169
- self.num_answer_blanks = kwargs.get("num_answer_blanks", 4)
170
-
171
- def _select_random_grammar(self):
172
- """Select and set a random grammar. Called from refresh() to ensure each PDF gets different grammar."""
173
- which_grammar = self.rng.choice(range(4))
159
+ @classmethod
160
+ def _select_random_grammar(cls, rng):
161
+ """Select and return a random grammar configuration."""
162
+ which_grammar = rng.choice(range(4))
174
163
 
175
164
  if which_grammar == 0:
176
165
  # todo: make a few different kinds of grammars that could be picked
177
- self.grammar_str_good = """
166
+ grammar_str_good = """
178
167
  <expression> ::= <term> | <expression> + <term> | <expression> - <term>
179
168
  <term> ::= <factor> | <term> * <factor> | <term> / <factor>
180
169
  <factor> ::= <number>
@@ -182,17 +171,17 @@ class ValidStringsInLanguageQuestion(LanguageQuestion):
182
171
  <digit> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
183
172
  """
184
173
  # Adding in a plus to number
185
- self.grammar_str_bad = """
174
+ grammar_str_bad = """
186
175
  <expression> ::= <term> | <expression> + <term> | <expression> - <term>
187
176
  <term> ::= <factor> | <term> * <factor> | <term> / <factor>
188
177
  <factor> ::= <number>
189
178
  <number> ::= <digit> + | <digit> <number>
190
179
  <digit> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
191
180
  """
192
- self.include_spaces = False
193
- self.MAX_LENGTH = 30
181
+ include_spaces = False
182
+ max_length = 30
194
183
  elif which_grammar == 1:
195
- self.grammar_str_good = """
184
+ grammar_str_good = """
196
185
  <sentence> ::= <subject> <verb> <object>
197
186
  <subject> ::= The cat | A dog | The bird | A child | <adjective> <animal>
198
187
  <animal> ::= cat | dog | bird | child
@@ -200,7 +189,7 @@ class ValidStringsInLanguageQuestion(LanguageQuestion):
200
189
  <verb> ::= chases | sees | hates | loves
201
190
  <object> ::= the ball | the toy | the tree | <adjective> <object>
202
191
  """
203
- self.grammar_str_bad = """
192
+ grammar_str_bad = """
204
193
  <sentence> ::= <subject> <verb> <object>
205
194
  <subject> ::= The human | The dog | A bird | Some child | A <adjective> <animal>
206
195
  <animal> ::= cat | dog | bird | child
@@ -208,10 +197,10 @@ class ValidStringsInLanguageQuestion(LanguageQuestion):
208
197
  <verb> ::= chases | sees | hates | loves
209
198
  <object> ::= the ball | the toy | the tree | <adjective> <object>
210
199
  """
211
- self.include_spaces = True
212
- self.MAX_LENGTH = 100
200
+ include_spaces = True
201
+ max_length = 100
213
202
  elif which_grammar == 2:
214
- self.grammar_str_good = """
203
+ grammar_str_good = """
215
204
  <poem> ::= <line> | <line> <poem>
216
205
  <line> ::= <subject> <verb> <object> <modifier>
217
206
  <subject> ::= whispers | shadows | dreams | echoes | <compound-subject>
@@ -223,7 +212,7 @@ class ValidStringsInLanguageQuestion(LanguageQuestion):
223
212
  <modifier> ::= silently | violently | mysteriously | endlessly | <recursive-modifier>
224
213
  <recursive-modifier> ::= <modifier> and <modifier>
225
214
  """
226
- self.grammar_str_bad = """
215
+ grammar_str_bad = """
227
216
  <bad-poem> ::= <almost-valid-line> | <bad-poem> <bad-poem>
228
217
  <almost-valid-line> ::= <tricky-subject> <tricky-verb> <tricky-object> <tricky-modifier>
229
218
  <tricky-subject> ::= whispers | shadows and and | <duplicate-subject>
@@ -238,103 +227,126 @@ class ValidStringsInLanguageQuestion(LanguageQuestion):
238
227
  <modifier-subject-swap> ::= whispers silently
239
228
  <duplicate-modifier> ::= silently silently
240
229
  """
241
- self.include_spaces = True
242
- self.MAX_LENGTH = 100
230
+ include_spaces = True
231
+ max_length = 100
243
232
  elif which_grammar == 3:
244
- self.grammar_str_good = """
233
+ grammar_str_good = """
245
234
  <A> ::= a <B> a |
246
235
  <B> ::= b <C> b |
247
236
  <C> ::= c <A> c |
248
237
  """
249
- self.grammar_str_bad = """
238
+ grammar_str_bad = """
250
239
  <A> ::= a <B> c
251
240
  <B> ::= b <C> a |
252
241
  <C> ::= c <A> b |
253
242
  """
254
- self.include_spaces = False
255
- self.MAX_LENGTH = 100
256
-
257
- self.grammar_good = BNF.parse_bnf(self.grammar_str_good, self.rng)
258
- self.grammar_bad = BNF.parse_bnf(self.grammar_str_bad, self.rng)
243
+ include_spaces = False
244
+ max_length = 100
245
+
246
+ return {
247
+ "grammar_str_good": grammar_str_good,
248
+ "grammar_str_bad": grammar_str_bad,
249
+ "include_spaces": include_spaces,
250
+ "max_length": max_length,
251
+ }
259
252
 
260
- def refresh(self, *args, **kwargs):
261
- super().refresh(*args, **kwargs)
253
+ @classmethod
254
+ def _build_context(cls, *, rng_seed=None, **kwargs):
255
+ rng = random.Random(rng_seed)
256
+
257
+ grammar_str_good = kwargs.get("grammar_str_good")
258
+ grammar_str_bad = kwargs.get("grammar_str_bad")
259
+
260
+ if grammar_str_good is not None and grammar_str_bad is not None:
261
+ include_spaces = kwargs.get("include_spaces", False)
262
+ max_length = kwargs.get("max_length", 30)
263
+ else:
264
+ selection = cls._select_random_grammar(rng)
265
+ grammar_str_good = selection["grammar_str_good"]
266
+ grammar_str_bad = selection["grammar_str_bad"]
267
+ include_spaces = selection["include_spaces"]
268
+ max_length = selection["max_length"]
262
269
 
263
- # Re-select random grammar for each refresh if not using fixed grammars
264
- if not self.fixed_grammars:
265
- self._select_random_grammar()
270
+ grammar_good = BNF.parse_bnf(grammar_str_good, rng)
271
+ grammar_bad = BNF.parse_bnf(grammar_str_bad, rng)
266
272
 
267
- self.answers = {}
273
+ num_answer_options = kwargs.get("num_answer_options", 4)
274
+ num_answer_blanks = kwargs.get("num_answer_blanks", 4)
268
275
 
269
- # Create answers with proper ca.Answer signature
270
- # value is the generated string, correct indicates if it's a valid answer
271
- good_string = self.grammar_good.generate(self.include_spaces)
272
- self.answers["answer_good"] = ca.Answer(
276
+ answer_options = []
277
+
278
+ good_string = grammar_good.generate(include_spaces)
279
+ answer_options.append(ca.Answer(
273
280
  value=good_string,
274
281
  kind=ca.Answer.CanvasAnswerKind.MULTIPLE_ANSWER,
275
282
  correct=True
276
- )
283
+ ))
277
284
 
278
- bad_string = self.grammar_bad.generate(self.include_spaces)
279
- self.answers["answer_bad"] = ca.Answer(
285
+ bad_string = grammar_bad.generate(include_spaces)
286
+ answer_options.append(ca.Answer(
280
287
  value=bad_string,
281
288
  kind=ca.Answer.CanvasAnswerKind.MULTIPLE_ANSWER,
282
289
  correct=False
283
- )
290
+ ))
284
291
 
285
- bad_early_string = self.grammar_bad.generate(self.include_spaces, early_exit=True)
286
- self.answers["answer_bad_early"] = ca.Answer(
292
+ bad_early_string = grammar_bad.generate(include_spaces, early_exit=True)
293
+ answer_options.append(ca.Answer(
287
294
  value=bad_early_string,
288
295
  kind=ca.Answer.CanvasAnswerKind.MULTIPLE_ANSWER,
289
296
  correct=False
290
- )
297
+ ))
291
298
 
292
- answer_text_set = {a.value for a in self.answers.values()}
299
+ answer_text_set = {a.value for a in answer_options}
293
300
  num_tries = 0
294
- while len(self.answers) < 10 and num_tries < self.MAX_TRIES:
295
-
296
- correct = self.rng.choice([True, False])
301
+ while len(answer_options) < 10 and num_tries < cls.MAX_TRIES:
302
+ correct = rng.choice([True, False])
297
303
  if not correct:
298
- early_exit = self.rng.choice([True, False])
304
+ early_exit = rng.choice([True, False])
299
305
  else:
300
306
  early_exit = False
301
307
 
302
308
  generated_string = (
303
- self.grammar_good
309
+ grammar_good
304
310
  if correct or early_exit
305
- else self.grammar_bad
306
- ).generate(self.include_spaces, early_exit=early_exit)
311
+ else grammar_bad
312
+ ).generate(include_spaces, early_exit=early_exit)
307
313
 
308
314
  is_correct = correct and not early_exit
309
315
 
310
- if len(generated_string) < self.MAX_LENGTH and generated_string not in answer_text_set:
311
- self.answers[f"answer_{num_tries}"] = ca.Answer(
316
+ if len(generated_string) < max_length and generated_string not in answer_text_set:
317
+ answer_options.append(ca.Answer(
312
318
  value=generated_string,
313
319
  kind=ca.Answer.CanvasAnswerKind.MULTIPLE_ANSWER,
314
320
  correct=is_correct
315
- )
321
+ ))
316
322
  answer_text_set.add(generated_string)
317
323
  num_tries += 1
318
-
319
- # Generate answers that will be used only for the latex version.
320
- self.featured_answers = {
321
- self.grammar_good.generate(),
322
- self.grammar_bad.generate(),
323
- self.grammar_good.generate(early_exit=True)
324
+
325
+ featured_answers = {
326
+ grammar_good.generate(),
327
+ grammar_bad.generate(),
328
+ grammar_good.generate(early_exit=True)
324
329
  }
325
- while len(self.featured_answers) < self.num_answer_options:
326
- self.featured_answers.add(
327
- self.rng.choice([
328
- lambda: self.grammar_good.generate(),
329
- lambda: self.grammar_bad.generate(),
330
- lambda: self.grammar_good.generate(early_exit=True),
330
+ while len(featured_answers) < num_answer_options:
331
+ featured_answers.add(
332
+ rng.choice([
333
+ lambda: grammar_good.generate(),
334
+ lambda: grammar_bad.generate(),
335
+ lambda: grammar_good.generate(early_exit=True),
331
336
  ])()
332
337
  )
333
-
334
- def _get_body(self, *args, **kwargs):
335
- """Build question body and collect answers."""
336
- answers = list(self.answers.values())
337
338
 
339
+ return {
340
+ "grammar_good": grammar_good,
341
+ "answer_options": answer_options,
342
+ "featured_answers": featured_answers,
343
+ "include_spaces": include_spaces,
344
+ "num_answer_blanks": num_answer_blanks,
345
+ }
346
+
347
+ @classmethod
348
+ def _build_body(cls, context):
349
+ """Build question body and collect answers."""
338
350
  body = ca.Section()
339
351
 
340
352
  body.add_element(
@@ -355,30 +367,26 @@ class ValidStringsInLanguageQuestion(LanguageQuestion):
355
367
  )
356
368
 
357
369
  body.add_element(
358
- ca.Code(self.grammar_good.get_grammar_string())
370
+ ca.Code(context["grammar_good"].get_grammar_string())
359
371
  )
360
372
 
361
373
  # Add in some answers as latex-only options to be circled
362
374
  latex_list = ca.OnlyLatex([])
363
- for answer in self.featured_answers:
375
+ for answer in context["featured_answers"]:
364
376
  latex_list.add_element(ca.Paragraph([f"- `{str(answer)}`"]))
365
377
  body.add_element(latex_list)
366
378
 
367
379
  # For Latex-only, ask students to generate some more.
368
380
  body.add_element(
369
381
  ca.OnlyLatex([
370
- ca.AnswerBlock([ca.AnswerTypes.String("", label="") for i in range(self.num_answer_blanks)])
382
+ ca.AnswerBlock([ca.AnswerTypes.String("", label="") for i in range(context["num_answer_blanks"])])
371
383
  ])
372
384
  )
373
385
 
374
- return body, answers
386
+ return body, list(context["answer_options"])
375
387
 
376
- def get_body(self, *args, **kwargs) -> ca.Section:
377
- """Build question body (backward compatible interface)."""
378
- body, _ = self._get_body(*args, **kwargs)
379
- return body
380
-
381
- def _get_explanation(self, *args, **kwargs):
388
+ @classmethod
389
+ def _build_explanation(cls, context):
382
390
  """Build question explanation."""
383
391
  explanation = ca.Section()
384
392
  explanation.add_element(
@@ -387,13 +395,6 @@ class ValidStringsInLanguageQuestion(LanguageQuestion):
387
395
  "Unfortunately, there isn't space here to demonstrate the derivation so please work through them on your own!"
388
396
  ])
389
397
  )
390
- return explanation, []
391
-
392
- def get_explanation(self, *args, **kwargs) -> ca.Section:
393
- """Build question explanation (backward compatible interface)."""
394
- explanation, _ = self._get_explanation(*args, **kwargs)
395
398
  return explanation
396
399
 
397
- def get_answers(self, *args, **kwargs) -> Tuple[ca.Answer.CanvasAnswerKind, List[Dict[str,Any]]]:
398
-
399
- return ca.Answer.CanvasAnswerKind.MULTIPLE_ANSWER, list(itertools.chain(*[a.get_for_canvas() for a in self.answers.values()]))
400
+