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: ID determina Nome, quindi ID determina Nome, ID.
  • Assioma dell’aumento: se allora Esempio: ID determina Nome, quindi ID, Cognome determina Nome, Cognome.
  • Assioma della transitività: se allora Esempio: Specie determina Regno e Regno determina Phylum, quindi Specie determina Phylum.

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: ID determina Nome e ID determina Cognome, quindi ID determina Nome, Cognome
  • Della decomposizione: se e allora Esempio: ID determina Nome, Cognome, quindi ID determina Nome.
  • Della pseudotransitività: se e allora Esempio: Se Matricola determina Facoltà e Facoltà, CodiceEsame determina Docente allora Matricola, CodiceEsame determina Docente.

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:

    1. Se è stata ottenuta per riflessività, allora . , quindi
    2. Se è stata ottenuta per aumento da una dipendenza funzionale , quindi e per qualche insieme di attributi . . Per ipotesi induttiva () da segue , infine da segue .
    3. 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