0%

Iteration as an Interface · capstone

Capstone project: Project: Build an Iterable Question Bank

Earlier projects handed out snapshots through named . This project combines the protocol from the last lessons with lazy filtering and bounded consumption.

What you are building

A QuestionBank that holds questions and answers three kinds of request:

for question in bank              every graded question, in order
bank.by_topic("loops")            a lazy view, filtered
bank.sample(3)                    at most three, from any source

The first is __iter__. The second and third are generators. None of them builds a list unless a list is what the caller asked for.

The decisions

__iter__ skips ungraded questions. A bank holds survey items too, and looping over the bank means looping over the questions that count. That is a real decision, and it is the reason __iter__ is a generator here rather than the one-line delegation from Lesson 4.

by_topic returns a generator, and says so. A caller may want the first match and nothing else, and this is the shape that lets them stop.

sample has an exact boundary. Asking for two questions yields and consumes two, not three. A zero or negative count does not start walking the bank.

summary returns a list. It is short, the caller will and measure it, and a generator would be worse for no gain. Lesson 6’s rule, applied.

Why is __iter__ written as a generator here rather than return iter(self._questions)?

Rules the grader checks

  • The bank is re-: looping twice sees everything twice.

  • by_topic and sample are generator , not functions returning lists.

  • sample works on an endless source, so it must stop on its own.

  • sample does not consume a question beyond the requested count.

  • first_unanswered examines no more questions than it needs to.

  • points_needed returns 0 for a target that is already reached at the start.

  • summary returns a list.

Finishing

Read your QuestionBank and ask, for each method, what a caller can do with what it returns. One of them gives back something you can measure and index; the rest give back something you can walk once and stop early. Both are right answers, to different questions.

Task

Build the QuestionBank and the two that use it.

add(question) stores a question. __iter__ is a generator yielding every question worth more than zero points, in order, skipping the ungraded ones.

by_topic(topic) is a generator yielding the graded questions whose topic matches. sample(count) is a generator yielding at most count graded questions. It requests no more graded questions from the bank’s than it yields; finding them may still inspect ungraded stored questions. A zero or negative count consumes nothing.

total_points is a read-only property summing the graded questions, computed with a generator rather than a .

summary() returns a list of "<prompt> (<points>)" , because the caller measures and indexes it.

Then two functions that take any of questions, not just a bank:

  • first_unanswered(questions, answered) returns the first question whose prompt is not in answered, or None. It must stop as soon as it finds one.

  • points_needed(questions, target) returns how many questions, taken in order, are needed before their points reach target, or None if they never do. Return 0 when target <= 0, because that target is reached before consuming a question.