danielutils 1.1.28__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- danielutils/__init__.py +50 -0
- danielutils/abstractions/__init__.py +3 -0
- danielutils/abstractions/db/__init__.py +10 -0
- danielutils/abstractions/db/database.py +206 -0
- danielutils/abstractions/db/database_definitions.py +219 -0
- danielutils/abstractions/db/database_exceptions.py +34 -0
- danielutils/abstractions/db/database_factory.py +70 -0
- danielutils/abstractions/db/database_initializer.py +261 -0
- danielutils/abstractions/db/database_models.py +213 -0
- danielutils/abstractions/db/dependencies.py +6 -0
- danielutils/abstractions/db/implementations/__init__.py +4 -0
- danielutils/abstractions/db/implementations/in_memory_database.py +365 -0
- danielutils/abstractions/db/implementations/persistent_in_memory_database.py +174 -0
- danielutils/abstractions/db/implementations/redis_database.py +403 -0
- danielutils/abstractions/db/implementations/sqlite_database.py +491 -0
- danielutils/abstractions/multiprogramming/__init__.py +3 -0
- danielutils/abstractions/multiprogramming/multi_id.py +26 -0
- danielutils/abstractions/multiprogramming/worker.py +104 -0
- danielutils/abstractions/multiprogramming/worker_pool.py +98 -0
- danielutils/abstractions/repl.py +125 -0
- danielutils/aliases.py +174 -0
- danielutils/async_/__init__.py +7 -0
- danielutils/async_/async_cmd.py +28 -0
- danielutils/async_/async_command.py +879 -0
- danielutils/async_/async_layered_command.py +125 -0
- danielutils/async_/async_retry_executor.py +86 -0
- danielutils/async_/async_worker_pool.py +110 -0
- danielutils/async_/time_strategy.py +73 -0
- danielutils/async_/utils.py +57 -0
- danielutils/better_builtins/__init__.py +3 -0
- danielutils/better_builtins/counter.py +48 -0
- danielutils/better_builtins/frange.py +266 -0
- danielutils/better_builtins/typed_builtins/__init__.py +4 -0
- danielutils/better_builtins/typed_builtins/factory.py +126 -0
- danielutils/better_builtins/typed_builtins/tdict.py +36 -0
- danielutils/better_builtins/typed_builtins/tlist.py +223 -0
- danielutils/better_builtins/typed_builtins/tset.py +67 -0
- danielutils/better_builtins/typed_builtins/ttuple.py +15 -0
- danielutils/colors.py +203 -0
- danielutils/context_managers/__init__.py +5 -0
- danielutils/context_managers/attr_context.py +40 -0
- danielutils/context_managers/multi_context.py +34 -0
- danielutils/context_managers/optional_context.py +33 -0
- danielutils/context_managers/state_context.py +25 -0
- danielutils/context_managers/temporary_file.py +108 -0
- danielutils/convenience.py +12 -0
- danielutils/conversions/__init__.py +2 -0
- danielutils/conversions/main_conversions.py +131 -0
- danielutils/conversions/specialized_conversions/__init__.py +2 -0
- danielutils/conversions/specialized_conversions/to_hex.py +25 -0
- danielutils/conversions/specialized_conversions/to_int.py +19 -0
- danielutils/custom_types.py +18 -0
- danielutils/data_structures/__init__.py +8 -0
- danielutils/data_structures/algorithms.py +52 -0
- danielutils/data_structures/comparer.py +47 -0
- danielutils/data_structures/default_dict.py +23 -0
- danielutils/data_structures/functions.py +35 -0
- danielutils/data_structures/graph/__init__.py +4 -0
- danielutils/data_structures/graph/binary_node.py +71 -0
- danielutils/data_structures/graph/graph.py +208 -0
- danielutils/data_structures/graph/multinode.py +101 -0
- danielutils/data_structures/graph/node.py +60 -0
- danielutils/data_structures/heap/__init__.py +3 -0
- danielutils/data_structures/heap/heap.py +119 -0
- danielutils/data_structures/heap/max_heap.py +18 -0
- danielutils/data_structures/heap/min_heap.py +18 -0
- danielutils/data_structures/queue/__init__.py +3 -0
- danielutils/data_structures/queue/atomic_queue.py +15 -0
- danielutils/data_structures/queue/priority_queue.py +110 -0
- danielutils/data_structures/queue/queue.py +92 -0
- danielutils/data_structures/stack.py +102 -0
- danielutils/data_structures/trees/__init__.py +2 -0
- danielutils/data_structures/trees/binary_syntax_tree.py +84 -0
- danielutils/data_structures/trees/binary_tree.py +63 -0
- danielutils/date.py +43 -0
- danielutils/date_time.py +23 -0
- danielutils/decorators/__init__.py +18 -0
- danielutils/decorators/atomic.py +45 -0
- danielutils/decorators/attach.py +61 -0
- danielutils/decorators/chain_decorators.py +39 -0
- danielutils/decorators/decorate_conditionally.py +32 -0
- danielutils/decorators/delay_call.py +47 -0
- danielutils/decorators/deprecate.py +46 -0
- danielutils/decorators/final.py +39 -0
- danielutils/decorators/limit_recursion.py +63 -0
- danielutils/decorators/memo.py +68 -0
- danielutils/decorators/normalize_decorator.py +55 -0
- danielutils/decorators/overload.py +204 -0
- danielutils/decorators/partially_implemented.py +32 -0
- danielutils/decorators/processify.py +74 -0
- danielutils/decorators/property.py +42 -0
- danielutils/decorators/singleton.py +59 -0
- danielutils/decorators/threadify.py +40 -0
- danielutils/decorators/timeout.py +82 -0
- danielutils/decorators/total_ordering.py +82 -0
- danielutils/decorators/validate.py +227 -0
- danielutils/exceptions.py +90 -0
- danielutils/functions/__init__.py +13 -0
- danielutils/functions/areoneof.py +47 -0
- danielutils/functions/check_foreach.py +39 -0
- danielutils/functions/factorial.py +24 -0
- danielutils/functions/flatten.py +33 -0
- danielutils/functions/foreach.py +20 -0
- danielutils/functions/isoftype.py +475 -0
- danielutils/functions/isoneof.py +71 -0
- danielutils/functions/multiloop.py +51 -0
- danielutils/functions/parallel_for.py +47 -0
- danielutils/functions/partition.py +45 -0
- danielutils/functions/powerset.py +31 -0
- danielutils/functions/subseteq.py +25 -0
- danielutils/functions/types_subseteq.py +51 -0
- danielutils/generators/__init__.py +3 -0
- danielutils/generators/conditional_generator.py +59 -0
- danielutils/generators/generator_from_stream.py +31 -0
- danielutils/generators/join_generators.py +109 -0
- danielutils/internet.py +92 -0
- danielutils/io_.py +451 -0
- danielutils/java/__init__.py +18 -0
- danielutils/java/interfaces/__init__.py +1 -0
- danielutils/java/interfaces/comparable.py +31 -0
- danielutils/java/java_interface.py +59 -0
- danielutils/logging_/__init__.py +2 -0
- danielutils/logging_/_impl/__init__.py +4 -0
- danielutils/logging_/_impl/builtin_impls/__init__.py +2 -0
- danielutils/logging_/_impl/builtin_impls/file_logger.py +35 -0
- danielutils/logging_/_impl/builtin_impls/print_logger.py +21 -0
- danielutils/logging_/_impl/log_level.py +13 -0
- danielutils/logging_/_impl/logger.py +109 -0
- danielutils/logging_/_impl/logger_strategy_impl_base.py +37 -0
- danielutils/logging_/utils.py +199 -0
- danielutils/lombok/__init__.py +1 -0
- danielutils/lombok/builder.py +68 -0
- danielutils/math_/__init__.py +2 -0
- danielutils/math_/constants.py +103 -0
- danielutils/math_/functions.py +34 -0
- danielutils/math_/math_print.py +52 -0
- danielutils/math_/math_symbols.py +125 -0
- danielutils/metaclasses/__init__.py +5 -0
- danielutils/metaclasses/atomic_class_meta.py +36 -0
- danielutils/metaclasses/implicit_data_deleter_meta.py +87 -0
- danielutils/metaclasses/instance_cache_meta.py +66 -0
- danielutils/metaclasses/interface.py +328 -0
- danielutils/metaclasses/overload_meta.py +48 -0
- danielutils/mock_/__init__.py +1 -0
- danielutils/mock_/mock_module.py +27 -0
- danielutils/path.py +55 -0
- danielutils/print_.py +123 -0
- danielutils/progress_bar/__init__.py +3 -0
- danielutils/progress_bar/ascii_progress_bar.py +161 -0
- danielutils/progress_bar/progress_bar.py +75 -0
- danielutils/progress_bar/progress_bar_pool.py +72 -0
- danielutils/protocols/__init__.py +3 -0
- danielutils/protocols/dictable.py +38 -0
- danielutils/protocols/evaluable.py +26 -0
- danielutils/protocols/serializable.py +51 -0
- danielutils/py.typed +0 -0
- danielutils/random_.py +47 -0
- danielutils/reflection/__init__.py +6 -0
- danielutils/reflection/declarations.py +288 -0
- danielutils/reflection/file/__init__.py +1 -0
- danielutils/reflection/file/file_reflection.py +85 -0
- danielutils/reflection/function/__init__.py +1 -0
- danielutils/reflection/function/function_reflections.py +152 -0
- danielutils/reflection/info_classes/__init__.py +6 -0
- danielutils/reflection/info_classes/argument_info.py +278 -0
- danielutils/reflection/info_classes/class_info.py +204 -0
- danielutils/reflection/info_classes/decorator_info.py +54 -0
- danielutils/reflection/info_classes/file_info.py +1180 -0
- danielutils/reflection/info_classes/folder_info.py +592 -0
- danielutils/reflection/info_classes/function_info.py +551 -0
- danielutils/reflection/info_classes/import_info.py +207 -0
- danielutils/reflection/interpreter/__init__.py +9 -0
- danielutils/reflection/interpreter/callstack.py +57 -0
- danielutils/reflection/interpreter/get_traceback.py +27 -0
- danielutils/reflection/interpreter/interpreter.py +41 -0
- danielutils/reflection/interpreter/is_debugging.py +13 -0
- danielutils/reflection/interpreter/os_.py +33 -0
- danielutils/reflection/interpreter/packages.py +33 -0
- danielutils/reflection/interpreter/python_version.py +33 -0
- danielutils/reflection/interpreter/signals.py +18 -0
- danielutils/reflection/interpreter/tracer.py +175 -0
- danielutils/reflection/module/__init__.py +3 -0
- danielutils/reflection/module/lazy_module.py +104 -0
- danielutils/reflection/module/module_reflections.py +34 -0
- danielutils/reflection/module/package_reflection.py +161 -0
- danielutils/retry_executor/__init__.py +3 -0
- danielutils/retry_executor/backoff_strategies/__init__.py +6 -0
- danielutils/retry_executor/backoff_strategies/constant_backoff.py +25 -0
- danielutils/retry_executor/backoff_strategies/exponential_backoff.py +26 -0
- danielutils/retry_executor/backoff_strategies/functional_backoff.py +29 -0
- danielutils/retry_executor/backoff_strategies/linear_backoff.py +23 -0
- danielutils/retry_executor/backoff_strategies/multiplicative_backoff.py +23 -0
- danielutils/retry_executor/backoff_strategies/no_backoff.py +17 -0
- danielutils/retry_executor/backoff_strategy.py +28 -0
- danielutils/retry_executor/retry_executor.py +55 -0
- danielutils/snippets/__init__.py +1 -0
- danielutils/snippets/try_get.py +28 -0
- danielutils/system/__init__.py +3 -0
- danielutils/system/independent.py +197 -0
- danielutils/system/layered_command.py +130 -0
- danielutils/system/windows/__init__.py +2 -0
- danielutils/system/windows/utils/__init__.py +1 -0
- danielutils/system/windows/utils/filetime.py +19 -0
- danielutils/system/windows/win32_ctime.py +183 -0
- danielutils/system/windows/windows.py +78 -0
- danielutils/testing/__init__.py +1 -0
- danielutils/testing/unittest_/__init__.py +2 -0
- danielutils/testing/unittest_/always_teardown_testcase.py +75 -0
- danielutils/testing/unittest_/auto_cwd_testcase.py +114 -0
- danielutils/text.py +169 -0
- danielutils/time.py +64 -0
- danielutils/tools/__init__.py +1 -0
- danielutils/tools/unittest_test_runner/__init__.py +9 -0
- danielutils/tools/unittest_test_runner/cli.py +70 -0
- danielutils/tools/unittest_test_runner/core/__init__.py +17 -0
- danielutils/tools/unittest_test_runner/core/discovery.py +322 -0
- danielutils/tools/unittest_test_runner/core/execution.py +244 -0
- danielutils/tools/unittest_test_runner/core/parser.py +163 -0
- danielutils/tools/unittest_test_runner/core/runner.py +561 -0
- danielutils/tools/unittest_test_runner/models.py +82 -0
- danielutils/university/__init__.py +7 -0
- danielutils/university/computability_and_complexity/__init__.py +2 -0
- danielutils/university/computability_and_complexity/discreate_finite_automaton.py +89 -0
- danielutils/university/computability_and_complexity/languages/__init__.py +0 -0
- danielutils/university/computability_and_complexity/languages/language.py +17 -0
- danielutils/university/computability_and_complexity/languages/sat.py +71 -0
- danielutils/university/computability_and_complexity/turing_machine.py +47 -0
- danielutils/university/databases/__init__.py +1 -0
- danielutils/university/databases/all.py +756 -0
- danielutils/university/image_proccesing/__init__.py +1 -0
- danielutils/university/image_proccesing/encoding/__init__.py +3 -0
- danielutils/university/image_proccesing/encoding/encoding.py +35 -0
- danielutils/university/image_proccesing/encoding/lossless/__init__.py +4 -0
- danielutils/university/image_proccesing/encoding/lossless/huffman.py +0 -0
- danielutils/university/image_proccesing/encoding/lossless/lossless_encoding.py +10 -0
- danielutils/university/image_proccesing/encoding/lossless/lzw.py +0 -0
- danielutils/university/image_proccesing/encoding/lossless/run_length.py +22 -0
- danielutils/university/image_proccesing/encoding/lossy/__init__.py +1 -0
- danielutils/university/image_proccesing/encoding/lossy/lossy_encoding.py +10 -0
- danielutils/university/image_proccesing/tansformations/__init__.py +0 -0
- danielutils/university/image_proccesing/tansformations/gaussian.py +0 -0
- danielutils/university/image_proccesing/tansformations/gradient.py +0 -0
- danielutils/university/image_proccesing/tansformations/hough.py +0 -0
- danielutils/university/image_proccesing/tansformations/laplacian.py +0 -0
- danielutils/university/image_proccesing/tansformations/transformation.py +11 -0
- danielutils/university/linear_algebra/__init__.py +1 -0
- danielutils/university/linear_algebra/matrix.py +404 -0
- danielutils/university/machine_learning/__init__.py +0 -0
- danielutils/university/machine_learning/activation_functions/__init__.py +2 -0
- danielutils/university/machine_learning/activation_functions/activation_function.py +11 -0
- danielutils/university/machine_learning/activation_functions/relu.py +11 -0
- danielutils/university/machine_learning/neuron.py +15 -0
- danielutils/university/oop/__init__.py +1 -0
- danielutils/university/oop/observer.py +31 -0
- danielutils/university/oop/strategy.py +9 -0
- danielutils/university/probability/__init__.py +7 -0
- danielutils/university/probability/conditional_variable/__init__.py +3 -0
- danielutils/university/probability/conditional_variable/conditional_variable.py +138 -0
- danielutils/university/probability/conditional_variable/continuous/__init__.py +0 -0
- danielutils/university/probability/conditional_variable/discrete/__init__.py +8 -0
- danielutils/university/probability/conditional_variable/discrete/bernoulli.py +38 -0
- danielutils/university/probability/conditional_variable/discrete/bernoulli_sum.py +31 -0
- danielutils/university/probability/conditional_variable/discrete/binomial.py +40 -0
- danielutils/university/probability/conditional_variable/discrete/conditional_from_discrete_probability_func.py +36 -0
- danielutils/university/probability/conditional_variable/discrete/discrete.py +42 -0
- danielutils/university/probability/conditional_variable/discrete/geometric.py +43 -0
- danielutils/university/probability/conditional_variable/discrete/poisson.py +32 -0
- danielutils/university/probability/conditional_variable/discrete/uniform.py +33 -0
- danielutils/university/probability/distributions.py +35 -0
- danielutils/university/probability/expressions/__init__.py +2 -0
- danielutils/university/probability/expressions/accumulation_expression.py +291 -0
- danielutils/university/probability/expressions/probability_expression.py +110 -0
- danielutils/university/probability/funcs/__init__.py +4 -0
- danielutils/university/probability/funcs/covariance.py +12 -0
- danielutils/university/probability/funcs/expected_value.py +41 -0
- danielutils/university/probability/funcs/probability_function.py +14 -0
- danielutils/university/probability/funcs/variance.py +17 -0
- danielutils/university/probability/operator.py +79 -0
- danielutils/university/probability/protocols.py +32 -0
- danielutils/university/probability/supp.py +101 -0
- danielutils/university/probability/transformation.py +44 -0
- danielutils/versioned_imports.py +31 -0
- danielutils-1.1.28.dist-info/METADATA +390 -0
- danielutils-1.1.28.dist-info/RECORD +287 -0
- danielutils-1.1.28.dist-info/WHEEL +5 -0
- danielutils-1.1.28.dist-info/licenses/LICENSE +21 -0
- danielutils-1.1.28.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,756 @@
|
|
|
1
|
+
from typing import Generator, Iterable, Optional, Union, List as List, Set as Set, Tuple as Tuple, Dict as Dict
|
|
2
|
+
from ...functions import powerset
|
|
3
|
+
from ...generators import generate_except
|
|
4
|
+
from ...data_structures import Queue
|
|
5
|
+
from ...reflection import get_python_version
|
|
6
|
+
|
|
7
|
+
if get_python_version() >= (3, 9):
|
|
8
|
+
from builtins import list as List, set as Set, tuple as Tuple, dict as Dict # type:ignore
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Attribute:
|
|
12
|
+
"""Attribute class as in the course
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
def create_many(cls, amount: int, offset: int = 0) -> List["Attribute"]:
|
|
17
|
+
"""Create multiple Attribute instances.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
amount (int): The number of Attribute instances to create.
|
|
21
|
+
offset (int, optional): The starting index. Defaults to 0.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
List[Attribute]: A list of Attribute instances.
|
|
25
|
+
"""
|
|
26
|
+
res = []
|
|
27
|
+
ABC = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
28
|
+
for i in range(offset, min(amount, len(ABC))):
|
|
29
|
+
res.append(Attribute(ABC[i]))
|
|
30
|
+
return res
|
|
31
|
+
|
|
32
|
+
def __init__(self, symbol: Optional[str] = None):
|
|
33
|
+
if symbol is None:
|
|
34
|
+
symbol = ""
|
|
35
|
+
self.symbol = "".join(sorted(symbol))
|
|
36
|
+
|
|
37
|
+
def __len__(self) -> int:
|
|
38
|
+
return len(self.symbol)
|
|
39
|
+
|
|
40
|
+
def __add__(self, other: "Attribute") -> "Attribute":
|
|
41
|
+
return Attribute(''.join(sorted(str("".join(list(set(self.symbol).union(set(other.symbol))))).upper())))
|
|
42
|
+
|
|
43
|
+
def __sub__(self, other: "Attribute") -> "Attribute":
|
|
44
|
+
res = self.symbol[:]
|
|
45
|
+
for s in other.symbol:
|
|
46
|
+
res = res.replace(s, "")
|
|
47
|
+
return Attribute(res)
|
|
48
|
+
|
|
49
|
+
def __lt__(self, other) -> int:
|
|
50
|
+
return self.symbol < other.symbol
|
|
51
|
+
|
|
52
|
+
def __contains__(self, other) -> bool:
|
|
53
|
+
if isinstance(other, Attribute):
|
|
54
|
+
return other.to_set().issubset(self.to_set())
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
def __eq__(self, other) -> bool:
|
|
58
|
+
if isinstance(other, Attribute):
|
|
59
|
+
return self.symbol == other.symbol
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
def __iter__(self) -> Generator["Attribute", None, None]:
|
|
63
|
+
for v in self.symbol:
|
|
64
|
+
yield Attribute(v)
|
|
65
|
+
|
|
66
|
+
def __hash__(self) -> int:
|
|
67
|
+
return hash(self.symbol)
|
|
68
|
+
|
|
69
|
+
def __str__(self) -> str:
|
|
70
|
+
return repr(self)
|
|
71
|
+
|
|
72
|
+
def __repr__(self) -> str:
|
|
73
|
+
return self.symbol
|
|
74
|
+
|
|
75
|
+
def __and__(self, other: "Attribute") -> "Attribute":
|
|
76
|
+
lst = list(
|
|
77
|
+
set(str(self)).intersection(set(str(other)))
|
|
78
|
+
)
|
|
79
|
+
return Attribute(''.join(lst))
|
|
80
|
+
|
|
81
|
+
def minimize(self, F: "FunctionalDependencyGroup") -> "Attribute":
|
|
82
|
+
"""week 8 page 22 slide 1
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
F (FunctionalDependencyGroup): Dependency Group
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Attribute: the minimization of the Attribute
|
|
89
|
+
"""
|
|
90
|
+
for X, Y in F.tuples():
|
|
91
|
+
if X.closure(F) == self:
|
|
92
|
+
return X
|
|
93
|
+
|
|
94
|
+
X = self.clone()
|
|
95
|
+
for A in self:
|
|
96
|
+
if A in (X - A).closure(F):
|
|
97
|
+
X -= A
|
|
98
|
+
|
|
99
|
+
return X
|
|
100
|
+
|
|
101
|
+
def closure(self, F: Iterable["FunctionDependency"]) -> "Attribute":
|
|
102
|
+
"""Compute the closure of the attribute under a set of functional dependencies.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
F (Iterable[FunctionDependency]): The set of functional dependencies.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Attribute: The closure of the attribute.
|
|
109
|
+
"""
|
|
110
|
+
X = self
|
|
111
|
+
|
|
112
|
+
# modified: algorithm from week 8
|
|
113
|
+
V = X.clone()
|
|
114
|
+
while True:
|
|
115
|
+
V_ = V.clone()
|
|
116
|
+
for dep in F:
|
|
117
|
+
Y, Z = dep.X, dep.Y
|
|
118
|
+
if Y in V:
|
|
119
|
+
if Z not in V:
|
|
120
|
+
V += Z
|
|
121
|
+
if V == V_:
|
|
122
|
+
break
|
|
123
|
+
return V
|
|
124
|
+
|
|
125
|
+
def update(self, other: "Attribute") -> "Attribute":
|
|
126
|
+
"""Update the attribute with the union of another attribute.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
other (Attribute): The other attribute to perform union with.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
Attribute: The updated attribute.
|
|
133
|
+
"""
|
|
134
|
+
self.symbol = str(''.join(
|
|
135
|
+
sorted(str("".join(list(
|
|
136
|
+
set(self.symbol).union(set(other.symbol))
|
|
137
|
+
))).upper())))
|
|
138
|
+
return self
|
|
139
|
+
|
|
140
|
+
def union(self, other: "Attribute") -> "Attribute":
|
|
141
|
+
"""Perform attribute union (self + other).
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
other (Attribute): The other attribute to perform union with.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Attribute: The result of the attribute union.
|
|
148
|
+
"""
|
|
149
|
+
return self + other
|
|
150
|
+
|
|
151
|
+
def intersection(self, other: "Attribute") -> "Attribute":
|
|
152
|
+
"""Perform attribute intersection (self & other).
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
other (Attribute): The other attribute to perform intersection with.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Attribute: The result of the attribute intersection.
|
|
159
|
+
"""
|
|
160
|
+
return self & other
|
|
161
|
+
|
|
162
|
+
def to_set(self) -> Set["Attribute"]:
|
|
163
|
+
"""Convert the attribute to a set of individual attributes.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Set[Attribute]: The set of individual attributes.
|
|
167
|
+
"""
|
|
168
|
+
return {Attribute(v) for v in self.symbol}
|
|
169
|
+
|
|
170
|
+
def clone(self) -> "Attribute":
|
|
171
|
+
"""Create a clone of the attribute.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
Attribute: The cloned attribute.
|
|
175
|
+
"""
|
|
176
|
+
return Attribute("".join(self.symbol[:]))
|
|
177
|
+
|
|
178
|
+
def is_empty(self) -> bool:
|
|
179
|
+
"""Check if the attribute is empty (has no symbols).
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
bool: True if the attribute is empty, otherwise False.
|
|
183
|
+
"""
|
|
184
|
+
return len(self) == 0
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class Relation:
|
|
188
|
+
"""Relation class as in the course
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
@classmethod
|
|
192
|
+
def from_strings(cls, lst: Iterable[str]) -> "Relation":
|
|
193
|
+
"""Create a Relation instance from a list of attribute symbols.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
lst (Iterable[str]): The list of attribute symbols.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Relation: The Relation instance created from the attribute symbols.
|
|
200
|
+
"""
|
|
201
|
+
return cls([Attribute(s) for s in lst])
|
|
202
|
+
|
|
203
|
+
@classmethod
|
|
204
|
+
def from_string(cls, s: str) -> "Relation":
|
|
205
|
+
"""Create a Relation instance from a space-separated string of attribute symbols.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
s (str): The space-separated string of attribute symbols.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
Relation: The Relation instance created from the attribute symbols.
|
|
212
|
+
"""
|
|
213
|
+
return Relation.from_strings(s.split())
|
|
214
|
+
|
|
215
|
+
def __init__(self, attributes: List[Attribute]):
|
|
216
|
+
self.attributes = attributes
|
|
217
|
+
|
|
218
|
+
def __contains__(self, attribute) -> bool:
|
|
219
|
+
if isinstance(attribute, Attribute):
|
|
220
|
+
return attribute in self.attributes
|
|
221
|
+
return False
|
|
222
|
+
|
|
223
|
+
def __eq__(self, other) -> bool:
|
|
224
|
+
if isinstance(other, Relation):
|
|
225
|
+
return self.to_attribute() == other.to_attribute()
|
|
226
|
+
return False
|
|
227
|
+
|
|
228
|
+
def __hash__(self) -> int:
|
|
229
|
+
res = 0
|
|
230
|
+
for A in self.to_attribute():
|
|
231
|
+
res += hash(A)
|
|
232
|
+
return res
|
|
233
|
+
|
|
234
|
+
def __str__(self) -> str:
|
|
235
|
+
return repr(self)
|
|
236
|
+
|
|
237
|
+
def __repr__(self) -> str:
|
|
238
|
+
return self.__class__.__name__ + str(self.attributes)
|
|
239
|
+
|
|
240
|
+
def __iter__(self) -> Generator[Attribute, None, None]:
|
|
241
|
+
yield from self.attributes
|
|
242
|
+
|
|
243
|
+
def __len__(self) -> int:
|
|
244
|
+
return len(self.attributes)
|
|
245
|
+
|
|
246
|
+
def to_attribute(self) -> Attribute:
|
|
247
|
+
"""Convert the Relation instance to a single Attribute.
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
Attribute: A single attribute that represents the Relation instance.
|
|
251
|
+
"""
|
|
252
|
+
res = self.attributes[0].clone()
|
|
253
|
+
for i in range(1, len(self.attributes)):
|
|
254
|
+
res += self.attributes[i]
|
|
255
|
+
# res.update(self.attributes[i])
|
|
256
|
+
return res
|
|
257
|
+
|
|
258
|
+
def is_decomposition_lossless(self, R: List["Relation"], F: "FunctionalDependencyGroup") -> bool:
|
|
259
|
+
"""Check if a decomposition is lossless according to a given functional dependency group.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
R (List[Relation]): The list of decomposed relations.
|
|
263
|
+
F (FunctionalDependencyGroup): The functional dependency group.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
bool: True if the decomposition is lossless, otherwise False.
|
|
267
|
+
"""
|
|
268
|
+
if len(R) == 2:
|
|
269
|
+
R1 = R[0].to_attribute()
|
|
270
|
+
R2 = R[1].to_attribute()
|
|
271
|
+
closure = (R1 & R2).closure(F)
|
|
272
|
+
if R1 in closure or R2 in closure:
|
|
273
|
+
return True
|
|
274
|
+
return False
|
|
275
|
+
raise NotImplementedError()
|
|
276
|
+
|
|
277
|
+
def is_decomposition_dependency_preserving(self, R: List["Relation"], F: "FunctionalDependencyGroup") -> bool:
|
|
278
|
+
"""Check if a decomposition is dependency-preserving according to a given functional dependency group.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
R (List[Relation]): The list of decomposed relations.
|
|
282
|
+
F (FunctionalDependencyGroup): The functional dependency group.
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
bool
|
|
286
|
+
"""
|
|
287
|
+
# week 10 page 2 slide 3
|
|
288
|
+
n = len(R)
|
|
289
|
+
for X, Y in F.tuples():
|
|
290
|
+
Z = X.clone()
|
|
291
|
+
Z_ = None
|
|
292
|
+
# TODO unclear what is supposed to happen
|
|
293
|
+
# while Z != Z_:
|
|
294
|
+
for _ in range(n):
|
|
295
|
+
for i in range(n):
|
|
296
|
+
Z = Z.union(
|
|
297
|
+
Z.intersection(R[i].to_attribute()).closure(F)
|
|
298
|
+
.intersection(R[i].to_attribute())
|
|
299
|
+
)
|
|
300
|
+
if Y not in Z:
|
|
301
|
+
return False
|
|
302
|
+
return True
|
|
303
|
+
|
|
304
|
+
def subsets(self) -> Generator[Attribute, None, None]:
|
|
305
|
+
"""Generate subsets of attributes in the Relation instance.
|
|
306
|
+
|
|
307
|
+
Yields:
|
|
308
|
+
Generator[Attribute, None, None]: An iterator over attribute subsets.
|
|
309
|
+
"""
|
|
310
|
+
for tup in generate_except(powerset(self), lambda index, _: index == 0):
|
|
311
|
+
res = tup[0]
|
|
312
|
+
for attr in tup[1:]:
|
|
313
|
+
res = res.union(attr)
|
|
314
|
+
yield res
|
|
315
|
+
|
|
316
|
+
def is_BCNF(self, F: "FunctionalDependencyGroup") -> bool:
|
|
317
|
+
"""Check if the Relation instance is in BCNF form according to a given functional dependency group.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
F (FunctionalDependencyGroup): The functional dependency group.
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
bool: True if the Relation instance is in BCNF, otherwise False.
|
|
324
|
+
"""
|
|
325
|
+
for f in F:
|
|
326
|
+
X, Y = f.tuple()
|
|
327
|
+
if not (f.is_trivial() or self.is_superkey(X, F)):
|
|
328
|
+
return False
|
|
329
|
+
return True
|
|
330
|
+
|
|
331
|
+
def is_3NF(self, F: "FunctionalDependencyGroup") -> bool:
|
|
332
|
+
"""Check if the Relation instance is in 3NF form according to a given functional dependency group.
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
F (FunctionalDependencyGroup): The functional dependency group.
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
bool: True if the Relation instance is in 3NF, otherwise False.
|
|
339
|
+
"""
|
|
340
|
+
|
|
341
|
+
def second_condition(X: Attribute, Y: Attribute) -> bool:
|
|
342
|
+
keys = self.find_all_keys(F)
|
|
343
|
+
|
|
344
|
+
def is_in_any_key(A: Attribute) -> bool:
|
|
345
|
+
for key in keys:
|
|
346
|
+
if A in key:
|
|
347
|
+
return True
|
|
348
|
+
return False
|
|
349
|
+
|
|
350
|
+
for A in Y:
|
|
351
|
+
if not (A in X or is_in_any_key(A)):
|
|
352
|
+
return False
|
|
353
|
+
return True
|
|
354
|
+
|
|
355
|
+
for X, Y in F.tuples():
|
|
356
|
+
if not (self.is_superkey(X, F) or second_condition(X, Y)):
|
|
357
|
+
return False
|
|
358
|
+
return True
|
|
359
|
+
|
|
360
|
+
def is_key(self, X: Attribute, F: "FunctionalDependencyGroup") -> bool:
|
|
361
|
+
"""Check if a given attribute is a key for the Relation instance.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
X (Attribute): The attribute to check.
|
|
365
|
+
F (FunctionalDependencyGroup): The functional dependency group.
|
|
366
|
+
|
|
367
|
+
Returns:
|
|
368
|
+
bool: True if the attribute is a key, otherwise False.
|
|
369
|
+
"""
|
|
370
|
+
if not self.is_superkey(X, F):
|
|
371
|
+
return False
|
|
372
|
+
R = self.to_attribute()
|
|
373
|
+
|
|
374
|
+
def subsets_of(X: Attribute):
|
|
375
|
+
for tup in generate_except(powerset(X), lambda _, v: len(v) in {0, len(X)}):
|
|
376
|
+
Y = tup[0]
|
|
377
|
+
for Y_ in tup[1:]:
|
|
378
|
+
Y.update(Y_)
|
|
379
|
+
yield Y
|
|
380
|
+
|
|
381
|
+
for Y in subsets_of(X):
|
|
382
|
+
if Y.closure(F) not in R:
|
|
383
|
+
return False
|
|
384
|
+
return True
|
|
385
|
+
|
|
386
|
+
def is_superkey(self, X: Attribute, F: "FunctionalDependencyGroup") -> bool:
|
|
387
|
+
"""Check if a given attribute is a superkey for the Relation instance.
|
|
388
|
+
|
|
389
|
+
Args:
|
|
390
|
+
X (Attribute): The attribute to check.
|
|
391
|
+
F (FunctionalDependencyGroup): The functional dependency group.
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
bool: True if the attribute is a superkey, otherwise False.
|
|
395
|
+
"""
|
|
396
|
+
return X.closure(F) == self.to_attribute()
|
|
397
|
+
|
|
398
|
+
def find_key(self, F: "FunctionalDependencyGroup") -> Attribute:
|
|
399
|
+
"""Find a key for the Relation instance.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
F (FunctionalDependencyGroup): The functional dependency group.
|
|
403
|
+
|
|
404
|
+
Returns:
|
|
405
|
+
Attribute: The key for the Relation instance.
|
|
406
|
+
"""
|
|
407
|
+
return self.to_attribute().minimize(F)
|
|
408
|
+
|
|
409
|
+
def find_all_keys(self, F: "FunctionalDependencyGroup") -> Set[Attribute]:
|
|
410
|
+
"""Find all keys for the Relation instance.
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
F (FunctionalDependencyGroup): The functional dependency group.
|
|
414
|
+
|
|
415
|
+
Returns:
|
|
416
|
+
Set[Attribute]: A set of all keys for the Relation instance.
|
|
417
|
+
"""
|
|
418
|
+
# week 9 page 1 slide 3
|
|
419
|
+
K: Attribute = self.find_key(F)
|
|
420
|
+
KeyQueue: Queue = Queue()
|
|
421
|
+
KeyQueue.push(K)
|
|
422
|
+
Keys: Set[Attribute] = set([K])
|
|
423
|
+
while not KeyQueue.is_empty():
|
|
424
|
+
K = KeyQueue.pop()
|
|
425
|
+
for X, Y in F.tuples():
|
|
426
|
+
if not Y.intersection(K).is_empty():
|
|
427
|
+
S = (K - Y).union(X)
|
|
428
|
+
|
|
429
|
+
for k in Keys:
|
|
430
|
+
if k in S:
|
|
431
|
+
break
|
|
432
|
+
else:
|
|
433
|
+
S_ = S.minimize(F)
|
|
434
|
+
KeyQueue.push(S_)
|
|
435
|
+
Keys.add(S_)
|
|
436
|
+
return Keys
|
|
437
|
+
|
|
438
|
+
def find_3NF_decomposition(self, F: "FunctionalDependencyGroup") -> List["Relation"]:
|
|
439
|
+
"""Find the 3NF decomposition of the Relation instance based on a given functional dependency group.
|
|
440
|
+
|
|
441
|
+
Args:
|
|
442
|
+
F (FunctionalDependencyGroup): The functional dependency group.
|
|
443
|
+
|
|
444
|
+
Returns:
|
|
445
|
+
List[Relation]: A list of decomposed relations in 3NF.
|
|
446
|
+
"""
|
|
447
|
+
# TODO add backtracking so this will be deterministic with the correct result
|
|
448
|
+
res = []
|
|
449
|
+
# 1
|
|
450
|
+
G = F.minimal_cover()
|
|
451
|
+
|
|
452
|
+
# 2
|
|
453
|
+
for f in sorted(G, key=lambda dep: (str(dep.tuple()[0]), str(dep.tuple()[1]))):
|
|
454
|
+
X, A = f.tuple()
|
|
455
|
+
res.append(X + A)
|
|
456
|
+
|
|
457
|
+
# 3
|
|
458
|
+
for decomp in res:
|
|
459
|
+
if self.is_key(decomp, F):
|
|
460
|
+
break
|
|
461
|
+
else:
|
|
462
|
+
res.append(self.find_key(F))
|
|
463
|
+
|
|
464
|
+
# 4
|
|
465
|
+
# TODO
|
|
466
|
+
return [Relation.from_string(attr.symbol) for attr in res]
|
|
467
|
+
|
|
468
|
+
def find_BCNF_decomposition(self, F: "FunctionalDependencyGroup") -> List["Relation"]:
|
|
469
|
+
"""Find the BCNF decomposition of the Relation instance based on a given functional dependency group.
|
|
470
|
+
week 10 page 16 slide 2
|
|
471
|
+
Args:
|
|
472
|
+
F (FunctionalDependencyGroup): The functional dependency group.
|
|
473
|
+
|
|
474
|
+
Returns:
|
|
475
|
+
List[Relation]: A list of decomposed relations in BCNF.
|
|
476
|
+
"""
|
|
477
|
+
|
|
478
|
+
def get_violation() -> Tuple[Attribute, Attribute]:
|
|
479
|
+
for f in F:
|
|
480
|
+
X, Y = f.tuple()
|
|
481
|
+
if not (f.is_trivial() or self.is_superkey(X, F)):
|
|
482
|
+
break
|
|
483
|
+
return X, Y
|
|
484
|
+
|
|
485
|
+
if self.is_BCNF(F):
|
|
486
|
+
return [self]
|
|
487
|
+
|
|
488
|
+
X, Y = get_violation()
|
|
489
|
+
closure = X.closure(F)
|
|
490
|
+
R1 = Relation(list(closure))
|
|
491
|
+
R2 = Relation(list(X.union(self.to_attribute() - closure)))
|
|
492
|
+
|
|
493
|
+
F_R1 = F.project_on(R1)
|
|
494
|
+
F_R2 = F.project_on(R2)
|
|
495
|
+
return R1.find_BCNF_decomposition(F_R1) + R2.find_BCNF_decomposition(F_R2)
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
class FunctionDependency:
|
|
499
|
+
"""FunctionDependency class as in the course
|
|
500
|
+
"""
|
|
501
|
+
|
|
502
|
+
@classmethod
|
|
503
|
+
def from_string(cls, s: str) -> "FunctionDependency":
|
|
504
|
+
"""Create a FunctionDependency instance from a string representation.
|
|
505
|
+
|
|
506
|
+
Args:
|
|
507
|
+
s (str): The string representation of the function dependency (e.g., "A->B").
|
|
508
|
+
|
|
509
|
+
Returns:
|
|
510
|
+
FunctionDependency: The FunctionDependency instance created from the string.
|
|
511
|
+
"""
|
|
512
|
+
key, value = s.split("->")
|
|
513
|
+
return cls(key, value)
|
|
514
|
+
|
|
515
|
+
@classmethod
|
|
516
|
+
def from_attributes(cls, key: Attribute, value: Attribute) -> "FunctionDependency":
|
|
517
|
+
"""Create a FunctionDependency instance from two Attribute instances.
|
|
518
|
+
|
|
519
|
+
Args:
|
|
520
|
+
key (Attribute): The key attribute.
|
|
521
|
+
value (Attribute): The value attribute.
|
|
522
|
+
|
|
523
|
+
Returns:
|
|
524
|
+
FunctionDependency: The FunctionDependency instance created from the attributes.
|
|
525
|
+
"""
|
|
526
|
+
return cls(key.symbol, value.symbol)
|
|
527
|
+
|
|
528
|
+
def __init__(self, key: Union[str, Attribute], value: Union[str, Attribute]):
|
|
529
|
+
if isinstance(key, str):
|
|
530
|
+
key = Attribute(key)
|
|
531
|
+
if isinstance(value, str):
|
|
532
|
+
value = Attribute(value)
|
|
533
|
+
self.X: Attribute = key.clone()
|
|
534
|
+
self.Y: Attribute = value.clone()
|
|
535
|
+
|
|
536
|
+
def __eq__(self, other) -> bool:
|
|
537
|
+
if isinstance(other, FunctionDependency):
|
|
538
|
+
return self.X == other.X and self.Y == other.Y
|
|
539
|
+
return False
|
|
540
|
+
|
|
541
|
+
def __str__(self) -> str:
|
|
542
|
+
return repr(self)
|
|
543
|
+
|
|
544
|
+
def __repr__(self) -> str:
|
|
545
|
+
return f"{self.X}->{self.Y}"
|
|
546
|
+
|
|
547
|
+
def __hash__(self) -> int:
|
|
548
|
+
return -hash(self.X) + hash(self.Y)
|
|
549
|
+
|
|
550
|
+
def __lt__(self, other: "FunctionDependency") -> int:
|
|
551
|
+
"""Compare two FunctionDependency instances.
|
|
552
|
+
|
|
553
|
+
Args:
|
|
554
|
+
other (FunctionDependency): The other FunctionDependency instance to compare.
|
|
555
|
+
|
|
556
|
+
Returns:
|
|
557
|
+
int: -1 if self is less than other, 0 if they are equal, 1 otherwise.
|
|
558
|
+
"""
|
|
559
|
+
a = self.X < other.X
|
|
560
|
+
if a != 0:
|
|
561
|
+
return a
|
|
562
|
+
return self.Y < other.Y
|
|
563
|
+
|
|
564
|
+
def __gt__(self, other) -> bool:
|
|
565
|
+
return not (self < other or self == other)
|
|
566
|
+
|
|
567
|
+
def is_trivial(self) -> bool:
|
|
568
|
+
"""Check if the FunctionDependency is trivial.
|
|
569
|
+
|
|
570
|
+
Returns:
|
|
571
|
+
bool: True if the FunctionDependency is trivial, otherwise False.
|
|
572
|
+
"""
|
|
573
|
+
return self.Y in self.X
|
|
574
|
+
|
|
575
|
+
def tuple(self) -> Tuple[Attribute, Attribute]:
|
|
576
|
+
"""Get the tuple representation of the FunctionDependency.
|
|
577
|
+
|
|
578
|
+
Returns:
|
|
579
|
+
Tuple[Attribute, Attribute]: The tuple representation (X, Y) of the FunctionDependency.
|
|
580
|
+
"""
|
|
581
|
+
return self.X, self.Y
|
|
582
|
+
|
|
583
|
+
def follows_from(self, s: Set["FunctionDependency"]) -> bool:
|
|
584
|
+
"""Check if the FunctionDependency follows from a set of other FunctionDependency instances.
|
|
585
|
+
|
|
586
|
+
Args:
|
|
587
|
+
s (Set[FunctionDependency]): The set of other FunctionDependency instances.
|
|
588
|
+
|
|
589
|
+
Returns:
|
|
590
|
+
bool: True if the FunctionDependency follows from the set, otherwise False.
|
|
591
|
+
"""
|
|
592
|
+
if self in s:
|
|
593
|
+
s.remove(self)
|
|
594
|
+
|
|
595
|
+
return self.Y in self.X.closure(s)
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
class FunctionalDependencyGroup:
|
|
599
|
+
"""FunctionalDependencyGroup class as in the course
|
|
600
|
+
"""
|
|
601
|
+
|
|
602
|
+
@classmethod
|
|
603
|
+
def from_dict(cls, dct: Dict[str, str]) -> "FunctionalDependencyGroup":
|
|
604
|
+
"""Create a FunctionalDependencyGroup instance from a dictionary.
|
|
605
|
+
|
|
606
|
+
Args:
|
|
607
|
+
dct (Dict[str, str]): The dictionary representing the functional dependencies.
|
|
608
|
+
|
|
609
|
+
Returns:
|
|
610
|
+
FunctionalDependencyGroup: The FunctionalDependencyGroup instance created from the dictionary.
|
|
611
|
+
"""
|
|
612
|
+
return cls([FunctionDependency(k, v) for k, v in dct.items()])
|
|
613
|
+
|
|
614
|
+
def __init__(self, dependencies: Iterable[FunctionDependency]):
|
|
615
|
+
self.dct: Dict[Attribute, Attribute] = {}
|
|
616
|
+
for dep in dependencies:
|
|
617
|
+
X, Y = dep.tuple()
|
|
618
|
+
if X not in self.dct:
|
|
619
|
+
self.dct[X] = Y
|
|
620
|
+
self.dct[X] += Y
|
|
621
|
+
|
|
622
|
+
def __iter__(self) -> Generator[FunctionDependency, None, None]:
|
|
623
|
+
for k, v in self.dct.items():
|
|
624
|
+
yield FunctionDependency.from_attributes(k, v)
|
|
625
|
+
|
|
626
|
+
def __str__(self) -> str:
|
|
627
|
+
return repr(self)
|
|
628
|
+
|
|
629
|
+
def __contains__(self, key: Attribute) -> bool:
|
|
630
|
+
return key in self.dct
|
|
631
|
+
|
|
632
|
+
def __getitem__(self, key: Attribute) -> Attribute:
|
|
633
|
+
return self.dct[key]
|
|
634
|
+
|
|
635
|
+
def __repr__(self) -> str:
|
|
636
|
+
res = "{ "
|
|
637
|
+
res += ", ".join(f"{k}->{v}" for k, v in self.dct.items())
|
|
638
|
+
res += " }"
|
|
639
|
+
return res
|
|
640
|
+
|
|
641
|
+
def __len__(self) -> int:
|
|
642
|
+
return len(self.dct)
|
|
643
|
+
|
|
644
|
+
def add(self, f: FunctionDependency) -> "FunctionalDependencyGroup":
|
|
645
|
+
"""Add a new FunctionDependency to the FunctionalDependencyGroup.
|
|
646
|
+
|
|
647
|
+
Args:
|
|
648
|
+
f (FunctionDependency): The FunctionDependency to add.
|
|
649
|
+
|
|
650
|
+
Returns:
|
|
651
|
+
FunctionalDependencyGroup: The modified FunctionalDependencyGroup.
|
|
652
|
+
"""
|
|
653
|
+
X, Y = f.tuple()
|
|
654
|
+
self.dct[X] = Y
|
|
655
|
+
return self
|
|
656
|
+
|
|
657
|
+
def minimal_cover(self) -> List[FunctionDependency]:
|
|
658
|
+
"""week 10 page 5 slide 6
|
|
659
|
+
|
|
660
|
+
Returns:
|
|
661
|
+
list[FunctionDependency]: result of minimal cover
|
|
662
|
+
"""
|
|
663
|
+
G: Set[FunctionDependency] = set()
|
|
664
|
+
for X, Y in self.tuples():
|
|
665
|
+
for A in Y:
|
|
666
|
+
G.add(FunctionDependency.from_attributes(X, A))
|
|
667
|
+
|
|
668
|
+
# minimal_g = set(range(len(G)+1))
|
|
669
|
+
|
|
670
|
+
# def backtracking_helper(G__: set, excluded: set):
|
|
671
|
+
# nonlocal minimal_g
|
|
672
|
+
# for f in set(G):
|
|
673
|
+
# X, A = f.tuple()
|
|
674
|
+
# if len(X) > 1:
|
|
675
|
+
# for B in X:
|
|
676
|
+
# if X-B not in excluded:
|
|
677
|
+
# if A in (X-B).closure(self):
|
|
678
|
+
# excluded.add(X-B)
|
|
679
|
+
# backtracking_helper(set(), excluded)
|
|
680
|
+
# X -= B
|
|
681
|
+
# excluded.remove(X-B)
|
|
682
|
+
# G__.add(FunctionDependency.from_attributes(X, A))
|
|
683
|
+
# if len(G__) < len(minimal_g):
|
|
684
|
+
# minimal_g = G__
|
|
685
|
+
|
|
686
|
+
G_: set = set()
|
|
687
|
+
for f in sorted(G, key=lambda dep: (str(dep.tuple()[0]), str(dep.tuple()[1]))):
|
|
688
|
+
X, A = f.tuple()
|
|
689
|
+
if len(X) > 1:
|
|
690
|
+
for B in X:
|
|
691
|
+
if A in (X - B).closure(self):
|
|
692
|
+
X -= B
|
|
693
|
+
G_.add(FunctionDependency.from_attributes(X, A))
|
|
694
|
+
G = set(G_)
|
|
695
|
+
del G_
|
|
696
|
+
minimal_g: Set[FunctionDependency] = set()
|
|
697
|
+
|
|
698
|
+
def backtracking_helper(G: Set[FunctionDependency], excluded: set):
|
|
699
|
+
nonlocal minimal_g
|
|
700
|
+
OG = set(G)
|
|
701
|
+
for f in sorted(OG, key=lambda dep: (str(dep.tuple()[0]), str(dep.tuple()[1]))):
|
|
702
|
+
if f not in excluded:
|
|
703
|
+
if f.follows_from(set(G)):
|
|
704
|
+
excluded.add(f)
|
|
705
|
+
backtracking_helper(set(OG), excluded)
|
|
706
|
+
G.remove(f)
|
|
707
|
+
excluded.remove(f)
|
|
708
|
+
if not minimal_g or len(G) < len(minimal_g):
|
|
709
|
+
minimal_g = set(G)
|
|
710
|
+
|
|
711
|
+
backtracking_helper(G, set())
|
|
712
|
+
# for f in set(G):
|
|
713
|
+
# if f.follows_from(set(G)):
|
|
714
|
+
# G.remove(f)
|
|
715
|
+
|
|
716
|
+
return sorted(minimal_g, key=lambda dep: (str(dep.tuple()[0]), str(dep.tuple()[1])))
|
|
717
|
+
|
|
718
|
+
def tuples(self) -> Generator[Tuple[Attribute, Attribute], None, None]:
|
|
719
|
+
"""Generate tuples (X, Y) of functional dependencies in the FunctionalDependencyGroup.
|
|
720
|
+
|
|
721
|
+
Yields:
|
|
722
|
+
Generator[Tuple[Attribute, Attribute], None, None]: Tuples (X, Y) of functional dependencies.
|
|
723
|
+
"""
|
|
724
|
+
for dep in self:
|
|
725
|
+
yield dep.tuple()
|
|
726
|
+
|
|
727
|
+
def to_set(self) -> Set[Attribute]:
|
|
728
|
+
"""Convert the FunctionalDependencyGroup to a set of attributes.
|
|
729
|
+
|
|
730
|
+
Returns:
|
|
731
|
+
Set[Attribute]: The set of attributes in the FunctionalDependencyGroup.
|
|
732
|
+
"""
|
|
733
|
+
return set(self.dct.keys()).union(set(self.dct.values()))
|
|
734
|
+
|
|
735
|
+
def project_on(self, R: Relation) -> "FunctionalDependencyGroup":
|
|
736
|
+
"""week 10 page 4 slide 3
|
|
737
|
+
|
|
738
|
+
Args:
|
|
739
|
+
R (Relation): relation to project ontro
|
|
740
|
+
|
|
741
|
+
Returns:
|
|
742
|
+
FunctionalDependencyGroup: projection group
|
|
743
|
+
"""
|
|
744
|
+
Ri = R.to_attribute()
|
|
745
|
+
G = FunctionalDependencyGroup([])
|
|
746
|
+
for X in R.subsets():
|
|
747
|
+
G.add(FunctionDependency(X, X.closure(self) & Ri))
|
|
748
|
+
return G
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
__all__ = [
|
|
752
|
+
"Attribute",
|
|
753
|
+
"Relation",
|
|
754
|
+
"FunctionDependency",
|
|
755
|
+
"FunctionalDependencyGroup"
|
|
756
|
+
]
|