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.
Files changed (287) hide show
  1. danielutils/__init__.py +50 -0
  2. danielutils/abstractions/__init__.py +3 -0
  3. danielutils/abstractions/db/__init__.py +10 -0
  4. danielutils/abstractions/db/database.py +206 -0
  5. danielutils/abstractions/db/database_definitions.py +219 -0
  6. danielutils/abstractions/db/database_exceptions.py +34 -0
  7. danielutils/abstractions/db/database_factory.py +70 -0
  8. danielutils/abstractions/db/database_initializer.py +261 -0
  9. danielutils/abstractions/db/database_models.py +213 -0
  10. danielutils/abstractions/db/dependencies.py +6 -0
  11. danielutils/abstractions/db/implementations/__init__.py +4 -0
  12. danielutils/abstractions/db/implementations/in_memory_database.py +365 -0
  13. danielutils/abstractions/db/implementations/persistent_in_memory_database.py +174 -0
  14. danielutils/abstractions/db/implementations/redis_database.py +403 -0
  15. danielutils/abstractions/db/implementations/sqlite_database.py +491 -0
  16. danielutils/abstractions/multiprogramming/__init__.py +3 -0
  17. danielutils/abstractions/multiprogramming/multi_id.py +26 -0
  18. danielutils/abstractions/multiprogramming/worker.py +104 -0
  19. danielutils/abstractions/multiprogramming/worker_pool.py +98 -0
  20. danielutils/abstractions/repl.py +125 -0
  21. danielutils/aliases.py +174 -0
  22. danielutils/async_/__init__.py +7 -0
  23. danielutils/async_/async_cmd.py +28 -0
  24. danielutils/async_/async_command.py +879 -0
  25. danielutils/async_/async_layered_command.py +125 -0
  26. danielutils/async_/async_retry_executor.py +86 -0
  27. danielutils/async_/async_worker_pool.py +110 -0
  28. danielutils/async_/time_strategy.py +73 -0
  29. danielutils/async_/utils.py +57 -0
  30. danielutils/better_builtins/__init__.py +3 -0
  31. danielutils/better_builtins/counter.py +48 -0
  32. danielutils/better_builtins/frange.py +266 -0
  33. danielutils/better_builtins/typed_builtins/__init__.py +4 -0
  34. danielutils/better_builtins/typed_builtins/factory.py +126 -0
  35. danielutils/better_builtins/typed_builtins/tdict.py +36 -0
  36. danielutils/better_builtins/typed_builtins/tlist.py +223 -0
  37. danielutils/better_builtins/typed_builtins/tset.py +67 -0
  38. danielutils/better_builtins/typed_builtins/ttuple.py +15 -0
  39. danielutils/colors.py +203 -0
  40. danielutils/context_managers/__init__.py +5 -0
  41. danielutils/context_managers/attr_context.py +40 -0
  42. danielutils/context_managers/multi_context.py +34 -0
  43. danielutils/context_managers/optional_context.py +33 -0
  44. danielutils/context_managers/state_context.py +25 -0
  45. danielutils/context_managers/temporary_file.py +108 -0
  46. danielutils/convenience.py +12 -0
  47. danielutils/conversions/__init__.py +2 -0
  48. danielutils/conversions/main_conversions.py +131 -0
  49. danielutils/conversions/specialized_conversions/__init__.py +2 -0
  50. danielutils/conversions/specialized_conversions/to_hex.py +25 -0
  51. danielutils/conversions/specialized_conversions/to_int.py +19 -0
  52. danielutils/custom_types.py +18 -0
  53. danielutils/data_structures/__init__.py +8 -0
  54. danielutils/data_structures/algorithms.py +52 -0
  55. danielutils/data_structures/comparer.py +47 -0
  56. danielutils/data_structures/default_dict.py +23 -0
  57. danielutils/data_structures/functions.py +35 -0
  58. danielutils/data_structures/graph/__init__.py +4 -0
  59. danielutils/data_structures/graph/binary_node.py +71 -0
  60. danielutils/data_structures/graph/graph.py +208 -0
  61. danielutils/data_structures/graph/multinode.py +101 -0
  62. danielutils/data_structures/graph/node.py +60 -0
  63. danielutils/data_structures/heap/__init__.py +3 -0
  64. danielutils/data_structures/heap/heap.py +119 -0
  65. danielutils/data_structures/heap/max_heap.py +18 -0
  66. danielutils/data_structures/heap/min_heap.py +18 -0
  67. danielutils/data_structures/queue/__init__.py +3 -0
  68. danielutils/data_structures/queue/atomic_queue.py +15 -0
  69. danielutils/data_structures/queue/priority_queue.py +110 -0
  70. danielutils/data_structures/queue/queue.py +92 -0
  71. danielutils/data_structures/stack.py +102 -0
  72. danielutils/data_structures/trees/__init__.py +2 -0
  73. danielutils/data_structures/trees/binary_syntax_tree.py +84 -0
  74. danielutils/data_structures/trees/binary_tree.py +63 -0
  75. danielutils/date.py +43 -0
  76. danielutils/date_time.py +23 -0
  77. danielutils/decorators/__init__.py +18 -0
  78. danielutils/decorators/atomic.py +45 -0
  79. danielutils/decorators/attach.py +61 -0
  80. danielutils/decorators/chain_decorators.py +39 -0
  81. danielutils/decorators/decorate_conditionally.py +32 -0
  82. danielutils/decorators/delay_call.py +47 -0
  83. danielutils/decorators/deprecate.py +46 -0
  84. danielutils/decorators/final.py +39 -0
  85. danielutils/decorators/limit_recursion.py +63 -0
  86. danielutils/decorators/memo.py +68 -0
  87. danielutils/decorators/normalize_decorator.py +55 -0
  88. danielutils/decorators/overload.py +204 -0
  89. danielutils/decorators/partially_implemented.py +32 -0
  90. danielutils/decorators/processify.py +74 -0
  91. danielutils/decorators/property.py +42 -0
  92. danielutils/decorators/singleton.py +59 -0
  93. danielutils/decorators/threadify.py +40 -0
  94. danielutils/decorators/timeout.py +82 -0
  95. danielutils/decorators/total_ordering.py +82 -0
  96. danielutils/decorators/validate.py +227 -0
  97. danielutils/exceptions.py +90 -0
  98. danielutils/functions/__init__.py +13 -0
  99. danielutils/functions/areoneof.py +47 -0
  100. danielutils/functions/check_foreach.py +39 -0
  101. danielutils/functions/factorial.py +24 -0
  102. danielutils/functions/flatten.py +33 -0
  103. danielutils/functions/foreach.py +20 -0
  104. danielutils/functions/isoftype.py +475 -0
  105. danielutils/functions/isoneof.py +71 -0
  106. danielutils/functions/multiloop.py +51 -0
  107. danielutils/functions/parallel_for.py +47 -0
  108. danielutils/functions/partition.py +45 -0
  109. danielutils/functions/powerset.py +31 -0
  110. danielutils/functions/subseteq.py +25 -0
  111. danielutils/functions/types_subseteq.py +51 -0
  112. danielutils/generators/__init__.py +3 -0
  113. danielutils/generators/conditional_generator.py +59 -0
  114. danielutils/generators/generator_from_stream.py +31 -0
  115. danielutils/generators/join_generators.py +109 -0
  116. danielutils/internet.py +92 -0
  117. danielutils/io_.py +451 -0
  118. danielutils/java/__init__.py +18 -0
  119. danielutils/java/interfaces/__init__.py +1 -0
  120. danielutils/java/interfaces/comparable.py +31 -0
  121. danielutils/java/java_interface.py +59 -0
  122. danielutils/logging_/__init__.py +2 -0
  123. danielutils/logging_/_impl/__init__.py +4 -0
  124. danielutils/logging_/_impl/builtin_impls/__init__.py +2 -0
  125. danielutils/logging_/_impl/builtin_impls/file_logger.py +35 -0
  126. danielutils/logging_/_impl/builtin_impls/print_logger.py +21 -0
  127. danielutils/logging_/_impl/log_level.py +13 -0
  128. danielutils/logging_/_impl/logger.py +109 -0
  129. danielutils/logging_/_impl/logger_strategy_impl_base.py +37 -0
  130. danielutils/logging_/utils.py +199 -0
  131. danielutils/lombok/__init__.py +1 -0
  132. danielutils/lombok/builder.py +68 -0
  133. danielutils/math_/__init__.py +2 -0
  134. danielutils/math_/constants.py +103 -0
  135. danielutils/math_/functions.py +34 -0
  136. danielutils/math_/math_print.py +52 -0
  137. danielutils/math_/math_symbols.py +125 -0
  138. danielutils/metaclasses/__init__.py +5 -0
  139. danielutils/metaclasses/atomic_class_meta.py +36 -0
  140. danielutils/metaclasses/implicit_data_deleter_meta.py +87 -0
  141. danielutils/metaclasses/instance_cache_meta.py +66 -0
  142. danielutils/metaclasses/interface.py +328 -0
  143. danielutils/metaclasses/overload_meta.py +48 -0
  144. danielutils/mock_/__init__.py +1 -0
  145. danielutils/mock_/mock_module.py +27 -0
  146. danielutils/path.py +55 -0
  147. danielutils/print_.py +123 -0
  148. danielutils/progress_bar/__init__.py +3 -0
  149. danielutils/progress_bar/ascii_progress_bar.py +161 -0
  150. danielutils/progress_bar/progress_bar.py +75 -0
  151. danielutils/progress_bar/progress_bar_pool.py +72 -0
  152. danielutils/protocols/__init__.py +3 -0
  153. danielutils/protocols/dictable.py +38 -0
  154. danielutils/protocols/evaluable.py +26 -0
  155. danielutils/protocols/serializable.py +51 -0
  156. danielutils/py.typed +0 -0
  157. danielutils/random_.py +47 -0
  158. danielutils/reflection/__init__.py +6 -0
  159. danielutils/reflection/declarations.py +288 -0
  160. danielutils/reflection/file/__init__.py +1 -0
  161. danielutils/reflection/file/file_reflection.py +85 -0
  162. danielutils/reflection/function/__init__.py +1 -0
  163. danielutils/reflection/function/function_reflections.py +152 -0
  164. danielutils/reflection/info_classes/__init__.py +6 -0
  165. danielutils/reflection/info_classes/argument_info.py +278 -0
  166. danielutils/reflection/info_classes/class_info.py +204 -0
  167. danielutils/reflection/info_classes/decorator_info.py +54 -0
  168. danielutils/reflection/info_classes/file_info.py +1180 -0
  169. danielutils/reflection/info_classes/folder_info.py +592 -0
  170. danielutils/reflection/info_classes/function_info.py +551 -0
  171. danielutils/reflection/info_classes/import_info.py +207 -0
  172. danielutils/reflection/interpreter/__init__.py +9 -0
  173. danielutils/reflection/interpreter/callstack.py +57 -0
  174. danielutils/reflection/interpreter/get_traceback.py +27 -0
  175. danielutils/reflection/interpreter/interpreter.py +41 -0
  176. danielutils/reflection/interpreter/is_debugging.py +13 -0
  177. danielutils/reflection/interpreter/os_.py +33 -0
  178. danielutils/reflection/interpreter/packages.py +33 -0
  179. danielutils/reflection/interpreter/python_version.py +33 -0
  180. danielutils/reflection/interpreter/signals.py +18 -0
  181. danielutils/reflection/interpreter/tracer.py +175 -0
  182. danielutils/reflection/module/__init__.py +3 -0
  183. danielutils/reflection/module/lazy_module.py +104 -0
  184. danielutils/reflection/module/module_reflections.py +34 -0
  185. danielutils/reflection/module/package_reflection.py +161 -0
  186. danielutils/retry_executor/__init__.py +3 -0
  187. danielutils/retry_executor/backoff_strategies/__init__.py +6 -0
  188. danielutils/retry_executor/backoff_strategies/constant_backoff.py +25 -0
  189. danielutils/retry_executor/backoff_strategies/exponential_backoff.py +26 -0
  190. danielutils/retry_executor/backoff_strategies/functional_backoff.py +29 -0
  191. danielutils/retry_executor/backoff_strategies/linear_backoff.py +23 -0
  192. danielutils/retry_executor/backoff_strategies/multiplicative_backoff.py +23 -0
  193. danielutils/retry_executor/backoff_strategies/no_backoff.py +17 -0
  194. danielutils/retry_executor/backoff_strategy.py +28 -0
  195. danielutils/retry_executor/retry_executor.py +55 -0
  196. danielutils/snippets/__init__.py +1 -0
  197. danielutils/snippets/try_get.py +28 -0
  198. danielutils/system/__init__.py +3 -0
  199. danielutils/system/independent.py +197 -0
  200. danielutils/system/layered_command.py +130 -0
  201. danielutils/system/windows/__init__.py +2 -0
  202. danielutils/system/windows/utils/__init__.py +1 -0
  203. danielutils/system/windows/utils/filetime.py +19 -0
  204. danielutils/system/windows/win32_ctime.py +183 -0
  205. danielutils/system/windows/windows.py +78 -0
  206. danielutils/testing/__init__.py +1 -0
  207. danielutils/testing/unittest_/__init__.py +2 -0
  208. danielutils/testing/unittest_/always_teardown_testcase.py +75 -0
  209. danielutils/testing/unittest_/auto_cwd_testcase.py +114 -0
  210. danielutils/text.py +169 -0
  211. danielutils/time.py +64 -0
  212. danielutils/tools/__init__.py +1 -0
  213. danielutils/tools/unittest_test_runner/__init__.py +9 -0
  214. danielutils/tools/unittest_test_runner/cli.py +70 -0
  215. danielutils/tools/unittest_test_runner/core/__init__.py +17 -0
  216. danielutils/tools/unittest_test_runner/core/discovery.py +322 -0
  217. danielutils/tools/unittest_test_runner/core/execution.py +244 -0
  218. danielutils/tools/unittest_test_runner/core/parser.py +163 -0
  219. danielutils/tools/unittest_test_runner/core/runner.py +561 -0
  220. danielutils/tools/unittest_test_runner/models.py +82 -0
  221. danielutils/university/__init__.py +7 -0
  222. danielutils/university/computability_and_complexity/__init__.py +2 -0
  223. danielutils/university/computability_and_complexity/discreate_finite_automaton.py +89 -0
  224. danielutils/university/computability_and_complexity/languages/__init__.py +0 -0
  225. danielutils/university/computability_and_complexity/languages/language.py +17 -0
  226. danielutils/university/computability_and_complexity/languages/sat.py +71 -0
  227. danielutils/university/computability_and_complexity/turing_machine.py +47 -0
  228. danielutils/university/databases/__init__.py +1 -0
  229. danielutils/university/databases/all.py +756 -0
  230. danielutils/university/image_proccesing/__init__.py +1 -0
  231. danielutils/university/image_proccesing/encoding/__init__.py +3 -0
  232. danielutils/university/image_proccesing/encoding/encoding.py +35 -0
  233. danielutils/university/image_proccesing/encoding/lossless/__init__.py +4 -0
  234. danielutils/university/image_proccesing/encoding/lossless/huffman.py +0 -0
  235. danielutils/university/image_proccesing/encoding/lossless/lossless_encoding.py +10 -0
  236. danielutils/university/image_proccesing/encoding/lossless/lzw.py +0 -0
  237. danielutils/university/image_proccesing/encoding/lossless/run_length.py +22 -0
  238. danielutils/university/image_proccesing/encoding/lossy/__init__.py +1 -0
  239. danielutils/university/image_proccesing/encoding/lossy/lossy_encoding.py +10 -0
  240. danielutils/university/image_proccesing/tansformations/__init__.py +0 -0
  241. danielutils/university/image_proccesing/tansformations/gaussian.py +0 -0
  242. danielutils/university/image_proccesing/tansformations/gradient.py +0 -0
  243. danielutils/university/image_proccesing/tansformations/hough.py +0 -0
  244. danielutils/university/image_proccesing/tansformations/laplacian.py +0 -0
  245. danielutils/university/image_proccesing/tansformations/transformation.py +11 -0
  246. danielutils/university/linear_algebra/__init__.py +1 -0
  247. danielutils/university/linear_algebra/matrix.py +404 -0
  248. danielutils/university/machine_learning/__init__.py +0 -0
  249. danielutils/university/machine_learning/activation_functions/__init__.py +2 -0
  250. danielutils/university/machine_learning/activation_functions/activation_function.py +11 -0
  251. danielutils/university/machine_learning/activation_functions/relu.py +11 -0
  252. danielutils/university/machine_learning/neuron.py +15 -0
  253. danielutils/university/oop/__init__.py +1 -0
  254. danielutils/university/oop/observer.py +31 -0
  255. danielutils/university/oop/strategy.py +9 -0
  256. danielutils/university/probability/__init__.py +7 -0
  257. danielutils/university/probability/conditional_variable/__init__.py +3 -0
  258. danielutils/university/probability/conditional_variable/conditional_variable.py +138 -0
  259. danielutils/university/probability/conditional_variable/continuous/__init__.py +0 -0
  260. danielutils/university/probability/conditional_variable/discrete/__init__.py +8 -0
  261. danielutils/university/probability/conditional_variable/discrete/bernoulli.py +38 -0
  262. danielutils/university/probability/conditional_variable/discrete/bernoulli_sum.py +31 -0
  263. danielutils/university/probability/conditional_variable/discrete/binomial.py +40 -0
  264. danielutils/university/probability/conditional_variable/discrete/conditional_from_discrete_probability_func.py +36 -0
  265. danielutils/university/probability/conditional_variable/discrete/discrete.py +42 -0
  266. danielutils/university/probability/conditional_variable/discrete/geometric.py +43 -0
  267. danielutils/university/probability/conditional_variable/discrete/poisson.py +32 -0
  268. danielutils/university/probability/conditional_variable/discrete/uniform.py +33 -0
  269. danielutils/university/probability/distributions.py +35 -0
  270. danielutils/university/probability/expressions/__init__.py +2 -0
  271. danielutils/university/probability/expressions/accumulation_expression.py +291 -0
  272. danielutils/university/probability/expressions/probability_expression.py +110 -0
  273. danielutils/university/probability/funcs/__init__.py +4 -0
  274. danielutils/university/probability/funcs/covariance.py +12 -0
  275. danielutils/university/probability/funcs/expected_value.py +41 -0
  276. danielutils/university/probability/funcs/probability_function.py +14 -0
  277. danielutils/university/probability/funcs/variance.py +17 -0
  278. danielutils/university/probability/operator.py +79 -0
  279. danielutils/university/probability/protocols.py +32 -0
  280. danielutils/university/probability/supp.py +101 -0
  281. danielutils/university/probability/transformation.py +44 -0
  282. danielutils/versioned_imports.py +31 -0
  283. danielutils-1.1.28.dist-info/METADATA +390 -0
  284. danielutils-1.1.28.dist-info/RECORD +287 -0
  285. danielutils-1.1.28.dist-info/WHEEL +5 -0
  286. danielutils-1.1.28.dist-info/licenses/LICENSE +21 -0
  287. 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
+ ]