소개
안정적인 답을 저장하고 이번 라운드의 오답만 독립 연습으로 바꿉니다. 앞선 퀴즈 개념과 Python 함수, 리스트, 딕셔너리, 조건문, 반복문이 필요합니다. 이 단원은 앞의 세 규칙을 독립적으로 준비하므로 이전 단원의 파일이나 프로세스에 의존하지 않습니다.
WebIDE Terminal → New Terminal을 사용합니다. 좁은 창에서는 More에 있습니다. logic.py만 편집하고 engine.py, quiz.py, bank.json은 보존하세요. 읽기와 연습 예상 시간은 35분이며 초보자 실측 시간이 아닙니다.
제출된 답을 JSON으로 저장하기
이 단계에서는 답을 화면 위치에 묶지 않고 실제 라운드를 저장합니다. 준비된 엔진은 제출된 question_id/option_id 쌍만 담은 리스트를 반환합니다. 피드백과 점수는 엔진이 담당하며, 기록에는 스스로 선언한 정오 여부나 화면 번호가 없습니다.
WebIDE 터미널에서 시작합니다.
cd /home/labex/project
json.dump는 Python 리스트와 딕셔너리를 JSON으로 씁니다. with open(..., "w", encoding="utf-8")는 쓰기용 텍스트 파일을 열고 나중에 닫습니다. UTF-8은 텍스트의 이식성을 보장하고, ensure_ascii=False는 Unicode를 읽기 쉽게 남기며, indent=2는 파일을 보기 좋게 만듭니다. stream.write("\n")는 끝에 줄바꿈을 넣습니다. 바깥의 {"answers": answers}는 리스트를 식별합니다.
다음 전체 here-document는 준비된 세 규칙을 유지하면서 저장 함수를 추가합니다. >는 logic.py를 교체하고, 따옴표로 묶은 <<'PY'는 마지막 PY까지 텍스트를 그대로 씁니다. 연습 함수는 다음 단계에서 추가합니다.
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는 알고 있는 문제·선택지 순서를 유지하고 --output은 새로운 기록 이름을 지정합니다. 기존 출력 경로는 플레이 전에 거부되어 이전 기록을 보호합니다. 두 문제에 답하세요. sky에는 1(정답), tide에도 1(오답)을 입력합니다.
python3 quiz.py bank.json --ordered --count 2 --output round.json
Score: 1/2 answered
Saved round.json
cat으로 실제 기록을 봅니다.
cat round.json
sky/sun과 tide/sun이 들어 있습니다. 같은 선택지 ID라도 문제에 따라 의미가 다릅니다. 이 원래 라운드를 보존하세요. 실수로 다시 실행했다면 기존 출력 오류는 예상된 동작입니다. 덮어쓰지 말고 추가 실험에는 새 파일명을 쓰세요. 원본 파일명을 틀렸다면 ls로 확인하고 정확한 경로로 다시 실행하세요. Quiz error: ...는 저장된 라운드를 의미하지 않습니다.
이번 라운드의 오답만 다시 연습하기
이 단계에서는 각 문제의 실제 정답 ID로 오답을 다시 계산합니다. 딕셔너리가 응답한 문제 ID를 선택한 선택지 ID에 매핑합니다. 답을 했고 동시에 그 답이 해당 문제 자신의 correct_option_id와 다를 때만 문제를 포함합니다. 정답과 미응답 문제는 모두 제외합니다. 리스트에는 문제 객체 전체를 유지합니다. {answer["question_id"]: answer["option_id"] for answer in answers}에서 for 뒤 부분은 각 답을 순회하고, 앞의 키/값 부분은 딕셔너리 항목을 만듭니다. 이것이 딕셔너리 컴프리헨션입니다. [question for question in questions if ...]에서 for는 문제를 순회하고 if는 맞는 객체만 남깁니다. 이것이 리스트 컴프리헨션입니다. and는 답이 존재하고 틀렸다는 두 조건을 모두 요구하므로 미응답 문제를 오답으로 세지 않습니다. >>는 기존 규칙을 바꾸지 않고 덧붙입니다.
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는 원래 라운드를 읽고 --output은 별도의 새 기록을 씁니다. 누적 이력이 아니라 그 라운드의 모든 오답만 다시 풉니다. 실행하세요.
python3 quiz.py bank.json --practice round.json --output retry.json
tide 문제만 나옵니다. 위치가 바뀔 수 있으므로 Moon의 현재 표시 번호를 선택하세요. 결과는 Score: 1/1 answered입니다. 두 독립 기록을 확인합니다.
cat round.json retry.json
원래 기록은 tide/sun을 유지하고 재연습 기록은 tide/moon입니다. 문제은행은 바뀌지 않습니다. 이제 모두 맞힌 라운드를 만드세요. 개수는 양수이고 실제 추출은 min(count, len(questions))로 제한됩니다. 100은 세 문제를 한 번씩 묻습니다.
python3 quiz.py bank.json --ordered --count 100 --output perfect.json
sky, tide, roots에 1, 2, 2를 입력한 다음 그 라운드를 연습합니다.
python3 quiz.py bank.json --practice perfect.json --output empty.json
No mistakes to practice.
Score: 0/0 answered
Saved empty.json
빈 오답 집합도 유효합니다. 마지막으로 새 라운드를 시작해 답하기 전에 q를 입력하세요.
python3 quiz.py bank.json --output stopped.json
점수는 0/0이고 stopped.json의 answers 리스트는 비어 있습니다. 떠난 문제를 오답으로 저장하면 안 됩니다. 다섯 기록을 모두 보존하세요. 편집으로 구문 오류가 생기면 해당 단계의 전체 블록을 다시 실행합니다. 1단계는 파일을 교체하므로 이후 2단계의 추가 블록도 다시 실행해야 합니다. 기존 기록을 유지하고 추가 플레이에는 새 이름을 쓰세요.
요약
실제로 제출한 ID 쌍을 저장하고 원래 라운드를 유지한 채 독립적인 오답 연습을 만들었습니다. 유효한 빈 라운드와 중도 종료도 처리했습니다.



