-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdecisionTree.cpp
More file actions
581 lines (509 loc) · 20.6 KB
/
Copy pathdecisionTree.cpp
File metadata and controls
581 lines (509 loc) · 20.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
#include "decisionTree.h"
#include "list.h"
using namespace d_Tree;
struct d_Tree::decisionArch {
string var; //variabile su cui effettuo il confronto
string operand; //operatore
string type; //qualtitativo o quantitativo
};
//l'albero di decisione viene implementato mediante albero generico con struttura primo figlio-prossimo fratello
struct d_Tree::Node {
Label info;
decisionArch condition ; //specifica la condizione per arrivare a tale nodo, partendo dal padre.
Node *firstChild;
Node *nextSibling;
};
/*******************************************************************************************/
/**************************************FUNZIONI AUSILIARIE**********************************/
/*******************************************************************************************/
// crea un nodo con etichetta l, e restituisce il puntatore ad esso
decisionTree createNode(const Label l, const decisionArch a)
{
decisionTree t = new Node;
t->info = l;
t->condition = a;
t->firstChild = t->nextSibling = emptyTree;
return t;
};
// restituisce il puntatore al nodo dell'albero t, etichettato con la Label l
decisionTree getNode(const Label l, const decisionTree& t)
{
if (isEmpty(t) || l==emptyLabel) // caso albero o etichetta vuoti
return emptyTree;
if (t->info == l) // caso etichetta uguale a quella ricercata
return t;
//Chiamata ricorsiva di getNode su ciascuno dei figli di t, finché una delle chiamate non restituisce un valore diverso da emptyTree
decisionTree auxT = t->firstChild;
decisionTree resNode;
while (auxT != emptyTree){
resNode = getNode(l, auxT);
if (resNode == emptyTree) // non ho trovato cercando in questo sottoalbero, devo proseguire la scansione dei fratelli
auxT = auxT->nextSibling;
else // ho trovato: lo restituisco
return resNode;
}
return emptyTree; // se arrivo fino a qui, vuole dire che al termine di una ricerca esaustiva nell'albero il nodo non è stato trovato
};
//restituisce true se il nodo puntato da t ha un figlio con l'etichetta l
bool hasChildWithLabel(const Label l, const decisionTree& t)
{
if (isEmpty(t)) return false;
decisionTree child = t->firstChild;
while (!isEmpty(child)){
if (child->info == l)
return true;
else
child = child->nextSibling;
}
return false;
}
//analizza una stringa e stabilisce se si tratta di un numero
bool isNumber(const string& str)
{
for(int i=0; i<str.length(); i++)
if(!isdigit(str[i]))
return false;
return true;
}
//controlla la dimensione della condizione, con la relativa variabile
//dev'essere necessariamente >=2, se vi è un solo operatore dev'esserci almeno un altro carattere con cui effettuare un confronto
Error controlDim(const string str)
{
int dim=str.length();
if(dim<2)
return d_Tree::FAIL;
return OK;
}
//ritorna FAIL se il char della condizione non rientra tra quelli ammessi, OK altrimenti
Error controlCondition(const char c)
{
if(c==33 || (c<60 || c>62)) //sono accettati solo {=, !=, <, >, <=, >=}, verifico mediante codifica ASCII
return d_Tree::FAIL;
return d_Tree::OK;
}
//decodifica la condizione, inserendo i dati e restituendo la struct decisionArch con i relativi valori
decisionArch insertCondition(const string& str)
{
decisionArch a;
string strAux=str; //copia per eventuali modifiche
if( strAux == emptyCond ){ //questo caso si verifica di norma alla radice
a.var=emptyCond; //assegno valori simbolici, significano che non è presente alcuna condizione per tale nodo
a.operand=emptyCond;
return a;
}
//controllo i primi 2 char, costituiscono il possibile operatore
//per valori quali {!,=,<,>}, inserisco direttamente nel campo operand
//esclude tutti i casi in cui tali valori sono posizionati al secondo posto nella stringa, tranne nel caso dell'=
//in tal modo includo anche {!=,<=,>=}
for(int i=0; i<2 && controlCondition(strAux[i])==d_Tree::OK ;i++){
switch(strAux[i]){
case '!':
if(i==0)
a.operand=strAux[i];
break;
case '<':
if(i==0)
a.operand=strAux[i];
break;
case '>':
if(i==0)
a.operand=strAux[i];
break;
case '=':
if(i==0)
a.operand=strAux[i];
else
a.operand.push_back(strAux[i]);
break;
}
}
//riempio il campo variabile della struct, con i restanti caratteri
a.var=strAux.substr(a.operand.length());
if(isNumber(a.var))
a.type = "quantitativo";
else
a.type = "qualitativo";
return a;
}
int depth(const decisionTree& t){
if(t==emptyTree)
return 0;
if(t->firstChild != emptyTree)
return depth(t->firstChild)+1;
else if(t->firstChild==emptyTree && t->nextSibling!=emptyTree)
depth(t->nextSibling);
return 0;
}
//rimuove dalla label tutto ciò che si trova dopo il simbolo "_"
Label semplifyLabel(const Label& l){
Label aux=l;
for(int i=0; i<aux.length(); i++){
if(aux[i]=='_')
aux.erase(i, aux.length());
}
return aux;
}
//Funzione ausiliaria per gestire la stampa delle variabili
void print_dtAux(const decisionTree& t, liste::List& l)
{
if (isEmpty(t))
return;
if(!liste::member(semplifyLabel(t->info), l) && t->firstChild!=emptyTree)
liste::addCell(semplifyLabel(t->info), l);
decisionTree auxT = t->firstChild;
while (!isEmpty(auxT)) {
print_dtAux(auxT, l);
auxT = auxT->nextSibling;
}
};
/****************************************************************************************************/
/*****************************************FUNZIONI DEL TDD*******************************************/
/****************************************************************************************************/
//restituisce un albero decisionale vuoto
decisionTree d_Tree::createEmpty()
{
return emptyTree;
};
// restituisce true se l'albero decisionale è vuoto, false altrimenti
bool d_Tree::isEmpty(const decisionTree& t)
{
return (t==emptyTree);
};
//restituisce true se il nodo etichettato con la label l appartiene all'albero t e false altrimenti
bool d_Tree::member(const Label l, const decisionTree& t)
{
if (isEmpty(t)) // se l'albero e' vuoto, restituisco false
return false;
if (t->info == l) //etichetta trovata, restituisco true
return true;
//Chiamata ricorsiva di member su ciascuno dei figli di t, finché una delle chiamate non restituisce un valore diverso da false
decisionTree auxT = t->firstChild;
while (auxT != emptyTree) {
if (!member(l, auxT)) // non ho trovato cercando in questo sottoalbero, devo proseguire la scansione dei fratelli
auxT = auxT->nextSibling;
else // ho trovato: restituisco true
return true;
}
return false; // se arrivo fino a qui, vuole dire che al termine di una ricerca esaustiva nell'albero il nodo non è stato trovato
}
//aggiunge un nodo etichettato labelOfNodeToAdd, come figlio di labelOfNodeInTree, non sono ammessi duplicati
Error d_Tree::addNode(const Label labelOfNodeInTree, const Label labelOfNodeToAdd, string cond, decisionTree& t)
{
decisionArch a = insertCondition(cond); //la Add riceve una condizione come stringa da input, la decodifca inserendo nella relativa struct i valori
if ((labelOfNodeInTree == emptyLabel) && isEmpty(t)){ // labelOfNodeInTree è l'etichetta vuota e l'albero è vuoto
t = createNode(labelOfNodeToAdd, a); //attribuisco una radice all'albero precedentemente vuoto
return OK;
}
if (member(labelOfNodeToAdd, t)) // nell'albero esiste già un nodo con etichetta labelOfNodeToAdd
return FAIL;
decisionTree auxT = getNode(labelOfNodeInTree, t); // recupero il puntatore al nodo dell'albero che ha etichetta labelOfNodeInTree
if (auxT == emptyTree) // nell'albero non esiste un nodo con etichetta labelOfNodeInTree
return FAIL;
else{ // ho trovato il nodo auxT a cui aggiungere il figlio
decisionTree child = createNode(labelOfNodeToAdd, a); // creo un nodo child con l'etichetta labelOfNodeToAdd
child->nextSibling = auxT->firstChild; // il primo fratello di child sarà quello che era il primo figlio di auxT
auxT->firstChild = child; // child diventa il primo figlio di auxT
}
return OK;
};
//FUNZIONI AUSILIARIE PER IMPLEMENTARE LA DELETE
// deleteChild: funzione ausiliaria, viene chiamata solo se si e' verificato che il nodo etichettato con l e' uno dei figli di t
void deleteChild(const Label l, decisionTree& t)
{
decisionTree auxT = t->firstChild; // so che il firstChild c'é perché chiamo deleteChild solo se ho già verificato qs condizione
decisionTree prev = createEmpty(); // il prev all'inizio e' vuoto
while(auxT->info != l){
prev = auxT;
auxT = auxT->nextSibling;
}
// quando esco da questo while, auxT punta al nodo da cancellare
decisionTree lastSibling = auxT;
while(!isEmpty(lastSibling->nextSibling))
lastSibling = lastSibling->nextSibling;
// quando esco da questo while, lastSibling punta al fratello più a destra del nodo da cancellare
lastSibling->nextSibling = auxT->firstChild; // attacco i figli del nodo da cancellare al suo ultimo fratello
if (isEmpty(prev)) // se non c'é nessun fratello precedente, il nodo da rimuovere e' il primo: devo cambiare il puntatore al firstChild nel padre
t->firstChild = (t->firstChild)->nextSibling;
else // altrimenti, devo "saltare" il nodo da rimuovere nella catena dei fratelli
prev->nextSibling = auxT->nextSibling;
delete auxT; // in ogni caso, alla fine dealloco il nodo da rimuovere
}
//funzione ausiliaria, rimuove dall'albero il nodo etichettato con la Label l, se esiste
Error deleteElemAux(const Label l, decisionTree& t)
{
if (isEmpty(t))
return FAIL; // se t e' vuoto non c'e' niente da cancellare
if (hasChildWithLabel(l, t)){ // se t è il padre del nodo da rimuovere
deleteChild(l, t); // rimuovo il figlio di t etichiettato con l
return OK; // e restituisco OK
}
decisionTree child = t->firstChild; // altrimenti richiamo ricorsivamente sui figli di t finche' o cancello, o non ci sono piu' figli da esplorare
while (!isEmpty(child)){
if (deleteElemAux(l, child) == OK)
return OK;
else
child = child->nextSibling;
}
return FAIL;
}
// deleteElem (versione ricorsiva) rimuove dall'albero il nodo etichettato con la Label l
// e collega al padre di tale nodo tutti i suoi figli
// Restituisce FAIL se si tenta di cancellare la radice e questa ha
// dei figli (non si saprebbe a che padre attaccarli) oppure se non esiste
// un nodo nell'albero etichettato con la Label; cancella e restituisce OK altrimenti
Error d_Tree::deleteNode(const Label l, decisionTree& t)
{
if(!isEmpty(t) && t->info == l){ // il nodo da rimuovere e' la radice; si puo' rimuovere solo se non ha figli
if(t->firstChild == emptyTree){ // posso rimuovere la radice solo se non ha figli, nel qual caso
delete t; // dealloco
t = emptyTree; // e t diventa l'albero vuoto
}
else
return FAIL; // altrimenti non posso rimuoverla e restituisco FAIL
}
return deleteElemAux(l, t); // se sono arrivato fino a qui senza uscire dalla funzione, chiamo la funzione ausiliaria, che non fa i controlli relativi al caso "radice"
};
Error d_Tree::set(const Label l1, const Label l2, const string cond, decisionTree& t)
{
if(!member(l1, t))
return FAIL;
decisionTree auxT=getNode(l1,t);
auxT->info=l2;
if(auxT->condition.operand != emptyCond){
decisionArch a=insertCondition(cond); //inserisco la nuova condizione solo se non sto oeprando sulla radice
auxT->condition=a;
}
return OK;
};
Error d_Tree::makePrediction(decisionTree& t)
{
decisionTree auxT=t; //albero ausiliario per muovermi tra i nodi
string val;
bool found;
decisionTree child=t; //serve per mantenere un puntatore al nodo su cui opero
while(auxT->firstChild->firstChild!=emptyTree && child!=emptyTree){ //ciclo su tutta la profondità dell'albero
cout << semplifyLabel(auxT->info) << ":";
cin >> val;
child=auxT->firstChild;
do{
found=false;
if(child->condition.operand == "="){
if(child->condition.var==val){
auxT=child;
found=true;
}
else
child=child->nextSibling;
}
else if(child->condition.operand == ">"){
if(val > child->condition.var){
auxT=child;
found=true;
}
else
child=child->nextSibling;
}
else if(child->condition.operand == "<"){
if(val < child->condition.var){
auxT=child;
found=true;
}
else
child=child->nextSibling;
}
else if(child->condition.operand == "!="){
if(child->condition.var != val){
auxT=child;
found=true;
}
else
child=child->nextSibling;
}
else if(child->condition.operand == ">="){
if(val >= child->condition.var ){
auxT=child;
found=true;
}
else
child=child->nextSibling;
}
else if(child->condition.operand == "<="){
if(val <= child->condition.var){
auxT=child;
found=true;
}
else
child=child->nextSibling;
}
}
while(!found && child!=emptyTree);
}
if(child == emptyTree)
cout << "La predizione non può avere luogo in esiste un nodo per il quale non c'è un arco percorribile.\n";
else
cout << semplifyLabel(child->info) << ": " << child->firstChild->condition.var << endl;
return OK;
}
Error d_Tree::makePredictionSet(decisionTree& t)
{
decisionTree auxT=t;
string val;
bool found;
decisionTree child=t;
int cont=0;
liste::List l=liste::createEmpty(); //utilizzo una lista per salvare le variabili dell'albero
print_dtAux(t,l); //memorizza nella lista tutte le variabili
liste::List auxList=l;
while(auxList->next!=auxList->prev){
cout << auxList->next->info;
liste::deleteCell(auxList);
cout << " ";
cont++;
}
cout << endl;
string predictionVar = liste::deleteCell(l); //prelevo l'elemento per cui non è richiesto inserimento da input
for(int i=cont; i>0; i--){ //inserisco nella lista i valori letti da input
cin >> val;
liste::addCell(val, l);
}
while(auxT->firstChild->firstChild!=emptyTree && child!=emptyTree && !liste::isEmpty(l)){
child=auxT->firstChild;
found=false;
if(found==false)
val=liste::deleteCell(l);
do{
if(child->condition.operand == "="){
if(child->condition.var==val){
auxT=child;
found=true;
}
else
child=child->nextSibling;
}
else if(child->condition.operand == ">"){
if(val > child->condition.var){
auxT=child;
found=true;
}
else
child=child->nextSibling;
}
else if(child->condition.operand == "<"){
if(val < child->condition.var){
auxT=child;
found=true;
}
else
child=child->nextSibling;
}
else if(child->condition.operand == "!="){
if(child->condition.var != val){
auxT=child;
found=true;
}
else
child=child->nextSibling;
}
else if(child->condition.operand == ">="){
if(val >= child->condition.var ){
auxT=child;
found=true;
}
else
child=child->nextSibling;
}
else if(child->condition.operand == "<="){
if(val <= child->condition.var){
auxT=child;
found=true;
}
else
child=child->nextSibling;
}
}
while(!found && child!=emptyTree);
}
if(child == emptyTree)
cout << "La predizione non può avere luogo in esiste un nodo per il quale non c'è un arco percorribile.\n";
else
cout << predictionVar << ": " << child->firstChild->condition.var << endl;
return OK;
};
/**********************************FUNZIONI PER INPUT ED OUTPUT************************************/
// Funzione ausiliaria per la visualizzazione strutturata
void printTree(const decisionTree& t, int depth)
{
if (isEmpty(t)) return;
string indent = "--";
// Inserisco indentazione corrispondente alla profondita' raggiunta
for (int i=0; i<depth; i++)
cout << indent;
// Visualizzo il contenuto informativo associato a t
cout << "(" << t->info ;
if(t->condition.operand == emptyCond)
cout<< ")" << endl;
else
cout << "," << t->condition.operand << t->condition.var << ")" << endl;
// Chiamata ricorsiva di printTree su ciascuno dei figli di t (profondita' incrementata di 1)
decisionTree auxT = t->firstChild;
while (!isEmpty(auxT)) {
printTree(auxT, depth+1);
auxT = auxT->nextSibling;
}
}
void print(const d_Tree::decisionTree& t)
{
printTree(t, 0);
};
void d_Tree::print_dtVariables(const d_Tree::decisionTree& t)
{
if(d_Tree::isEmpty(t)){ //caso albero vuoto
cout << "L'albero risulta vuoto\n";
return;
}
liste::List l=liste::createEmpty(); //utilizzo una coda per salvare le variabili dell'albero
print_dtAux(t,l);
string e;
while(!liste::isEmpty(l)){
e=liste::deleteCell(l);
cout << e << " " ;
}
cout << endl;
};
d_Tree::decisionTree readFromStream(istream& str, d_Tree::decisionTree& t)
{
string line;
Label rootLabel, fatherLabel, childLabel;
string cond;
getline(str, line);
istringstream instream; // uso una variabile di tipo istringstream per poter scandire i pezzi di ogni riga usando >>
instream.clear();
instream.str(line);
instream >> rootLabel; // il primo elemento che si incontra nel file e' l'etichetta della radice, per convenzione su come deve essere fatto il file
addNode(emptyLabel, rootLabel, emptyCond, t); // la si inserisce nell'albero vuoto, indicando che il padre non c'e' (primo argomento emptyLabel)
getline(str, line); // poi si iniziano a scansionare le righe seguenti
instream.clear();
instream.str(line);
while (!str.eof()){
instream >> fatherLabel; // in ogni riga del file, il primo elemento e' l'etichetta del nodo padre e gli altri sono le etichette dei figli
while (!instream.eof()){ // finche' la riga corrente non e' terminata
instream >> childLabel; // leggo la prossima etichetta
instream >> cond; // leggo la condizione
d_Tree::addNode(fatherLabel, childLabel, cond, t);
}
getline(str, line); //per il corretto funzionamento è necessario che il file di testo si concluda in una nuova riga vuota
instream.clear();
instream.str(line);
}
str.clear();
return t;
}
d_Tree::decisionTree readFromFile(string nome_File, d_Tree::decisionTree& t)
{
ifstream ifs(nome_File.c_str()); // apertura di uno stream associato ad un file, in lettura
if (!ifs){
cout << "\nErrore apertura file, verificare di avere inserito un nome corretto\n";
return createEmpty();
}
return readFromStream(ifs, t);
};