initial commit
This commit is contained in:
119
Quizard.py
Normal file
119
Quizard.py
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
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")
|
||||||
34
poetry.lock
generated
Normal file
34
poetry.lock
generated
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand.
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "option"
|
||||||
|
version = "2.1.0"
|
||||||
|
description = "Rust like Option and Result types in Python"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7,<4"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "option-2.1.0-py3-none-any.whl", hash = "sha256:21ccd9a437dbee0341700367efb68e82065fd7a7dba09f8c3263cf2dc1a2b0e0"},
|
||||||
|
{file = "option-2.1.0.tar.gz", hash = "sha256:9fe95a231e54724d2382a5124b55cd84b82339edf1d4e88d6977cedffbfeadf1"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
typing-extensions = {version = ">=4.0", markers = "python_version < \"3.8\""}
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-extensions"
|
||||||
|
version = "4.7.1"
|
||||||
|
description = "Backported and Experimental Type Hints for Python 3.7+"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
groups = ["main"]
|
||||||
|
markers = "python_version == \"3.7\""
|
||||||
|
files = [
|
||||||
|
{file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"},
|
||||||
|
{file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
lock-version = "2.1"
|
||||||
|
python-versions = ">=3.7,<4"
|
||||||
|
content-hash = "ff4d54c7cdd727c8db07ae48de4b3afe30cf14fba5f0ea7832bb9ed39a0f0d48"
|
||||||
17
pyproject.toml
Normal file
17
pyproject.toml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
[project]
|
||||||
|
name = "quizard"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = ""
|
||||||
|
authors = [
|
||||||
|
{name = "ton1c",email = "sembl1@ya.ru"}
|
||||||
|
]
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.7,<4"
|
||||||
|
dependencies = [
|
||||||
|
"option (>=2.1.0,<3.0.0)"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
||||||
32
test_sample.py
Normal file
32
test_sample.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
from enum import Enum
|
||||||
|
from typing import Generic
|
||||||
|
|
||||||
|
from Quizard import QuizTask, TaskGenerator
|
||||||
|
|
||||||
|
|
||||||
|
class TagCategory(str, Enum):
|
||||||
|
TOPIC = "topic"
|
||||||
|
DIFFICULTY = "difficulty"
|
||||||
|
|
||||||
|
|
||||||
|
class TopicTag(str, Enum):
|
||||||
|
AVERAGE = "average"
|
||||||
|
VARIANCE = "variance"
|
||||||
|
|
||||||
|
|
||||||
|
class MyTaskGenerator(TaskGenerator[TagCategory, TopicTag]): ...
|
||||||
|
|
||||||
|
|
||||||
|
class AverageTask(MyTaskGenerator):
|
||||||
|
def generate(self):
|
||||||
|
return QuizTask(
|
||||||
|
"What is an average of 1, 2, 3 and 4?",
|
||||||
|
"2.5",
|
||||||
|
tags=[
|
||||||
|
(TagCategory.TOPIC, TopicTag.AVERAGE),
|
||||||
|
(TagCategory.TOPIC, TopicTag.VARIANCE),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
print(AverageTask().generate())
|
||||||
Reference in New Issue
Block a user