dbis-functional-dependencies 0.0.7__py3-none-any.whl → 1.0.0__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.
- dbis_functional_dependencies/BCNF.py +1761 -0
- dbis_functional_dependencies/fdcheck.py +113 -0
- {functional_dependencies → dbis_functional_dependencies}/fds.py +14 -10
- dbis_functional_dependencies/fdsbase.py +207 -0
- dbis_functional_dependencies-1.0.0.dist-info/METADATA +178 -0
- dbis_functional_dependencies-1.0.0.dist-info/RECORD +10 -0
- {dbis_functional_dependencies-0.0.7.dist-info → dbis_functional_dependencies-1.0.0.dist-info}/WHEEL +1 -1
- dbis_functional_dependencies-1.0.0.dist-info/top_level.txt +1 -0
- dbis_functional_dependencies-0.0.7.dist-info/METADATA +0 -23
- dbis_functional_dependencies-0.0.7.dist-info/RECORD +0 -10
- dbis_functional_dependencies-0.0.7.dist-info/top_level.txt +0 -1
- functional_dependencies/BCNF.py +0 -619
- functional_dependencies/fdcheck.py +0 -96
- functional_dependencies/fdsbase.py +0 -194
- {functional_dependencies → dbis_functional_dependencies}/__init__.py +0 -0
- {dbis_functional_dependencies-0.0.7.dist-info → dbis_functional_dependencies-1.0.0.dist-info}/LICENSE +0 -0
@@ -0,0 +1,1761 @@
|
|
1
|
+
"""
|
2
|
+
Created on 25.05.2022
|
3
|
+
|
4
|
+
@author: maxwellgerber
|
5
|
+
|
6
|
+
see https://gist.github.com/maxwellgerber/4caae07161ea66123de4d6c374387786
|
7
|
+
|
8
|
+
"""
|
9
|
+
from itertools import combinations, permutations, chain
|
10
|
+
import numpy
|
11
|
+
import datetime
|
12
|
+
from dbis_functional_dependencies.fdsbase import Attribute, Set, Notation, FD
|
13
|
+
|
14
|
+
|
15
|
+
class FunctionalDependencySet:
|
16
|
+
"""
|
17
|
+
a functional dependency set
|
18
|
+
"""
|
19
|
+
|
20
|
+
def __init__(
|
21
|
+
self,
|
22
|
+
attributes: str = "",
|
23
|
+
title: str = "",
|
24
|
+
description: str = "",
|
25
|
+
notation: Notation = None,
|
26
|
+
debug: bool = False,
|
27
|
+
):
|
28
|
+
"""
|
29
|
+
constructor
|
30
|
+
|
31
|
+
Args:
|
32
|
+
attributes(str): a string of attribute variable names of the scheme
|
33
|
+
title(str): a title for this functional Dependency Set
|
34
|
+
description(str): a description for this functional Dependency Set
|
35
|
+
notation(Notation): the notation to be used
|
36
|
+
debug(bool): if True switch debugging on
|
37
|
+
"""
|
38
|
+
self.title = title
|
39
|
+
self.description = description
|
40
|
+
if notation is None:
|
41
|
+
notation = Notation.utf8
|
42
|
+
self.notation = notation
|
43
|
+
self.debug = debug
|
44
|
+
self.isodate = datetime.datetime.now().isoformat()
|
45
|
+
# list of FDs of the scheme. An FD is stored as a tuple (x, y), meaning x -> y
|
46
|
+
self.dependencies = []
|
47
|
+
|
48
|
+
# set of attributes of the scheme
|
49
|
+
self.attributes = Set()
|
50
|
+
self.attribute_map = {}
|
51
|
+
|
52
|
+
self.isdecomposed = False
|
53
|
+
|
54
|
+
for attr in attributes:
|
55
|
+
self.add_attribute(attr)
|
56
|
+
|
57
|
+
def __iter__(self):
|
58
|
+
"""
|
59
|
+
makes me iterable
|
60
|
+
"""
|
61
|
+
for fd in self.dependencies:
|
62
|
+
yield fd
|
63
|
+
|
64
|
+
def __str__(self):
|
65
|
+
"""
|
66
|
+
return my text representation
|
67
|
+
"""
|
68
|
+
text = self.stringify_dependencies()
|
69
|
+
return text
|
70
|
+
|
71
|
+
def set_list_as_text_list(self, set_list: list, notation: Notation):
|
72
|
+
"""
|
73
|
+
convert a list of sets to a list of strings using the given delimiter
|
74
|
+
|
75
|
+
Args:
|
76
|
+
set_list(list): list of sets
|
77
|
+
notation(Notation): the notation to use
|
78
|
+
|
79
|
+
Returns:
|
80
|
+
list: of stringified sets
|
81
|
+
"""
|
82
|
+
text_list = []
|
83
|
+
for a_set in set_list:
|
84
|
+
text_list.append(Set.stringify_set(a_set, notation=notation))
|
85
|
+
text_list = sorted(text_list)
|
86
|
+
return text_list
|
87
|
+
|
88
|
+
def copy(self):
|
89
|
+
fds = FunctionalDependencySet()
|
90
|
+
fds.title = self.title
|
91
|
+
fds.description = self.description
|
92
|
+
fds.notation = self.notation
|
93
|
+
fds.debug = self.debug
|
94
|
+
fds.attributes = self.attributes.copy()
|
95
|
+
fds.attribute_map = self.attribute_map.copy()
|
96
|
+
fds.dependencies = self.dependencies.copy()
|
97
|
+
fds.isdecomposed = self.isdecomposed
|
98
|
+
return fds
|
99
|
+
|
100
|
+
def stringify_dependencies(self, fdsToStringify: list = []):
|
101
|
+
"""
|
102
|
+
stringifies the set of dependencies
|
103
|
+
|
104
|
+
Args:
|
105
|
+
fdsToStringify(list): set of fds in case only specific dependencies are needed
|
106
|
+
"""
|
107
|
+
text = "{"
|
108
|
+
delim = ""
|
109
|
+
if self.notation == Notation.math or self.notation == Notation.plain:
|
110
|
+
fdNotation = self.notation
|
111
|
+
else:
|
112
|
+
fdNotation = Notation.utf8
|
113
|
+
|
114
|
+
if fdsToStringify == []:
|
115
|
+
fdsToStringify = self.dependencies
|
116
|
+
for left, right in fdsToStringify:
|
117
|
+
fd = FD(left, right)
|
118
|
+
fdtext = FD.stringify_FD(fd, fdNotation)
|
119
|
+
text += f"{delim}{fdtext}"
|
120
|
+
delim = ","
|
121
|
+
text += "}"
|
122
|
+
return text
|
123
|
+
|
124
|
+
def add_attribute(
|
125
|
+
self, attr_var: str, attr_english_name: str = None, attr_german_name: str = None
|
126
|
+
):
|
127
|
+
"""
|
128
|
+
add attribute to the attribute set of the scheme
|
129
|
+
|
130
|
+
Args:
|
131
|
+
attr_var(string): attribute variable name to be added to the scheme
|
132
|
+
attr_english_name(string): the name of the attribute in english
|
133
|
+
attr_german_name(string): the name of the attribute in german
|
134
|
+
"""
|
135
|
+
if attr_english_name is None:
|
136
|
+
attr_english_name = attr_var
|
137
|
+
if attr_german_name is None:
|
138
|
+
attr_german_name = attr_english_name
|
139
|
+
attr = Attribute(attr_var, attr_english_name, attr_german_name)
|
140
|
+
self.add_an_attribute(attr)
|
141
|
+
|
142
|
+
def add_an_attribute(self, attr):
|
143
|
+
"""
|
144
|
+
add the given Attribute
|
145
|
+
"""
|
146
|
+
self.attributes.add(attr.var_name)
|
147
|
+
self.attribute_map[attr.var_name] = attr
|
148
|
+
|
149
|
+
def get_attribute(self, var_name):
|
150
|
+
return self.attribute_map[var_name]
|
151
|
+
|
152
|
+
def add_dependency(self, pre, post):
|
153
|
+
"""
|
154
|
+
add dependency to the dependency list of the scheme
|
155
|
+
|
156
|
+
Args:
|
157
|
+
pre(set): attributes that initiate the FD (left of the arrow)
|
158
|
+
post(set): attributes that are determined by the FD (right of the arrow)
|
159
|
+
"""
|
160
|
+
for i in chain(pre, post):
|
161
|
+
if i not in self.attributes:
|
162
|
+
# exception when an attribute is used that is not in the list of attributes of the dependency
|
163
|
+
raise Exception(f"Attribute {i} does not exist")
|
164
|
+
self.dependencies.append((set(pre), set(post)))
|
165
|
+
|
166
|
+
def remove_dependency(self, pre, post):
|
167
|
+
"""
|
168
|
+
remove dependency from the dependency list of the scheme
|
169
|
+
|
170
|
+
Args:
|
171
|
+
pre(str): attributes that initiate the FD (left of the arrow)
|
172
|
+
post(str): attributes that are determined by the FD (right of the arrow)
|
173
|
+
"""
|
174
|
+
for i in chain(pre, post):
|
175
|
+
if i not in self.attributes:
|
176
|
+
# exception when an attribute is used that is not in the list of attributes of the dependency
|
177
|
+
raise Exception(f"Attribute {i} does not exist")
|
178
|
+
self.dependencies.remove((set(pre), set(post)))
|
179
|
+
|
180
|
+
def get_attr_closure(self, attr):
|
181
|
+
"""
|
182
|
+
get the close of the given attribute
|
183
|
+
|
184
|
+
Args:
|
185
|
+
attr(str): the name of the attribute to calculate the closure for
|
186
|
+
|
187
|
+
Returns:
|
188
|
+
set: the closure of the attribute
|
189
|
+
"""
|
190
|
+
# closure set is build up iteratively, until it does not expand anymore
|
191
|
+
closure = set(attr)
|
192
|
+
# set of previous iteration
|
193
|
+
last = set()
|
194
|
+
while closure != last:
|
195
|
+
last = closure.copy()
|
196
|
+
# check all FDs whether their initiators are part of the closure
|
197
|
+
# and add closure of the respective FD to the calculating closure
|
198
|
+
for dep in self.dependencies:
|
199
|
+
left, right = dep
|
200
|
+
if left and left.issubset(closure):
|
201
|
+
closure.update(right)
|
202
|
+
return closure
|
203
|
+
|
204
|
+
def attribute_combinations(self, attributeSet: Set):
|
205
|
+
"""
|
206
|
+
generator for keys
|
207
|
+
"""
|
208
|
+
for i in range(1, len(attributeSet) + 1):
|
209
|
+
for keys in combinations(attributeSet, i):
|
210
|
+
yield keys
|
211
|
+
|
212
|
+
def find_candidate_keys(self, verbose: bool = False, genEx: bool = False):
|
213
|
+
"""
|
214
|
+
find candidate keys of the scheme
|
215
|
+
|
216
|
+
Args:
|
217
|
+
verbose(bool): if True show the steps
|
218
|
+
"""
|
219
|
+
generatedTasks = []
|
220
|
+
ans = []
|
221
|
+
# check closures of all attributes and attribute combinations iteratively
|
222
|
+
# smaller candidate keys added first
|
223
|
+
for keys in self.attribute_combinations(self.attributes):
|
224
|
+
closure = self.get_attr_closure(keys)
|
225
|
+
k = set(keys)
|
226
|
+
if closure == self.attributes:
|
227
|
+
# no subset of currently checked key is already in
|
228
|
+
if not any([x.issubset(k) for x in ans]):
|
229
|
+
generatedTasks.append(
|
230
|
+
(
|
231
|
+
f"Es ist {k} ein Schlüsselkandidat. Also ist {self.attributes} voll funktional abhängig von {k}.",
|
232
|
+
True,
|
233
|
+
)
|
234
|
+
)
|
235
|
+
generatedTasks.append(
|
236
|
+
(
|
237
|
+
f"Die Attributhülle von {k} ist {self.attributes}. Aus diesem Grund ist {self.attributes} voll funktional abhängig von {k}.",
|
238
|
+
False,
|
239
|
+
)
|
240
|
+
)
|
241
|
+
|
242
|
+
if verbose:
|
243
|
+
print(f"found candidate keys {k}")
|
244
|
+
ans.append(k)
|
245
|
+
else:
|
246
|
+
generatedTasks.append(
|
247
|
+
(
|
248
|
+
f"Es ist {k} ein Superschlüssel, aber kein Schlüsselkandidat.",
|
249
|
+
True,
|
250
|
+
)
|
251
|
+
)
|
252
|
+
generatedTasks.append(
|
253
|
+
(
|
254
|
+
f"Es ist {k} ein Superschlüssel. Also ist {self.attributes} voll funktional abhängig von {k}.",
|
255
|
+
False,
|
256
|
+
)
|
257
|
+
)
|
258
|
+
else:
|
259
|
+
generatedTasks.append(
|
260
|
+
(f"Es ist {k} weder Superschlüssel noch Schlüsselkandidat.", True)
|
261
|
+
)
|
262
|
+
if len(k) <= 2:
|
263
|
+
generatedTasks.append((f"Aus {k} leitet man {closure} ab.", True))
|
264
|
+
|
265
|
+
if genEx:
|
266
|
+
print(f"=======================================")
|
267
|
+
print(
|
268
|
+
f"Aussagen zum Finden von Schlüsselkandidaten und Berechnen von Attributhüllen:"
|
269
|
+
)
|
270
|
+
for task in generatedTasks:
|
271
|
+
print(task)
|
272
|
+
print(f"=======================================")
|
273
|
+
|
274
|
+
return ans
|
275
|
+
|
276
|
+
def isFDinBCNF(self, fd):
|
277
|
+
"""
|
278
|
+
test whether the given fd is in BCNF
|
279
|
+
|
280
|
+
Returns:
|
281
|
+
bool: True if the left side of the functional dependency is a superkey
|
282
|
+
"""
|
283
|
+
left, right = fd
|
284
|
+
# do a super key check for the left side
|
285
|
+
leftIsSuperKey = self.isSuperKey(left) or (left >= right)
|
286
|
+
return leftIsSuperKey
|
287
|
+
|
288
|
+
def isSuperKey(self, attrSet):
|
289
|
+
"""
|
290
|
+
check wether the given attribute Set is a superkey
|
291
|
+
|
292
|
+
Args:
|
293
|
+
attrSet(Set): the set of attributes to check
|
294
|
+
|
295
|
+
Returns:
|
296
|
+
bool: True if the attributeSet is a super key
|
297
|
+
"""
|
298
|
+
closure_left = self.get_attr_closure(attrSet)
|
299
|
+
return closure_left == self.attributes
|
300
|
+
|
301
|
+
def isIdentical(self, fdsetToCompare):
|
302
|
+
"""
|
303
|
+
tests whether a given second fdset has the same attributes and fds as self
|
304
|
+
"""
|
305
|
+
# check whether attributes are same
|
306
|
+
if self.attributes != fdsetToCompare.attributes:
|
307
|
+
return False
|
308
|
+
|
309
|
+
# check whether all self dependencies are contained in compareFDSet dependencies
|
310
|
+
for selfPre, selfPost in self.dependencies:
|
311
|
+
found = False
|
312
|
+
for comparePre, comparePost in fdsetToCompare.dependencies:
|
313
|
+
if selfPre == comparePre and selfPost == comparePost:
|
314
|
+
found = True
|
315
|
+
break
|
316
|
+
if not found:
|
317
|
+
return False
|
318
|
+
|
319
|
+
# check whether all compareFDSet dependencies are contained in self dependencies
|
320
|
+
for comparePre, comparePost in self.dependencies:
|
321
|
+
found = False
|
322
|
+
for selfPre, selfPost in fdsetToCompare.dependencies:
|
323
|
+
if selfPre == comparePre and selfPost == comparePost:
|
324
|
+
found = True
|
325
|
+
break
|
326
|
+
if not found:
|
327
|
+
return False
|
328
|
+
|
329
|
+
return True
|
330
|
+
|
331
|
+
def isBCNF(self, verbose: bool = False):
|
332
|
+
"""
|
333
|
+
tests whether i am in BCNF:
|
334
|
+
every left side of every dependency is a superkey
|
335
|
+
"""
|
336
|
+
result = True
|
337
|
+
for fd in self.dependencies:
|
338
|
+
fdInBCNF = self.isFDinBCNF(fd)
|
339
|
+
result = result and fdInBCNF
|
340
|
+
if not result:
|
341
|
+
if verbose:
|
342
|
+
print(f"fd {fd} is NOT in BCNF")
|
343
|
+
return False
|
344
|
+
if verbose:
|
345
|
+
print(f"fd {fd} is in BCNF")
|
346
|
+
|
347
|
+
return result
|
348
|
+
|
349
|
+
def getNonBCNF(self):
|
350
|
+
"""
|
351
|
+
get first non BCNF dependency from my dependencies
|
352
|
+
"""
|
353
|
+
for fd in self.dependencies:
|
354
|
+
if not self.isFDinBCNF(fd):
|
355
|
+
return fd
|
356
|
+
return None
|
357
|
+
|
358
|
+
def decompose2(self, verbose: bool = False, genEx: bool = False):
|
359
|
+
"""
|
360
|
+
decomposition algorithm according to DBIS-VL
|
361
|
+
Source: https://dbis.rwth-aachen.de/dbis-vl/RelDesign#page=82
|
362
|
+
|
363
|
+
Args:
|
364
|
+
verbose(bool): if True show steps
|
365
|
+
genEx(bool): if True generate exercise tasks in form of sentences that can be either true or false
|
366
|
+
"""
|
367
|
+
|
368
|
+
# https://en.wikipedia.org/wiki/Linear_A version of Algorithm ...
|
369
|
+
|
370
|
+
generatedTasks = []
|
371
|
+
step = 0
|
372
|
+
|
373
|
+
self.isdecomposed = True
|
374
|
+
fds = FunctionalDependencySet()
|
375
|
+
fds.attributes = self.attributes.copy()
|
376
|
+
fds.dependencies = self.dependencies.copy()
|
377
|
+
fds.completeFDsetToClosure(verbose=verbose)
|
378
|
+
not_bcnf = [fds]
|
379
|
+
bcnf = []
|
380
|
+
while not_bcnf:
|
381
|
+
fds = not_bcnf.pop(0)
|
382
|
+
#
|
383
|
+
if fds.isBCNF(verbose=verbose):
|
384
|
+
generatedTasks.append(
|
385
|
+
(f"Die Abhängigkeitsmenge {fds} ist in BCNF.", True)
|
386
|
+
)
|
387
|
+
bcnf.append(fds.attributes)
|
388
|
+
if verbose:
|
389
|
+
print(f"fdset {fds} is in BCNF")
|
390
|
+
else:
|
391
|
+
generatedTasks.append(
|
392
|
+
(f"Die Abhängigkeitsmenge {fds} ist in BCNF.", False)
|
393
|
+
)
|
394
|
+
if verbose:
|
395
|
+
print(f"fdset {fds} is not yet in BCNF")
|
396
|
+
new_fds1 = FunctionalDependencySet()
|
397
|
+
new_fds2 = FunctionalDependencySet()
|
398
|
+
for dep in fds.dependencies:
|
399
|
+
left, right = dep
|
400
|
+
if (
|
401
|
+
(fds.get_attr_closure(left) != fds.attributes)
|
402
|
+
and (left != right)
|
403
|
+
and (len(left.intersection(right)) == 0)
|
404
|
+
):
|
405
|
+
step += 1
|
406
|
+
generatedTasks.append(
|
407
|
+
(
|
408
|
+
f"In Schritt {step} zerlegen wir entlang der Abhängigkeit {dep}.",
|
409
|
+
True,
|
410
|
+
)
|
411
|
+
)
|
412
|
+
if verbose:
|
413
|
+
print(f"decompose along the dependency {dep}")
|
414
|
+
|
415
|
+
# create new fdsets along the dependency we decompose with
|
416
|
+
new_fds1.attributes = left | right
|
417
|
+
new_fds2.attributes = fds.attributes - right
|
418
|
+
# find dependencies that belong to the new fdsets
|
419
|
+
new_fds1.dependencies = [
|
420
|
+
fd
|
421
|
+
for fd in fds.dependencies
|
422
|
+
if (fd[0] | fd[1]) <= new_fds1.attributes
|
423
|
+
]
|
424
|
+
new_fds2.dependencies = [
|
425
|
+
fd
|
426
|
+
for fd in fds.dependencies
|
427
|
+
if (fd[0] | fd[1]) <= new_fds2.attributes
|
428
|
+
]
|
429
|
+
# new_fds2.dependencies = new_fds2.dependencies + [(fd[0], fd[1].intersection(new_fds2.attributes)) for fd in fds.dependencies if fd[0] <= new_fds2.attributes and len(fd[1].intersection(new_fds2.attributes)) != 0]
|
430
|
+
break
|
431
|
+
else:
|
432
|
+
generatedTasks.append(
|
433
|
+
(f"Wir zerlegen entlang der Abhängigkeit {dep}.", False)
|
434
|
+
)
|
435
|
+
not_bcnf.append(new_fds1)
|
436
|
+
not_bcnf.append(new_fds2)
|
437
|
+
if verbose:
|
438
|
+
print(
|
439
|
+
f"new fdsets with attributes {new_fds1.attributes} and {new_fds2.attributes}"
|
440
|
+
)
|
441
|
+
self.tables = bcnf
|
442
|
+
|
443
|
+
generatedTasks.append(
|
444
|
+
(f"Die Zerlegung enthält mehr als drei Relationen.", len(self.tables) > 3)
|
445
|
+
)
|
446
|
+
|
447
|
+
if genEx:
|
448
|
+
print(f"=======================================")
|
449
|
+
print(f"Aussagen zur Dekomposition:")
|
450
|
+
for task in generatedTasks:
|
451
|
+
print(task)
|
452
|
+
print(f"=======================================")
|
453
|
+
|
454
|
+
return bcnf
|
455
|
+
|
456
|
+
def decompose(self, verbose: bool = False):
|
457
|
+
"""
|
458
|
+
decomposition algorithm
|
459
|
+
|
460
|
+
Args:
|
461
|
+
verbose(bool): if True show steps of the decomposition
|
462
|
+
"""
|
463
|
+
self.isdecomposed = True
|
464
|
+
self.tables = [self.attributes]
|
465
|
+
for dep in self.dependencies:
|
466
|
+
left, right = dep
|
467
|
+
for attr_set in self.tables:
|
468
|
+
# newset contains the unity of attributes of the FD
|
469
|
+
newset = left.symmetric_difference(right)
|
470
|
+
# if newset is real subset, extra attributes still exist
|
471
|
+
# --> need to break it up
|
472
|
+
if newset.issubset(attr_set) and newset != attr_set:
|
473
|
+
if verbose:
|
474
|
+
print(
|
475
|
+
f"splitting {attr_set} into {attr_set.difference(right)} and {newset}"
|
476
|
+
)
|
477
|
+
# split attributes of the FD closure off the attribute set
|
478
|
+
attr_set.difference_update(right)
|
479
|
+
|
480
|
+
# add new BCNF set to list of attribute sets
|
481
|
+
self.tables.append(newset)
|
482
|
+
return self.tables
|
483
|
+
|
484
|
+
def decompose_all(self):
|
485
|
+
## Messy sets and tuples to get rid of duplicates, eew
|
486
|
+
tables_possibilities = []
|
487
|
+
|
488
|
+
for ordering in permutations(self.dependencies):
|
489
|
+
tbl = [self.attributes.copy()]
|
490
|
+
|
491
|
+
for dep in ordering:
|
492
|
+
left, right = dep
|
493
|
+
for attr_set in tbl:
|
494
|
+
newset = left.symmetric_difference(right)
|
495
|
+
if newset.issubset(attr_set) and newset != attr_set:
|
496
|
+
attr_set.difference_update(right)
|
497
|
+
tbl.append(newset)
|
498
|
+
|
499
|
+
tbl = [tuple(x) for x in tbl]
|
500
|
+
tables_possibilities.append(tuple(tbl))
|
501
|
+
|
502
|
+
return set(tables_possibilities)
|
503
|
+
|
504
|
+
def is_lossy(self):
|
505
|
+
"""
|
506
|
+
check for lossyness
|
507
|
+
|
508
|
+
Returns:
|
509
|
+
bool: True if if one of my dependencies is not preserved
|
510
|
+
"""
|
511
|
+
if not self.isdecomposed:
|
512
|
+
raise Exception("Can't tell if lossy if the FD hasn't been decomposed yet")
|
513
|
+
for dep in self.dependencies:
|
514
|
+
if not self.is_preserved(dep):
|
515
|
+
return True
|
516
|
+
return False
|
517
|
+
|
518
|
+
def is_preserved(self, dep):
|
519
|
+
"""
|
520
|
+
check whether the given dependency is preserved
|
521
|
+
|
522
|
+
Args:
|
523
|
+
dep(): the dependency to check
|
524
|
+
|
525
|
+
Returns:
|
526
|
+
bool: True if the dependency is preserved
|
527
|
+
"""
|
528
|
+
left, right = dep
|
529
|
+
pre = left.symmetric_difference(right)
|
530
|
+
for attr_set in self.tables:
|
531
|
+
if pre == attr_set:
|
532
|
+
return True
|
533
|
+
return False
|
534
|
+
|
535
|
+
def calculate_fds_in_subset(self, subset):
|
536
|
+
"""
|
537
|
+
calculate all dependencies in a subset. Also includes dependencies for which
|
538
|
+
attribute parts are missing because they are not in the subset. Does not include
|
539
|
+
original dependencies that have lost all there attributes in precondition or closure
|
540
|
+
|
541
|
+
"""
|
542
|
+
subset_dependencies = []
|
543
|
+
for dep in self.dependencies:
|
544
|
+
new_dep_pre = set()
|
545
|
+
new_dep_post = set()
|
546
|
+
left, right = dep
|
547
|
+
# check whether attributes occur in pre or post of the original FD
|
548
|
+
for attr in left:
|
549
|
+
if attr in subset:
|
550
|
+
new_dep_pre.add(attr)
|
551
|
+
for attr in right:
|
552
|
+
if attr in subset:
|
553
|
+
new_dep_post.add(attr)
|
554
|
+
# only add new dependency if none of both sides is empty
|
555
|
+
if new_dep_pre != set() and new_dep_post != set():
|
556
|
+
subset_dependencies.append((new_dep_pre, new_dep_post))
|
557
|
+
return subset_dependencies
|
558
|
+
|
559
|
+
def is2NF(self):
|
560
|
+
"""
|
561
|
+
calculates whether the FD set is in 2NF: Every attribute has to depend on the whole CK.
|
562
|
+
Check for every attribute whether there is a part of any of the CKs which has the attribute in its closure
|
563
|
+
"""
|
564
|
+
ckeys = self.find_candidate_keys()
|
565
|
+
# check every non-ck-attribute
|
566
|
+
for attr in self.attributes:
|
567
|
+
skip = False
|
568
|
+
for ckey in ckeys:
|
569
|
+
for ckey_part in ckey:
|
570
|
+
if attr == ckey_part:
|
571
|
+
skip = True
|
572
|
+
|
573
|
+
if skip == True:
|
574
|
+
continue
|
575
|
+
|
576
|
+
# check every key candidate
|
577
|
+
for ckey in ckeys:
|
578
|
+
# check every subset of keys (not yet)
|
579
|
+
for ckey_part in ckey:
|
580
|
+
ckey_part_closure = self.get_attr_closure(ckey_part)
|
581
|
+
if attr in ckey_part_closure:
|
582
|
+
return False
|
583
|
+
return True
|
584
|
+
|
585
|
+
def is3NF(self):
|
586
|
+
"""
|
587
|
+
calculates whether the FD set is in 3NF: There are no dependencies between non-key attributes
|
588
|
+
"""
|
589
|
+
ckeys = self.find_candidate_keys()
|
590
|
+
|
591
|
+
for dep in self.dependencies:
|
592
|
+
left, right = dep
|
593
|
+
# get all attributes of an fd
|
594
|
+
dep_attributes = set()
|
595
|
+
dep_attributes.update(left)
|
596
|
+
dep_attributes.update(right)
|
597
|
+
dep_has_ckey_attr = False
|
598
|
+
|
599
|
+
# check all attributes of the fd whether at least one of them is contained in a ckey
|
600
|
+
for attr in dep_attributes:
|
601
|
+
for ckey in ckeys:
|
602
|
+
if set(attr).issubset(ckey):
|
603
|
+
dep_has_ckey_attr = True
|
604
|
+
break
|
605
|
+
if not dep_has_ckey_attr:
|
606
|
+
return False
|
607
|
+
return True
|
608
|
+
|
609
|
+
def generate_cluster(self, shape: str = "box", indent: str = " "):
|
610
|
+
"""
|
611
|
+
graphviz digraph subgraph (cluster) generation for this functional dependency set
|
612
|
+
|
613
|
+
Args:
|
614
|
+
shape(str): the shape to use - default: box
|
615
|
+
indent(str): indentation - default: two spaces
|
616
|
+
Return:
|
617
|
+
str: graphviz markup
|
618
|
+
"""
|
619
|
+
markup = ""
|
620
|
+
# sort dependencies by largest pre
|
621
|
+
dependencies = self.dependencies.copy()
|
622
|
+
dependencies.sort(key=lambda dep: len(dep[0]), reverse=True)
|
623
|
+
|
624
|
+
# collect attributes that are only on the right side
|
625
|
+
only_post = self.attributes.copy()
|
626
|
+
# generate clusters
|
627
|
+
cluster_markup = ""
|
628
|
+
for dep in dependencies:
|
629
|
+
pre, post = dep
|
630
|
+
only_post -= pre
|
631
|
+
cluster_name = "".join(sorted(pre))
|
632
|
+
cluster_markup += f"{indent}subgraph cluster_{cluster_name}{{\n"
|
633
|
+
cluster_markup += f'{indent} label="{cluster_name}"\n'
|
634
|
+
for attrVar in sorted(pre):
|
635
|
+
attr = self.attribute_map[attrVar]
|
636
|
+
cluster_markup += (
|
637
|
+
f'{indent}{indent}{attrVar} [shape={shape} label="{attr}"]\n'
|
638
|
+
)
|
639
|
+
cluster_markup += f"{indent}}}\n"
|
640
|
+
|
641
|
+
# generate arrows
|
642
|
+
arrow_markup = ""
|
643
|
+
for dep in dependencies:
|
644
|
+
pre, post = dep
|
645
|
+
for attrVar in sorted(post):
|
646
|
+
arrow_markup += f"{indent}{sorted(pre)[0]}->{attrVar}\n"
|
647
|
+
|
648
|
+
# create markup for only post attributes
|
649
|
+
only_post_markup = ""
|
650
|
+
for attrVar in sorted(only_post):
|
651
|
+
attr = self.attribute_map[attrVar]
|
652
|
+
only_post_markup += f'{indent}{attrVar} [shape={shape} label="{attr}"]\n'
|
653
|
+
|
654
|
+
# concatenate markup
|
655
|
+
markup += only_post_markup
|
656
|
+
markup += cluster_markup
|
657
|
+
markup += arrow_markup
|
658
|
+
return markup
|
659
|
+
|
660
|
+
def as_graphviz(self, withCluster: bool = True):
|
661
|
+
"""
|
662
|
+
|
663
|
+
convert me to a graphviz markup e.g. to try out in
|
664
|
+
|
665
|
+
http://magjac.com/graphviz-visual-editor/
|
666
|
+
or
|
667
|
+
http://diagrams.bitplan.com
|
668
|
+
|
669
|
+
Return:
|
670
|
+
str: the graphviz markup for this functional DependencySet
|
671
|
+
"""
|
672
|
+
markup = f"#generated by {__file__} on {self.isodate}\n"
|
673
|
+
markup += "digraph functionalDependencySet{"
|
674
|
+
# add title see https://stackoverflow.com/a/6452088/1497139
|
675
|
+
markup += f"""
|
676
|
+
// title
|
677
|
+
labelloc="t";
|
678
|
+
label="{self.title}"
|
679
|
+
"""
|
680
|
+
if not withCluster:
|
681
|
+
markup += "// Attribute variables \n"
|
682
|
+
for attrVar in sorted(self.attributes):
|
683
|
+
attr = self.attribute_map[attrVar]
|
684
|
+
markup += f""" {attrVar} [ shape=box label="{attr}"] \n"""
|
685
|
+
else:
|
686
|
+
markup += self.generate_cluster()
|
687
|
+
markup += "}"
|
688
|
+
return markup
|
689
|
+
|
690
|
+
def left_reduction(self, verbose: bool = False, genEx: bool = False):
|
691
|
+
"""
|
692
|
+
executes a left reduction on the dependencies from this fdset
|
693
|
+
|
694
|
+
Args:
|
695
|
+
verbose(bool): if True show steps
|
696
|
+
genEx(bool): if True generate exercise tasks in form of sentences that can be either true or false
|
697
|
+
"""
|
698
|
+
|
699
|
+
generatedTasks = []
|
700
|
+
reductionCounter = 0
|
701
|
+
|
702
|
+
if genEx:
|
703
|
+
self.notation = Notation.math
|
704
|
+
|
705
|
+
remaining_deps = self.dependencies.copy()
|
706
|
+
while remaining_deps:
|
707
|
+
dep = remaining_deps.pop(0)
|
708
|
+
pre, post = dep
|
709
|
+
wasDepAlreadyReduced = False
|
710
|
+
for attr in sorted(pre):
|
711
|
+
if post <= self.get_attr_closure(pre - {attr}):
|
712
|
+
generatedTasks.append(
|
713
|
+
(
|
714
|
+
f"Das Attribut {attr} kann aus der Abhängigkeit {dep} reduziert werden.",
|
715
|
+
True,
|
716
|
+
)
|
717
|
+
)
|
718
|
+
if not wasDepAlreadyReduced:
|
719
|
+
generatedTasks.append(
|
720
|
+
(
|
721
|
+
f"Die Abhängigkeit {self.stringify_dependencies(fdsToStringify=[dep])} kann nicht reduziert werden, da die Attributhülle von {Set.stringify_set(post, self.notation)} nicht alle Attribute aus {Set.stringify_set(pre, self.notation)} enthält.",
|
722
|
+
False,
|
723
|
+
)
|
724
|
+
)
|
725
|
+
generatedTasks.append(
|
726
|
+
(
|
727
|
+
f"Die Abhängigkeit {self.stringify_dependencies(fdsToStringify=[dep])} kann reduziert werden, da die Attributhülle von {Set.stringify_set(post, self.notation)} nicht alle Attribute aus {Set.stringify_set(pre, self.notation)} enthält.",
|
728
|
+
False,
|
729
|
+
)
|
730
|
+
)
|
731
|
+
reductionCounter += 1
|
732
|
+
else:
|
733
|
+
generatedTasks.append(
|
734
|
+
(
|
735
|
+
f"Die Abhängigkeit {self.stringify_dependencies(fdsToStringify=[dep])} wird mehrmals reduziert.",
|
736
|
+
True,
|
737
|
+
)
|
738
|
+
)
|
739
|
+
if verbose:
|
740
|
+
print(
|
741
|
+
f"removed {attr} from lhs of dependency {dep}, new lhs {(pre - {attr})}"
|
742
|
+
)
|
743
|
+
self.remove_dependency(pre, post)
|
744
|
+
self.add_dependency(pre - {attr}, post)
|
745
|
+
pre = pre - {attr}
|
746
|
+
wasDepAlreadyReduced = True
|
747
|
+
else:
|
748
|
+
generatedTasks.append(
|
749
|
+
(
|
750
|
+
f"Das Attribut {attr} kann aus der Abhängigkeit {self.stringify_dependencies(fdsToStringify=[dep])} reduziert werden.",
|
751
|
+
False,
|
752
|
+
)
|
753
|
+
)
|
754
|
+
if not wasDepAlreadyReduced:
|
755
|
+
generatedTasks.append(
|
756
|
+
(
|
757
|
+
f"Die Abhängigkeit {self.stringify_dependencies(fdsToStringify=[dep])} kann nicht reduziert werden.",
|
758
|
+
True,
|
759
|
+
)
|
760
|
+
)
|
761
|
+
else:
|
762
|
+
generatedTasks.append(
|
763
|
+
(
|
764
|
+
f"Die Linksreduktion beinhaltet die Abhängigkeit {self.stringify_dependencies(fdsToStringify=[dep])}.",
|
765
|
+
True,
|
766
|
+
)
|
767
|
+
)
|
768
|
+
|
769
|
+
generatedTasks.append(
|
770
|
+
(
|
771
|
+
f"Es kann nur eine der ursprünglichen funktionalen Abhängigkeiten reduziert werden.",
|
772
|
+
reductionCounter == 1,
|
773
|
+
)
|
774
|
+
)
|
775
|
+
generatedTasks.append(
|
776
|
+
(
|
777
|
+
f"Es können mehr als zwei der ursprünglichen funktionalen Abhängigkeiten reduziert werden.",
|
778
|
+
reductionCounter > 2,
|
779
|
+
)
|
780
|
+
)
|
781
|
+
|
782
|
+
if genEx:
|
783
|
+
print(f"=======================================")
|
784
|
+
print(f"Aussagen zur Linksreduktion (Schritt 1a):")
|
785
|
+
for task in generatedTasks:
|
786
|
+
print(task)
|
787
|
+
print(f"=======================================")
|
788
|
+
|
789
|
+
def isCorrectLeftReduction(self, proposedReduction, verbose: bool = False):
|
790
|
+
"""
|
791
|
+
Checks a given left reduction of self for correctness
|
792
|
+
|
793
|
+
Args:
|
794
|
+
proposedReduction(FunctionalDependencySet): The proposed left reduction of self, the original FDSet
|
795
|
+
verbose(bool): Optional argument for detailed output
|
796
|
+
Return:
|
797
|
+
bool: Is the left reduction correct
|
798
|
+
"""
|
799
|
+
if verbose:
|
800
|
+
print("CHECKING: Is the proposed left reduction of self correct:\n")
|
801
|
+
print("1. Is the amount of dependencies of the solution the same as self:")
|
802
|
+
|
803
|
+
# equal amount of dependencies
|
804
|
+
if len(proposedReduction.dependencies) != len(self.dependencies):
|
805
|
+
if verbose:
|
806
|
+
print(
|
807
|
+
f"FAILURE: The solution had a differing amount of dependencies: \n \t Task: {len(self.dependencies)}\n\tSolution: {len(proposedReduction.dependencies)}"
|
808
|
+
)
|
809
|
+
return False
|
810
|
+
if verbose:
|
811
|
+
print(
|
812
|
+
f"Both the task and the solution have {len(self.dependencies)} dependencies\n"
|
813
|
+
)
|
814
|
+
print(
|
815
|
+
f"2. Checking whether the solution dependencies could result from a task dependency:"
|
816
|
+
)
|
817
|
+
|
818
|
+
# equal right sides of dependencies
|
819
|
+
# left sides are subsets of original left sides
|
820
|
+
for propDep in proposedReduction.dependencies:
|
821
|
+
leftProp, rightProp = propDep
|
822
|
+
if not leftProp:
|
823
|
+
if verbose:
|
824
|
+
print(
|
825
|
+
f"FAIL: One of the dependencies has an empty left side. This cannot happen."
|
826
|
+
)
|
827
|
+
return False
|
828
|
+
isCorrect = False
|
829
|
+
for oriDep in self.dependencies:
|
830
|
+
leftOri, rightOri = oriDep
|
831
|
+
if verbose:
|
832
|
+
print(
|
833
|
+
f"\tComparing solution dependency {leftProp} -> {rightProp} with task dependency {leftOri} -> {rightOri}"
|
834
|
+
)
|
835
|
+
if rightProp == rightOri and leftProp <= leftOri:
|
836
|
+
isCorrect = True
|
837
|
+
if verbose:
|
838
|
+
print(
|
839
|
+
f"\tThe solution dependency can result from the task dependency"
|
840
|
+
)
|
841
|
+
break
|
842
|
+
if not isCorrect:
|
843
|
+
if verbose:
|
844
|
+
print(
|
845
|
+
f"FAILURE: The dependency {leftProp} -> {rightProp} can't result from any task dependency"
|
846
|
+
)
|
847
|
+
return False
|
848
|
+
if verbose:
|
849
|
+
print(f"All solution dependencies can result from the task dependencies\n")
|
850
|
+
print(
|
851
|
+
f"3. Checking whether the closures of all attribute subsets have remained the same:"
|
852
|
+
)
|
853
|
+
|
854
|
+
# check whether attribute closures of all attribute subsets are still the same
|
855
|
+
if not self.isAllAttributeClosuresSame(proposedReduction, verbose):
|
856
|
+
return False
|
857
|
+
if verbose:
|
858
|
+
print(f"4. Checking whether the proposed reduction is complete:")
|
859
|
+
|
860
|
+
# reduction is complete: reduction of the reduction remains the same
|
861
|
+
doubleReduction = proposedReduction.copy()
|
862
|
+
doubleReduction.left_reduction()
|
863
|
+
if not doubleReduction.isIdentical(proposedReduction):
|
864
|
+
if verbose:
|
865
|
+
print(f"FAILURE: The proposed reduction is not complete")
|
866
|
+
return False
|
867
|
+
if verbose:
|
868
|
+
print(f"The proposed reduction is complete\n")
|
869
|
+
print(f"SUCCESS: The left reduction is correct")
|
870
|
+
|
871
|
+
return True
|
872
|
+
|
873
|
+
def isAllAttributeClosuresSame(self, fdsetToCompare, verbose: bool = False):
|
874
|
+
"""
|
875
|
+
check whether attribute closures of all attribute subsets are still the same
|
876
|
+
"""
|
877
|
+
combs = list(self.attribute_combinations(self.attributes))
|
878
|
+
for comb in combs:
|
879
|
+
if verbose:
|
880
|
+
print(f"\tChecking set of attributes: {set(comb)}")
|
881
|
+
if self.get_attr_closure(comb) != fdsetToCompare.get_attr_closure(comb):
|
882
|
+
if verbose:
|
883
|
+
print(
|
884
|
+
f"FAILURE: The closure for the subset {set(comb)} changed from {self.get_attr_closure(comb)} to {fdsetToCompare.get_attr_closure(comb)}"
|
885
|
+
)
|
886
|
+
return False
|
887
|
+
if verbose:
|
888
|
+
print(f"All subsets of attributes have the same closure\n")
|
889
|
+
|
890
|
+
return True
|
891
|
+
|
892
|
+
def completeFDsetToClosure(self, verbose: bool = False):
|
893
|
+
"""
|
894
|
+
generates the closure F^+ of an FD set F
|
895
|
+
"""
|
896
|
+
combs = list(self.attribute_combinations(self.attributes))
|
897
|
+
for comb in combs:
|
898
|
+
if verbose:
|
899
|
+
print(f"\tChecking set of attributes: {set(comb)}")
|
900
|
+
closure = self.get_attr_closure(comb)
|
901
|
+
if verbose:
|
902
|
+
print(f"Closure of {set(comb)} is {closure}")
|
903
|
+
rhs_combs = list(self.attribute_combinations(closure))
|
904
|
+
for rhs_comb in rhs_combs:
|
905
|
+
self.dependencies.append((set(comb), set(rhs_comb)))
|
906
|
+
if verbose:
|
907
|
+
print(f"adding dependency {set(comb)}, {set(rhs_comb)}")
|
908
|
+
return
|
909
|
+
|
910
|
+
def right_reduction(self, verbose: bool = False, genEx: bool = False):
|
911
|
+
"""
|
912
|
+
executes a right reduction on the dependencies from this fdset
|
913
|
+
|
914
|
+
Args:
|
915
|
+
verbose(bool): if True show steps
|
916
|
+
genEx(bool): if True generate exercise tasks in form of sentences that can be either true or false
|
917
|
+
"""
|
918
|
+
if genEx:
|
919
|
+
self.notation = Notation.math
|
920
|
+
|
921
|
+
generatedTasks = []
|
922
|
+
reductionCounter = 0
|
923
|
+
remaining_deps = self.dependencies.copy()
|
924
|
+
while remaining_deps:
|
925
|
+
dep = remaining_deps.pop(0)
|
926
|
+
pre, post = dep
|
927
|
+
wasDepAlreadyReduced = False
|
928
|
+
for attr in sorted(post):
|
929
|
+
self.remove_dependency(pre, post)
|
930
|
+
self.add_dependency(pre, post - {attr})
|
931
|
+
if {attr} <= set(self.get_attr_closure(pre)):
|
932
|
+
if not wasDepAlreadyReduced:
|
933
|
+
generatedTasks.append(
|
934
|
+
(
|
935
|
+
f"Die Abhängigkeit {self.stringify_dependencies(fdsToStringify=[dep])} kann reduziert werden, da die Attributhülle von {post} nicht alle Attribute aus {pre} enthält.",
|
936
|
+
False,
|
937
|
+
)
|
938
|
+
)
|
939
|
+
generatedTasks.append(
|
940
|
+
(
|
941
|
+
f"Das Attribut {attr} kann aus der Abhängigkeit {self.stringify_dependencies(fdsToStringify=[dep])} reduziert werden.",
|
942
|
+
True,
|
943
|
+
)
|
944
|
+
)
|
945
|
+
generatedTasks.append(
|
946
|
+
(
|
947
|
+
f"Das Attribut {attr} kann aus der Abhängigkeit {self.stringify_dependencies(fdsToStringify=[dep])} reduziert werden, da bei Entfernen von {attr} von der rechten Seite der Abhängigkeit die Attributhülle von {pre} immer noch {attr} enthält.",
|
948
|
+
True,
|
949
|
+
)
|
950
|
+
)
|
951
|
+
generatedTasks.append(
|
952
|
+
(
|
953
|
+
f"Das Attribut {attr} kann aus der Abhängigkeit {self.stringify_dependencies(fdsToStringify=[dep])} reduziert werden, da bei Entfernen von {attr} von der rechten Seite der Abhängigkeit die Attributhülle von {post - {attr}} immer noch {attr} enthält.",
|
954
|
+
True,
|
955
|
+
)
|
956
|
+
)
|
957
|
+
reductionCounter += 1
|
958
|
+
else:
|
959
|
+
generatedTasks.append(
|
960
|
+
(
|
961
|
+
f"Die Abhängigkeit {self.stringify_dependencies(fdsToStringify=[dep])} kann mehrmals reduziert werden.",
|
962
|
+
True,
|
963
|
+
)
|
964
|
+
)
|
965
|
+
if verbose:
|
966
|
+
print(
|
967
|
+
f"removed {attr} from rhs of dependency {dep}, new rhs {(post - {attr})}"
|
968
|
+
)
|
969
|
+
post = post - {attr}
|
970
|
+
wasDepAlreadyReduced = True
|
971
|
+
else:
|
972
|
+
generatedTasks.append(
|
973
|
+
(
|
974
|
+
f"Das Attribut {attr} kann aus der Abhängigkeit {self.stringify_dependencies(fdsToStringify=[dep])} reduziert werden.",
|
975
|
+
False,
|
976
|
+
)
|
977
|
+
)
|
978
|
+
self.remove_dependency(pre, post - {attr})
|
979
|
+
self.add_dependency(pre, post)
|
980
|
+
if wasDepAlreadyReduced:
|
981
|
+
generatedTasks.append(
|
982
|
+
(
|
983
|
+
f"Die Abhängigkeit {self.stringify_dependencies(fdsToStringify=[dep])} wird insgesamt zu {self.stringify_dependencies(fdsToStringify=[(pre, post)])} reduziert.",
|
984
|
+
True,
|
985
|
+
)
|
986
|
+
)
|
987
|
+
else:
|
988
|
+
generatedTasks.append(
|
989
|
+
(
|
990
|
+
f"Die Abhängigkeit {self.stringify_dependencies(fdsToStringify=[dep])} kann nicht reduziert werden.",
|
991
|
+
True,
|
992
|
+
)
|
993
|
+
)
|
994
|
+
|
995
|
+
generatedTasks.append(
|
996
|
+
(
|
997
|
+
f"Es kann nur eine der ursprünglichen funktionalen Abhängigkeiten reduziert werden.",
|
998
|
+
reductionCounter == 1,
|
999
|
+
)
|
1000
|
+
)
|
1001
|
+
generatedTasks.append(
|
1002
|
+
(
|
1003
|
+
f"Es können mehr als zwei der ursprünglichen funktionalen Abhängigkeiten reduziert werden.",
|
1004
|
+
reductionCounter > 2,
|
1005
|
+
)
|
1006
|
+
)
|
1007
|
+
|
1008
|
+
if genEx:
|
1009
|
+
print(f"=======================================")
|
1010
|
+
print(f"Aussagen zur Rechtsreduktion (Schritt 1b):")
|
1011
|
+
for task in generatedTasks:
|
1012
|
+
print(task)
|
1013
|
+
print(f"=======================================")
|
1014
|
+
|
1015
|
+
def isCorrectRightReduction(self, proposedReduction, verbose: bool = False):
|
1016
|
+
"""
|
1017
|
+
Checks a given right reduction of self for correctness
|
1018
|
+
|
1019
|
+
Args:
|
1020
|
+
self: an FDSet after left reduction, but before right reduction
|
1021
|
+
proposedReduction(FunctionalDependencySet): The proposed right reduction of self
|
1022
|
+
verbose(bool): Optional argument for detailed output
|
1023
|
+
Return:
|
1024
|
+
bool: Is the right reduction correct
|
1025
|
+
"""
|
1026
|
+
if verbose:
|
1027
|
+
print("CHECKING: Is the proposed right reduction of self correct:\n")
|
1028
|
+
print("1. Is the amount of dependencies of the solution the same as self:")
|
1029
|
+
|
1030
|
+
# equal amount of dependencies
|
1031
|
+
if len(proposedReduction.dependencies) != len(self.dependencies):
|
1032
|
+
if verbose:
|
1033
|
+
print(
|
1034
|
+
f"FAILURE: The solution had a differing amount of dependencies: \n \t Task: {len(self.dependencies)}\n\tSolution: {len(proposedReduction.dependencies)}"
|
1035
|
+
)
|
1036
|
+
return False
|
1037
|
+
if verbose:
|
1038
|
+
print(
|
1039
|
+
f"Both the task and the solution have {len(self.dependencies)} dependencies\n"
|
1040
|
+
)
|
1041
|
+
print(
|
1042
|
+
f"2. Checking whether the solution dependencies could result from a task dependency:"
|
1043
|
+
)
|
1044
|
+
|
1045
|
+
# equal left sides of dependencies
|
1046
|
+
# right sides are subsets of original right sides
|
1047
|
+
for propDep in proposedReduction.dependencies:
|
1048
|
+
leftProp, rightProp = propDep
|
1049
|
+
if not leftProp:
|
1050
|
+
if verbose:
|
1051
|
+
print(
|
1052
|
+
f"FAIL: One of the dependencies has an empty left side. This cannot happen."
|
1053
|
+
)
|
1054
|
+
return False
|
1055
|
+
isCorrect = False
|
1056
|
+
for oriDep in self.dependencies:
|
1057
|
+
leftOri, rightOri = oriDep
|
1058
|
+
if verbose:
|
1059
|
+
print(
|
1060
|
+
f"\tComparing solution dependency {leftProp} -> {rightProp} with task dependency {leftOri} -> {rightOri}"
|
1061
|
+
)
|
1062
|
+
if leftProp == leftOri and rightProp <= rightOri:
|
1063
|
+
isCorrect = True
|
1064
|
+
if verbose:
|
1065
|
+
print(
|
1066
|
+
f"\tThe solution dependency can result from the task dependency"
|
1067
|
+
)
|
1068
|
+
break
|
1069
|
+
if not isCorrect:
|
1070
|
+
if verbose:
|
1071
|
+
print(
|
1072
|
+
f"FAILURE: The dependency {leftProp} -> {rightProp} can't result from any task dependency"
|
1073
|
+
)
|
1074
|
+
return False
|
1075
|
+
if verbose:
|
1076
|
+
print(f"All solution dependencies can result from the task dependencies\n")
|
1077
|
+
print(
|
1078
|
+
f"3. Checking whether the closures of all attribute subsets have remained the same:"
|
1079
|
+
)
|
1080
|
+
|
1081
|
+
# check whether attribute closures of all attribute subsets are still the same
|
1082
|
+
if not self.isAllAttributeClosuresSame(proposedReduction, verbose):
|
1083
|
+
return False
|
1084
|
+
if verbose:
|
1085
|
+
print(f"4. Checking whether the proposed reduction is complete:")
|
1086
|
+
|
1087
|
+
# reduction is complete: reduction of the reduction remains the same
|
1088
|
+
doubleReduction = proposedReduction.copy()
|
1089
|
+
doubleReduction.right_reduction()
|
1090
|
+
if not doubleReduction.isIdentical(proposedReduction):
|
1091
|
+
if verbose:
|
1092
|
+
print(f"FAILURE: The proposed reduction is not complete")
|
1093
|
+
return False
|
1094
|
+
if verbose:
|
1095
|
+
print(f"The proposed reduction is complete\n")
|
1096
|
+
print(f"SUCCESS: The right reduction is correct")
|
1097
|
+
|
1098
|
+
return True
|
1099
|
+
|
1100
|
+
def remove_empty_fds(self, verbose: bool = False, genEx: bool = False):
|
1101
|
+
"""
|
1102
|
+
remove empty fds of form "A → {}" from this fdset
|
1103
|
+
|
1104
|
+
Args:
|
1105
|
+
verbose(bool): if True show steps
|
1106
|
+
genEx(bool): if True generate exercise tasks in form of sentences that can be either true or false
|
1107
|
+
"""
|
1108
|
+
removeCounter = 0
|
1109
|
+
deps_copy = self.dependencies.copy()
|
1110
|
+
for dep in deps_copy:
|
1111
|
+
pre, post = dep
|
1112
|
+
if len(post) == 0:
|
1113
|
+
removeCounter += 1
|
1114
|
+
if verbose:
|
1115
|
+
print(f"removed {dep} because rhs is empty")
|
1116
|
+
self.remove_dependency(pre, post)
|
1117
|
+
|
1118
|
+
if genEx:
|
1119
|
+
print(f"=======================================")
|
1120
|
+
print(f"Aussagen zum Entfernen leerer FDs (Schritt 1c):")
|
1121
|
+
print(
|
1122
|
+
(
|
1123
|
+
f"Es werden zwei Abhängigkeiten entfernt, da sie eine leere rechte Seite haben.",
|
1124
|
+
removeCounter == 2,
|
1125
|
+
)
|
1126
|
+
)
|
1127
|
+
print(
|
1128
|
+
(
|
1129
|
+
f"Es werden zwei Abhängigkeiten entfernt, da sie eine leere linke Seite haben.",
|
1130
|
+
False,
|
1131
|
+
)
|
1132
|
+
)
|
1133
|
+
print(
|
1134
|
+
(f"Es werden mehr als zwei Abhängigkeiten entfernt.", removeCounter > 2)
|
1135
|
+
)
|
1136
|
+
print(f"=======================================")
|
1137
|
+
|
1138
|
+
def isCorrectRemovingEmptyFDs(self, proposedSolution, verbose: bool = False):
|
1139
|
+
"""
|
1140
|
+
Checks a given removing of empty fds of self for correctness
|
1141
|
+
|
1142
|
+
Args:
|
1143
|
+
self: an FDSet after removing empty fds
|
1144
|
+
proposedSolution(FunctionalDependencySet): The proposed solution of removing fds of self
|
1145
|
+
verbose(bool): Optional argument for detailed output
|
1146
|
+
Return:
|
1147
|
+
bool: Is the solution correct
|
1148
|
+
"""
|
1149
|
+
if verbose:
|
1150
|
+
print(
|
1151
|
+
"CHECKING: Is the proposed removal of empty dependencies of self correct:\n"
|
1152
|
+
)
|
1153
|
+
print(
|
1154
|
+
"1. Does the solution have an equal or less amount of dependencies than self:"
|
1155
|
+
)
|
1156
|
+
|
1157
|
+
# Equal amount of dependencies
|
1158
|
+
if len(proposedSolution.dependencies) <= len(self.dependencies):
|
1159
|
+
if verbose:
|
1160
|
+
print(
|
1161
|
+
f"FAILURE: The solution had more dependencies: \n \t Task: {len(self.dependencies)}\n\tSolution: {len(proposedSolution.dependencies)}"
|
1162
|
+
)
|
1163
|
+
if verbose:
|
1164
|
+
print(
|
1165
|
+
f"The proposed solution has equal or less dependencies ({len(proposedSolution.dependencies)}) than self ({len(self.dependencies)})\n"
|
1166
|
+
)
|
1167
|
+
print(
|
1168
|
+
f"2. Checking if every complete dependency in self is in the proposed solution:"
|
1169
|
+
)
|
1170
|
+
|
1171
|
+
# check if the correct dependencies remain
|
1172
|
+
for dep in self.dependencies:
|
1173
|
+
left, right = dep
|
1174
|
+
if right != Set() and (left, right) not in proposedSolution.dependencies:
|
1175
|
+
if verbose:
|
1176
|
+
print(
|
1177
|
+
f"FAILURE: Dependency {left} -> {right} is not in the proposed solution."
|
1178
|
+
)
|
1179
|
+
return False
|
1180
|
+
elif verbose:
|
1181
|
+
print(f"\tThe dependency {left} -> {right} is in the proposed solution")
|
1182
|
+
if verbose:
|
1183
|
+
print(f"All complete dependencies of self are in the proposed solution.\n")
|
1184
|
+
print(f"3. Check if all empty dependencies are removed:")
|
1185
|
+
|
1186
|
+
fds = self.copy()
|
1187
|
+
fds.remove_empty_fds()
|
1188
|
+
if not fds.isIdentical(proposedSolution):
|
1189
|
+
if verbose:
|
1190
|
+
print(f"FAILURE: Not all empty dependencies were removed")
|
1191
|
+
return False
|
1192
|
+
|
1193
|
+
if verbose:
|
1194
|
+
print(f"All empty dependencies have been removed.")
|
1195
|
+
print(f"SUCCESS: The proposed solution is correct.")
|
1196
|
+
|
1197
|
+
return True
|
1198
|
+
|
1199
|
+
def combine_fds(self, verbose: bool = False, genEx: bool = False):
|
1200
|
+
"""
|
1201
|
+
combines fds with equal left sides
|
1202
|
+
|
1203
|
+
Args:
|
1204
|
+
verbose(bool): if True show steps
|
1205
|
+
genEx(bool): if True generate exercise tasks in form of sentences that can be either true or false
|
1206
|
+
"""
|
1207
|
+
if genEx:
|
1208
|
+
self.notation = Notation.math
|
1209
|
+
|
1210
|
+
generatedTasks = []
|
1211
|
+
combined_dependencies = []
|
1212
|
+
combineCounter = 0
|
1213
|
+
deps_copy = self.dependencies.copy()
|
1214
|
+
while self.dependencies:
|
1215
|
+
pre, post = self.dependencies.pop(0)
|
1216
|
+
new_post = post
|
1217
|
+
deps_copy = self.dependencies.copy()
|
1218
|
+
for dep in deps_copy:
|
1219
|
+
left, right = dep
|
1220
|
+
if left == pre:
|
1221
|
+
combineCounter += 1
|
1222
|
+
new_post = new_post | right
|
1223
|
+
if verbose:
|
1224
|
+
print(
|
1225
|
+
f"combined dependencies {dep} and {(pre, post)} to new dependency {(pre, new_post)}"
|
1226
|
+
)
|
1227
|
+
self.remove_dependency(left, right)
|
1228
|
+
generatedTasks.append(
|
1229
|
+
(
|
1230
|
+
f"Die Abhängigkeiten {self.stringify_dependencies(fdsToStringify=[dep])} und {self.stringify_dependencies(fdsToStringify=[(pre, post)])} werden zu {self.stringify_dependencies(fdsToStringify=[(pre, new_post)])} zusammengefasst.",
|
1231
|
+
True,
|
1232
|
+
)
|
1233
|
+
)
|
1234
|
+
generatedTasks.append(
|
1235
|
+
(
|
1236
|
+
f"Die Abhängigkeiten {self.stringify_dependencies(fdsToStringify=[dep])} und {self.stringify_dependencies(fdsToStringify=[(pre, new_post)])} werden zu {self.stringify_dependencies(fdsToStringify=[(pre, post)])} zusammengefasst.",
|
1237
|
+
False,
|
1238
|
+
)
|
1239
|
+
)
|
1240
|
+
combined_dependencies.append((pre, new_post))
|
1241
|
+
self.dependencies = combined_dependencies
|
1242
|
+
generatedTasks.append(
|
1243
|
+
(
|
1244
|
+
f"Es werden an zwei oder mehr Stellen Abhängigkeiten zusammengefasst.",
|
1245
|
+
combineCounter >= 2,
|
1246
|
+
)
|
1247
|
+
)
|
1248
|
+
|
1249
|
+
for dep in self.dependencies:
|
1250
|
+
pre, post = dep
|
1251
|
+
generatedTasks.append(
|
1252
|
+
(
|
1253
|
+
f"Die kanonische Überdeckung enthält die funktionale Abhängigkeit {self.stringify_dependencies(fdsToStringify=[dep])}.",
|
1254
|
+
True,
|
1255
|
+
)
|
1256
|
+
)
|
1257
|
+
for attr in post:
|
1258
|
+
generatedTasks.append(
|
1259
|
+
(
|
1260
|
+
f"Die kanonische Überdeckung enthält die funktionale Abhängigkeit {self.stringify_dependencies(fdsToStringify=[(pre, post - {attr})])}.",
|
1261
|
+
False,
|
1262
|
+
)
|
1263
|
+
)
|
1264
|
+
|
1265
|
+
if genEx:
|
1266
|
+
print(f"=======================================")
|
1267
|
+
print(f"Aussagen zum Zusammenfassen von FDs (Schritt 1d):")
|
1268
|
+
for task in generatedTasks:
|
1269
|
+
print(task)
|
1270
|
+
print(f"=======================================")
|
1271
|
+
|
1272
|
+
def getFDsByLeftSide(self, left: Set, dependencies: list):
|
1273
|
+
filtered = list(filter(lambda dependecy: dependecy[0] == left, dependencies))
|
1274
|
+
return filtered
|
1275
|
+
|
1276
|
+
def isCorrectCombinationOfDependencies(
|
1277
|
+
self, proposedSolution, verbose: bool = False
|
1278
|
+
):
|
1279
|
+
"""
|
1280
|
+
Checks a given removing of empty fds of self for correctness
|
1281
|
+
|
1282
|
+
Args:
|
1283
|
+
self: an FDSet after combination of dependencies
|
1284
|
+
proposedSolution(FunctionalDependencySet): The proposed solution of combination of dependencies of self
|
1285
|
+
verbose(bool): Optional argument for detailed output
|
1286
|
+
Return:
|
1287
|
+
bool: Is the solution correct
|
1288
|
+
"""
|
1289
|
+
if verbose:
|
1290
|
+
print(
|
1291
|
+
"CHECKING: Is the proposed combination of dependencies of self correct:\n"
|
1292
|
+
)
|
1293
|
+
print(
|
1294
|
+
"1. Does the solution have an equal or less amount of dependencies than self:"
|
1295
|
+
)
|
1296
|
+
|
1297
|
+
# Equal amount of dependencies
|
1298
|
+
if len(proposedSolution.dependencies) <= len(self.dependencies):
|
1299
|
+
if verbose:
|
1300
|
+
print(
|
1301
|
+
f"FAILURE: The solution had more dependencies: \n\tTask: {len(self.dependencies)}\n\tSolution: {len(proposedSolution.dependencies)}"
|
1302
|
+
)
|
1303
|
+
if verbose:
|
1304
|
+
print(
|
1305
|
+
f"The proposed solution has equal or less dependencies ({len(proposedSolution.dependencies)}) than self ({len(self.dependencies)})\n"
|
1306
|
+
)
|
1307
|
+
print(
|
1308
|
+
f"2. Checking if every combined dependency in self is in the proposed solution:"
|
1309
|
+
)
|
1310
|
+
|
1311
|
+
# check if the correct dependencies remain
|
1312
|
+
dependencies = self.dependencies.copy()
|
1313
|
+
for dep in proposedSolution.dependencies:
|
1314
|
+
left, originalRight = dep
|
1315
|
+
right = originalRight.copy()
|
1316
|
+
leftDeps = self.getFDsByLeftSide(left, dependencies)
|
1317
|
+
for leftDep in leftDeps:
|
1318
|
+
if leftDep[1] <= originalRight:
|
1319
|
+
dependencies.remove(leftDep)
|
1320
|
+
right -= leftDep[1]
|
1321
|
+
else:
|
1322
|
+
if verbose:
|
1323
|
+
print(
|
1324
|
+
f"FAILURE: Dependecy {left} -> {leftDep[1]} is not part of the combined dependency {left} -> {right}."
|
1325
|
+
)
|
1326
|
+
return False
|
1327
|
+
if verbose:
|
1328
|
+
print(
|
1329
|
+
f"\tThe dependency {left} -> {right} cannot be combined further."
|
1330
|
+
)
|
1331
|
+
if len(right) > 0:
|
1332
|
+
if verbose:
|
1333
|
+
print(
|
1334
|
+
f"FAILURE: Right side {dep[1]} contains too many attributes {right}."
|
1335
|
+
)
|
1336
|
+
return False
|
1337
|
+
if len(dependencies) > 0:
|
1338
|
+
if verbose:
|
1339
|
+
print("FAILURE: Some dependencies have not made it into the solution.")
|
1340
|
+
return False
|
1341
|
+
if verbose:
|
1342
|
+
print(f"SUCCESS: The proposed solution is correct.")
|
1343
|
+
return True
|
1344
|
+
|
1345
|
+
def canonical_cover(self, verbose: bool = False, genEx: bool = False):
|
1346
|
+
"""
|
1347
|
+
determines the canonical cover of this fdset
|
1348
|
+
|
1349
|
+
4 substeps with respective functions
|
1350
|
+
|
1351
|
+
https://git.rwth-aachen.de/i5/teaching/dbis-vl/-/raw/main/6-RelDesign/6-RelationaleEntwurfstheorie.pdf#page=39
|
1352
|
+
|
1353
|
+
Args:
|
1354
|
+
verbose(bool): if True show steps
|
1355
|
+
genEx(bool): if True generate exercise tasks in form of sentences that can be either true or false
|
1356
|
+
"""
|
1357
|
+
self.left_reduction(verbose, genEx)
|
1358
|
+
self.right_reduction(verbose, genEx)
|
1359
|
+
if verbose:
|
1360
|
+
print(f"after right reduction:")
|
1361
|
+
for fd in self.dependencies:
|
1362
|
+
print(fd)
|
1363
|
+
self.remove_empty_fds(verbose, genEx)
|
1364
|
+
self.combine_fds(verbose, genEx)
|
1365
|
+
if verbose:
|
1366
|
+
print(f"canonical cover:")
|
1367
|
+
for fd in self.dependencies:
|
1368
|
+
print(fd)
|
1369
|
+
|
1370
|
+
def isCorrectCanonicalCover(self, proposedSolution, verbose: bool = False):
|
1371
|
+
"""
|
1372
|
+
Checks if a give FunctionalDependencySet is a canonical cover of self
|
1373
|
+
|
1374
|
+
Args:
|
1375
|
+
proposedSolution(FunctionalDependencySet): The proposed canoncial cover of self
|
1376
|
+
verbose(bool): if True shows steps for debugging
|
1377
|
+
Return:
|
1378
|
+
bool: Correct?
|
1379
|
+
"""
|
1380
|
+
if verbose:
|
1381
|
+
print("CHECKING: Is the proposed canonical cover of self correct:\n")
|
1382
|
+
print("1. Do self and proposedSolution have the same closures:")
|
1383
|
+
if not proposedSolution.isAllAttributeClosuresSame(self, verbose=verbose):
|
1384
|
+
if verbose:
|
1385
|
+
print(
|
1386
|
+
"FAILURE: The closures of self and proposedSolution are different."
|
1387
|
+
)
|
1388
|
+
if verbose:
|
1389
|
+
print("2. Checking if the canonical cover is complete:")
|
1390
|
+
|
1391
|
+
doubleCover = proposedSolution.copy()
|
1392
|
+
doubleCover.canonical_cover(verbose=verbose)
|
1393
|
+
if proposedSolution.isIdentical(doubleCover):
|
1394
|
+
if verbose:
|
1395
|
+
print("SUCCESS: The proposedSolution is correct")
|
1396
|
+
return True
|
1397
|
+
|
1398
|
+
if verbose:
|
1399
|
+
print("FAILURE: The proposedSolution is an incomplete canonical cover")
|
1400
|
+
return False
|
1401
|
+
|
1402
|
+
def create_new_fdsets(self, verbose: bool = False, genEx: bool = False):
|
1403
|
+
"""
|
1404
|
+
create fdsets from the dependencies resulting from the canonical cover
|
1405
|
+
|
1406
|
+
Args:
|
1407
|
+
verbose(bool): if True show steps
|
1408
|
+
genEx(bool): if True generate exercise tasks in form of sentences that can be either true or false
|
1409
|
+
|
1410
|
+
Return:
|
1411
|
+
list[FunctionalDependencySet]: list of fdsets created from the dependencies resulting from the canonical cover
|
1412
|
+
"""
|
1413
|
+
if genEx:
|
1414
|
+
self.notation = Notation.math
|
1415
|
+
|
1416
|
+
deps = self.dependencies.copy()
|
1417
|
+
existsDepWithMultipleFDs = False
|
1418
|
+
generatedTasks = []
|
1419
|
+
i = 1
|
1420
|
+
new_fdsets = []
|
1421
|
+
while deps:
|
1422
|
+
tmp = deps.pop(0)
|
1423
|
+
pre, post = tmp
|
1424
|
+
new_attributes = pre | post
|
1425
|
+
new_deps = [tmp]
|
1426
|
+
for dep in deps:
|
1427
|
+
left, right = dep
|
1428
|
+
if left | right <= new_attributes:
|
1429
|
+
new_deps.append(dep)
|
1430
|
+
fds = FunctionalDependencySet(new_attributes, "R" + str(i))
|
1431
|
+
if verbose:
|
1432
|
+
print(f"creating a new fdset with attributes {new_attributes}")
|
1433
|
+
generatedTasks.append(
|
1434
|
+
(
|
1435
|
+
f"Es wird eine neue Relation mit Attributmenge {new_attributes} erstellt.",
|
1436
|
+
True,
|
1437
|
+
)
|
1438
|
+
)
|
1439
|
+
i += 1
|
1440
|
+
for dep in new_deps:
|
1441
|
+
left, right = dep
|
1442
|
+
fds.add_dependency(left, right)
|
1443
|
+
if len(fds.dependencies) > 1:
|
1444
|
+
existsDepWithMultipleFDs = True
|
1445
|
+
generatedTasks.append(
|
1446
|
+
(
|
1447
|
+
f"Es gibt eine Abhängigkeit, die in mehreren Relationen enthalten ist.",
|
1448
|
+
True,
|
1449
|
+
)
|
1450
|
+
)
|
1451
|
+
new_fdsets.append(fds)
|
1452
|
+
|
1453
|
+
if existsDepWithMultipleFDs:
|
1454
|
+
generatedTasks.append(
|
1455
|
+
(
|
1456
|
+
f"Es gibt eine Abhängigkeit, die in mehreren Relationen enthalten ist.",
|
1457
|
+
True,
|
1458
|
+
)
|
1459
|
+
)
|
1460
|
+
else:
|
1461
|
+
generatedTasks.append(
|
1462
|
+
(
|
1463
|
+
f"Es gibt eine Abhängigkeit, die in mehreren Relationen enthalten ist.",
|
1464
|
+
False,
|
1465
|
+
)
|
1466
|
+
)
|
1467
|
+
|
1468
|
+
generatedTasks.append(
|
1469
|
+
(
|
1470
|
+
f"Es wird eine neue Relation pro Abhängigkeit in der kanonischen Überdeckung erstellt.",
|
1471
|
+
True,
|
1472
|
+
)
|
1473
|
+
)
|
1474
|
+
generatedTasks.append(
|
1475
|
+
(
|
1476
|
+
f"Es wird eine neue Relation pro Abhängigkeit in der ursprünglichen Menge funktionaler Abhängigkeiten erstellt.",
|
1477
|
+
False,
|
1478
|
+
)
|
1479
|
+
)
|
1480
|
+
generatedTasks.append(
|
1481
|
+
(
|
1482
|
+
f"Es wird für jeden Schlüsselkandidaten eine neue Relation erstellt.",
|
1483
|
+
False,
|
1484
|
+
)
|
1485
|
+
)
|
1486
|
+
generatedTasks.append(
|
1487
|
+
(
|
1488
|
+
f"Es werden insgesamt drei neue Relationen erstellt.",
|
1489
|
+
len(new_fdsets) == 3,
|
1490
|
+
)
|
1491
|
+
)
|
1492
|
+
generatedTasks.append(
|
1493
|
+
(
|
1494
|
+
f"Die Abhängigkeit {self.stringify_dependencies(fdsToStringify=[fds.dependencies[0]])} ist in keiner neuen Relation enthalten.",
|
1495
|
+
False,
|
1496
|
+
)
|
1497
|
+
)
|
1498
|
+
|
1499
|
+
if genEx:
|
1500
|
+
print(f"=======================================")
|
1501
|
+
print(f"Aussagen zum Hinzufügen von Relationen (Schritt 2):")
|
1502
|
+
for task in generatedTasks:
|
1503
|
+
print(task)
|
1504
|
+
print(f"=======================================")
|
1505
|
+
|
1506
|
+
return new_fdsets
|
1507
|
+
|
1508
|
+
def isCorrectCreationOfNewFDS(self, fdsets: list, verbose: bool = False):
|
1509
|
+
"""
|
1510
|
+
Checks if fdsets were correctly created from canonical cover
|
1511
|
+
|
1512
|
+
Args:
|
1513
|
+
fdsets(list): List of fds
|
1514
|
+
verbose(bool): Debugging tool
|
1515
|
+
Return:
|
1516
|
+
bool: Correct?
|
1517
|
+
"""
|
1518
|
+
if verbose:
|
1519
|
+
print("CHECKING: Is the proposed list of fdsets a correct solution:\n")
|
1520
|
+
print("1. Are there more new sets than dependencies:")
|
1521
|
+
if len(fdsets) > len(self.dependencies):
|
1522
|
+
if verbose:
|
1523
|
+
print(
|
1524
|
+
f"FAILURE: The number of fdsets, {len(fdsets)}, if bigger than the number of dependencies, {len(self.dependencies)}."
|
1525
|
+
)
|
1526
|
+
|
1527
|
+
if verbose:
|
1528
|
+
print("2. Have the dependencies and attributes been correctly transfered:")
|
1529
|
+
depsCopy = [0] * len(self.dependencies)
|
1530
|
+
for fds in fdsets:
|
1531
|
+
for dep in fds.dependencies:
|
1532
|
+
left, right = dep
|
1533
|
+
if not left | right <= fds.attributes:
|
1534
|
+
if verbose:
|
1535
|
+
print(
|
1536
|
+
f"FAILURE: The dependency, {left} -> {right}, has more attributes than {fds.attributes}"
|
1537
|
+
)
|
1538
|
+
return False
|
1539
|
+
if (left, right) in self.dependencies:
|
1540
|
+
i = self.dependencies.index((left, right))
|
1541
|
+
depsCopy[i] = 1
|
1542
|
+
else:
|
1543
|
+
if verbose:
|
1544
|
+
print(
|
1545
|
+
f"FAILURE: The dependency, {left} -> {right}, is not in self.dependencies."
|
1546
|
+
)
|
1547
|
+
return False
|
1548
|
+
if numpy.prod(depsCopy) != 1:
|
1549
|
+
if verbose:
|
1550
|
+
print(
|
1551
|
+
f"FAILURE: {len(depsCopy)} dependencies where not part of the solution."
|
1552
|
+
)
|
1553
|
+
return False
|
1554
|
+
if verbose:
|
1555
|
+
print("SUCCESS: The creation of fdsets was correct.")
|
1556
|
+
return True
|
1557
|
+
|
1558
|
+
def synthesize(self, verbose: bool = False, genEx: bool = False):
|
1559
|
+
"""
|
1560
|
+
synthesize algorithm
|
1561
|
+
|
1562
|
+
see https://git.rwth-aachen.de/i5/teaching/dbis-vl/-/raw/main/6-RelDesign/6-RelationaleEntwurfstheorie.pdf#page=76
|
1563
|
+
and Kemper page 197
|
1564
|
+
|
1565
|
+
Args:
|
1566
|
+
verbose(bool): if True show steps
|
1567
|
+
genEx(bool): if True generate exercise tasks in form of sentences that can be either true or false
|
1568
|
+
|
1569
|
+
Return:
|
1570
|
+
list[FunctionalDependencySet]: list of synthesized fdsets deriving from this fdset
|
1571
|
+
"""
|
1572
|
+
generatedTasks = []
|
1573
|
+
|
1574
|
+
keys = self.find_candidate_keys(verbose=verbose, genEx=genEx)
|
1575
|
+
self.canonical_cover(verbose, genEx)
|
1576
|
+
fdsets = self.create_new_fdsets(verbose, genEx)
|
1577
|
+
fdsets_before_key_relation = fdsets.copy()
|
1578
|
+
fdsets_with_key = self.create_optional_key_scheme(keys, fdsets, verbose, genEx)
|
1579
|
+
reduced_fdsets = self.remove_subset_relations(fdsets_with_key, verbose, genEx)
|
1580
|
+
|
1581
|
+
generatedTasks.append(
|
1582
|
+
(
|
1583
|
+
f"Wir erhalten zum Schluss die gleiche Anzahl an Relationen wie vor Schritt 3.",
|
1584
|
+
len(fdsets_before_key_relation) == len(reduced_fdsets),
|
1585
|
+
)
|
1586
|
+
)
|
1587
|
+
|
1588
|
+
if genEx:
|
1589
|
+
print(f"=======================================")
|
1590
|
+
print(f"Aussagen zum Endergebnis des Synthesealgorithmus (nach Schritt 4):")
|
1591
|
+
for task in generatedTasks:
|
1592
|
+
print(task)
|
1593
|
+
print(f"=======================================")
|
1594
|
+
|
1595
|
+
return reduced_fdsets
|
1596
|
+
|
1597
|
+
def create_optional_key_scheme(
|
1598
|
+
self, keys, fdsets, verbose: bool = False, genEx: bool = False
|
1599
|
+
):
|
1600
|
+
"""
|
1601
|
+
creates a new fdset if key is not subset of any of the existing sets attributes
|
1602
|
+
|
1603
|
+
Args:
|
1604
|
+
verbose(bool): if True show steps
|
1605
|
+
genEx(bool): if True generate exercise tasks in form of sentences that can be either true or false
|
1606
|
+
|
1607
|
+
Return:
|
1608
|
+
list[FunctionalDependencySet]: The list of fdsets with relation that has key candidate of original scheme
|
1609
|
+
"""
|
1610
|
+
generatedTasks = []
|
1611
|
+
|
1612
|
+
for key in keys:
|
1613
|
+
for fds in fdsets:
|
1614
|
+
if set(key) <= fds.attributes:
|
1615
|
+
generatedTasks.append(
|
1616
|
+
(
|
1617
|
+
f"Es braucht keine Schlüsselrelation hinzugefügt werden, da die Attribute von mindestens einem der {len(key) + 1} Schlüsselkandidaten in mindestens einer neuen Relation enthalten sind.",
|
1618
|
+
False,
|
1619
|
+
)
|
1620
|
+
)
|
1621
|
+
generatedTasks.append(
|
1622
|
+
(
|
1623
|
+
f"Wir können eine neue Schlüsselrelation mit der Attributmenge {set(keys[0])} hinzufügen.",
|
1624
|
+
False,
|
1625
|
+
)
|
1626
|
+
)
|
1627
|
+
generatedTasks.append(
|
1628
|
+
(
|
1629
|
+
f"Die Relation mit Attributmenge {fds.attributes} enthält alle Attribute eines Schlüsselkandidaten.",
|
1630
|
+
True,
|
1631
|
+
)
|
1632
|
+
)
|
1633
|
+
if genEx:
|
1634
|
+
print(f"=======================================")
|
1635
|
+
print(
|
1636
|
+
f"Aussagen zum Hinzufügen einer Schlüsselrelation (Schritt 3):"
|
1637
|
+
)
|
1638
|
+
for task in generatedTasks:
|
1639
|
+
print(task)
|
1640
|
+
print(f"=======================================")
|
1641
|
+
return fdsets
|
1642
|
+
generatedTasks.append(
|
1643
|
+
(
|
1644
|
+
f"Es muss eine neue Schlüsselrelation hinzugefügt werden, da keine aktuelle Relation die Attribute eines Schlüsselkandidaten enthält.",
|
1645
|
+
True,
|
1646
|
+
)
|
1647
|
+
)
|
1648
|
+
generatedTasks.append(
|
1649
|
+
(
|
1650
|
+
f"Die hinzuzufügende Schlüsselrelation ist in der Attributmenge eindeutig.",
|
1651
|
+
not (len(keys) > 1),
|
1652
|
+
)
|
1653
|
+
)
|
1654
|
+
key = set(keys[0])
|
1655
|
+
generatedTasks.append(
|
1656
|
+
(
|
1657
|
+
f"Wir können eine neue Schlüsselrelation mit der Attributmenge {key} hinzufügen.",
|
1658
|
+
True,
|
1659
|
+
)
|
1660
|
+
)
|
1661
|
+
fds = FunctionalDependencySet(key, "R" + str(len(fdsets) + 1))
|
1662
|
+
m = len(key) // 2
|
1663
|
+
# fds.add_dependency(sorted(key)[:m], sorted(key)[m:])
|
1664
|
+
fds.add_dependency(key, key)
|
1665
|
+
fdsets.append(fds)
|
1666
|
+
|
1667
|
+
if verbose:
|
1668
|
+
print(f"adding a new fdset with attributes {key}")
|
1669
|
+
if genEx:
|
1670
|
+
print(f"=======================================")
|
1671
|
+
print(f"Aussagen zum Hinzufügen einer Schlüsselrelation (Schritt 3):")
|
1672
|
+
for task in generatedTasks:
|
1673
|
+
print(task)
|
1674
|
+
print(f"=======================================")
|
1675
|
+
|
1676
|
+
return fdsets
|
1677
|
+
|
1678
|
+
def remove_subset_relations(
|
1679
|
+
self, fdsets, verbose: bool = False, genEx: bool = False
|
1680
|
+
):
|
1681
|
+
"""
|
1682
|
+
removes fdsets with attributes that are a subset of another fdset
|
1683
|
+
|
1684
|
+
Args:
|
1685
|
+
verbose(bool): if True show steps
|
1686
|
+
genEx(bool): if True generate exercise tasks in form of sentences that can be either true or false
|
1687
|
+
|
1688
|
+
Return:
|
1689
|
+
list[FunctionalDependencySet]: The reduced list of fdsets
|
1690
|
+
"""
|
1691
|
+
generatedTasks = []
|
1692
|
+
removeCounter = 0
|
1693
|
+
|
1694
|
+
if self.debug:
|
1695
|
+
print(fdsets)
|
1696
|
+
for fds in fdsets.copy():
|
1697
|
+
attributes = fds.attributes
|
1698
|
+
conflict = next(
|
1699
|
+
(
|
1700
|
+
fdset
|
1701
|
+
for fdset in fdsets
|
1702
|
+
if fds.title != fdset.title and attributes <= fdset.attributes
|
1703
|
+
),
|
1704
|
+
None,
|
1705
|
+
)
|
1706
|
+
if conflict is not None:
|
1707
|
+
fdsets.remove(fds)
|
1708
|
+
removeCounter += 1
|
1709
|
+
generatedTasks.append(
|
1710
|
+
(
|
1711
|
+
f"Die Relation mit Attributmenge {fds.attributes} wird entfernt.",
|
1712
|
+
True,
|
1713
|
+
)
|
1714
|
+
)
|
1715
|
+
if len(fds.dependencies) > 0:
|
1716
|
+
generatedTasks.append(
|
1717
|
+
(
|
1718
|
+
f"Es muss die Relation entfernt werden, die die Abhängigkeit {fds.dependencies[0]} enthält.",
|
1719
|
+
True,
|
1720
|
+
)
|
1721
|
+
)
|
1722
|
+
|
1723
|
+
removeCounter += 1
|
1724
|
+
generatedTasks.append(
|
1725
|
+
(
|
1726
|
+
f"Die Relation mit Attributmenge {fds.attributes} wird entfernt.",
|
1727
|
+
True,
|
1728
|
+
)
|
1729
|
+
)
|
1730
|
+
if len(fds.dependencies) > 0:
|
1731
|
+
generatedTasks.append(
|
1732
|
+
(
|
1733
|
+
f"Es muss die Relation entfernt werden, die die Abhängigkeit {fds.dependencies[0]} enthält.",
|
1734
|
+
True,
|
1735
|
+
)
|
1736
|
+
)
|
1737
|
+
if verbose:
|
1738
|
+
print(f"removing the fdset {fds}")
|
1739
|
+
else:
|
1740
|
+
generatedTasks.append(
|
1741
|
+
(
|
1742
|
+
f"Die Relation mit Attributmenge {fds.attributes} wird entfernt.",
|
1743
|
+
False,
|
1744
|
+
)
|
1745
|
+
)
|
1746
|
+
|
1747
|
+
generatedTasks.append(
|
1748
|
+
(
|
1749
|
+
f"Es gibt Relationen, deren Attributmengen Teilmengen voneinander sind.",
|
1750
|
+
removeCounter > 0,
|
1751
|
+
)
|
1752
|
+
)
|
1753
|
+
|
1754
|
+
if genEx:
|
1755
|
+
print(f"=======================================")
|
1756
|
+
print(f"Aussagen zum Entfernen von Relationen (Schritt 4):")
|
1757
|
+
for task in generatedTasks:
|
1758
|
+
print(task)
|
1759
|
+
print(f"=======================================")
|
1760
|
+
|
1761
|
+
return fdsets
|