added quick_start.md
This commit is contained in:
BIN
clean.xlsx
Normal file
BIN
clean.xlsx
Normal file
Binary file not shown.
142
docs/quick_start.md
Normal file
142
docs/quick_start.md
Normal file
@ -0,0 +1,142 @@
|
||||
# Основные понятия
|
||||
|
||||
Для того чтобы составить варианты, вам сперва нужно: 1) написать все задачи/генераторы задач, 2) составить из них пул задач и 3) описать структуру варианта. Разберём по пунктам:
|
||||
|
||||
1.1 Задача - это основной строительный блок варианта. В `QuizGen` задача представляет собой класс с тремя значениями. Одно обязательные - это вопрос задачи `question`. И два опциональных -- ответ к задаче `answer` и теги `tags` (про которые позже)
|
||||
|
||||
```{python}
|
||||
from modules.task import QTask
|
||||
from modules.tag import Tags
|
||||
|
||||
task = QTask(
|
||||
question="Посчитайте дисперсию для следующего ряда: 5, 6, 7, 8, 9",
|
||||
answer="2", # можно опустить или не давать ответ: answer = None
|
||||
tags=Tags(
|
||||
("тема", "дисперсия"),
|
||||
("сложность", "лёгкая"),
|
||||
...
|
||||
) # теги можно определять любые. Первое значение - это категория, второе - это значение в этой категории.
|
||||
)
|
||||
```
|
||||
|
||||
1.2 Генератор задач - это класс, с функцией generate(), которая при вызове генерирует задачу `QTask`:
|
||||
|
||||
```{python}
|
||||
from moduels.task.factory.default import QTaskFactoryDefault
|
||||
from modules.task import QTask
|
||||
from modules.tag import Tags
|
||||
|
||||
#Сначала определим функцию, которая будет использоваться для генерации задач
|
||||
def task_generator_function():
|
||||
# генерируем два случайных целых числа от 1 до 9
|
||||
alpha = random.randint(1, 9)
|
||||
beta = random.randint(1, 9)
|
||||
|
||||
# Используем сгенерированные числа, чтобы составить задачу
|
||||
question = f"Чему равна дисперсия величины V({alpha} * X + {beta} * Y), если X и Y подчинены стандартному нормальному закону распределения и независимы друг от друга?"
|
||||
answer = f"{alpha ** 2 + beta ** 2}"
|
||||
|
||||
return QTask(
|
||||
question,
|
||||
answer,
|
||||
)
|
||||
|
||||
# С её помощью создадим генератор задач
|
||||
variance_task_factory = QTaskFactoryDefault(task_generator_function)
|
||||
|
||||
# Получаем генератор, который умеет производит задачи с разными числами, каждый раз, когда мы вызываем фукнцию generate():
|
||||
|
||||
variance_task_factory.generate() # => Чему равна дисперсия величины V(3 * X + 5 * Y), если X и Y подчинены стандартному нормальному закону распределения и независимы друг от друга? Ответ: 34
|
||||
|
||||
variance_task_factory.generate() # => Чему равна дисперсия величины V(6 * X + 2 * Y), если X и Y подчинены стандартному нормальному закону распределения и независимы друг от друга? Ответ: 40
|
||||
```
|
||||
|
||||
2. Пул задач. Пул - это массив задач и генераторов задач, из которых составляются варианты.
|
||||
|
||||
```{python}
|
||||
from modules.variant_builder.task_pool import QTaskPool
|
||||
from modules.task import QTask
|
||||
|
||||
tasks = [
|
||||
QTask(
|
||||
question="Текст задачи 1"
|
||||
),
|
||||
QTask(
|
||||
question="Текст задачи 2"
|
||||
),
|
||||
QTask(
|
||||
question="Текст задачи 3"
|
||||
),
|
||||
variance_task_factory # каждый раз, когда в вариант отбирается генератор, он генерирует для этого варианта новую задачу с уникальными значениями
|
||||
]
|
||||
|
||||
# Инициируем пул задач
|
||||
task_pool = QTaskPool(tasks)
|
||||
```
|
||||
|
||||
3. Описание структуры варианта. Для описания структуры варианта используется специальный класс `VariantFactory`. Чтобы его создать, из скольки задач будут состоять варианты, пул задач из которых подбираются задачи и (опционально) правило отбора задач. Если последнее не указать, то будет использоваться правило, которые старается минимизировать число пересечений между любыми двумя вариантами и по возможности равномерно распределить задачи.
|
||||
|
||||
Чтобы понять, как определяется структура варианта, рассмотрим простой пример. Допустим, мы хотим сделать вариант, состоящий из трёх задач и у каждой задачи есть две вариации.
|
||||
|
||||
```{python}
|
||||
from modules.variant_builder import VariantFactory
|
||||
|
||||
# Сначала создадим пул задач
|
||||
task_pool = QTaskPool([
|
||||
# Задача 1
|
||||
QTask(
|
||||
question="1.1"
|
||||
tags=Tag("order", "first") # Тег, чтобы отличать первую задачу от второй и третий
|
||||
),
|
||||
QTask(
|
||||
question="1.2"
|
||||
tags=Tag("order", "first")
|
||||
),
|
||||
|
||||
# Задача 2
|
||||
QTask(
|
||||
question="2.1"
|
||||
tags=Tag("order", "second")
|
||||
),
|
||||
QTask(
|
||||
question="2.2"
|
||||
tags=Tag("order", "second")
|
||||
),
|
||||
|
||||
# Задача 3
|
||||
QTask(
|
||||
question="3.1"
|
||||
tags=Tag("order", "third")
|
||||
),
|
||||
QTask(
|
||||
question="3.2"
|
||||
tags=Tag("order", "third")
|
||||
),
|
||||
])
|
||||
|
||||
# Инициируем генератор вариантов
|
||||
vf = VariantFactory(number_of_tasks=3, task_pool = task_pool)
|
||||
|
||||
# Теперь самое главное - укажем, что первая задача в варианте должна быть задачей один, вторая - задачей два, а третья - задачей три
|
||||
vf.task[0].must.include_tag("order", "first") # первая задача должна быть задачей с пометкой order = first
|
||||
vf.task[1].must.include_tag("order", "second") # вторая задача должна быть задачей с пометкой order = second
|
||||
vf.task[2].must.include_tag("order", "third") # первая задача должна быть задачей с пометкой order = third
|
||||
|
||||
# доступные методы include_all_tags() - должна включать все теги из списка, include_any_tag() - должна включать хотя один тег из списка, be_one_of() - должна быть одной из задач или должна быть сгенерирована определённым генератором, not(lambda b: b.must...) - логическое отрицание, or(lambda b: b.must..., lambda b: b.must...) - логическое или.
|
||||
|
||||
# Сгенерируем 10 вариантов:
|
||||
variants = vf.generate_variants(number_of_variants=10) # можем указать любое число
|
||||
```
|
||||
|
||||
Генератор вариантов сгенерирует 10 вариантов так, чтобы они были максимально уникальны относительно друг друга. Варианты, которые составляет `VariantFactory` представлены классом `QVariant`, который есть просто собрание задач и по сути представляет собой обычный массив:
|
||||
|
||||
```{python}
|
||||
first_variant = variants[0] # первый вариант
|
||||
first_task_of_first_variant = first_variant[0] # первая задача первого варианта
|
||||
|
||||
print(first_task_of_first_variant.question) # => 1.2
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
from collections.abc import Iterable
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from itertools import chain
|
||||
from typing import Generic, overload, override
|
||||
|
||||
from modules.utils.types import C, V
|
||||
@ -29,12 +30,37 @@ class Tags(Generic[C, V]):
|
||||
|
||||
_dict: dict[C, set[V]]
|
||||
|
||||
def __init__(self, iter: Iterable[Tag[C, V]] | None = None):
|
||||
@overload
|
||||
def __init__(
|
||||
self, iter: Iterable[Tag[C, V] | tuple[C, V]] | None = None
|
||||
) -> None: ...
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self,
|
||||
iter: Tag[C, V] | tuple[C, V] | None = None,
|
||||
**kwargs: Tag[C, V] | tuple[C, V],
|
||||
) -> None: ...
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
iter: Iterable[Tag[C, V] | tuple[C, V]] | Tag[C, V] | tuple[C, V] | None = None,
|
||||
**kwargs: Tag[C, V] | tuple[C, V],
|
||||
) -> None:
|
||||
self._dict = {}
|
||||
|
||||
if iter:
|
||||
if isinstance(iter, Iterable) and not isinstance(iter, tuple):
|
||||
for tag in iter:
|
||||
self._dict.setdefault(tag.cat, set()).add(tag.val)
|
||||
if isinstance(tag, Tag):
|
||||
self._dict.setdefault(tag.cat, set()).add(tag.val)
|
||||
else:
|
||||
self._dict.setdefault(tag[0], set()).add(tag[1])
|
||||
else:
|
||||
for tag in chain([iter], kwargs.values()):
|
||||
if isinstance(iter, Tag):
|
||||
self._dict.setdefault(tag.cat, set()).add(tag.val)
|
||||
elif isinstance(iter, tuple):
|
||||
self._dict.setdefault(tag[0], set()).add(tag[1])
|
||||
|
||||
@overload
|
||||
def has_tag(self, category: C, value: V) -> bool: ...
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import uuid
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Generic, runtime_checkable
|
||||
from typing import Callable, Generic
|
||||
|
||||
from option import Option
|
||||
|
||||
|
||||
31
modules/task/factory/default.py
Normal file
31
modules/task/factory/default.py
Normal file
@ -0,0 +1,31 @@
|
||||
from typing import Callable, override
|
||||
|
||||
from modules.tag import Tags
|
||||
from modules.task import QTask
|
||||
from modules.task.factory import QTaskFactory
|
||||
from modules.utils.types import A, C, Q, V
|
||||
|
||||
|
||||
class QTaskFactoryDefault(QTaskFactory[C, V, Q, A]):
|
||||
_generator: Callable[[], QTask[C, V, Q, A]]
|
||||
|
||||
def __init__(self, generator: Callable[[], QTask[C, V, Q, A]]):
|
||||
self._generator = generator
|
||||
|
||||
@override
|
||||
def generate(self) -> QTask[C, V, Q, A]:
|
||||
return self._generator()
|
||||
|
||||
|
||||
def task_generator_function():
|
||||
alpha = random.randint(1, 9)
|
||||
beta = random.randint(1, 9)
|
||||
|
||||
question = f"Чему равна дисперсия величины V({alpha} * X + {beta} * Y), если X и Y подчинены стандартному нормальному закону распределения и независимы друг от друга?"
|
||||
answer = f"{alpha ** 2 + beta ** 2}"
|
||||
tags = Tags(("тема", "дисперсия"), ("сложность", "лёгкая"))
|
||||
|
||||
return QTask(question, answer, tags)
|
||||
|
||||
|
||||
factory = QTaskFactoryDefault(task_generator_function)
|
||||
@ -1,3 +1,4 @@
|
||||
from collections.abc import Iterable
|
||||
from typing import Generic
|
||||
|
||||
from modules.task import QTask
|
||||
@ -22,12 +23,12 @@ class VariantFactory(Generic[C, V, Q, A]):
|
||||
def __init__(
|
||||
self,
|
||||
number_of_tasks: int,
|
||||
task_pool: QTaskPool[C, V, Q, A],
|
||||
task_pool: Iterable[QTask[C, V, Q, A]],
|
||||
task_selector: QTaskSelector[C, V, Q, A] | None = None,
|
||||
):
|
||||
self.task = [VariantTask() for _ in range(number_of_tasks)]
|
||||
self.number_of_tasks = number_of_tasks
|
||||
self.task_pool = task_pool
|
||||
self.task_pool = QTaskPool[C, V, Q, A](task_pool)
|
||||
self.task_selector = (
|
||||
task_selector if task_selector is not None else LeastUsedTaskSelector()
|
||||
)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -1,13 +1,11 @@
|
||||
import uuid
|
||||
from math import inf
|
||||
from test import best_candidate, min_max_intersections
|
||||
from typing import override
|
||||
|
||||
from modules.task import QTask
|
||||
from modules.utils.types import A, C, Q, V
|
||||
from modules.utils.utils import rnd
|
||||
from modules.variant_builder.context import DynamicCtx
|
||||
from modules.variant_builder.task_pool import QTaskPool
|
||||
from modules.variant_builder.task_selector import QTaskSelector
|
||||
|
||||
|
||||
@ -41,9 +39,6 @@ class LeastUsedTaskSelector(QTaskSelector[C, V, Q, A]):
|
||||
if min_max_intersections > max_intersections:
|
||||
min_max_intersections = max_intersections
|
||||
|
||||
if len(ctx.current_variant_tasks) == 6:
|
||||
print(min_max_intersections)
|
||||
|
||||
best_candidates: list[QTask[C, V, Q, A]] = []
|
||||
|
||||
for task, score in zip(filtered_tasks, task_scores):
|
||||
|
||||
BIN
parserd.xlsx
Normal file
BIN
parserd.xlsx
Normal file
Binary file not shown.
29
poetry.lock
generated
29
poetry.lock
generated
@ -13,6 +13,18 @@ files = [
|
||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "et-xmlfile"
|
||||
version = "2.0.0"
|
||||
description = "An implementation of lxml.xmlfile for the standard library"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa"},
|
||||
{file = "et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.2.2"
|
||||
@ -41,6 +53,21 @@ files = [
|
||||
{file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openpyxl"
|
||||
version = "3.1.5"
|
||||
description = "A Python library to read/write Excel 2010 xlsx/xlsm files"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2"},
|
||||
{file = "openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
et-xmlfile = "*"
|
||||
|
||||
[[package]]
|
||||
name = "option"
|
||||
version = "2.1.0"
|
||||
@ -150,4 +177,4 @@ files = [
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.8,<4"
|
||||
content-hash = "aa060e205a9e141d4941339e4d9d39cde82de5f8d0900aca25f331ddf46e2a05"
|
||||
content-hash = "af9b1bb0ddd8587d40decb8a6cb9a61735ebd8cd1354e18afd1bfa55aed0eb33"
|
||||
|
||||
@ -9,7 +9,8 @@ readme = "README.md"
|
||||
requires-python = ">=3.8,<4"
|
||||
dependencies = [
|
||||
"option (>=2.1.0,<3.0.0)",
|
||||
"pytest (>=8.3.5,<9.0.0)"
|
||||
"pytest (>=8.3.5,<9.0.0)",
|
||||
"openpyxl (>=3.1.5,<4.0.0)"
|
||||
]
|
||||
|
||||
|
||||
|
||||
BIN
samples/clean.xlsx
Normal file
BIN
samples/clean.xlsx
Normal file
Binary file not shown.
BIN
samples/Варианты ДКР2 2023 2024 2025.xlsx
Normal file
BIN
samples/Варианты ДКР2 2023 2024 2025.xlsx
Normal file
Binary file not shown.
33
test.py
33
test.py
@ -1,33 +0,0 @@
|
||||
from math import inf
|
||||
|
||||
tasks1 = [f"1.{i + 1}" for i in range(5)]
|
||||
tasks2 = [f"2.{i + 1}" for i in range(5)]
|
||||
tasks3 = [f"3.{i + 1}" for i in range(5)]
|
||||
|
||||
all_variants = []
|
||||
|
||||
for i in tasks1:
|
||||
for j in tasks2:
|
||||
for k in tasks3:
|
||||
all_variants.append([i, j, k])
|
||||
|
||||
selected_variants = [all_variants[0]]
|
||||
|
||||
for _ in range(9):
|
||||
best_candidate = all_variants[0]
|
||||
min_max_intersections = inf
|
||||
for variant in all_variants:
|
||||
max_intersections = -inf
|
||||
for selected_variant in selected_variants:
|
||||
intersections = 0
|
||||
for j in range(3):
|
||||
if selected_variant[j] == variant[j]:
|
||||
intersections += 1
|
||||
if max_intersections < intersections:
|
||||
max_intersections = intersections
|
||||
if max_intersections < min_max_intersections:
|
||||
min_max_intersections = max_intersections
|
||||
best_candidate = variant
|
||||
selected_variants.append(best_candidate)
|
||||
|
||||
print(any([]), sep="\n")
|
||||
43
variants.py
Normal file
43
variants.py
Normal file
@ -0,0 +1,43 @@
|
||||
from openpyxl import load_workbook
|
||||
|
||||
from modules.tag import Tag
|
||||
from modules.task import QTask
|
||||
from modules.variant_builder import VariantFactory
|
||||
|
||||
wb = load_workbook(filename="./clean.xlsx")
|
||||
ws = wb["data"]
|
||||
|
||||
for i in list(range(6 * 5))[::5]:
|
||||
tasks: list[QTask] = []
|
||||
|
||||
name = str(ws.cell(column=1, row=(i + 1)).value)
|
||||
|
||||
wb.create_sheet(name)
|
||||
ws_sp = wb[name]
|
||||
|
||||
for ind, col in enumerate(
|
||||
ws.iter_cols(min_col=2, max_col=8, min_row=i + 1, max_row=i + 5)
|
||||
):
|
||||
for cell in col:
|
||||
tasks.append(
|
||||
QTask[str, str, str, str | None](
|
||||
question=str(cell.value), tags=Tag("topic", str(ind))
|
||||
)
|
||||
)
|
||||
|
||||
vf = VariantFactory(7, tasks)
|
||||
_ = vf.task[0].must.include_tag("topic", "0")
|
||||
_ = vf.task[1].must.include_tag("topic", "1")
|
||||
_ = vf.task[2].must.include_tag("topic", "2")
|
||||
_ = vf.task[3].must.include_tag("topic", "3")
|
||||
_ = vf.task[4].must.include_tag("topic", "4")
|
||||
_ = vf.task[5].must.include_tag("topic", "5")
|
||||
_ = vf.task[6].must.include_tag("topic", "6")
|
||||
variants = vf.generate_variants(number_of_variants=8)
|
||||
|
||||
for ind, variant in enumerate(variants):
|
||||
cell = ws_sp.cell(column=1, row=ind + 1, value=f"Вариант {ind + 1}")
|
||||
for task_ind, task in enumerate(variant.tasks):
|
||||
cell = ws_sp.cell(column=task_ind + 2, row=ind + 1, value=task.question)
|
||||
|
||||
wb.save("parserd.xlsx")
|
||||
BIN
варианты.xlsx
Normal file
BIN
варианты.xlsx
Normal file
Binary file not shown.
Reference in New Issue
Block a user