diff --git a/__pycache__/test.cpython-313.pyc b/__pycache__/test.cpython-313.pyc new file mode 100644 index 0000000..c3d97e7 Binary files /dev/null and b/__pycache__/test.cpython-313.pyc differ diff --git a/example.py b/example.py index d830d5f..edfdcb3 100644 --- a/example.py +++ b/example.py @@ -1,54 +1,41 @@ -from enum import Enum -from typing import Generic +from modules.tag import Tag +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 + + tasks_on_third_topic + + tasks_on_forth_topic + + tasks_on_fifth_topic + + tasks_on_sixth_topic + + tasks_on_seventh_topic +) +vf = VariantFactory(number_of_tasks=7, task_pool=task_pool) -class TagCategory(str, Enum): - TOPIC = "topic" - DIFFICULTY = "difficulty" +_ = vf.task[0].must.include_tag("topic", "1") +_ = vf.task[1].must.include_tag("topic", "2") +_ = vf.task[2].must.include_tag("topic", "3") +_ = vf.task[3].must.include_tag("topic", "4") +_ = vf.task[4].must.include_tag("topic", "5") +_ = vf.task[5].must.include_tag("topic", "6") +_ = vf.task[6].must.include_tag("topic", "7") +variants = vf.generate_variants(number_of_variants=30) -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__, - ) - - -class AverageTask(MyTaskGenerator): - def generate(self): - return QuizTask( - "What is an average of 3, 4, 5 and 6?", - "4.5", - tags=[ - (TagCategory.TOPIC, TopicTag.AVERAGE), - ], - ) - - -class AverageTask1(MyTaskGenerator): - def generate(self): - return QuizTask( - "What is an average of 1, 2, 3 and 4?", - "2.5", - 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") +i = 0 +for variant in variants: + print(f"Variant {i + 1}:") + print(*[task.question for task in variant.tasks]) + i += 1 diff --git a/modules/tag/__init__.py b/modules/tag/__init__.py index 5a0bc6b..63e7e17 100644 --- a/modules/tag/__init__.py +++ b/modules/tag/__init__.py @@ -20,7 +20,6 @@ class Tag(Generic[C, V]): val: V -@dataclass class Tags(Generic[C, V]): """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]] = field(default_factory=dict) + _dict: dict[C, set[V]] def __init__(self, iter: Iterable[Tag[C, V]] | None = None): + self._dict = {} + if iter: for tag in iter: self._dict.setdefault(tag.cat, set()).add(tag.val) @@ -101,9 +102,7 @@ class Tags(Generic[C, V]): lines: list[str] = [] for category, values in self._dict.items(): cat_str = str(category.value if isinstance(category, Enum) else category) - val_strs = sorted( - [str(v.value if isinstance(v, Enum) else v for v in values)] - ) + val_strs = [str(v.value if isinstance(v, Enum) else v) for v in values] lines.append(f"{cat_str}: {', '.join(val_strs)}") return "\n".join(lines) @@ -113,6 +112,7 @@ class Tags(Generic[C, V]): Yields: Tag[C, V]: Each tag in the collection. """ + for category, values in self._dict.items(): for value in values: yield Tag(category, value) diff --git a/modules/tag/__pycache__/__init__.cpython-313.pyc b/modules/tag/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..e8973d3 Binary files /dev/null and b/modules/tag/__pycache__/__init__.cpython-313.pyc differ diff --git a/modules/task/__init__.py b/modules/task/__init__.py index 0bc683d..4659093 100644 --- a/modules/task/__init__.py +++ b/modules/task/__init__.py @@ -1,37 +1,42 @@ import uuid -from dataclasses import dataclass -from typing import Generic, override +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Generic, override from option import Option 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.utils import indent +if TYPE_CHECKING: + from modules.task.factory.metadata import QTaskFactoryMetadata + @dataclass class QTask(Generic[C, V, Q, A]): question: Q answer: A tags: Tags[C, V] - factory_metadata: Option[QTaskFactoryMetadata[C, V]] = Option[ - QTaskFactoryMetadata[C, V] + id: uuid.UUID + factory_metadata: Option["QTaskFactoryMetadata[C, V]"] = Option[ + "QTaskFactoryMetadata[C, V]" ].maybe(None) - id: uuid.UUID = uuid.uuid4() def __init__( self, question: Q, - answer: A, - tags: Tags[C, V] | list[Tag[C, V]] | None = None, + answer: A = None, + tags: Tags[C, V] | list[Tag[C, V]] | Tag[C, V] | None = None, ): self.question = question self.answer = answer + if isinstance(tags, Tag): + self.tags = Tags[C, V]([tags]) if isinstance(tags, list): self.tags = Tags[C, V](tags) elif isinstance(tags, Tags): self.tags = tags + self.id = uuid.uuid4() @override def __str__(self) -> str: diff --git a/modules/task/__pycache__/__init__.cpython-313.pyc b/modules/task/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..51f9135 Binary files /dev/null and b/modules/task/__pycache__/__init__.cpython-313.pyc differ diff --git a/modules/task/factory/__init__.py b/modules/task/factory/__init__.py index c06e4fa..edd97cf 100644 --- a/modules/task/factory/__init__.py +++ b/modules/task/factory/__init__.py @@ -9,7 +9,6 @@ from modules.task.factory.metadata import QTaskFactoryMetadata from modules.utils.types import A, C, Q, V -@runtime_checkable class QTaskFactory(ABC, Generic[C, V, Q, A]): id: uuid.UUID metadata: QTaskFactoryMetadata[C, V] diff --git a/modules/task/factory/__pycache__/__init__.cpython-313.pyc b/modules/task/factory/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..7457e8f Binary files /dev/null and b/modules/task/factory/__pycache__/__init__.cpython-313.pyc differ diff --git a/modules/task/factory/__pycache__/metadata.cpython-313.pyc b/modules/task/factory/__pycache__/metadata.cpython-313.pyc new file mode 100644 index 0000000..87c0ee2 Binary files /dev/null and b/modules/task/factory/__pycache__/metadata.cpython-313.pyc differ diff --git a/modules/task/factory/metadata.py b/modules/task/factory/metadata.py index 2fab93d..0137a2e 100644 --- a/modules/task/factory/metadata.py +++ b/modules/task/factory/metadata.py @@ -2,7 +2,7 @@ import uuid from dataclasses import dataclass, field from typing import Generic -from option import NONE, Option +from option import Option from modules.utils.types import C, V diff --git a/modules/utils/__pycache__/types.cpython-313.pyc b/modules/utils/__pycache__/types.cpython-313.pyc new file mode 100644 index 0000000..7de74e7 Binary files /dev/null and b/modules/utils/__pycache__/types.cpython-313.pyc differ diff --git a/modules/utils/__pycache__/utils.cpython-313.pyc b/modules/utils/__pycache__/utils.cpython-313.pyc index 1dfdd49..a50d8c3 100644 Binary files a/modules/utils/__pycache__/utils.cpython-313.pyc and b/modules/utils/__pycache__/utils.cpython-313.pyc differ diff --git a/modules/utils/utils.py b/modules/utils/utils.py index 50e3d76..16304ae 100644 --- a/modules/utils/utils.py +++ b/modules/utils/utils.py @@ -1,6 +1,11 @@ +from random import Random from typing import TypeVar def indent(text: str, spaces: int = 2) -> str: prefix = " " * spaces return "\n".join(prefix + line for line in text.splitlines()) + + +rnd = Random() +rnd.seed(42) diff --git a/modules/variant/__pycache__/__init__.cpython-313.pyc b/modules/variant/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..10a4291 Binary files /dev/null and b/modules/variant/__pycache__/__init__.cpython-313.pyc differ diff --git a/modules/variant_builder/__init__.py b/modules/variant_builder/__init__.py index 4302334..1b3dce2 100644 --- a/modules/variant_builder/__init__.py +++ b/modules/variant_builder/__init__.py @@ -1,16 +1,96 @@ -from dataclasses import dataclass from typing import Generic +from modules.task import QTask from modules.utils.types import A, C, Q, V 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_selector import QTaskSelector from modules.variant_builder.variant_set import QVariantSet +from modules.variant_builder.variant_task import VariantTask -@dataclass -class VariantBuilder(Generic[C, V, Q, A]): +class VariantFactory(Generic[C, V, Q, A]): task_pool: QTaskPool[C, V, Q, A] - previos_variants: QVariantSet[C, V, Q, A] - current_variant: QVariant[C, V, Q, A] + previous_variants: QVariantSet[C, V, Q, A] = QVariantSet() 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 diff --git a/modules/variant_builder/__pycache__/__init__.cpython-313.pyc b/modules/variant_builder/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..13eb2fc Binary files /dev/null and b/modules/variant_builder/__pycache__/__init__.cpython-313.pyc differ diff --git a/modules/variant_builder/__pycache__/context.cpython-313.pyc b/modules/variant_builder/__pycache__/context.cpython-313.pyc new file mode 100644 index 0000000..5de9e24 Binary files /dev/null and b/modules/variant_builder/__pycache__/context.cpython-313.pyc differ diff --git a/modules/variant_builder/__pycache__/default_task_selector.cpython-313.pyc b/modules/variant_builder/__pycache__/default_task_selector.cpython-313.pyc new file mode 100644 index 0000000..7867403 Binary files /dev/null and b/modules/variant_builder/__pycache__/default_task_selector.cpython-313.pyc differ diff --git a/modules/variant_builder/__pycache__/task_pool.cpython-313.pyc b/modules/variant_builder/__pycache__/task_pool.cpython-313.pyc new file mode 100644 index 0000000..b62855b Binary files /dev/null and b/modules/variant_builder/__pycache__/task_pool.cpython-313.pyc differ diff --git a/modules/variant_builder/__pycache__/task_selector.cpython-313.pyc b/modules/variant_builder/__pycache__/task_selector.cpython-313.pyc new file mode 100644 index 0000000..02bc048 Binary files /dev/null and b/modules/variant_builder/__pycache__/task_selector.cpython-313.pyc differ diff --git a/modules/variant_builder/__pycache__/variant_set.cpython-313.pyc b/modules/variant_builder/__pycache__/variant_set.cpython-313.pyc new file mode 100644 index 0000000..6cefaf1 Binary files /dev/null and b/modules/variant_builder/__pycache__/variant_set.cpython-313.pyc differ diff --git a/modules/variant_builder/__pycache__/variant_task.cpython-313.pyc b/modules/variant_builder/__pycache__/variant_task.cpython-313.pyc new file mode 100644 index 0000000..cdf9e49 Binary files /dev/null and b/modules/variant_builder/__pycache__/variant_task.cpython-313.pyc differ diff --git a/modules/variant_builder/context.py b/modules/variant_builder/context.py index 3f7c925..d4ec410 100644 --- a/modules/variant_builder/context.py +++ b/modules/variant_builder/context.py @@ -1,6 +1,7 @@ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import Generic +from modules.task import QTask from modules.utils.types import A, C, Q, V from modules.variant import QVariant from modules.variant_builder.task_pool import QTaskPool @@ -8,8 +9,9 @@ from modules.variant_builder.variant_set import QVariantSet @dataclass -class DynamicFilterCtx(Generic[C, V, Q, A]): +class DynamicCtx(Generic[C, V, Q, A]): task_pool: QTaskPool[C, V, Q, A] - previous_variants: QVariantSet[C, V, Q, A] - current_variant: QVariant[C, V, Q, A] - task_number: int + previous_variants: QVariantSet[C, V, Q, A] = field( + default_factory=QVariantSet[C, V, Q, A] + ) + current_variant_tasks: list[QTask[C, V, Q, A]] = field(default_factory=list) diff --git a/modules/variant_builder/default_task_selector.py b/modules/variant_builder/default_task_selector.py new file mode 100644 index 0000000..fe1a406 --- /dev/null +++ b/modules/variant_builder/default_task_selector.py @@ -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 diff --git a/modules/variant_builder/filters/__pycache__/__init__.cpython-313.pyc b/modules/variant_builder/filters/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..deae6c4 Binary files /dev/null and b/modules/variant_builder/filters/__pycache__/__init__.cpython-313.pyc differ diff --git a/modules/variant_builder/filters/__pycache__/builder.cpython-313.pyc b/modules/variant_builder/filters/__pycache__/builder.cpython-313.pyc new file mode 100644 index 0000000..bfb6877 Binary files /dev/null and b/modules/variant_builder/filters/__pycache__/builder.cpython-313.pyc differ diff --git a/modules/variant_builder/filters/__pycache__/types.cpython-313.pyc b/modules/variant_builder/filters/__pycache__/types.cpython-313.pyc new file mode 100644 index 0000000..f07dcb2 Binary files /dev/null and b/modules/variant_builder/filters/__pycache__/types.cpython-313.pyc differ diff --git a/modules/variant_builder/filters/builder.py b/modules/variant_builder/filters/builder.py index afd70d7..a1737bf 100644 --- a/modules/variant_builder/filters/builder.py +++ b/modules/variant_builder/filters/builder.py @@ -5,11 +5,8 @@ from typing import Callable, Generic, Self, overload, override from modules.tag import Tag, Tags from modules.utils.types import A, C, Q, V 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.composite import CompositeFilterDynamic 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_include_all_tags import ( MustIncludeAllTagsFilter, @@ -92,6 +89,19 @@ class FilterBuilder(Generic[C, V, Q, A]): ) -> Self: 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( self, filter: Callable[["FilterBuilder[C, V, Q, A]"], "FilterBuilder[C, V, Q, A]"], diff --git a/modules/variant_builder/filters/dynamic/__init__.py b/modules/variant_builder/filters/dynamic/__init__.py index dedf8c4..276e402 100644 --- a/modules/variant_builder/filters/dynamic/__init__.py +++ b/modules/variant_builder/filters/dynamic/__init__.py @@ -5,16 +5,15 @@ from typing import Generic, Protocol, override, runtime_checkable from modules.task import QTask from modules.utils.types import A, C, Q, V 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.variant_set import QVariantSet -@runtime_checkable class FilterDynamic(ABC, Generic[C, V, Q, A]): @abstractmethod 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: ... def __invert__(self) -> "FilterDynamic[C, V, Q, A]": @@ -33,7 +32,7 @@ class DynamicFilterNegator(FilterDynamic[C, V, Q, A]): @override 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: return not self.filter.check_if_satisfied(task, ctx) diff --git a/modules/variant_builder/filters/dynamic/__pycache__/__init__.cpython-313.pyc b/modules/variant_builder/filters/dynamic/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..e5b419a Binary files /dev/null and b/modules/variant_builder/filters/dynamic/__pycache__/__init__.cpython-313.pyc differ diff --git a/modules/variant_builder/filters/dynamic/__pycache__/composite.cpython-313.pyc b/modules/variant_builder/filters/dynamic/__pycache__/composite.cpython-313.pyc new file mode 100644 index 0000000..d420ddd Binary files /dev/null and b/modules/variant_builder/filters/dynamic/__pycache__/composite.cpython-313.pyc differ diff --git a/modules/variant_builder/filters/dynamic/composite.py b/modules/variant_builder/filters/dynamic/composite.py index 8c7213c..fe31b1e 100644 --- a/modules/variant_builder/filters/dynamic/composite.py +++ b/modules/variant_builder/filters/dynamic/composite.py @@ -3,7 +3,7 @@ from typing import override from modules.task import QTask 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 @@ -14,7 +14,7 @@ class CompositeFilterDynamic(FilterDynamic[C, V, Q, A]): @override 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: return all(filter.check_if_satisfied(task, ctx) for filter in self.filters) diff --git a/modules/variant_builder/filters/dynamic/must_not.py b/modules/variant_builder/filters/dynamic/must_not.py deleted file mode 100644 index 4b4ef62..0000000 --- a/modules/variant_builder/filters/dynamic/must_not.py +++ /dev/null @@ -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]): - diff --git a/modules/variant_builder/filters/static/CompositeFilterStatic b/modules/variant_builder/filters/static/CompositeFilterStatic deleted file mode 100644 index feac656..0000000 --- a/modules/variant_builder/filters/static/CompositeFilterStatic +++ /dev/null @@ -1 +0,0 @@ - e diff --git a/modules/variant_builder/filters/static/__pycache__/__init__.cpython-313.pyc b/modules/variant_builder/filters/static/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..f1afd8f Binary files /dev/null and b/modules/variant_builder/filters/static/__pycache__/__init__.cpython-313.pyc differ diff --git a/modules/variant_builder/filters/static/__pycache__/composite.cpython-313.pyc b/modules/variant_builder/filters/static/__pycache__/composite.cpython-313.pyc new file mode 100644 index 0000000..79be6cc Binary files /dev/null and b/modules/variant_builder/filters/static/__pycache__/composite.cpython-313.pyc differ diff --git a/modules/variant_builder/filters/static/__pycache__/must_be_one_of.cpython-313.pyc b/modules/variant_builder/filters/static/__pycache__/must_be_one_of.cpython-313.pyc new file mode 100644 index 0000000..c4fe061 Binary files /dev/null and b/modules/variant_builder/filters/static/__pycache__/must_be_one_of.cpython-313.pyc differ diff --git a/modules/variant_builder/filters/static/__pycache__/must_include_all_tags.cpython-313.pyc b/modules/variant_builder/filters/static/__pycache__/must_include_all_tags.cpython-313.pyc new file mode 100644 index 0000000..3ae27de Binary files /dev/null and b/modules/variant_builder/filters/static/__pycache__/must_include_all_tags.cpython-313.pyc differ diff --git a/modules/variant_builder/filters/static/__pycache__/must_include_any_tag.cpython-313.pyc b/modules/variant_builder/filters/static/__pycache__/must_include_any_tag.cpython-313.pyc new file mode 100644 index 0000000..a3b563b Binary files /dev/null and b/modules/variant_builder/filters/static/__pycache__/must_include_any_tag.cpython-313.pyc differ diff --git a/modules/variant_builder/filters/static/composite_filter.py b/modules/variant_builder/filters/static/composite_filter.py deleted file mode 100644 index 48c5a11..0000000 --- a/modules/variant_builder/filters/static/composite_filter.py +++ /dev/null @@ -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) diff --git a/modules/variant_builder/filters/static/must_not.py b/modules/variant_builder/filters/static/must_not.py deleted file mode 100644 index 4b0c503..0000000 --- a/modules/variant_builder/filters/static/must_not.py +++ /dev/null @@ -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 diff --git a/modules/variant_builder/task_pool.py b/modules/variant_builder/task_pool.py index a72ad4f..fb616b2 100644 --- a/modules/variant_builder/task_pool.py +++ b/modules/variant_builder/task_pool.py @@ -1,3 +1,4 @@ +import random from dataclasses import dataclass, field from typing import Generic @@ -5,6 +6,6 @@ from modules.task import QTask from modules.utils.types import A, C, Q, V -@dataclass(frozen=True) -class QTaskPool(Generic[C, V, Q, A]): - pool: list[QTask[C, V, Q, A]] +class QTaskPool(list[QTask[C, V, Q, A]], Generic[C, V, Q, A]): + def shuffle(self): + random.shuffle(self) diff --git a/modules/variant_builder/task_selector.py b/modules/variant_builder/task_selector.py index 0e66b2e..6b8d38f 100644 --- a/modules/variant_builder/task_selector.py +++ b/modules/variant_builder/task_selector.py @@ -1,13 +1,15 @@ +from abc import ABC, abstractmethod from dataclasses import dataclass -from typing import Protocol +from typing import Generic, Protocol from modules.task import QTask from modules.utils.types import A, C, Q, V +from modules.variant_builder.context import DynamicCtx from modules.variant_builder.task_pool import QTaskPool -@dataclass -class QTaskSelector(Protocol[C, V, Q, A]): - task_pool: QTaskPool[C, V, Q, A] - - def select(self, filtered_task_pool_indexes: list[int]) -> QTask[C, V, Q, A]: ... +class QTaskSelector(ABC, Generic[C, V, Q, A]): + @abstractmethod + def select( + self, filtered_tasks: list[QTask[C, V, Q, A]], ctx: DynamicCtx[C, V, Q, A] + ) -> QTask[C, V, Q, A]: ... diff --git a/modules/variant_builder/variant_set.py b/modules/variant_builder/variant_set.py index 817a4c1..9e7ec1a 100644 --- a/modules/variant_builder/variant_set.py +++ b/modules/variant_builder/variant_set.py @@ -6,9 +6,4 @@ from modules.variant import QVariant @dataclass -class QVariantSet(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 +class QVariantSet(list[QVariant[C, V, Q, A]], Generic[C, V, Q, A]): ... diff --git a/modules/variant_builder/variant_task.py b/modules/variant_builder/variant_task.py index 71046f5..2a8bd97 100644 --- a/modules/variant_builder/variant_task.py +++ b/modules/variant_builder/variant_task.py @@ -1,6 +1,15 @@ +from dataclasses import dataclass, field from typing import Generic +from option import Option + 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]): + must: FilterBuilder[C, V, Q, A] + + def __init__(self): + self.must = FilterBuilder[C, V, Q, A]() diff --git a/test.py b/test.py new file mode 100644 index 0000000..364809c --- /dev/null +++ b/test.py @@ -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")