Files
eduplan-cpp-eltex/diag/facts_drill.py
T

148 lines
6.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Тренажёр «числа и факты» — добор по промахам квиза (22.09: algo 1/8, linux 3/8, net 3/8).
Использование (из каталога diag):
python3 facts_drill.py # интерактивно, 20 случайных карточек
python3 facts_drill.py --count 60 --all # все 60
python3 facts_drill.py --domain algo # только алгоритмы
python3 facts_drill.py --list # вывести все вопросы с ответами (читать)
python3 facts_drill.py --answers "20|62|ttl|..." # проверить строку ответов
python3 facts_drill.py --answers "..." --count 60 --seed 42 # тот же порядок, что в прогоне
python3 facts_drill.py --selftest # проверить сам набор карточек
Ответы сравниваются нестрого: регистр, «ё», тире и лишние пробелы не важны.
Результат пишется в results_facts.json.
"""
import argparse
import json
import os
import random
import re
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
FACTS_PATH = os.path.join(HERE, "facts.json")
def norm(s: str) -> str:
s = (s or "").lower().replace("ё", "е")
s = s.replace("–", "-").replace("—", "-").replace("−", "-")
s = re.sub(r"[^0-9a-zа-я\-\s]", " ", s)
return re.sub(r"\s+", " ", s).strip()
def load():
with open(FACTS_PATH, encoding="utf-8") as f:
return json.load(f)["facts"]
def check(fact, answer: str) -> bool:
a = norm(answer)
if not a:
return False
return any(a == norm(x) for x in fact.get("accept", []))
def selftest(facts) -> int:
problems = []
for i, f in enumerate(facts, 1):
for key in ("domain", "q", "a", "accept"):
if not f.get(key):
problems.append(f"карточка {i}: пустое поле {key}")
if f.get("accept") and not any(norm(x) for x in f["accept"]):
problems.append(f"карточка {i}: варианты ответа нормализуются в пустоту")
if not check(f, f["accept"][0]):
problems.append(f"карточка {i}: канонический ответ не проходит проверку")
doms = {}
for f in facts:
doms[f["domain"]] = doms.get(f["domain"], 0) + 1
print(f"карточек: {len(facts)}; по доменам: {doms}")
if problems:
print("ПРОБЛЕМЫ:")
for p in problems:
print(" -", p)
return 1
print("SELFTEST OK")
return 0
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--answers", default=None, help="ответы через | (тот же порядок, что в интерактиве)")
ap.add_argument("--count", type=int, default=20)
ap.add_argument("--all", action="store_true", help="взять все карточки")
ap.add_argument("--domain", default=None, choices=["algo", "linux", "net", "c_cpp"])
ap.add_argument("--seed", type=int, default=42)
ap.add_argument("--list", action="store_true")
ap.add_argument("--selftest", action="store_true")
args = ap.parse_args()
facts = load()
if args.selftest:
sys.exit(selftest(facts))
if args.domain:
facts = [f for f in facts if f["domain"] == args.domain]
if not facts:
print("нет карточек под фильтр")
sys.exit(1)
if args.list:
for i, f in enumerate(facts, 1):
print(f"{i:2d}. [{f['domain']}] {f['q']}")
print(f" -> {f['a']}")
print(f"\nвсего: {len(facts)} (полный ответ в facts.json: {FACTS_PATH})")
sys.exit(0)
rng = random.Random(args.seed)
pool = facts[:]
rng.shuffle(pool)
picked = pool if args.all else pool[:min(args.count, len(pool))]
results = []
if args.answers is not None:
given = [x.strip() for x in args.answers.split("|")]
if len(given) < len(picked):
print(f"ответов меньше, чем карточек: {len(given)} < {len(picked)} (проверяю сколько есть)")
for i, f in enumerate(picked):
ans = given[i] if i < len(given) else ""
results.append({"n": i + 1, "domain": f["domain"], "q": f["q"], "answer": ans,
"ok": check(f, ans), "correct": f["a"]})
else:
print(f"Карточек в прогоне: {len(picked)}. Пустой ввод = не знаю, 'q' = выход.\n")
for i, f in enumerate(picked, 1):
print(f"[{i}/{len(picked)}] ({f['domain']}) {f['q']}")
try:
ans = input(" > ").strip()
except (EOFError, KeyboardInterrupt):
print()
break
if ans.lower() in ("q", "quit", "выход"):
break
ok = check(f, ans)
print(" ok" if ok else " нет. верно: " + f["a"])
results.append({"n": len(results) + 1, "domain": f["domain"], "q": f["q"],
"answer": ans, "ok": ok, "correct": f["a"]})
ok = sum(1 for r in results if r["ok"])
total = len(results)
per = {}
for r in results:
d = per.setdefault(r["domain"], {"total": 0, "ok": 0})
d["total"] += 1
d["ok"] += 1 if r["ok"] else 0
with open(os.path.join(HERE, "results_facts.json"), "w", encoding="utf-8") as fh:
json.dump({"seed": args.seed, "correct": ok, "total": total,
"per_domain": per, "details": results}, fh, ensure_ascii=False, indent=2)
print(f"\n=== ИТОГ КАРТОЧЕК: {ok}/{total} ===")
for d, v in sorted(per.items()):
share = 100 * v["ok"] / v["total"] if v["total"] else 0
mark = "ок" if share >= 80 else ("добор" if share >= 50 else "дыра")
print(f" {d:8s} {v['ok']}/{v['total']} ({share:.0f}%) — {mark}")
print("Подробности: results_facts.json")
sys.exit(0 if total and ok == total else 1)
if __name__ == "__main__":
main()