working!
This commit is contained in:
BIN
__pycache__/test.cpython-313.pyc
Normal file
BIN
__pycache__/test.cpython-313.pyc
Normal file
Binary file not shown.
81
example.py
81
example.py
@ -1,54 +1,41 @@
|
|||||||
from enum import Enum
|
from modules.tag import Tag
|
||||||
from typing import Generic
|
from modules.task import QTask
|
||||||
|
from modules.task.factory import QTaskFactory
|
||||||
|
from modules.variant_builder import VariantFactory
|
||||||
|
from modules.variant_builder.task_pool import QTaskPool
|
||||||
|
|
||||||
from option import Some
|
tasks_on_first_topic = [QTask(f"1.{i + 1}", tags=Tag("topic", "1")) for i in range(5)]
|
||||||
|
tasks_on_second_topic = [QTask(f"2.{i + 1}", tags=Tag("topic", "2")) for i in range(5)]
|
||||||
|
tasks_on_third_topic = [QTask(f"3.{i + 1}", tags=Tag("topic", "3")) for i in range(5)]
|
||||||
|
tasks_on_forth_topic = [QTask(f"4.{i + 1}", tags=Tag("topic", "4")) for i in range(5)]
|
||||||
|
tasks_on_fifth_topic = [QTask(f"5.{i + 1}", tags=Tag("topic", "5")) for i in range(5)]
|
||||||
|
tasks_on_sixth_topic = [QTask(f"6.{i + 1}", tags=Tag("topic", "6")) for i in range(5)]
|
||||||
|
tasks_on_seventh_topic = [QTask(f"7.{i + 1}", tags=Tag("topic", "7")) for i in range(5)]
|
||||||
|
|
||||||
from Quizard import Quizard, QuizTask, QuizTaskGeneratorMetadata, TaskGenerator
|
task_pool = QTaskPool(
|
||||||
|
tasks_on_first_topic
|
||||||
|
+ tasks_on_second_topic
|
||||||
class TagCategory(str, Enum):
|
+ tasks_on_third_topic
|
||||||
TOPIC = "topic"
|
+ tasks_on_forth_topic
|
||||||
DIFFICULTY = "difficulty"
|
+ tasks_on_fifth_topic
|
||||||
|
+ tasks_on_sixth_topic
|
||||||
|
+ tasks_on_seventh_topic
|
||||||
class TopicTag(str, Enum):
|
|
||||||
AVERAGE = "average"
|
|
||||||
VARIANCE = "variance"
|
|
||||||
|
|
||||||
|
|
||||||
class MyTaskGenerator(TaskGenerator[TagCategory, TopicTag]):
|
|
||||||
def __init__(self):
|
|
||||||
self.metadata = QuizTaskGeneratorMetadata[TagCategory, TopicTag].from_values(
|
|
||||||
name=self.__class__.__name__,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
vf = VariantFactory(number_of_tasks=7, task_pool=task_pool)
|
||||||
|
|
||||||
class AverageTask(MyTaskGenerator):
|
_ = vf.task[0].must.include_tag("topic", "1")
|
||||||
def generate(self):
|
_ = vf.task[1].must.include_tag("topic", "2")
|
||||||
return QuizTask(
|
_ = vf.task[2].must.include_tag("topic", "3")
|
||||||
"What is an average of 3, 4, 5 and 6?",
|
_ = vf.task[3].must.include_tag("topic", "4")
|
||||||
"4.5",
|
_ = vf.task[4].must.include_tag("topic", "5")
|
||||||
tags=[
|
_ = vf.task[5].must.include_tag("topic", "6")
|
||||||
(TagCategory.TOPIC, TopicTag.AVERAGE),
|
_ = vf.task[6].must.include_tag("topic", "7")
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
variants = vf.generate_variants(number_of_variants=30)
|
||||||
|
|
||||||
class AverageTask1(MyTaskGenerator):
|
i = 0
|
||||||
def generate(self):
|
for variant in variants:
|
||||||
return QuizTask(
|
print(f"Variant {i + 1}:")
|
||||||
"What is an average of 1, 2, 3 and 4?",
|
print(*[task.question for task in variant.tasks])
|
||||||
"2.5",
|
i += 1
|
||||||
tags=[
|
|
||||||
(TagCategory.TOPIC, TopicTag.VARIANCE),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
default_amount = Some(1)
|
|
||||||
|
|
||||||
|
|
||||||
quizard = Quizard([AverageTask(), AverageTask1()], 3)
|
|
||||||
|
|
||||||
quizard.fillTaskPool()
|
|
||||||
|
|
||||||
print(*[task.generator_metadata.id for task in quizard.taskPool], sep="\n\n")
|
|
||||||
|
|||||||
@ -20,7 +20,6 @@ class Tag(Generic[C, V]):
|
|||||||
val: V
|
val: V
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Tags(Generic[C, V]):
|
class Tags(Generic[C, V]):
|
||||||
"""A collection of tags grouped by category
|
"""A collection of tags grouped by category
|
||||||
|
|
||||||
@ -28,9 +27,11 @@ class Tags(Generic[C, V]):
|
|||||||
_dict: (dict[C, set[V]]): Internal dictionary storing tags grouped by category.
|
_dict: (dict[C, set[V]]): Internal dictionary storing tags grouped by category.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_dict: dict[C, set[V]] = field(default_factory=dict)
|
_dict: dict[C, set[V]]
|
||||||
|
|
||||||
def __init__(self, iter: Iterable[Tag[C, V]] | None = None):
|
def __init__(self, iter: Iterable[Tag[C, V]] | None = None):
|
||||||
|
self._dict = {}
|
||||||
|
|
||||||
if iter:
|
if iter:
|
||||||
for tag in iter:
|
for tag in iter:
|
||||||
self._dict.setdefault(tag.cat, set()).add(tag.val)
|
self._dict.setdefault(tag.cat, set()).add(tag.val)
|
||||||
@ -101,9 +102,7 @@ class Tags(Generic[C, V]):
|
|||||||
lines: list[str] = []
|
lines: list[str] = []
|
||||||
for category, values in self._dict.items():
|
for category, values in self._dict.items():
|
||||||
cat_str = str(category.value if isinstance(category, Enum) else category)
|
cat_str = str(category.value if isinstance(category, Enum) else category)
|
||||||
val_strs = sorted(
|
val_strs = [str(v.value if isinstance(v, Enum) else v) for v in values]
|
||||||
[str(v.value if isinstance(v, Enum) else v for v in values)]
|
|
||||||
)
|
|
||||||
lines.append(f"{cat_str}: {', '.join(val_strs)}")
|
lines.append(f"{cat_str}: {', '.join(val_strs)}")
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
@ -113,6 +112,7 @@ class Tags(Generic[C, V]):
|
|||||||
Yields:
|
Yields:
|
||||||
Tag[C, V]: Each tag in the collection.
|
Tag[C, V]: Each tag in the collection.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for category, values in self._dict.items():
|
for category, values in self._dict.items():
|
||||||
for value in values:
|
for value in values:
|
||||||
yield Tag(category, value)
|
yield Tag(category, value)
|
||||||
|
|||||||
BIN
modules/tag/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
modules/tag/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
@ -1,37 +1,42 @@
|
|||||||
import uuid
|
import uuid
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, field
|
||||||
from typing import Generic, override
|
from typing import TYPE_CHECKING, Generic, override
|
||||||
|
|
||||||
from option import Option
|
from option import Option
|
||||||
|
|
||||||
from modules.tag import Tag, Tags
|
from modules.tag import Tag, Tags
|
||||||
from modules.task.factory.metadata import QTaskFactoryMetadata
|
|
||||||
from modules.utils.types import A, C, Q, V
|
from modules.utils.types import A, C, Q, V
|
||||||
from modules.utils.utils import indent
|
from modules.utils.utils import indent
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from modules.task.factory.metadata import QTaskFactoryMetadata
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class QTask(Generic[C, V, Q, A]):
|
class QTask(Generic[C, V, Q, A]):
|
||||||
question: Q
|
question: Q
|
||||||
answer: A
|
answer: A
|
||||||
tags: Tags[C, V]
|
tags: Tags[C, V]
|
||||||
factory_metadata: Option[QTaskFactoryMetadata[C, V]] = Option[
|
id: uuid.UUID
|
||||||
QTaskFactoryMetadata[C, V]
|
factory_metadata: Option["QTaskFactoryMetadata[C, V]"] = Option[
|
||||||
|
"QTaskFactoryMetadata[C, V]"
|
||||||
].maybe(None)
|
].maybe(None)
|
||||||
id: uuid.UUID = uuid.uuid4()
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
question: Q,
|
question: Q,
|
||||||
answer: A,
|
answer: A = None,
|
||||||
tags: Tags[C, V] | list[Tag[C, V]] | None = None,
|
tags: Tags[C, V] | list[Tag[C, V]] | Tag[C, V] | None = None,
|
||||||
):
|
):
|
||||||
self.question = question
|
self.question = question
|
||||||
self.answer = answer
|
self.answer = answer
|
||||||
|
if isinstance(tags, Tag):
|
||||||
|
self.tags = Tags[C, V]([tags])
|
||||||
if isinstance(tags, list):
|
if isinstance(tags, list):
|
||||||
self.tags = Tags[C, V](tags)
|
self.tags = Tags[C, V](tags)
|
||||||
elif isinstance(tags, Tags):
|
elif isinstance(tags, Tags):
|
||||||
self.tags = tags
|
self.tags = tags
|
||||||
|
self.id = uuid.uuid4()
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
|
|||||||
BIN
modules/task/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
modules/task/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
@ -9,7 +9,6 @@ from modules.task.factory.metadata import QTaskFactoryMetadata
|
|||||||
from modules.utils.types import A, C, Q, V
|
from modules.utils.types import A, C, Q, V
|
||||||
|
|
||||||
|
|
||||||
@runtime_checkable
|
|
||||||
class QTaskFactory(ABC, Generic[C, V, Q, A]):
|
class QTaskFactory(ABC, Generic[C, V, Q, A]):
|
||||||
id: uuid.UUID
|
id: uuid.UUID
|
||||||
metadata: QTaskFactoryMetadata[C, V]
|
metadata: QTaskFactoryMetadata[C, V]
|
||||||
|
|||||||
BIN
modules/task/factory/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
modules/task/factory/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
modules/task/factory/__pycache__/metadata.cpython-313.pyc
Normal file
BIN
modules/task/factory/__pycache__/metadata.cpython-313.pyc
Normal file
Binary file not shown.
@ -2,7 +2,7 @@ import uuid
|
|||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Generic
|
from typing import Generic
|
||||||
|
|
||||||
from option import NONE, Option
|
from option import Option
|
||||||
|
|
||||||
from modules.utils.types import C, V
|
from modules.utils.types import C, V
|
||||||
|
|
||||||
|
|||||||
BIN
modules/utils/__pycache__/types.cpython-313.pyc
Normal file
BIN
modules/utils/__pycache__/types.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
@ -1,6 +1,11 @@
|
|||||||
|
from random import Random
|
||||||
from typing import TypeVar
|
from typing import TypeVar
|
||||||
|
|
||||||
|
|
||||||
def indent(text: str, spaces: int = 2) -> str:
|
def indent(text: str, spaces: int = 2) -> str:
|
||||||
prefix = " " * spaces
|
prefix = " " * spaces
|
||||||
return "\n".join(prefix + line for line in text.splitlines())
|
return "\n".join(prefix + line for line in text.splitlines())
|
||||||
|
|
||||||
|
|
||||||
|
rnd = Random()
|
||||||
|
rnd.seed(42)
|
||||||
|
|||||||
BIN
modules/variant/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
modules/variant/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
@ -1,16 +1,96 @@
|
|||||||
from dataclasses import dataclass
|
|
||||||
from typing import Generic
|
from typing import Generic
|
||||||
|
|
||||||
|
from modules.task import QTask
|
||||||
from modules.utils.types import A, C, Q, V
|
from modules.utils.types import A, C, Q, V
|
||||||
from modules.variant import QVariant
|
from modules.variant import QVariant
|
||||||
|
from modules.variant_builder.context import DynamicCtx
|
||||||
|
from modules.variant_builder.default_task_selector import LeastUsedTaskSelector
|
||||||
|
from modules.variant_builder.filters import Filter
|
||||||
from modules.variant_builder.task_pool import QTaskPool
|
from modules.variant_builder.task_pool import QTaskPool
|
||||||
from modules.variant_builder.task_selector import QTaskSelector
|
from modules.variant_builder.task_selector import QTaskSelector
|
||||||
from modules.variant_builder.variant_set import QVariantSet
|
from modules.variant_builder.variant_set import QVariantSet
|
||||||
|
from modules.variant_builder.variant_task import VariantTask
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
class VariantFactory(Generic[C, V, Q, A]):
|
||||||
class VariantBuilder(Generic[C, V, Q, A]):
|
|
||||||
task_pool: QTaskPool[C, V, Q, A]
|
task_pool: QTaskPool[C, V, Q, A]
|
||||||
previos_variants: QVariantSet[C, V, Q, A]
|
previous_variants: QVariantSet[C, V, Q, A] = QVariantSet()
|
||||||
current_variant: QVariant[C, V, Q, A]
|
|
||||||
task_selector: QTaskSelector[C, V, Q, A]
|
task_selector: QTaskSelector[C, V, Q, A]
|
||||||
|
task: list[VariantTask[C, V, Q, A]]
|
||||||
|
number_of_tasks: int
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
number_of_tasks: int,
|
||||||
|
task_pool: QTaskPool[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_selector = (
|
||||||
|
task_selector if task_selector is not None else LeastUsedTaskSelector()
|
||||||
|
)
|
||||||
|
|
||||||
|
def generate_variants(self, number_of_variants: int) -> QVariantSet[C, V, Q, A]:
|
||||||
|
variant_task_filters: list[Filter[C, V, Q, A]] = [
|
||||||
|
b.must.build() for b in self.task
|
||||||
|
]
|
||||||
|
static_filter_matches_per_task = self._get_static_filter_matches(
|
||||||
|
variant_task_filters
|
||||||
|
)
|
||||||
|
|
||||||
|
dynamic_context = DynamicCtx(
|
||||||
|
self.task_pool,
|
||||||
|
)
|
||||||
|
|
||||||
|
for _ in range(number_of_variants):
|
||||||
|
variant_tasks: list[QTask[C, V, Q, A]] = []
|
||||||
|
for task_index in range(self.number_of_tasks):
|
||||||
|
dynamic_filtered_matches = self._get_dynamic_filter_matches(
|
||||||
|
static_filter_matches_per_task[task_index],
|
||||||
|
variant_task_filters[task_index],
|
||||||
|
dynamic_context,
|
||||||
|
)
|
||||||
|
|
||||||
|
selected_task = self.task_selector.select(
|
||||||
|
dynamic_filtered_matches, dynamic_context
|
||||||
|
)
|
||||||
|
variant_tasks.append(selected_task)
|
||||||
|
dynamic_context.current_variant_tasks.append(selected_task)
|
||||||
|
|
||||||
|
variant = QVariant(variant_tasks)
|
||||||
|
dynamic_context.previous_variants.append(variant)
|
||||||
|
dynamic_context.current_variant_tasks.clear()
|
||||||
|
|
||||||
|
return dynamic_context.previous_variants
|
||||||
|
|
||||||
|
def _get_static_filter_matches(
|
||||||
|
self, task_filters: list[Filter[C, V, Q, A]]
|
||||||
|
) -> list[list[QTask[C, V, Q, A]]]:
|
||||||
|
statical_filter_matches_per_task: list[list[QTask[C, V, Q, A]]] = [
|
||||||
|
[] for _ in range(self.number_of_tasks)
|
||||||
|
]
|
||||||
|
|
||||||
|
for task in self.task_pool:
|
||||||
|
task_filter_index = 0
|
||||||
|
for filter in task_filters:
|
||||||
|
if filter.static.is_satisfied(task):
|
||||||
|
statical_filter_matches_per_task[task_filter_index].append(task)
|
||||||
|
task_filter_index += 1
|
||||||
|
|
||||||
|
return statical_filter_matches_per_task
|
||||||
|
|
||||||
|
def _get_dynamic_filter_matches(
|
||||||
|
self,
|
||||||
|
statically_filtered_pool: list[QTask[C, V, Q, A]],
|
||||||
|
filter: Filter[C, V, Q, A],
|
||||||
|
ctx: DynamicCtx[C, V, Q, A],
|
||||||
|
) -> list[QTask[C, V, Q, A]]:
|
||||||
|
dynamic_filter_matches: list[QTask[C, V, Q, A]] = []
|
||||||
|
|
||||||
|
for task in statically_filtered_pool:
|
||||||
|
if filter.dynamic.check_if_satisfied(task, ctx):
|
||||||
|
dynamic_filter_matches.append(task)
|
||||||
|
|
||||||
|
return dynamic_filter_matches
|
||||||
|
|||||||
BIN
modules/variant_builder/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
modules/variant_builder/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
modules/variant_builder/__pycache__/context.cpython-313.pyc
Normal file
BIN
modules/variant_builder/__pycache__/context.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
modules/variant_builder/__pycache__/task_pool.cpython-313.pyc
Normal file
BIN
modules/variant_builder/__pycache__/task_pool.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
modules/variant_builder/__pycache__/variant_set.cpython-313.pyc
Normal file
BIN
modules/variant_builder/__pycache__/variant_set.cpython-313.pyc
Normal file
Binary file not shown.
BIN
modules/variant_builder/__pycache__/variant_task.cpython-313.pyc
Normal file
BIN
modules/variant_builder/__pycache__/variant_task.cpython-313.pyc
Normal file
Binary file not shown.
@ -1,6 +1,7 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, field
|
||||||
from typing import Generic
|
from typing import Generic
|
||||||
|
|
||||||
|
from modules.task import QTask
|
||||||
from modules.utils.types import A, C, Q, V
|
from modules.utils.types import A, C, Q, V
|
||||||
from modules.variant import QVariant
|
from modules.variant import QVariant
|
||||||
from modules.variant_builder.task_pool import QTaskPool
|
from modules.variant_builder.task_pool import QTaskPool
|
||||||
@ -8,8 +9,9 @@ from modules.variant_builder.variant_set import QVariantSet
|
|||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class DynamicFilterCtx(Generic[C, V, Q, A]):
|
class DynamicCtx(Generic[C, V, Q, A]):
|
||||||
task_pool: QTaskPool[C, V, Q, A]
|
task_pool: QTaskPool[C, V, Q, A]
|
||||||
previous_variants: QVariantSet[C, V, Q, A]
|
previous_variants: QVariantSet[C, V, Q, A] = field(
|
||||||
current_variant: QVariant[C, V, Q, A]
|
default_factory=QVariantSet[C, V, Q, A]
|
||||||
task_number: int
|
)
|
||||||
|
current_variant_tasks: list[QTask[C, V, Q, A]] = field(default_factory=list)
|
||||||
|
|||||||
63
modules/variant_builder/default_task_selector.py
Normal file
63
modules/variant_builder/default_task_selector.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
class LeastUsedTaskSelector(QTaskSelector[C, V, Q, A]):
|
||||||
|
task_usage_count: dict[uuid.UUID, int]
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.task_usage_count = {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
def select(
|
||||||
|
self, filtered_tasks: list[QTask[C, V, Q, A]], ctx: DynamicCtx[C, V, Q, A]
|
||||||
|
) -> QTask[C, V, Q, A]:
|
||||||
|
rnd.shuffle(filtered_tasks)
|
||||||
|
|
||||||
|
task_scores: list[int] = []
|
||||||
|
|
||||||
|
min_max_intersections = inf
|
||||||
|
for task in filtered_tasks:
|
||||||
|
max_intersections = 0
|
||||||
|
for variant in ctx.previous_variants:
|
||||||
|
intersections = 0
|
||||||
|
for t in ctx.current_variant_tasks:
|
||||||
|
if t in variant.tasks:
|
||||||
|
intersections += 1
|
||||||
|
if task in variant.tasks:
|
||||||
|
intersections += 1
|
||||||
|
if intersections > max_intersections:
|
||||||
|
max_intersections = intersections
|
||||||
|
task_scores.append(max_intersections)
|
||||||
|
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):
|
||||||
|
if score == min_max_intersections:
|
||||||
|
best_candidates.append(task)
|
||||||
|
|
||||||
|
least_used_score = inf
|
||||||
|
least_used_task: QTask[C, V, Q, A] = best_candidates[0]
|
||||||
|
|
||||||
|
for task in best_candidates:
|
||||||
|
if self.task_usage_count.setdefault(task.id, 0) < least_used_score:
|
||||||
|
least_used_score = self.task_usage_count[task.id]
|
||||||
|
least_used_task = task
|
||||||
|
|
||||||
|
self.task_usage_count[least_used_task.id] += 1
|
||||||
|
|
||||||
|
return least_used_task
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -5,11 +5,8 @@ from typing import Callable, Generic, Self, overload, override
|
|||||||
from modules.tag import Tag, Tags
|
from modules.tag import Tag, Tags
|
||||||
from modules.utils.types import A, C, Q, V
|
from modules.utils.types import A, C, Q, V
|
||||||
from modules.variant_builder.filters import Filter
|
from modules.variant_builder.filters import Filter
|
||||||
from modules.variant_builder.filters.composite import CompositeFilter
|
|
||||||
from modules.variant_builder.filters.dynamic import FilterDynamic
|
from modules.variant_builder.filters.dynamic import FilterDynamic
|
||||||
from modules.variant_builder.filters.dynamic.composite import CompositeFilterDynamic
|
|
||||||
from modules.variant_builder.filters.static import FilterStatic
|
from modules.variant_builder.filters.static import FilterStatic
|
||||||
from modules.variant_builder.filters.static.composite import CompositeFilterStatic
|
|
||||||
from modules.variant_builder.filters.static.must_be_one_of import MustBeOneOfFilter
|
from modules.variant_builder.filters.static.must_be_one_of import MustBeOneOfFilter
|
||||||
from modules.variant_builder.filters.static.must_include_all_tags import (
|
from modules.variant_builder.filters.static.must_include_all_tags import (
|
||||||
MustIncludeAllTagsFilter,
|
MustIncludeAllTagsFilter,
|
||||||
@ -92,6 +89,19 @@ class FilterBuilder(Generic[C, V, Q, A]):
|
|||||||
) -> Self:
|
) -> Self:
|
||||||
return self.add_static(MustIncludeAllTagsFilter(tag, **kwargs))
|
return self.add_static(MustIncludeAllTagsFilter(tag, **kwargs))
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def include_tag(self, category: C, value: V) -> Self: ...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def include_tag(self, category: Tag[C, V], value: None = None) -> Self: ...
|
||||||
|
|
||||||
|
def include_tag(self, category: C | Tag[C, V], value: V | None = None) -> Self:
|
||||||
|
if isinstance(category, Tag):
|
||||||
|
return self.include_all_tags(category)
|
||||||
|
else:
|
||||||
|
assert value is not None
|
||||||
|
return self.include_all_tags([Tag(category, value)])
|
||||||
|
|
||||||
def be_inverse_of(
|
def be_inverse_of(
|
||||||
self,
|
self,
|
||||||
filter: Callable[["FilterBuilder[C, V, Q, A]"], "FilterBuilder[C, V, Q, A]"],
|
filter: Callable[["FilterBuilder[C, V, Q, A]"], "FilterBuilder[C, V, Q, A]"],
|
||||||
|
|||||||
@ -5,16 +5,15 @@ from typing import Generic, Protocol, override, runtime_checkable
|
|||||||
from modules.task import QTask
|
from modules.task import QTask
|
||||||
from modules.utils.types import A, C, Q, V
|
from modules.utils.types import A, C, Q, V
|
||||||
from modules.variant import QVariant
|
from modules.variant import QVariant
|
||||||
from modules.variant_builder.context import DynamicFilterCtx
|
from modules.variant_builder.context import DynamicCtx
|
||||||
from modules.variant_builder.task_pool import QTaskPool
|
from modules.variant_builder.task_pool import QTaskPool
|
||||||
from modules.variant_builder.variant_set import QVariantSet
|
from modules.variant_builder.variant_set import QVariantSet
|
||||||
|
|
||||||
|
|
||||||
@runtime_checkable
|
|
||||||
class FilterDynamic(ABC, Generic[C, V, Q, A]):
|
class FilterDynamic(ABC, Generic[C, V, Q, A]):
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def check_if_satisfied(
|
def check_if_satisfied(
|
||||||
self, task: QTask[C, V, Q, A], ctx: DynamicFilterCtx[C, V, Q, A]
|
self, task: QTask[C, V, Q, A], ctx: DynamicCtx[C, V, Q, A]
|
||||||
) -> bool: ...
|
) -> bool: ...
|
||||||
|
|
||||||
def __invert__(self) -> "FilterDynamic[C, V, Q, A]":
|
def __invert__(self) -> "FilterDynamic[C, V, Q, A]":
|
||||||
@ -33,7 +32,7 @@ class DynamicFilterNegator(FilterDynamic[C, V, Q, A]):
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
def check_if_satisfied(
|
def check_if_satisfied(
|
||||||
self, task: QTask[C, V, Q, A], ctx: DynamicFilterCtx[C, V, Q, A]
|
self, task: QTask[C, V, Q, A], ctx: DynamicCtx[C, V, Q, A]
|
||||||
) -> bool:
|
) -> bool:
|
||||||
return not self.filter.check_if_satisfied(task, ctx)
|
return not self.filter.check_if_satisfied(task, ctx)
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@ -3,7 +3,7 @@ from typing import override
|
|||||||
|
|
||||||
from modules.task import QTask
|
from modules.task import QTask
|
||||||
from modules.utils.types import A, C, Q, V
|
from modules.utils.types import A, C, Q, V
|
||||||
from modules.variant_builder.context import DynamicFilterCtx
|
from modules.variant_builder.context import DynamicCtx
|
||||||
from modules.variant_builder.filters.dynamic import FilterDynamic
|
from modules.variant_builder.filters.dynamic import FilterDynamic
|
||||||
|
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ class CompositeFilterDynamic(FilterDynamic[C, V, Q, A]):
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
def check_if_satisfied(
|
def check_if_satisfied(
|
||||||
self, task: QTask[C, V, Q, A], ctx: DynamicFilterCtx[C, V, Q, A]
|
self, task: QTask[C, V, Q, A], ctx: DynamicCtx[C, V, Q, A]
|
||||||
) -> bool:
|
) -> bool:
|
||||||
return all(filter.check_if_satisfied(task, ctx) for filter in self.filters)
|
return all(filter.check_if_satisfied(task, ctx) for filter in self.filters)
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +0,0 @@
|
|||||||
from modules.utils.types import A, C, Q, V
|
|
||||||
from modules.variant_builder.filters.dynamic import FilterDynamic
|
|
||||||
|
|
||||||
|
|
||||||
class FilterNegatorDynamic(FilterDynamic[C, V, Q, A]):
|
|
||||||
|
|
||||||
@ -1 +0,0 @@
|
|||||||
e
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,16 +0,0 @@
|
|||||||
from dataclasses import dataclass
|
|
||||||
from typing import override
|
|
||||||
|
|
||||||
from modules.task import QTask
|
|
||||||
from modules.utils.types import A, C, Q, V
|
|
||||||
from modules.variant_builder.filters import FilterBuilder
|
|
||||||
from modules.variant_builder.filters.static import FilterStatic
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class CompositeFilterStatic(FilterStatic[C, V, Q, A]):
|
|
||||||
filters: list[FilterStatic[C, V, Q, A]]
|
|
||||||
|
|
||||||
@override
|
|
||||||
def is_satisfied(self, task: QTask[C, V, Q, A]) -> bool:
|
|
||||||
return all(filter.is_satisfied(task) for filter in self.filters)
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
from dataclasses import dataclass
|
|
||||||
from typing import override
|
|
||||||
|
|
||||||
from modules.task import QTask
|
|
||||||
from modules.utils.types import A, C, Q, V
|
|
||||||
from modules.variant_builder.filters.static import FilterStatic
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class StaticFilterNegator(FilterStatic[C, V, Q, A]):
|
|
||||||
filter: FilterStatic[C, V, Q, A]
|
|
||||||
|
|
||||||
@override
|
|
||||||
def is_satisfied(self, task: QTask[C, V, Q, A]) -> bool:
|
|
||||||
return not self.filter.is_satisfied(task)
|
|
||||||
|
|
||||||
@override
|
|
||||||
def __invert__(self) -> "FilterStatic[C, V, Q, A]":
|
|
||||||
return self.filter
|
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
import random
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Generic
|
from typing import Generic
|
||||||
|
|
||||||
@ -5,6 +6,6 @@ from modules.task import QTask
|
|||||||
from modules.utils.types import A, C, Q, V
|
from modules.utils.types import A, C, Q, V
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
class QTaskPool(list[QTask[C, V, Q, A]], Generic[C, V, Q, A]):
|
||||||
class QTaskPool(Generic[C, V, Q, A]):
|
def shuffle(self):
|
||||||
pool: list[QTask[C, V, Q, A]]
|
random.shuffle(self)
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Protocol
|
from typing import Generic, Protocol
|
||||||
|
|
||||||
from modules.task import QTask
|
from modules.task import QTask
|
||||||
from modules.utils.types import A, C, Q, V
|
from modules.utils.types import A, C, Q, V
|
||||||
|
from modules.variant_builder.context import DynamicCtx
|
||||||
from modules.variant_builder.task_pool import QTaskPool
|
from modules.variant_builder.task_pool import QTaskPool
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
class QTaskSelector(ABC, Generic[C, V, Q, A]):
|
||||||
class QTaskSelector(Protocol[C, V, Q, A]):
|
@abstractmethod
|
||||||
task_pool: QTaskPool[C, V, Q, A]
|
def select(
|
||||||
|
self, filtered_tasks: list[QTask[C, V, Q, A]], ctx: DynamicCtx[C, V, Q, A]
|
||||||
def select(self, filtered_task_pool_indexes: list[int]) -> QTask[C, V, Q, A]: ...
|
) -> QTask[C, V, Q, A]: ...
|
||||||
|
|||||||
@ -6,9 +6,4 @@ from modules.variant import QVariant
|
|||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class QVariantSet(Generic[C, V, Q, A]):
|
class QVariantSet(list[QVariant[C, V, Q, A]], Generic[C, V, Q, A]): ...
|
||||||
variants: list[QVariant[C, V, Q, A]] = field(default_factory=list)
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
for variant in self.variants:
|
|
||||||
yield variant
|
|
||||||
|
|||||||
@ -1,6 +1,15 @@
|
|||||||
|
from dataclasses import dataclass, field
|
||||||
from typing import Generic
|
from typing import Generic
|
||||||
|
|
||||||
|
from option import Option
|
||||||
|
|
||||||
from modules.utils.types import A, C, Q, V
|
from modules.utils.types import A, C, Q, V
|
||||||
|
from modules.variant_builder.filters import Filter
|
||||||
|
from modules.variant_builder.filters.builder import FilterBuilder
|
||||||
|
|
||||||
|
|
||||||
class VariantTask(Generic[C, V, Q, A]):
|
class VariantTask(Generic[C, V, Q, A]):
|
||||||
|
must: FilterBuilder[C, V, Q, A]
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.must = FilterBuilder[C, V, Q, A]()
|
||||||
|
|||||||
33
test.py
Normal file
33
test.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
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")
|
||||||
Reference in New Issue
Block a user