Il software
Come già anticipato il software per il calcolo delle regole di associazione
é scritto interamente in linguaggio C.
Se si desidera vedere il codice, visitare la pagina del codice
commentato.
Qui di seguito spiegheremo invece a grandi linee ciò che fa il programma.
Questi sono i principali passagi che segue il programma:
- Controllo dei parametri passati da riga di comando:
l'utente é abilitato a decidere i parametri principali per il calcolo
delle regole di associazione. I parametri accettati dal programma sono:
- "-h", il programma stamperà a video i possibili parametri
e terminerà senza effettuare l'analisi.
- "-s num", il limite minimo del supporto sará posto pari al valore
indicato da num (in millesimi). Se dunque mettessi "-s 200", la soglia
del supporto sará posta pari a 0.2 .
Il valore di default per il supporto é 0.01 .
- "-c num", il limite minimo della confidenza sará posto pari al valore
indicato da num (in millesimi). Se dunque mettessi "-c 400", la soglia
della confidenza sará posta pari a 0.4 .
Il valore di default per la confidenza é 0.5 .
- "-p "host=db2.stat.unipd.it dbname=daniele user=daniele password=daniele" ",
in questo modo vengono modificati i parametri di connessione al database.
- Connessione al database e preparazione dell'analisi:
il programma cerca ora di connettersi al database. Per fare questo utilizza alcune funzioni
fornite da PostgreSQL:
- PGconn *PQconnectdb(const char *conninfo);
Questa funzione serve ad aprire una nuova connessione con il
database per poter "dialogare" con esso.
La funzione vuole come parametro una stringa del tipo indicato poco sopra
("-p "host=...."") e restituisce un puntatore ad una variabile di tipo
PGconn.
- ConnStatusType PQstatus(const PGconn *conn);
Questa funzione serve a controllare lo stato corrente della connessione
e il valore di ritorno puó essere CONNECTION_OK o CONNECTION_BAD.
- void PQfinish(PGconn *conn);
Questa funzione serve a chiudere la connessione con il server e a
liberare la memoria usata dall'oggetto conn.
- PGresult *PQexec(PGconn *conn, const char *command);
Questa funzione serve a sottomettere un comando al server e
attende il risultato della richiesta.
- int PQntuples(const PGresult *res);
Questa funzione ritorna il numero di righe presenti nel
risultato della query.
- ExecStatusType PQresultStatus(const PGresult *res);
Questa funzione serve a controllare che il comando sottomesso dalla
funzione PQexec abbia avuto successo. I valori di ritorno sono
molteplici, ma quelli utilizzati dal programma sono PGRES_COMMAND_OK
(per i comandi che non ritornano dati, come i "declare ...") e
PGRES_TUPLES_OK (per i comandi che ritornano delle righe, come
i "fetch all ..." o i "select ...").
- void PQclear(PGresult *res);
Questa funzione serve a liberare la memoria allocata da un
risultato di una query.
- char *PQerrorMessage(const PGconn *conn);
Questa funzione ritorna il messaggio di errore generato più
recentemente da una qualsiasi operazione su PostgreSQL.
Per maggiori informazioni sulle funzioni e sui differenti tipi di variabili di PostgreSQL,
vedere la documentazione disponibile sul sito
al capitolo "Client Interfaces, libpq - C Library".
- Calcolo delle regole di associazione:
Una volta che mi sono connesso al database, devo "dialogare" con esso per avere le informazioni
che utilizzerò nel calcolo delle regole di associazione.
Il calcolo avviene per mezzo dell'algoritmo Apriori, e si può dividere in due passi:
- Conta a livello 1:
Innanzi tutto devo calcolare quante volte un determinato prodotto compare nelle transazioni.
Le informazioni che acquisisco mi serviranno sia per l'analisi di item di lughezza 1
(da qui la "conta a livello 1"), sia per il calcolo dei valori di soglia degli item
dei passi successivi, sia per la presentazione dei risultati nell'output (che dovranno essere
comprensibili e già "decodificati"). I dati raccolti in questo passo saranno dunque
conservati in una struttura apposita organizzata come un albero binario, con, all'interno di
ciascun nodo, le informazioni relative al codice del prodotto (un intero), alla sua descrizione
(tramite una stringa) e alla frequenza con cui il prodotto si presenta nelle transazioni.
Troviamo in questo passaggio un'altra funzione appartenente alla libreria di PostgreSQL:
- char *PQgetvalue(const PGresult *res,int row_number,int column_number);
Questa funzione serve ad estrarre (sotto forma di stringa) il valore della "column_number"
colonna della "row_number" tupla presente nel risultato res (precedentemente ottenuto come
valore di ritorno della funzione PQexec).
Inoltre troviamo anche alcune funzioni esclusive del "livello 1":
- struct liv1 *aggliv1(struct liv1 *liv,int l,PGconn *conn2);
Questa funzione si occupa di sistemare nella struttura ad albero binario i singoli elementi
come descritto qui sopra.
- void taglia(struct liv1 *liv,int livello);
Questa funzione si occupa di effettuare una prima "potatura" dell'albero binario: si occupa
infatti principalmente di cercare quegli elementi che hanno una frequenza di comparsa nel
database superiore alla soglia voluta e (solo per questi) chiama la funzione cuci
(descritta qui sotto) e la funzione insout.
- void cuci(struct liv1 *r1,int a,int livello);
Lo scopo principale di questa funzione é quello di trovare un secondo elemento "b" da
associare ad "a" (parametro della funzione) che abbia una frequenza maggiore della soglia,
per poter analizzare nel passo successivo l'item composto dall'unione dei due elementi.
Quando viene trovato un secondo elemento adatto a tale scopo, viene chiamata la funzione scrivi2
(descritta più sotto).
- struct h3 *insout(int liv,int a,int fr,struct h3 *out);
Questa funzione si occupa di inserire in una lista "stabile" di output (descritta meglio in
seguito) gli elementi (gli item di livello 1) con frequenza di comparsa
nelle transazioni maggiore della soglia.
- void scrivi2(int a,int n);
Questa funzione si occupa di memorizzare nella struttura rooth3 (descritta meglio in seguito)
gli item di lunghezza 2 trovati come possibili candidati per l'analisi al passo successivo.
- Conta a livello n(>1):
Una volta che ho effettuato l'analisi a livello 1, ho alcune strutture da utilizzare per continuare
il mio calcolo:
- struct h3 *rooth3;
Da questo puntatore é possibile accedere ad un hash tree in cui sono registrati
gli item-set candidati per essere rilevanti nell'analisi.
- struct h3 *outh3;
In questa struttura, che, come la precedente, é organizzata come un hash tree,
vengono memorizzati gli item-set rilevanti trovati nel corso
dell'analisi che verranno poi forniti in output.
Per mezzo di un ciclo while (che continua finché ho item-set candidati ad essere rilevanti)
procedo quindi nell'analisi. É da notare (e lo faremo con maggiore cura nelle successive pagine
della documentazione) come ogni volta che prenderemo in considerazione un insieme di item di
lunghezza maggiore, dovremo rileggere tutte le transazioni per calcolare la frequenza di comparsa
dei singoli item.
Le funzioni che troviamo in questo secondo passo sono le seguenti:
- struct transaz *prenditr(PGresult *res,struct transaz *t);
Questa funzione ha il semplice scopo di estarre dalla base di dati una transazione.
- void conta(struct transaz *t,struct h3 *n,int i,int l,int dove);
Questa funzione serve ad effettuare il conteggio degli item-set frequenti come spiegato
nella figura qui sotto.

- void incrementa(struct transaz *t,struct transaz *l);
Questa funzione (chiamata dalla funzione conta), serve solo ad incrementare gli
item set quando presenti in una transazione.
- struct h3 *unisci(struct h3 *z,int liv,struct h3 *r,int lcorr);
Questa funzione serve ad identificare gli item-set candidati ad essere frequenti per
i passi successivi.
- struct h3 *copia(struct h3 *r,struct transaz *a,struct transaz *b,int liv,int lcorr);
Questa funzione (chiamata dalla funzione unisci) serve a scrivere effettivamente gli item-set
candidati ad essere frequenti, unendo due item-set frequenti di lunghezza n-1 per ottenerne uno
di lunghezza n.
- struct h3 *scriviout(struct transaz *a,int liv,int lcorr,struct h3 *o);
Questa funzione (chiamata dalla funzione unisci) é analoga alla insout nel caso
della conta a livello 1: si occupa di inserire nell'hash tree "stabile" di output outh3
gli item di livello n con frequenza di comparsa nelle transazioni maggiore della soglia.
- struct h3 *taglia2(struct h3 *r,int liv,struct h3 *o,int lcorr);
- int cerca(struct transaz *f,int liv,struct h3 *o);
- int cerca2(struct transaz *f2,struct h3 *o,int liv);
Queste funzioni si occupano di tagliare un item-set candidato frequente al livello n se anche
solo uno dei suoi sotto insiemi di grandezza n-1 non risulta frequente.
- struct h3 *pulisci(struct h3 *t,struct h3 *r);
- void pul(struct h3 *t);
- struct h3 *isci(struct h3 *r);
Queste funzioni si occupano di "ripulire" da eventuali liste o transazioni vuote le
principali strutture utilizzate.
É da notare come i due principali elementi di semplificazione del calcolo sono determinati
dai tagli che imponiamo nell'identificazione degli item-set candidati ad essere frequenti al passo
successivo. In primo luogo (tramite le funzioni unisci e copia) prendiamo in considerazione
solamente gli item-set di n elementi creati unendo due item-set di n-1 elementi, di cui i primi n-2
(ordinati, naturalmente) sono uguali. In secondo luogo (per mezzo delle funzioni taglia2, cerca e
cerca2) tagliamo un item-set di n elementi se anche solo un suo sotto insieme di item di grandezza
n-1 non é frequente.
- Disconnessione dal Database: Finita l'analisi, il programma, prima di continuare, si sconnette dal
database e libera la memoria allocata per la connessione e per l'interrogazione del database stesso.
- Scrittura dell'output: Il programma si occupa ora della scrittura dell'output. La scrittura viene fatta
su due file, uno nominato "supp.html", in cui vengono descitti i supporti calcolati
(vedi esempio) e uno nominato "conf.html", in cui vengono mostrate le regole
(con rispettivi supporti e confidenze) calcolate (vedi esempio).
Le principali strutture utilizzate in questa fase sono:
- struct liv1 *root1;
In questa struttura nella "conta a livello 1" sono stati memorizzati tutti i singoli elementi
trovati, con le rispettive spiegazioni (sotto forma di stringa). Ogni volta che,
durante la scrittura, il programma troverà un elemento (sotto forma di numero)
da stampare, verrà interrogato questo albero binario per una piú chiara
presentazione dei risultati.
- struct supp *sup;
Questa struttura, come la seguente, é organizzata come un "albero binario esteso",
cioè come un albero binario ordinato per supporto, che tenga in considerazione
la diversità di due elementi che hanno lo stesso valore della variabile di
interesse.
- struct trin *tri;
Questa struttura é organizzata come la precedente (da cui il nome: tri, da "albero
trinario"), ma serve a ordinare le regole di associazione in base alla confidenza in
previsione della stampa del file conf.html .
- struct h3 *outh3;
Questa struttura (già descritta in precedenza) é ad hash tree e
contiene gli item-set rilevanti trovati nel corso dell'analisi.
Le funzioni utilizzate per l'organizzazione e la presentazione dell'output sono le seguenti:
- struct h3 *suppconf(struct h3 *o);
Questa funzione si occupa di trovare le transazioni memorizzate in outh3 e di chiamare
le funzioni inss e insc (descritte sotto).
- struct supp *inss(struct transaz *l,struct supp *s);
Questa funzione si occupa di inserire gli item-set frequenti nella struttura sup
(dopo averne calcolato il supporto).
- void insc(struct transaz *l);
Questa funzione si occupa, grazie anche alle funzioni estrai e cercaconf,
di cercare sotto-item-set di item set frequenti e di inserirli nella struttura tri, se
la confidenza calcolata supera il valore posto come soglia.
- int **estrai(int n,int *lista,int grand,int **p,int *corr,int ora);
Questa funzione serve per estrarre da un item-set tutti i possibili sotto-item-set
per poterne poi calcolare il valore di confidenza.
- void cercaconf(int **p,struct transaz *l);
Questa funzione serve per calcolare il valore di confidenza dei possibili sotto-item-set
e decidere quindi se la regola trovata é interessante o meno.
- float cercaconf2(int *da,struct h3 *o,int liv);
Questa funzione si occupa nella pratica di cercare il supporto di un sotto-item-set trovato
per poter calcolare la confidenza di una certa regola.
- struct trin *insconf(int *pk,int *a,float suppda,float supptot,float conf,struct trin *t);
Questa funzione viene chiamata da cercaconf ogni qual volta la regola trovata risulta
interessante per inserire un nuovo elemento nella struttura tri.
- int intlen(int *s);
Questa é una semplice funzione di utilità utilizzata per conoscere la lunghezza
di un array di interi.
- struct supp *scrivisupp(FILE *file,struct supp *sup,struct liv1 *r1);
Questa funzione serve per trasferire su file i dati memorizzati nella struttura sup.
- void scriviconf(FILE *file,struct trin *tri,struct liv1 *root1);
Questa funzione é analoga alla precedente per quel che riguarda la
scrittura del file conf.html utilizzando i dati memorizzati nella struttura tri.
- char *trovaspiegaz(int n,struct liv1 *r,char *s);
Questa funzione é utilizzata dalle due precedenti per esplorare la struttura root1
per ricercare la spiegazione di un elemento al momento della scrittura su file.
Si ricorda che il codice é stato testato sotto Linux.
Per la compilazione del codice ho utilizzato il compilatore standard gcc con
la seguente stringa di comando: "gcc apriori.c -o apriori -lpq" (dopo aver installato
le librerie necessarie alla connessione e al dialogo con il database PostgreSQL,
disponibili gratuitamente sul sito del DBMS).
Per leggere il codice C sviluppato, visualizzare la pagina del codice
commentato.
Data creazione: 17 Settembre 2010
Data ultima modifica: 30 Dicembre 2012