L’insieme di dipendenze funzionali è definito come l’insieme di tutte le dipendenze funzionali ottenute con gli assiomi di Armstrong.
Assiomi di Armstrong
- se allora
- Assioma della riflessività: se allora
Esempio:
IDdeterminaNome, quindiIDdeterminaNome, ID. - Assioma dell’aumento: se allora
Esempio:
IDdeterminaNome, quindiID, CognomedeterminaNome, Cognome. - Assioma della transitività: se allora
Esempio:
SpeciedeterminaRegnoeRegnodeterminaPhylum, quindiSpeciedeterminaPhylum.
Dimostreremo che , cioè che la chiusura di un insieme di dipendenze funzionali può essere ottenuta a partire da applicando ricorsivamente gli assiomi di Armstrong.
Conseguenze
Da questi assiomi, derivano tre regole necessarie per derivare nuove dipendenze funzionali in da quelle già trovate in , fino a trovarle tutte.
- Regola dell’unione: se e allora
Esempio:
IDdeterminaNomeeIDdeterminaCognome, quindiIDdeterminaNome, Cognome - Della decomposizione: se e allora
Esempio:
IDdeterminaNome, Cognome, quindiIDdeterminaNome. - Della pseudotransitività: se e allora
Esempio: Se
MatricoladeterminaFacoltàeFacoltà, CodiceEsamedeterminaDocentealloraMatricola, CodiceEsamedeterminaDocente.
Dimostrazioni
Regola dell’unione
- Se allora per aumento,
- se allora per aumento,
- dato che , per transitività si ha che .
Regola della decomposizione
- Se allora per riflessività,
- se allora , quindi per transitività.
Regola della pseudotransitività
- Se allora per aumento,
- se allora , quindi per transitività.
Dimostrazione
Per dimostrare che bisogna verificare sia che che .
1. Dimostrazione
Si può procedere per induzione, incrementando di 1 a ogni passo il numero di applicazioni di assiomi di Armstrong.
-
Caso base :
-
Ipotesi induttiva : (assumiamo che tutte le dipendenze prodotte nel passo e nei passi precedenti sono soddisfatte da ogni istanza legale)
-
Passo induttivo: Per l’ipotesi induttiva ogni dipendenza funzionale ottenuta a partire da applicando gli assiomi di Armstrong un numero di volte minore o uguale a è in . Dobbiamo dimostrarlo per un numero di volte uguale a . Si possono presentare tre casi:
- Se è stata ottenuta per riflessività, allora . , quindi
- Se è stata ottenuta per aumento da una dipendenza funzionale , quindi e per qualche insieme di attributi . . Per ipotesi induttiva () da segue , infine da segue .
- Se è stata ottenuta per transitività a due dipendenze funzionali e . . Per ipotesi induttiva (), da segue , da segue .
Abbiamo dimostrato che .
2. Dimostrazione
Supponiamo per assurdo che esista una dipendenza funzionale .
Studiamo una particolare istanza di (ma senza perdita di generalità) per incontrare una contraddizione, dimostrando che .
L’istanza studiata ha solo due tuple, uguali sugli attributi (la chiusura di un insieme di attributi ) e diverse su cioè tutti i rimanenti.
Prima parte: è un’istanza legale
Sia una dipendenza funzionale qualsiasi in e supponiamo per assurdo che non sia soddisfatta da , quindi che non sia legale. Se così fosse, le due tuple di dovrebbero avere gli stessi valori per e diversi valori per , in modo che non determini .
Ciò implica che (i valori uguali tra le due tuple) e (almeno qualche attributo nell’insieme è diverso tra le due tuple).
per il lemma della chiusura di un insieme di attributi.
Abbiamo e , quindi per transitività .
Sempre per il lemma della chiusura, stavolta applicato nel senso opposto, , che contraddice ; quindi, tornando alla premessa iniziale, è legale.
Seconda parte
inizialmente abbiamo supposto per assurdo che , poi abbiamo mostrato che (come definita) è un’istanza legale, quindi soddisfa .
Ricordiamo che per definizione le due tuple sono uguali su , quindi per il lemma coincidono sugli attributi e quindi visto che , allora devono anche coincidere su . Perciò è nella chiusura di () e per il lemma vale , contraddicendo la premessa iniziale.
Implementazione in Python del calcolo “manuale” della chiusura di Armstrong
from itertools import chain, combinations
def powerset(iterable):
s = list(iterable)
return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))
class Dependency:
def __init__(self, determinant: set, determined: set):
self.determinant = determinant
self.determined = determined
def __repr__(self):
return (f"{self.determinant} -> {self.determined}")
def __eq__(self, other: Dependency):
if self.determinant == other.determinant and \
self.determined == other.determined:
return True
return False
def __hash__(self):
return hash((frozenset(self.determinant), frozenset(self.determined)))
def is_trivial(self):
return self.determined.issubset(self.determinant)
def augment(self, attribute: set) -> Dependency:
if isinstance(attribute, str):
attribute = {attribute}
return Dependency(
self.determinant | attribute,
self.determined | attribute
)
def reflexivity(self) -> set[Dependency]:
return {
Dependency(self.determinant, set(subset))
for subset in powerset(self.determinant) if subset
}
def transitivity(self, other: Dependency) -> Dependency:
if self.determined == other.determinant:
return Dependency(self.determinant, other.determined)
else:
return self
class RelationScheme:
def __init__(self, attributes: set, dependencies: set[dependencies]):
self.attributes = attributes
self.dependencies = dependencies
def __repr__(self):
return '\n'.join({str(d) for d in self.dependencies})
def apply_augment(self) -> None:
new_dependencies = {
d.augment(a)
for d in self.dependencies
for a in self.attributes
}
self.dependencies |= new_dependencies
def apply_reflexivity(self) -> None:
new_dependencies = {
reflexive_dep
for d in self.dependencies
for reflexive_dep in d.reflexivity()
}
self.dependencies |= new_dependencies
def apply_transitivity(self) -> None:
new_dependencies = {
d1.transitivity(d2)
for d1 in self.dependencies
for d2 in self.dependencies
}
self.dependencies |= new_dependencies
def armstrong_closure(self) -> None:
prev_n = 0
while prev_n < len(self.dependencies):
prev_n = len(self.dependencies)
self.apply_reflexivity()
self.apply_augment()
self.apply_transitivity()
def filter_non_trivials(self) -> None:
self.dependencies = {d for d in self.dependencies if not d.is_trivial()}Esempio:
r = RelationScheme(
{"CodiceFiscale", "Nome", "Cognome", "Città", "Regione"},
{
Dependency({"CodiceFiscale"}, {"Nome", "Cognome", "Provincia"}),
Dependency({"Provincia"}, {"Regione"}),
}
)
r.armstrong_closure()
total = len(r.dependencies)
r.filter_non_trivials()
non_trivials = len(r.dependencies)
print(*r.dependencies, sep='\n')
print(f'dipendenze non banali: {non_trivials}')
print(f'dipendenze banali: {total-non_trivials}')Produce 560 dipendenze funzionali, di cui 192 banali.
dipendenze non banali: 1077
dipendenze banali: 600