Turn Mistakes into a Practice Round

PythonBeginner
Practice Now

Introduction

Save stable answers and turn only this round's mistakes into independent practice. You need the earlier quiz concepts plus Python functions, lists, dictionaries, conditions and loops. This unit independently prepares the three earlier rules; it needs no files or processes from a previous unit.

Use WebIDE Terminal → New Terminal (More on narrow windows). Edit only logic.py; preserve engine.py, quiz.py and bank.json. Estimated reading and practice time: 35 minutes, not measured beginner timing.

Save Submitted Answers as JSON

In this step, save a real round without tying answers to screen positions. The prepared engine returns a list containing only submitted question_id/option_id pairs. It owns feedback and score; a record has no self-reported correctness or screen number.

Start in the WebIDE terminal:

cd /home/labex/project

json.dump writes Python lists/dictionaries as JSON. with open(..., "w", encoding="utf-8") opens a text file for writing and closes it afterward. UTF-8 keeps text portable; ensure_ascii=False keeps Unicode readable and indent=2 makes the file legible. stream.write("\n") ends it with a newline. The wrapper {"answers": answers} identifies the list.

The complete here-document below preserves the ready three rules and adds the new saving function. > replaces logic.py; quoted <<'PY' writes literal text through the final PY. The practice function is added in the next step.

cat > logic.py <<'PY'
import json


def choose_questions(questions, count, rng):
    return rng.sample(questions, min(count, len(questions)))


def move_options(question, rng):
    options = question["options"].copy()
    rng.shuffle(options)
    return options


def read_answer(options, ask, tell):
    while True:
        text = ask("Answer (number or q): ").strip()
        if text.lower() == "q":
            return None
        for position, option in enumerate(options, start=1):
            if text == str(position):
                return option["option_id"]
        tell("Enter one displayed number, or q to stop.")


def save_answers(path, answers):
    with open(path, "w", encoding="utf-8") as stream:
        json.dump({"answers": answers}, stream, ensure_ascii=False, indent=2)
        stream.write("\n")
PY

--ordered keeps known question/choice order; --output names a new record. Existing output paths are refused before play, protecting earlier records. Play two questions: enter 1 for sky (correct), then 1 for tide (wrong).

python3 quiz.py bank.json --ordered --count 2 --output round.json
Score: 1/2 answered
Saved round.json

cat displays the actual record:

cat round.json

It contains sky/sun and tide/sun. The same option ID has different meaning in the two questions. Keep this original round. If you rerun accidentally, the existing-output error is expected: choose a new filename for extra experiments rather than overwriting it. If you mistype a source filename, inspect ls and rerun the correct path; a Quiz error: ... is not a saved round.

Practice Only This Round's Mistakes

In this step, recompute mistakes from each question's real correct ID. A dictionary maps answered question IDs to selected option IDs. Include a question only if it was answered and that answer differs from its own correct_option_id. Correct and unanswered questions must both stay out. The list preserves whole question objects. In {answer["question_id"]: answer["option_id"] for answer in answers}, the part after for visits each answer and the key/value part before it builds one dictionary entry. This is a dictionary comprehension. In [question for question in questions if ...], for visits each question and if keeps only matching objects; this is a list comprehension. and requires both conditions: an answer exists, and it is wrong. This prevents an unanswered question from being counted as a mistake. >> appends without replacing the existing rules:

cat >> logic.py <<'PY'
def wrong_questions(questions, answers):
    selected = {answer["question_id"]: answer["option_id"] for answer in answers}
    return [question for question in questions
            if question["question_id"] in selected
            and selected[question["question_id"]] != question["correct_option_id"]]
PY

--practice reads the original round; --output writes a separate new record. It retries all and only that round's mistakes, not an accumulating history. Run:

python3 quiz.py bank.json --practice round.json --output retry.json

Only the tide question appears. Select the displayed number for Moon, which may move. The result is Score: 1/1 answered. Inspect both independent records:

cat round.json retry.json

The original still has tide/sun; retry has tide/moon. The bank is unchanged. Now create a fully correct round. Counts are positive, capped by min(count, len(questions)); 100 asks all three questions once.

python3 quiz.py bank.json --ordered --count 100 --output perfect.json

Enter 1, 2, 2 for sky, tide and roots. Then practice it:

python3 quiz.py bank.json --practice perfect.json --output empty.json
No mistakes to practice.
Score: 0/0 answered
Saved empty.json

An empty mistake set is valid. Finally start a new round and enter q before answering:

python3 quiz.py bank.json --output stopped.json

The score is 0/0 and stopped.json has an empty answers list. It must not contain a wrong answer for the question you left. Preserve all five records. If editing caused a syntax error, repeat the step's complete block; because step 1 replaces the file, follow it with the step 2 append block again. Keep existing records and use new names for extra play.

Summary

You saved real submitted ID pairs, kept the original round and built independent mistake practice, including valid empty and stopped rounds.