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