Quizard/Quizard.py
2025-04-11 16:07:57 +03:00

120 lines
3.0 KiB
Python

from dataclasses import dataclass, field
from enum import Enum
from typing import (
Dict,
Generic,
List,
Optional,
Protocol,
Set,
Tuple,
TypeVar,
Union,
cast,
)
from option import NONE, Err, Ok, Option, Result, Some
C = TypeVar("C", default=str)
V = TypeVar("V", default=str)
@dataclass
class Tags(Generic[C, V]):
_dict: Dict[C, Set[V]] = field(default_factory=dict)
@staticmethod
def from_list(tags: List[Tuple[C, V]]) -> "Tags[C, V]":
tag_dict: Dict[C, Set[V]] = {}
for tag in tags:
tag_dict.setdefault(tag[0], set()).add(tag[1])
return Tags(tag_dict)
def has_tag(self, category: C, value: V) -> bool:
return value in self._dict.get(category, set())
def add_tag(self, category: C, value: V) -> None:
self._dict.setdefault(category, set()).add(value)
def remove_tag(self, category: C, value: V) -> None:
self._dict.get(category, set()).discard(value)
def __str__(self):
if len(self._dict) == 0:
return "No tags"
lines = []
for category, values in self._dict.items():
cat_str = category.value if isinstance(category, Enum) else str(category)
val_strs = [v.value if isinstance(v, Enum) else str(v) for v in values]
value_list = ", ".join(str(v) for v in sorted(val_strs))
lines.append(f"{cat_str}: {value_list}")
return "\n".join(lines)
def indent(text: str, spaces: int = 2) -> str:
prefix = " " * spaces
return "\n".join(prefix + line for line in text.splitlines())
@dataclass
class QuizTask(Generic[C, V]):
question: str
answer: str
tags: Option[Tags[C, V]]
def __init__(
self,
question: str,
answer: str,
tags: Optional[Union[Tags[C, V], List[Tuple[C, V]]]] = None,
):
self.question = question
self.answer = answer
if tags is None:
self.tags = cast(Option[Tags[C, V]], NONE)
elif isinstance(tags, list):
self.tags = Some(Tags[C, V].from_list(tags))
else:
self.tags = Some(tags)
def has_tag(self, category: C, value: V) -> bool:
return self.tags.map_or(lambda t: t.has_tag(category, value), False)
def __str__(self):
return f"Question:\n{indent(self.question)}\nAnswer:\n{indent(self.answer)}\nTags:\n{indent(self.tags.map_or(lambda t: str(t), "{}"))}"
class TaskGenerator(Protocol, Generic[C, V]):
def generate(self) -> QuizTask[C, V]: ...
@dataclass
class TaskPool(Generic[C, V]):
tasks: list[TaskGenerator[C, V]]
def __len__(self):
return len(self.tasks)
@dataclass
class QuizVariant:
tasks: list[QuizTask]
@dataclass
class VariantSet:
variants: list[QuizVariant]
class Quizard(Generic[C, V]):
def shuffle_tasks_to_variants(
self, tasks: TaskPool[C, V], number_of_variants: int, seed: int = 0
) -> Result[VariantSet, str]:
if len(tasks) == 0:
return Err("There must be at least one task")
return Err("Not implemented")