Upload
others
View
12
Download
0
Embed Size (px)
Citation preview
1
Univerzitet u Beogradu 2
Elektrotehnički fakultet 3
Programiranje 2 4
OO1P2, SI1P2 5
6
Rešeni ispitni zadaci 7
8
Radna verzija skripte sa rešenim ispitnim zadacima na kursevimaOO1P2 i SI1P2
Ovaj materijal je podložan promenama i može sadržati greškeZa sve uočene nedostatke i nejasnoće, obratiti se predmetnom asistentu:
[email protected]@etf.bg.ac.rs
Verzija: 0.413. juni 2014
9
Sadržaj 10
1 Ispitni zadaci 2 11
1.1 Jun 2011 - zadatak 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 12
1.1.1 Analiza problema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 13
1.1.2 Analiza rešenja . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 14
1.1.3 Drugo rešenje . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 15
1.2 Jun 2012 - zadatak 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 16
1.2.1 Analiza Problema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 17
1.2.2 Strukture podataka . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 18
1.2.3 Definisanje koraka u rešenju . . . . . . . . . . . . . . . . . . . . . . . . . . 12 19
1.2.4 Glavni program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 20
1.2.5 Implementiranje pozivanih funkcija . . . . . . . . . . . . . . . . . . . . . . 13 21
1.2.6 Kompletan program i zaključak . . . . . . . . . . . . . . . . . . . . . . . . 20 22
1.2.7 Drugo rešenje . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 23
1
Glava 1 24
Ispitni zadaci 25
Zadatak 1.1 Jun 2011 - zadatak 1 26
Neka se u datoteci teniseri.txt nalaze podaci o teniserima po sledećem formatu: šifra 27
igrača (ceo broj), ime (najviše 30 znakova), prezime (najviše 30 znakova) i broj poena na ATP 28
rang listi (ceo broj). U datoteci wimbledon.txt se nalaze podaci o plasmanu tenisera na 29
ovom teniskom turniru. U svakom redu datoteke se nalazi šifra tenisera (ceo broj), broj poena 30
koji brani na turniru (ceo broj) i broj poena koji je osvojio na turniru (ceo broj). Raspored 31
tenisera u datotekama ne mora biti identičan, niti svi teniseri iz prve datoteke moraju postojati 32
u drugoj datoteci. Napisati program na programskom jeziku C koji pročita sadržaj ulaznih tekst 33
datoteka i formira jednostruko ulančanu listu, a zatim u izlaznu datoteku atplista.txt za 34
svakog tenisera upiše novi broj poena po formatu kao u prvoj ulaznoj datoteci. Novi broj poena 35
se dobija tako što se od starog broja poena na ATP listi oduzme broj poena koje teniser brani, a 36
zatim doda broj poena koje je teniser osvojio na turniru. Voditi računa o ispravnom korišćenju 37
zauzetih resursa. 38
1.1.1 Analiza problema 39
Za razliku od prethodnog zadatka, jedan od postavljenih zahteva je obrada dve ulazne 40
datoteke i formiranje treće datoteke. Ključna stvar koju bi trebalo ustanoviti jeste da li je 41
neophodno učitavati podatke iz obe datoteke u memoriju i čuvati ih u vidu ulančane liste ili ne. 42
Ako je problem moguće rešiti tako da se sadržaj jedne od datoteka ne mora čitati više puta, 43
tada se njen sadržaj ne mora čuvati tokom izvršavanja programa (u vidu liste npr.). 44
Sledi spisak zahteva koje bi trebalo ispuniti: 45
1. Otvaranje datoteka; 46
2. Formiranje jednostruko ulančane liste; 47
3. Računanje novih poena za svakog od igrača čiji su podaci sadržani u prvoj datoteci; 48
4. Formirnaje izlazne datoteke; 49
5. Oslobađanje zauzetih resursa (datoteke i dinamička memorija). 50
Prva datoteka predstavlja spisak tenisera, sa njihovim trenutnim bodovima na ATP listi, 51
dok se druga datoteka koristi za ažuriranje broja bodova. Svaki od tenisera iz prve datoteke 52
može se naći najviše jedanput u datoteci wimbledon.txt. Prema tome, da bi se sračunali novi 53
poeni, potrebno je za tenisere koji su učestvovali na turniru Wimbledon pronaći odgovarajuće 54
2
podatke iz prve datoteke, i potom promeniti broj poena. Kako je za svaki red druge datoteke 55
neophodno pronaći trenutni broj poena tenisera (iz prve datoteke), zaključuje se da će se više 56
od jednog puta pretraživati podaci iz prve datoteke. Sa druge strane, jedan red druge datoteke 57
biće korišćen samo jedanput – radi modifikovanja poena tenisera koji je njime predstavljen. 58
Uzevši to u obzir, donosi se odluka da se sadržaj prve datoteke predstavi preko ulančane 59
liste (zbog višestruke pretrage), a da se sadržaj druge datoteke jednostavno učitava red po red, 60
iskoristi, i potom pređe na naredni. 61
Listing 1.1 sadrži kôd rešenja ovog zadatka. Budući da je obrada jednostavnija, moguće je 62
sve obaviti unutar glavne funkcije. 63
3
Listing 1.1: Kompletan program64
1 #include <stdio.h> 65
2 #include <stdlib.h> 66
3 #include <string.h> 67
4 #define MAX_NAME_ARRAY_LENGTH 31 68
5 69
6 typedef struct elem { 70
7 int playerId; 71
8 char firstName[MAX_NAME_ARRAY_LENGTH]; 72
9 char lastName[MAX_NAME_ARRAY_LENGTH]; 73
10 int points; 74
11 struct elem *next; 75
12 } Elem; 76
13 77
14 int main () { 78
15 Elem *head = NULL, *tail = NULL, // first and last elements of the list 79
16 *newElem, *current; // auxiliary variables 80
17 FILE *input1, *input2, *izlaz; 81
18 82
19 int playerId, pointsToDefend, pointsWon, points; 83
20 char firstName[MAX_NAME_ARRAY_LENGTH], lastName[MAX_NAME_ARRAY_LENGTH]; 84
21 85
22 if ((input1 = fopen("teniseri.txt","r")) == NULL) { 86
23 printf("Greska u otvaranju ulazne datocurrente teniseri.txt!"); 87
24 exit(1); 88
25 } 89
26 if ((input2 = fopen("wimbledon.txt","r")) == NULL) { 90
27 printf("Greska u otvaranju ulazne datocurrente wimbledon.txt!"); 91
28 exit(1); 92
29 } 93
30 if ((izlaz = fopen("atplista.txt","w")) == NULL) { 94
31 printf("Greska u otvaranju izlazne datocurrente atplista.txt!"); 95
32 exit(4); 96
33 } 97
34 98
35 while(fscanf(input1, "%d%s%s%d", &playerId, firstName, lastName, &points)!=EOF) { 99
36 if ((newElem = malloc(sizeof(Elem))) == NULL) { 100
37 printf("Greska u alociranju memorije!"); 101
38 exit(3); 102
39 } 103
40 104
41 newElem->next = NULL; newElem->points = points; newElem->playerId = playerId; 105
42 106
43 /* Copying string content from buffers firstName and lastName into 107
corresponding fields of the element structure. */ 108
44 strcpy(newElem->firstName, firstName); 109
45 strcpy(newElem->lastName, lastName); 110
46 111
47 if (head == NULL) head = newElem; 112
48 else tail->next = newElem; 113
49 tail = newElem; 114
50 } 115
51 116
52 while(fscanf(input2, "%d%d%d", &playerId, &pointsToDefend, &pointsWon)!=EOF) { 117
53 current = head; 118
54 while (current != NULL && current->playerId != playerId) 119
55 current = current->next; 120
56 if (current != NULL) 121
57 current->points += (pointsWon - pointsToDefend); 122
58 } 123
4
59 124
60 current = head; 125
61 while (current != NULL) { 126
62 fprintf(izlaz,"%d %s %s %d\n", current->playerId, current->firstName, 127
63 current->lastName, current->points); 128
64 current = current->next; 129
65 } 130
66 131
67 current = head; 132
68 while (current != NULL) { 133
69 tail = current; 134
70 current = current->next; 135
71 free(tail); 136
72 } 137
73 fclose(input1); fclose(input2); fclose(izlaz); 138
74 return 0; 139
75 } 140141
1.1.2 Analiza rešenja 142
Nakon što se otvore ulazne i izlazna datoteka, i proveri uspešnost otvaranja (što je neophodno 143
uraditi), prelazi se na učitavanje sadržaja prve datoteke input1. Učitavanje se obavlja u while 144
petlji, i pritom se koristi funkcija fscanf, kako bi se učitavanje obavilo na najjednostavniji 145
način. Na ovom mestu napominjemo da se pri učitavanju istovremeno i proverava da li se 146
čitanjem došlo do kraja datoteke; ako jeste, čitanje se ne nastavlja. Takođe napominjemo da bi 147
bilo nekorektno proveravati da li postoji još sadržaja u datoteci primenom funkcije feof()1, 2. 148
Dakle, kôd kakva je dat u listingu 1.2 nije korektan i ne preporučuje se upotreba funkcije feof 149
na taj način. 150
Listing 1.2: Primer nekorektne upotrebe funkcije feof()
1 while(!feof(ulaz1)){2 fscanf("%d%s%s%d", &sifra, ime, prezime, &bodovi);3 //...4 }
Podaci se pritom učitavaju u lokalne podatke (1.3), a potom kopiraju u odgovarajuća polja 151
dinamički alociranog elementa newElem. Učitane tekstualne podatke je neophodno kopirati 152
pomoću funkcije strcpy u odgovarajuće stringove alocirane strukture (v. listing 1.4). Operator 153
dodele nije moguće ovde upotrebiti, jer se radi o statičkim nizovima karaktera unutar strukture 154
Elem. 155
Listing 1.3: Lokalne prihvatne promenljive
1 int playerId, pointsToDefend, pointsWon, points;2 char firstName[MAX_NAME_ARRAY_LENGTH], lastName[MAX_NAME_ARRAY_LENGTH];
Nakon učitavanja potrebnih podataka, vrši se ulančavanje novog elementa u listu. Nakon 156
ove while petlje, formirana je lista sa zapisima o svim teniserima i njihovim poenima na ATP 157
listi. 158
1http://www.cplusplus.com/reference/cstdio/feof/2http://stackoverflow.com/questions/5431941/while-feof-file-is-always-wrong
5
Listing 1.4: Kopiranje ucitanih stringova u iz prihvatnih promenljivih(tzv. baferi) u odgovarajuca polja strukture
1 /* Copying string content from buffers firstName and lastName intocorresponding fields of the element structure. */
2 strcpy(newElem->firstName, firstName);3 strcpy(newElem->lastName, lastName);
Potom, prelazi se na drugi deo u kome se vrši modifikacija broja bodova na osnovu sadržaja 159
druge datoteke. Ponovo, učitava se sadržaj datoteke, red po red, svaki se analizira, pronalazi se 160
teniser na koga se podaci iz tekućeg reda odnose, i njegovi poeni se postavljaju na odgovarajuću 161
vrednost. Kôd koji realizuje ovaj korak obrade izdvojen je u listingu 1.5. Ukoliko je teniser čiji 162
su podaci učitani iz tekućeg reda datoteke na ATP listi, promenljiva current će pokazivati na 163
element koji predstavlja tog tenisera, nakon završetka while petlje, kojom se realizuje pretraga 164
po šifri. Nakon toga, broj poena će biti promenjen za odgovarajući iznos, kako je opisano u 165
postavci zadatka. 166
Sada je evidentno da za rešavanje postavljenog problema nije bilo potrebe da se sadržaj druge 167
datoteke učitava. Pored većeg obima posla koji bi pritom bilo potrebno obaviti i nepostojanja 168
nikakvog korisnog efekta od učitavanja i druge datoteke u listu, još jedna loša strana ovakve 169
odluke je nepotrebno zauzeće memorije, što bi trebalo izbeći ukoliko ne postoji stvarna potreba 170
za tim. 171
Listing 1.5: Citanje uspeha tenisera na Wimbledon-u uz ažuriranje poena naATP listi
1 while(fscanf(input2, "%d%d%d", &playerId, &pointsToDefend, &pointsWon)!=EOF) {2 current = head;3 while (current != NULL && current->playerId != playerId)4 current = current->next;5 if (current != NULL)6 current->points += (pointsWon - pointsToDefend);7 }
Kada je ovaj korak kompletiran, može se pristupiti formiranju izlazne datoteke, pošto su 172
sada svi potrebni podaci formirani. Kôd prikazan u listingu 1.6 generiše po jedan red za svakog 173
od tenisera (predstavljenog jednim elementom liste), po istom formatu kao što je slučaj sa 174
ulaznim podacima (opisano u postavci zadatka). 175
Nakon ispisa, preostaje da se dealocira zauzeta memorija, i da se zatvore datoteke koje su 176
otvorene. 177
Listing 1.6: Formiranje izlazne datoteke
1 current = head;2 while (current != NULL) {3 fprintf(izlaz,"%d %s %s %d\n", current->playerId, current->firstName,4 current->lastName, current->points);5 current = current->next;6 }
Još jedanput napominjemo da je korektno pretpostaviti da sadržaj datoteke odgovara for- 178
matu koji je opisan u zadatku, i da nije potrebno proveravati uspešnost konverzija, niti obrađi- 179
vati situaciju u kojoj je red duži nego što je to formatom propisano. 180
6
1.1.3 Drugo rešenje 181
U listingu 1.7 dat je prikaz još jednog rešenja, koje je realizovano putem funkcija.. 182
7
Listing 1.7: Alternativno rešenje zadatka183
1 #include <stdio.h> 184
2 #include <stdlib.h> 185
3 #include <string.h> 186
4 187
5 typedef struct elem { 188
6 int playerId; 189
7 char firstName[31]; 190
8 char lastName[31]; 191
9 int points; 192
10 struct elem *next; 193
11 } Elem; 194
12 195
13 /* 196
14 Prototypes of functions being used within the solution. 197
15 Must be specified before the function invocation. 198
16 */ 199
17 // reads data representing player’s information from the file 200
18 Elem* readPlayerData(FILE* atpF); 201
19 202
20 // inserts the element within the list 203
21 Elem* insert(Elem* head, Elem* newElement); 204
22 205
23 // updates players scores, according to the information 206
24 // written in the tournamentFile 207
25 void updatePlayersScore(Elem* list, FILE* tournamentFile); 208
26 209
27 210
28 int main () { 211
29 Elem *head = NULL, *tail = NULL, // first and last list elements 212
30 *player, *current; // auxiliary pointers 213
31 FILE *input1, *input2, *output; 214
32 215
33 if ((input1 = fopen("teniseri.txt","r")) == NULL) { 216
34 printf("Greska u otvaranju ulazne datocurrente teniseri.txt!"); 217
35 exit(1); 218
36 } 219
37 if ((input2 = fopen("wimbledon.txt","r")) == NULL) { 220
38 printf("Greska u otvaranju ulazne datocurrente wimbledon.txt!"); 221
39 exit(1); 222
40 } 223
41 if ((output = fopen("atplist.txt","w")) == NULL) { 224
42 printf("Greska u otvaranju outputne datocurrente atplist.txt!"); 225
43 exit(4); 226
44 } 227
45 228
46 while((player = readPlayerData(input1))!=NULL) { 229
47 head = insert(head, player); 230
48 } 231
49 updatePlayersScore(head, input2); 232
50 233
51 current = head; 234
52 235
53 while (current != NULL) { 236
54 fprintf(output,"%d %s %s %d\n", current->playerId, current->firstName, 237
current->lastName, current->points); 238
55 current = current->next; 239
56 } 240
57 241
58 current = head; 242
8
59 while (current != NULL) { 243
60 tail = current; 244
61 current = current->next; 245
62 free(tail); 246
63 } 247
64 248
65 fclose(input1); 249
66 fclose(input2); 250
67 fclose(output); 251
68 return 0; 252
69 } 253
70 254
71 Elem* readPlayerData(FILE* atpF) { 255
72 char firstName[31], lastName[31]; 256
73 int playerId, points; 257
74 258
75 if(fscanf(atpF, "%d%s%s%d", &playerId, firstName, lastName, &points)!=EOF) { 259
76 Elem* newElement = malloc(sizeof(Elem)); 260
77 if(newElement==NULL) { 261
78 printf("Greska pri alociranju!"); 262
79 exit(1); 263
80 } 264
81 newElement->playerId = playerId; 265
82 newElement->points = points; 266
83 strcpy(newElement->firstName, firstName); 267
84 strcpy(newElement->lastName, lastName); 268
85 return newElement; 269
86 } 270
87 return NULL; 271
88 } 272
89 273
90 Elem* insert(Elem* head, Elem* newElement) { 274
91 Elem* current = head; 275
92 // adding element to the end of the list 276
93 if(current){ 277
94 while(current->next) 278
95 current = current->next; 279
96 current->next = newElement; 280
97 } 281
98 newElement->next = NULL; 282
99 return head ? head : newElement; 283
100 } 284
101 285
102 void updatePlayersScore(Elem* list, FILE* tournamentFile) { 286
103 int playerId, pointsToDefend, pointsWon; 287
104 while(fscanf(tournamentFile, "%d%d%d", &playerId, &pointsToDefend, &pointsWon) 288
!=EOF) { 289
105 Elem* current = list; 290
106 while (current != NULL && current->playerId != playerId) 291
107 current = current->next; 292
108 293
109 if (current != NULL) 294
110 current->points += (pointsWon - pointsToDefend); 295
111 } 296
112 } 297298
9
Zadatak 1.2 Jun 2012 - zadatak 1 299
Tekstualna datoteka kalendar.txt u svakom redu sadrži informacije o zakazanim aktiv- 300
nostima u jednom danu. Za svaku aktivnost navodi se vreme početka, vreme završetka i kratak 301
opis. Format jednog reda datoteke je dat u priloženom primeru. Napisati program na program- 302
skom jeziku C koji čita red po red iz datoteke, pravi strukturu aktivnosti na osnovu podataka 303
iz pročitanog reda i stavlja je u listu u hronološkom poretku. Potom, polazeći od početka liste 304
pronalazi aktivnosti koje se preklapaju. Ako se detektuje preklapanje između dve aktivnosti, iz 305
liste se izbacuje aktivnost koja počinje kasnije ili, ako obe počinju u isto vreme, izbacuje se ona 306
koja je druga u poretku. Aktivnost se može preklapati sa proizvoljno mnogo drugih aktivnosti. 307
Aktivnosti koje se izbace iz liste, ispisuju se u izlaznu tekstualnu datoteku preklapanja.txt. Za 308
kratak opis aktivnosti koristi se maksimalno 80 znakova. Na kraju program treba da ispravno 309
oslobodi zauzete resurse. Učitavanje i brisanje liste realizovati kao potprograme. 310
08:00-10:30 Ispit iz Osnova računarske tehnike.10:00-10:30 Pregled kod doktora.11:00-11:15 Preuzeti skripte iz štamparije.09:00-09:30 Odbrana domaćeg iz Praktikuma iz programiranja 2.
Tabela 1.1: Primer ulaza (kalendar.txt)
09:00-09:30 Odbrana domaćeg iz Praktikuma iz programiranja 2.10:00-10:30 Pregled kod doktora.
Tabela 1.2: Primer izlaza (preklapanja.txt):
Kompletan kod dat je u listingu 1.25. 311
Rešenje demonstrira nekoliko bitnih komponenti i tehnika u rešavanju zadataka: 312
• Slojevito pisanje programa 313
• Rad sa tekstualnom datotekom 314
• Različite metode učitavanja podataka iz datoteke 315
• Mehanizam obrade neželjenih situacija 316
• Pravilno rukovanje dinamičkom memorijom 317
1.2.1 Analiza Problema 318
Problem se može razložiti na sledeće potprobleme: 319
• Otvaranje ulazne i izlazne datoteke 320
• Provera mogućnosti rada sa datotekama 321
• Učitavanje sadržaja datoteke, pri čemu se pročitani sadržaj transformiše u odgovarajuću 322
strukturu podataka i dodaje u listu 323
Svaki red datoteke odgovara jednom podatku, pa se formira po jedan objekat strukture 324
Activity za svaki učitani podatak. Ovaj objekat se formira dinamički. 325
Ulančavanje se vrši tako da se aktivnosti predstavljene elementima liste smeštaju tako 326
da postoji jasan poredak između njih: aktivnost koja počinje ranije u listi se mora i nalaziti 327
ranije. 328
10
• Centralna obrada - uklanjanje termina sa poklapanjem uz njihov ispis. Neophodno je 329
obraditi sadržaj učitanih termina, ustanoviti da li postoji preklapanje nekih aktivnosti, i 330
ukoliko postoji par (a1, a2), gde a1 započinje ranije, ispisati podatke o a2, a potom ga i 331
ukloniti. 332
Tokom rešavanja zadatka, neophodno je voditi računa o svim ovde navedenim stavkama. 333
Rešavanje određenih koraka biće ostvareno kroz potprograme (funkcije), a neki će biti rešeni 334
direktno (odmah navodeći kôd). 335
1.2.2 Strukture podataka 336
Pre pristupanja pisanju rešenja, potrebno je ustanoviti kakve su strukture podataka po- 337
trebne. Budući da se aktivnost karakteriše vremenom početka i završetka, pogodno je uvesti 338
strukturu koja predstavlja vreme – Time, i sastoji se od komponenti koje opisuju sate i minute. 339
Po prirodi problema, sati i minuti su celobrojne veličine. 340
Listing 1.8: Struktura za opis vremena
1 typedef struct sTime {2 int hours, minutes;3 } Time;
Bilo bi nepogodno raditi bez ovakve strukture, budući da je svaka aktivnost opisana počet- 341
nim i krajnjim vremenom, te bi trebalo uvesti 4 promenljive. Na ovaj način, vreme je opisano 342
jednim podatkom, što čini program čitljivijim i razumljivijim. 343
Zatim, potrebno je definisati strukturu koja predstavlja samu aktivnost. Pored polja koja 344
opisuju početno i krajnje vreme, i koja su po tipu Time, definiše se i opis, za koji je rečeno da je 345
dugačak najviše 80 karaktera. Kako nam je poznato da opis neće biti duži od 80, opredeljujemo 346
se za niz od 81 karaktera (80 učitanih i 1 za NULL karakter).Tehnički, budući da se ovde definiše 347
strukturni tip, moguće je bilo izostaviti identifikator sTime. 348
Listing 1.9: Struktura za opis aktivnosti
1 typedef struct sActivity {2 Time startTime, endTime;3 char description[DEFAULT_TEXT_LEN + 1];4 } Activity;
Potom, kako je zadatak neophodno rešiti koristeći ulančanu listu, definiše se struktura koja 349
predstavlja jedan element liste. Polje pActivity unutar te strukture je pokazivač na objekat 350
aktivnosti na koju se odnosi. 351
Listing 1.10: Struktura elementa liste
1 typedef struct sListElement {2 Activity * pActivity;3 struct sListElement * next;4 } ListElement;
Na ovom mestu, moguće je primeniti jedno od dva alternativna rešenja: da element li- 352
ste sadrži pokazivač na aktivnost, kao što je to ovde slučaj, ili da sadrži aktivnost direktno: 353
11
Activity activity. Generalno, boljim izborom se smatra rešenje u kome je informacioni 354
sadržaj elementa liste predstavljen pokazivačem umesto da se sadrži u elementu liste direktno. 355
Opravdanje za ovo stanovište leži u činjenici da lista predstavlja kolekciju objekata, uređenih na 356
određeni način (ili neuređenih), koji mogu postojati i izvan elementa liste – aktivnost ne mora 357
biti ulančana da bi postojala; takođe, jedna aktivnost može biti u dve ulančane liste: jednoj 358
koja sadrži aktivnosti u okviru pojedinačnih dana, drugoj koja sadrži sve aktivnosti itd. 359
Sa druge strane, pored prednosti, ovaj pristup ima osobine koje se mogu smatrati nedosta- 360
cima: potreban je dodatni memorijski prostor potreban za smeštanje pokazivača na informaci- 361
oni sadržaj, moguće je da pokazivač ima vredenost NULL, pa je potrebno vršiti proveru da li 362
pokazivač na podatke ima vrednost NULL itd. 363
Oba ovde opisana pristupa smatraju se korektnim za upotrebu, s tim što prvi pristup, u 364
kome je informacioni sadržaj direktno smešten u okviru strukture elementa liste, nije uvek 365
zadovoljavajući. 366
1.2.3 Definisanje koraka u rešenju 367
Sada možemo krenuti sa pisanjem programa. Najbolji metod je razmišljati “u koracima”, i 368
krenuti od pisanja glavnog programa. Koraci koji se pritom preduzimaju su: 369
• Otvaranje datoteka; 370
• Provera mogućnosti rada sa datotekom (uspešnost otvaranja datoteke); 371
• Repetitivno učitavanje podatka o jednoj aktivnosti (pri čemu se zapravo iz ulazne datoteke 372
pročita po jedan red) i smeštanje tih podataka u objekat tipa Activity 373
• Ulančavanje učitane aktivnosti, uz održavanje uređenosti – potrebno je smestiti novu 374
aktivnost tako da svi njeni prethodnici u listi započinju pre nje ili istovremeno sa njom, a 375
svi njeni sledbenici nakon nje. Ovo sugeriše da će biti potrebno vršiti poređenje vremena 376
početka dveju aktivnosti; 377
• Prelazak na obradu učitanih aktivnosti (pronalaženje preklapanja i uklanjanje preklo- 378
pljenih aktivnosti koje započinju kasnije), nakon što se pročitaju svi podaci iz ulazne 379
datoteke. Pri proveri preklapanja, biće neophodno porediti vremena početka i završetka 380
dveju aktivnosti. 381
• Zatvaranje otvorenih datoteka; 382
• Oslobađanje memorije zauzete za učitane podatke. 383
Prilikom pisanja kôda rešenja, svaki “krupniji” korak, u kome se ostvaruje nekakva obrada 384
nad podacima, može se izdvojiti u zasebnu funkciju. Ovaj postupak se naziva razvoj programa 385
u koracima preciziranja i ime se postižu dve stvari: 386
• Kôd postaje razumljiviji za čitanje i razumevanje; 387
• Prilikom pisanja koda, nastavlja se sa pisanjem naredbi kojima se ostvaruje tekući cilj, 388
umesto da se prelazi na pisanje naredbi koje vrše sekundarne akcije (npr. provere, kalku- 389
lacije i sl.). 390
Drugi cilj je značajan, jer se na ovaj način ne gubi iz vida šta je potrebno u osnovnom 391
problemu rešiti, i time se sprečavaju greške koje nastaju zbog pada koncentracije ili previda. 392
Dakle, dok se piše kôd kojim se rešava osnovni problem, pozivaju se (unapred) funkcije 393
koje još uvek nisu napisane. Tada je bitno shvatiti kakve je akcije potrebno obaviti (npr. 394
12
poređenje vremena početka dve aktivnosti), odlučiti se da se te akcije obave putem funkcije 395
(ma koliko trivijalno možda delovale) i odlučiti se za način dobavljanja rezultata obrade od 396
te funkcije (povratnom vrednošću ili prenosom argumenata po adresi). Nakon što je kôd koji 397
rešava osnovni problem kompletiran, može se pristupiti pisanju pozvanih funkcija, u skladu sa 398
odlukama donetim u momentu kada je uočena potreba za njima. 399
1.2.4 Glavni program 400
Postupak učitavanja realizuje se putem while petlje: 401
Listing 1.11: Ucitavanje i ulancavanje aktivnosti
1 while ((currActivity = readActivity(scheduleFile)) != NULL) {2 activityList = insert(activityList, currActivity);3 }
Kako je potrebno svaki red iz ulazne datoteke učitati, postupak se mora vršiti u petlji. 402
Pritom, u svakom koraku petlje, iz datoteke je potrebno učitati novu aktivnost i napraviti 403
dinamički alociranu Activity strukturu. Zbog toga je uslov petlje sačinjen od poziva funkcije 404
readActivity, koja iz datoteke scheduleFile učitava podatke zapisane u jednom redu. 405
Na ovom mestu nije napisan kôd koji ostvaruje učitavanje, već je doneta odluka da se taj 406
posao delegira nekoj funkciji readActivity. Ta funkcija će kasnije biti napisana i objašnjena. 407
Trenutno, bitno je odlučiti se na osnovu čega će funkcija obezbediti novu aktivnost (na osnovu 408
jednog reda datoteke – predaje joj se argument koji predstavlja pokazivač na FILE strukturu, 409
tj. datoteku), i šta će biti njen rezultat (pokazivač na novoalocirani objekat koji predstavlja 410
aktivnost). Slično je učinjeno i sa funkcijom insert, koja učitanu aktivnost (currActivity) 411
ulančava u listu activityList. 412
Naredni korak koji se ostvaruje u main funkciji je obrada učitanih podataka. To se ne čini 413
direktno u main funkciji, već se ponovo delegira nova funkcija koja će vršiti tu obradu. 414
Listing 1.12: Poziv centralne obrade
1 printActivityList(activityList, stdout);2 removeOverlappedActivities(activityList, overlapingFile);3 printActivityList(activityList, stdout);
Kompletna funkcija main izgleda kao u listingu 1.13. 415
1.2.5 Implementiranje pozivanih funkcija 416
Kada su osnovni koraci objašnjeni, potrebno je napisati funkcije koje ostvaruju prethodno 417
usvojene korake. U listingu 1.14 prikazana je funkcija koja obavlja učitavanje podataka o 418
pojedinačnim aktivnostima. 419
Unutar funkcije, alocira se aktivnost koja se smešta u promenljivu actv. Potom, u po- 420
lja actv->startTime.hours, actv->startTime.minutes, actv->endTime.hours, 421
actv->endTime.minutes se smeštaju podaci o početku i završetku aktivnosti - to su brojevi 422
razdvojeni znacima : i -, kako je dato u primeru ulazne datoteke. Zbog toga, format-string za 423
funkciju scanf izgleda ovako: %d:%d-%d:%d. 424
Nakon učitavanja numeričkih podataka, potrebno je obaviti proveru njene uspešnosti. Uko- 425
liko je povratna vrednost funkcije fscanf 3 jednaka konstanti EOF, to je indikacija da se 426
3Povratna vrednost za fscanf http://www.cplusplus.com/reference/cstdio/fscanf/#return
13
Listing 1.13: Glavni program
1 int main() {2 ListElement* activityList = NULL; Activity* currActivity;3 int s = 0;4 FILE* scheduleFile = fopen("kalendar.txt", "r");5 FILE* overlapingFile = fopen("preklapanja.txt", "w");6
7 if (scheduleFile == NULL) {8 fprintf(stderr, "Cannot open input file."); s = 1; goto END;9 }
10 if (overlapingFile == NULL) {11 fprintf(stderr, "Cannot open output file."); s = 1; goto END;12 }13
14 while ((currActivity = readActivity(scheduleFile)) != NULL) {15 activityList = insert(activityList, currActivity);16 }17
18 printActivityList(activityList, stdout);19 removeOverlappedActivities(activityList, overlapingFile);20 printActivityList(activityList, stdout);21
22 END: if (scheduleFile) fclose(scheduleFile);23 if (overlapingFile) fclose(overlapingFile);24
25 dealocate(activityList);26 return s;27 }
Listing 1.14: Ucitavanje jedne aktivnosti
1 Activity* readActivity(FILE* f) {2 int numOfData;3 Activity* actv = (Activity*)malloc(sizeof(Activity));4 if(!actv) {5 printf("Error in memory allocation");6 exit(2);7 }8 numOfData = fscanf(f, "%d:%d-%d:%d ",9 &actv->startTime.hours, &actv->startTime.minutes,
10 &actv->endTime.hours, &actv->endTime.minutes);11
12 if (numOfData == EOF) {13 free(actv); //nepotrebno smo alocirali14 return NULL;15 }16
17 fgets(actv->description, DEFAULT_TEXT_LEN, f);18
19 return actv;20 }
14
čitanjem došlo do kraja datoteke. Tada je potrebno osloboditi memoriju zauzetu za aktivnost 427
(actv), jer neće biti iskorišćena, a korisniku se vraća NULL kao rezultat – ne postoji nikakav 428
validan podatak koji se može vratiti. 429
Poslednji korak u učitavanju je dobavljanje teksta koji predstavlja kratak opis aktivnosti. 430
Budući da se radi o tekstu koji se proteže do kraja linije, može se učitati pomoću funkcije 431
fgets4, pozivom fgets(actv->description,DEFAULT_TEXT_LEN,f). Ta funkcija pri- 432
hvata adresu od koje se započinje smeštanje učitanog sadržaja, maksimalni broj karaktera koji 433
se može učitati i datoteku iz koje se čita. Potrebno je napomenuti da ova funkcija zadržava znak 434
za prelaz u novi red (\n), a nakon njega dodaje znak za kraj stringa (\0). Međutim, ukoliko je 435
linija koja se učitava duža od prihvatnog stringa, učitaće maksimalni broj karaktera, a nakon 436
toga će dodati znak kraj stringa. 437
Pri rešavanju zadataka, može se smatrati da je sadržaj ulazne datoteke uvek u skladu sa 438
opisanim formatom, pa se provera ne mora vršiti! Iz tog razloga, u listingu 1.14 nije navedena 439
nijedna provera o korektnosti učitanih podataka – ni da li su svi potrebni podaci učitani (da 440
li je 4 cela broja uspešno pročitano ili nije), ni da li komponente vremena imaju odgovarajuće 441
vrednosti (minuti u opsegu 0-59, sati 0-23), i oslanjalo se na pretpostavku da je dužina linije 442
najviše 80 karaktera (nisu vršene dodatne aktinvosti koje obezbeđuju da se učita čitava linija). 443
Radi kompletnosti, u nastavku se daje diskusija obrade ovih izuzetnih situacija, koja se pri 444
rešavanju ispitnih zadataka ne mora obavljati. 445Pri čitanju podataka funkcijom fscanf, moguće je da dođe do greške ako ulazni podaci ne zadovoljavaju format koji je 446
zadat. Na primer, ako linija sadrži 9:5-xy:20 P2 konsultacije, tada će fscanf prekinuti čitanje kada dođe do karaktera 447x, i biće vraćena vrednost 2 (jer su 9 i 5 uspešno konvertovani podaci). Međutim, naredno čitanje funkcijom fscanf, ili bilo 448kojom drugom funkcijom, nastaviće se od karaktera x – taj karakter je doveo do prekida prethodnog učitavanja, i smatra 449se neiskorišćenim. Ovde bi bilo potrebno proveriti povratnu vrednost funkcije fscanf, i ukoliko je manja od 4, prikazati 450poruku o problemu. Ukoliko bi se nastavila obrada (zanemarivši loše formiranu liniju ulaznog fajla), potrebno bi bilo preći 451na narednu liniju. Budući da je naredni korak učitavanje ostatka tekuće linije (kratak opis aktivnosti), ostatak tekuće 452(neavalidne) linije biće pročitan, i preći će se na narednu liniju. Međutim, ova funkcija ne bi trebalo da vrati pozivaocu 453strukturu formiranu na osnovu nevalidnih podataka, pa bi bilo ispravno da se u ovoj situaciji oslobodi memorija zauzeta 454za aktivnost (kao u slučaju greške pri čitanju), i da se vrati NULL. U delu 1.2.7, listing 1.28 je prikazano još jedno rešenje 455ovog problema, u kome se na efikasniji način obezbeđuje učitavanje teksta do završetka linije. 456
U listingu 1.15 prikazujemo kôd kojim se obezbeđuje da se čitava linija učita do kraja (do znaka za novi red), u slučaju 457da je duža od formatom propisanih 80 karaktera. Ukoliko se desi da je linija duža od 80 karaktera, mora se “preskočiti” 458ostatak, kako bi čitanje narednog validnog seta podataka započelo u narednoj liniji (u suprotnom, učitavanje nastavlja na 459mestu gde je prethodno učitavanje prekinuto). 460
Nakon funkcije za unos strukture, potrebno je obezbediti funkciju koja će dodati novi ele- 461
ment koji predstavlja aktivnost u listu aktivnosti. To se postiže funkcijom insert, prikazanoj u 462
listingu 1.16. 463
Kako je potrebno da se sadržaj liste uvek održava uređenim, tako da aktivnost koja na nižoj 464
poziciji u listi ranije započinje nego neka druga, pri umetanju se prvo pronalazi mesto na koje bi 465
se umetnula nova aktivnost. Stoga, potrebno je ispitivati početno vreme svakog elementa liste 466
– curr i uporediti sa vremenom početka nove aktivnosti – actv. Novu aktivnost umećemo 467
u listu na poziciju za koju važi da su sve aktivnosti koje su u listi “ispred” počinju ranije ili 468
istovremeno sa njom, a sve koje su u listi “iza” počinju nakon nje. 469
Ukoliko je aktivnost na koju se odnosi element curr pre aktivnosti na koju se odnosi actv, 470
potrebno je nastaviti kretanje kroz listu. Tek kada se u listi dođe do prve aktivnosti koja ne 471
započinje pre aktivnosti actv (ili istovremeno sa njom), kretanje kroz listu se zaustavlja, a 472
curr pokazuje na element koji se odnosi na prvu aktivnost koja počinje nakon početka actv. 473
Dakle, aktivnost actv se dodaje ispred elementa curr. 474
Proveru koja aktivnost je “ranija” možemo obaviti direktno u uslovu grananja. Međutim, 475
praktičnije je upotrebiti poziv funkcije koja radi ovo poređenje: 476
isActivityBeforeOrEqual(curr->pActivity,actv) 477
Gledano sa praktične strane pri rešavanju zadatka, ne menja se fokus razmišljanja – i dalje 478
4http://www.cplusplus.com/reference/cstdio/fgets/
15
Listing 1.15: Ucitavanje citave linije (dopuna funkciji readActivity)
1 // ... Prethodno ucitani podaci preko fscanf ...2 // Ucita se opis aktivnosti3 fgets(actv->description, DEFAULT_TEXT_LEN, f);4
5 if(strlen(actv->description) == DEFAULT_TEXT_LEN6 && actv->description[DEFAULT_TEXT_LEN-1] != ’\n’){7 /* procitano je tacno 80 karaktera, i poslednji nije znak za prelaz u novi
red,8 sto znaci da postoji jos karaktera do prelaska u novi red. */9 while(1){
10 /* prihvatni niz znakova koji se zanemaruju - ostatak linije */11 char tmp[DEFAULT_TEXT_LEN+1];12 /* Ucita se jos najvise DEFAULT_TEXT_LEN karaktera, ili do pojave znaka \n */13 fgets(tmp, DEFAULT_TEXT_LEN, f);14
15 /* Ukoliko je ucitano manje od DEFAULT_TEXT_LEN karaktera, ili je ucitano16 toliko, ali poslednji je jednak znaku za prelaz u novi red,17 zakljucujemo da su procitani svi znaci koji postoje u tom redu i da se ova18 petlja moze napustiti. */19 if(strlen(tmp) < DEFAULT_TEXT_LEN || tmp[DEFAULT_TEXT_LEN-1] == ’\n’) break;20 }21 }22 //...23
Listing 1.16: Umetanje aktivnosti u listu
1 ListElement* insert(ListElement* lstFirst, Activity* actv) {2 ListElement* newActv = (ListElement*) malloc(sizeof(ListElement));3
4 newActv->pActivity = actv;5
6 ListElement* curr = lstFirst, *prev = NULL;7 while (curr) {8 if (isActivityBeforeOrEqual(curr->pActivity, actv))9 prev = curr, curr = curr->next;
10 else break; // pronasli smo mesto!11 }12
13 if (!prev) lstFirst = newActv; //prvi element14 else prev->next = newActv; //15 newActv->next = curr; // ulancavanmo u postojecu16
17 return lstFirst; // vracamo glavu liste18 }
16
se razmišlja o tome kako se ulančava novi element u listu, dok se samo implementiranje provere 479
(koja može biti komplikovana!) ostavlja za kasnije, čime se umanjuju šanse da se nešto previdi 480
pri samom ulančavanju, na čemu bi trebalo da bude glavni fokus u ovom trenutku. 481
Ilustracije radi, provera koja bi ustanovila da li postoji bilo kakvo poklapanje bi glasila kao 482
u listingu 1.17. Napor potreban da se analizira postojanje preklapanja, i vreme potrebno za to, 483
mogu uticati na loše napisan kod za umetanje elementa, pa je zato delegiranje provere novoj 484
funkciji u ovom koraku najbolje rešenje. Štaviše, ako se ponovo pogleda analiza problema, može 485
se shvatiti da će poređenje vremena početka aktivnosti biti potrebno na još nekom od mesta 486
(prilikom izbacivanja, v. 1.2.3), pa je i sa te strane uputno da se ovakva provera ostvari u 487
funkciji koja se ovde jednostavno poziva. 488
Listing 1.17: Direktno napisana provera preklapanja
1 if(a1->startTime.hours < a2->startTime.hours ||2 (a1->startTime.hours==a2->startTime.hours &&3 a1->startTime.minutes < a2->startTime.minutes)) //...
Nakon umetanja, potrebno je realizovati i funkciju za uklanjanje aktivnosti koje se prekla- 489
paju; to je funkcija removeOverlappedActivities, koja se poziva u main-u, i čiji kôd je 490
prikazan u listingu 1.18. Zahtev zadatka je da se za aktivnosti koje se preklapaju uklone sve 491
osim aktivnosti sa najmanjim početnim vremenom (prva u posmatranom skupu preklopljenih). 492
Da bi se uklonila sva poklapanja, kroz listu se prolazi sa dva pokazivača. Prvi pokazivač – 493
pi pokazuje na element čiji se duplikati traže; drugi pokazivač – pj koristi se da se, za fiksirano 494
pi, ispitaju svi elementi koji su u listi posle njega (aktivnosti koje počinju kasnije). Ukoliko se 495
aktivnost na koju se odnosi element pj poklapa sa aktivnošću predstavljenu elementom pi, tada 496
je potrebno ukloniti element pj. Prvo se štampa aktivnost koja se izbacuje iz liste, a potom 497
se radi prevezivanje pokazivača i dealocira memorija koju je zauzimala, a potom se element 498
uklanja iz liste. 499
Listing 1.18: Uklanjanje preklopljenih aktivnosti
1 ListElement* removeOverlappedActivities(ListElement* aList, FILE* output) {2 ListElement * pi = aList, * pj = NULL, * pjPrev, * old;3 while (pi) {4 pj = pi->next;5 while (pj) {6 if (isOverlaped(pi->pActivity, pj->pActivity)) {7 printActivity(pj->pActivity, output);8 pi->next = pj->next;9 free(pj->pActivity);
10 free(pj);11 pj = pi->next;12 } else {13 pj = pj->next;14 }15 }16 pi = pi->next;17 }18
19 return aList;20 }
Pri dealociranju memorije, jako je važno obratiti pažnju na to da li je struktura sa poda- 500
17
cima dinamički alocirana i element liste sadrži pokazivač na tu sturkuturu ili je sastavni deo 501
strukture elementa liste (nije pokazivačko polje). Pošto je u ovom rešenju aktivnost alocirana di- 502
namički (Element sadrži Activity* actv, a ne Activity actv), neophodno je osloboditi 503
memoriju koju zauzima aktivnost, a tek potom i memoriju koju zauzima element liste. 504
Za pokazivač pj nije neophodno da se sa početka liste radi ispitivanje duplikata – nemoguće 505
je da ispred pi postoji preklopljeni termin (pk), inače bi pi već bio uklonjen, kao termin koji 506
se preklapa sa pk. 507
I u ovom slučaju, proveru postojanja preklapanja delegiramo funkciji isOverlapped, koja 508
za dve zadate aktivnosti proverava da li se preklapaju, budući da je za proveru preklapanja 509
aktivnosti potrebno utvrditi kakav je odnos između perioda (početno i završno vreme) te dve 510
aktivnosti. 511
Pre nastavka, potrebno je prodiskutovati deo koda u kome se prevezuju elementi liste. 512
Naime, prevezivanje se ostvaruje naredbom pi->next=pj->next. Može delovati da je ovo 513
pogrešno, jer pi ne mora biti u listi neposredno ispred pj. Međutim, ovo upravo jeste slučaj! 514
Ako ovu funkciju koristimo samo za obradu liste aktivnosti kod koje se aktivnosti koje se 515
preklapaju nalaze neposredno jedna ispred druge (ne postoji aktivnost koja je između neke dve 516
preklopljene aktivnosti, a da se ne preklapa sa ranijom aktivnošću), ovakav pristup je korektan. 517
Naime, kako je lista aktivnosti vremenski uređena, sve aktivnosti koje se mogu poklopiti 518
sa aktivnošću pi u listi se nalaze isključivo iza pi. Prema tome, kada se uklanja prva ak- 519
tivnost pj koja se poklapa sa pi (a započinje kasnije), ona se nalazi neposredno iza pi, t.j. 520
pi->sled == pj. Ako postoji još jedna aktivnost koja se preklapa sa pi, osim pj, ona mora 521
slediti aktivnost pj, t.j. to je pj->next. Ukoliko pj->next ne preklapa sa pi, to znači da 522
ne postoji nijedna druga aktivnost preklopljena sa pi. Ako se pj->next poklapa sa pi, ona 523
će biti uklonjena već u narednoj iteraciji, a njen prethodnik u listi će biti opet pi. Važno je 524
uočiti da ova funkcija nikada neće ostaviti listu praznom, tačnije, element koji je prvi u listi 525
nikada neće biti izbačen! 526
Alternativno, ukoliko se ovo svojstvo ne uoči, generalni postupak uklanjanja elementa (v. 527
listing 1.19.) se može sprovesti, uvek prateći i evidenciju o tome koji je element prethodnik 528
onom elementu koji se potencijalno izbacuje (pret). 529
Listing 1.19: Uklanjanje preklopljenih elemenata bez uocene specificnosti
1 Elem* pret = NULL;2 while(pi){3 pj = pi->next;4 pret = pi;5 while(pj){6 if(isOverlapped(p1->aActivity, pj->pActivity)){7 printActivity(pj->activity, ouput);8 pret->next = pj->next; // pret!=NULL uvek, zbog pret=pi;9 free(pj->pActivity);
10 free(pj);11 }12 else{13 pret = pj; pj = pj->next; // pracenje prethodnika14 }15 }16 pi = pi->next;17 }
Sada je preostalo još analizirati funkcije koje su pozivane pri pisanju do sada analiziranih 530
funkcija. To su 531
18
• isActivityBeforeOrEqual, koja poredi dve aktivnosti i vraća 1 u slučaju da aktiv- 532
nost predstavljena prvim argumentom počinje pre ili istovremeno sa aktivnošću predsta- 533
vljenom drugim argumentom. 534
• isOverlaped, koja poredi dve aktivnosti i vraća 1 ukoliko postoji prekpalanje u inter- 535
valima trajanja tih aktivnosti 536
• printActivity, koja je pozivana pre izbacivanja aktivnosti iz liste. 537
Budući da se provera da li je jedna aktivnost pre druge svodi na poređenje početnih vremena, 538
i na ovom mestu se ta provera ostvaruje poredeći da li je podatak koji predstavlja startno vreme 539
prve aktivnosti manji ili jednak startnom vremenu druge. 540
Listing 1.20: Provera da li je aktivnost pre druge zadate aktivnosti
1 int isActivityBeforeOrEqual(Activity* a1, Activity* a2) {2 return isTimeBeforeOrEqual(a1->startTime, a2->startTime);3 }
Preklapanje između dve aktivnosti se proverava na sledeći način: dve aktivnosti se prekla- 541
paju ukoliko aktivnost koja počinje kasnije ima početno vreme veće od završnog vremena one 542
aktivnosti koja počinje ranije. Bilo koja od zadate dve aktivnosti može biti ona koja počinje 543
ranije. Pošto je potrebno uraditi istu proveru odnosa između vremena početka i završetka ak- 544
tivnosti, poređenje odnosa je takođe realizovano funkcijama. Leva strana operatora || ispituje 545
da li ima preklapanja u slučaju da je a1 ranija aktivnost (počinje pre a2 ili istovremeno sa 546
njom), a desna strana tog operatora ispituje postojanje preklapanja ako je a2 ranija aktivnost. 547
Listing 1.21: Provera postojanja preklapanja zadatih aktivnosti
1 int isOverlaped(Activity* a1, Activity* a2) {2 return ( isTimeBeforeOrEqual(a1->startTime, a2->startTime)3 && isTimeBefore(a2->startTime, a1->endTime) )4 || ( isTimeBeforeOrEqual(a2->startTime, a1->startTime)5 && isTimeBefore(a1->startTime, a2->endTime) );6 }
I ova funkcija je realizovana uvodeći nove funkcije, koje bi trebalo da uporede vremena. 548
Funkcija koja poroverava da li je prvi zadati vremenski trenutak pre drugog data je u listingu 549
1.22, a funkcija koja proverava da li je pre ili istovremeno sa drugim trenutkom u listingu 1.23. 550
Listing 1.22: Poredenje dva vremenska trenutka na strogu nejednakost
1 int isTimeBefore(Time t1, Time t2) {2 return (t1.hours < t2.hours) ||3 (t1.hours == t2.hours && t1.minutes < t2.minutes);4 }
Na kraju, potrebno je osloboditi elemente liste. Funkcija deallocate oslobađa dinamički 551
zauzetu memoriju. Važno je osloboditi i prostor koji zauzima element liste, ali i prostor koji 552
zauzima struktura koja sadrži podatke o aktivnosti, budući da je ona dinamički alocirana. Kôd 553
je dat u listingu 1.24. 554
19
Listing 1.23: Poredenje dva vremenska trenutka na nejednakost (uz mogucujednakost)
1 int isTimeBeforeOrEqual(Time t1, Time t2) {2 return (t1.hours < t2.hours) ||3 (t1.hours == t2.hours && t1.minutes <= t2.minutes);4 }
Listing 1.24: Oslobadanje elemenata liste
1 void dealocate(ListElement* actv){2 ListElement* tmp;3 while(actv){4 tmp = actv;5 actv = actv->next;6 /* Obavezno osloboditi i memoriju koju zauzima dinamicki alocirana7 struktura koja predstavlja aktivnost! */8 free(tmp->pActivity);9 free(tmp);
10 }11 }
1.2.6 Kompletan program i zaključak 555
U listingu 1.25 dat je kompletan program. 556
Program koji je ovde priložen ilustruje dobre programerske postupke i način organizacije 557
kôda i struktuiranja rešenja, koje bi trebalo usvojiti – podela odgovornosti na više funkcija, 558
formiranje rešenja “odozgo na dole”, imenovanje promenljivih i funkcija tako da njihova namena 559
bude jasna iz samog identifikatora itd. 560
Rešenje, po obimu (163 linije kôda), može delovati preveliko, ali, ako se pažljivije pogleda 561
kôd, veliki deo linija “otpada” na deklaracije funkcija, na prorede (prazne redove) i redove koji 562
sadrže samo vitičaste zagrade. U ovom rešenju je takođe prikazano kako pažljiva analiza pro- 563
blema može da dovede do pojednostavljenja ili optimizovanja pisanog programa, ali je ukazano 564
i da generalni algoritmi mogu da se primene, bez ikakve modifikacije, i da bi se ponašali jednako 565
efikasno. 566
Na kraju, ukazano je na činjenicu da kalkulacije koje naizgled deluju jednostavno (kao 567
što je utvrđivanje preklapanja termina) mogu da postanu komplikovani izrazi, i da upotreba 568
funkcija omogućuje da se takve kompleksne operacije izdvoje izvan osnovne obrade, i da se 569
realizuju kasnije, u više koraka, što pisanje programa čini jednostavnijim, a kôd postaje čitljiviji 570
i razumljiviji. 571
20
Listing 1.25: Kompletan program572
1 #include <stdio.h> 573
2 #include <stdlib.h> 574
3 #include <string.h> 575
4 #define DEFAULT_TEXT_LEN 80 576
5 577
6 typedef struct sTime { 578
7 int hours, minutes; 579
8 } Time; 580
9 581
10 typedef struct sActivity { 582
11 Time startTime, endTime; 583
12 char description[DEFAULT_TEXT_LEN + 1]; 584
13 } Activity; 585
14 586
15 typedef struct sListElement { 587
16 Activity * pActivity; 588
17 struct sListElement * next; 589
18 } ListElement; 590
19 591
20 ListElement* insert(ListElement* l, Activity* act); 592
21 Activity* readActivity(FILE* f); 593
22 ListElement* removeOverlappedActivities(ListElement* aList, FILE* output); 594
23 void printActivity(Activity* actv, FILE * f); 595
24 void printActivityList(ListElement* aList, FILE* f); 596
25 int isOverlaped(Activity* a1, Activity* a2); 597
26 int isTimeBeforeOrEqual(Time t1, Time t2); 598
27 int isActivityBeforeOrEqual(Activity* a1, Activity* t2); 599
28 int isTimeBefore (Time t1, Time t2); 600
29 int isActivityBefore(Activity* a1, Activity* a2); 601
30 void dealocate(ListElement* actv); 602
31 603
32 int main() { 604
33 ListElement* activityList = NULL; Activity* currActivity; 605
34 int s = 0; 606
35 FILE* scheduleFile = fopen("kalendar.txt", "r"); 607
36 FILE* overlapingFile = fopen("preklapanja.txt", "w"); 608
37 609
38 if (scheduleFile == NULL) { 610
39 fprintf(stderr, "Cannot open input file."); s = 1; goto END; 611
40 } 612
41 if (overlapingFile == NULL) { 613
42 fprintf(stderr, "Cannot open output file."); s = 1; goto END; 614
43 } 615
44 616
45 while ((currActivity = readActivity(scheduleFile)) != NULL) { 617
46 activityList = insert(activityList, currActivity); 618
47 } 619
48 620
49 printActivityList(activityList, stdout); 621
50 removeOverlappedActivities(activityList, overlapingFile); 622
51 printActivityList(activityList, stdout); 623
52 624
53 END: if (scheduleFile) fclose(scheduleFile); 625
54 if (overlapingFile) fclose(overlapingFile); 626
55 627
56 dealocate(activityList); 628
57 return s; 629
58 } 630
59 631
21
60 Activity* readActivity(FILE* f) { 632
61 int numOfData; 633
62 Activity* actv = (Activity*)malloc(sizeof(Activity)); 634
63 if(!actv) { 635
64 printf("Error in memory allocation"); 636
65 exit(2); 637
66 } 638
67 numOfData = fscanf(f, "%d:%d-%d:%d ", 639
68 &actv->startTime.hours, &actv->startTime.minutes, 640
69 &actv->endTime.hours, &actv->endTime.minutes); 641
70 642
71 if (numOfData == EOF) { 643
72 free(actv); //nepotrebno smo alocirali 644
73 return NULL; 645
74 } 646
75 647
76 fgets(actv->description, DEFAULT_TEXT_LEN, f); 648
77 649
78 return actv; 650
79 } 651
80 652
81 ListElement* insert(ListElement* lstFirst, Activity* actv) { 653
82 ListElement* newActv = (ListElement*) malloc(sizeof(ListElement)); 654
83 655
84 newActv->pActivity = actv; 656
85 657
86 ListElement* curr = lstFirst, *prev = NULL; 658
87 while (curr) { 659
88 if (isActivityBeforeOrEqual(curr->pActivity, actv)) 660
89 prev = curr, curr = curr->next; 661
90 else break; // pronasli smo mesto! 662
91 } 663
92 664
93 if (!prev) lstFirst = newActv; //prvi element 665
94 else prev->next = newActv; // 666
95 newActv->next = curr; // ulancavanmo u postojecu 667
96 668
97 return lstFirst; // vracamo glavu liste 669
98 } 670
99 671
100 ListElement* removeOverlappedActivities(ListElement* aList, FILE* output) { 672
101 ListElement * pi = aList, * pj = NULL, * pjPrev, * old; 673
102 while (pi) { 674
103 pj = pi->next; 675
104 while (pj) { 676
105 if (isOverlaped(pi->pActivity, pj->pActivity)) { 677
106 printActivity(pj->pActivity, output); 678
107 pi->next = pj->next; 679
108 free(pj->pActivity); 680
109 free(pj); 681
110 pj = pi->next; 682
111 } else { 683
112 pj = pj->next; 684
113 } 685
114 } 686
115 pi = pi->next; 687
116 } 688
117 689
118 return aList; 690
119 } 691
22
120 692
121 int isOverlaped(Activity* a1, Activity* a2) { 693
122 return ( isTimeBeforeOrEqual(a1->startTime, a2->startTime) 694
123 && isTimeBefore(a2->startTime, a1->endTime) ) 695
124 || ( isTimeBeforeOrEqual(a2->startTime, a1->startTime) 696
125 && isTimeBefore(a1->startTime, a2->endTime) ); 697
126 } 698
127 699
128 int isTimeBeforeOrEqual(Time t1, Time t2) { 700
129 return (t1.hours < t2.hours) || 701
130 (t1.hours == t2.hours && t1.minutes <= t2.minutes); 702
131 } 703
132 704
133 int isTimeBefore(Time t1, Time t2) { 705
134 return (t1.hours < t2.hours) || 706
135 (t1.hours == t2.hours && t1.minutes < t2.minutes); 707
136 } 708
137 709
138 int isActivityBeforeOrEqual(Activity* a1, Activity* a2) { 710
139 return isTimeBeforeOrEqual(a1->startTime, a2->startTime); 711
140 } 712
141 713
142 void printActivity(Activity* actv, FILE* f) { 714
143 fprintf(f, "%d:%d-%d:%d ", actv->startTime.hours, actv->startTime.minutes, 715
144 actv->endTime.hours, actv->endTime.minutes); 716
145 fputs(actv->description, f); 717
146 } 718
147 719
148 void printActivityList(ListElement* aList, FILE* f) { 720
149 ListElement* p = aList; 721
150 while (p) { 722
151 printActivity(p->pActivity, f); 723
152 p = p->next; 724
153 } 725
154 } 726
155 727
156 void dealocate(ListElement* actv){ 728
157 ListElement* tmp; 729
158 while(actv){ 730
159 tmp = actv; 731
160 actv = actv->next; 732
161 /* Obavezno osloboditi i memoriju koju zauzima dinamicki alocirana 733
162 struktura koja predstavlja aktivnost! */ 734
163 free(tmp->pActivity); 735
164 free(tmp); 736
165 } 737
166 } 738739
23
1.2.7 Drugo rešenje 740
U ovom odeljku, biće prikazano kompletno rešenje, koje prevazilazi okvire zahteva koji se 741
postavljaju za rešenja zadataka na ispitima, ali može poslužiti kao dobra referenca, u kojoj su 742
prikazane dobre programerske prakse koje bi trebalo koristiti pri svakom ozbiljnijem rešavanju 743
problema. 744
U listingu 1.26 prikazan je fajl zaglavlja (header file), u kome su prototipovi funkcija, sim- 745
boličke konstante i pomoćni makroi. Prikazani kôd sadrži komentare pojedinih elemenata, tako 746
da u tekstu neće biti diskutovani. Preporuka je da header fajlovi sadrže dovoljnu količinu ko- 747
mentara koji opisuju namene makroa, simboličkih konstanti, kao i uloge funkcija koje postoje, 748
i time pruže dokumentaciju samom programeru. 749
U listingu 1.28 prikazano je još jedno rešenje ovog zadatka. Najveće razlike vide se u funkciji 750
readActivity, koja obezbeđuje da se učitavanje izvrši do kraja linije, koristeći mehanizam 751
koji je malo drugačiji od onog opisanog ranije, prikazanog u listingu 1.15, ali se baziraju na 752
istoj ideji. 753
24
Listing 1.26: Drugo rešenje - zaglavlje754
1 #ifndef _RASPORED_H_ 755
2 #define _RASPORED_H_ 756
3 757
4 // Default length of short text. 758
5 #define DEFAULT_TEXT_LEN 80 759
6 760
7 #define RESULT_OK 100 761
8 #define RESULT_FAILED -1 762
9 763
10 // Memory management functions/macros 764
11 // Used to make the source code independent of a concrete implementation. 765
12 #define MEM_ALLOC(Type) (Type*) malloc (sizeof(Type)) 766
13 #define MEM_FREE(ptr) free(ptr) 767
14 768
15 /* 769
16 * USER-DEFINED TYPES 770
17 */ 771
18 772
19 // Abstract type Time. 773
20 typedef struct sTime { 774
21 int hours; 775
22 int minutes; 776
23 } Time; 777
24 778
25 // Abstract type/concept Activity. 779
26 typedef struct sActivity { 780
27 Time startTime; 781
28 Time endTime; 782
29 char description[DEFAULT_TEXT_LEN+1]; 783
30 } Activity; 784
31 785
32 // Linked list element/node. 786
33 typedef struct sListElement { 787
34 Activity *pActivity; 788
35 struct sListElement *next; 789
36 } ListElement; 790
37 791
38 // Linked list 792
39 typedef struct { 793
40 ListElement *head; 794
41 ListElement *tail; 795
42 } List; 796
43 797
44 798
45 /* 799
46 * UTILITY FUNCTIONS FOR USER-DEFINED TYPES 800
47 */ 801
48 802
49 // Checks whether t1 is before t2. 803
50 // Returns 1 if true. Otherwise, 0 is returned. 804
51 int isTimeBefore(Time t1, Time t2); 805
52 806
53 // Checks whether time t1 is less or equal than t2. 807
54 // Returns 1 if true. Otherwise, 0 is returned. 808
55 int isTimeBeforeOrEqual(Time t1, Time t2); 809
56 810
57 // Creates an empty list. 811
58 List* createList(); 812
59 813
25
60 // Deallocates list. 814
61 // Returns RESULT_OK on success. Otherwise, RESULT_FAILED is returned. 815
62 int freeList (List *list); 816
63 817
64 818
65 /* 819
66 * PROBLEM DOMAIN FUNCTIONS 820
67 */ 821
68 822
69 // Checks whether activity a1 starts before or at the same time as activity a2. 823
70 // Returns 1 if true. Otherwise, 0 is returned. 824
71 int isActivityBeforeOrEqual(Activity *a1, Activity *a2); 825
72 826
73 // Prints activity actv to file f. 827
74 void printActivity(Activity *actv, FILE *f); 828
75 829
76 // Insertes activity actv to list. 830
77 // Returns RESULT_OK on success. Otherwise, RESULT_FAILED is returned. 831
78 int insertActivity(List *list, Activity *actv); 832
79 833
80 // Read an activity from the file f. 834
81 // On success, the function returns a pointer to a completly loaded activity object 835
. 836
82 // Otherwise, a NULL pointer is returned. 837
83 Activity* readActivity(FILE *f); 838
84 839
85 // Checks whether activities a1 and a2 are overlapped. 840
86 // Returns 1 if the activities are overlapped. Otherwise, 0 is returned. 841
87 int isOverlapped(Activity *a1, Activity *a2); 842
88 843
89 // Removes overlapped activities from a chronologically ordered list of activities. 844
90 // Returns RESULT_OK on success. Otherwise, RESULT_FAILED is returned. 845
91 int removeOverlappedActivities(List *list, FILE *output); 846
92 847
93 #endif 848849
26
Listing 1.27: Drugo rešenje850
1 #include <stdio.h> 851
2 #include <stdlib.h> 852
3 #include <string.h> 853
4 #include "raspored.h" 854
5 855
6 /* Time utility functions. */ 856
7 int isTimeBefore(Time t1, Time t2) { 857
8 return (t1.hours<t2.hours) || 858
9 (t1.hours==t2.hours&& t1.minutes<t2.minutes); 859
10 } 860
11 861
12 int isTimeBeforeOrEqual(Time t1, Time t2) { 862
13 return (t1.hours<t2.hours) || 863
14 (t1.hours==t2.hours && t1.minutes<=t2.minutes); 864
15 } 865
16 866
17 int isActivityBeforeOrEqual(Activity *a1, Activity *a2) { 867
18 return isTimeBeforeOrEqual(a1->startTime, a2->startTime); 868
19 } 869
20 870
21 Activity* readActivity(FILE *f) { 871
22 int result, lastChar; 872
23 Activity* actv = MEM_ALLOC(Activity); 873
24 if (actv == NULL) 874
25 return NULL; 875
26 876
27 result = fscanf(f, "%d:%d-%d:%d ", 877
28 &actv->startTime.hours, &actv->startTime.minutes, 878
29 &actv->endTime.hours, &actv->endTime.minutes); 879
30 880
31 if (result == EOF) { 881
32 free(actv); 882
33 return NULL; 883
34 } 884
35 885
36 // Set ’\0’ at position DEFAULT_TEXT_LEN-1 for determining incomplete line of 886
text. 887
37 // If the line is longer than DEFAULT_TEXT_LEN, the indicator will be 888
overwritten 889
38 // with a character value from the input. 890
39 actv->description[DEFAULT_TEXT_LEN - 1] = ’\0’; 891
40 892
41 fgets(actv->description, DEFAULT_TEXT_LEN, f); 893
42 894
43 // Reliability feature: 895
44 // If a description text in the file is longer than DEFAULT_TEXT_LEN, 896
45 // skip the rest of the line, and move the cursor to the next line. 897
46 lastChar = actv->description[DEFAULT_TEXT_LEN - 1]; 898
47 if (lastChar != ’\0’ && lastChar != ’\n’) { 899
48 char tmp[DEFAULT_TEXT_LEN +1], *result = NULL; 900
49 do { 901
50 tmp[DEFAULT_TEXT_LEN - 1] = ’\0’; 902
51 result = fgets (tmp, DEFAULT_TEXT_LEN, f); 903
52 } while (result != NULL 904
53 && tmp[DEFAULT_TEXT_LEN - 1] != ’\0’ 905
54 && tmp[DEFAULT_TEXT_LEN - 1] != ’\n’); 906
55 } 907
56 return actv; 908
57 } 909
27
58 910
59 List* createList() { 911
60 List *list = MEM_ALLOC(List); 912
61 list->head = list->tail = NULL; 913
62 return list; 914
63 } 915
64 916
65 int insertActivity(List *list, Activity *actv) { 917
66 ListElement* curr = list->head, *prev = NULL; 918
67 ListElement *newElement = MEM_ALLOC(ListElement); 919
68 if (newElement == NULL) { 920
69 printf ("Error in function insertActivity: could not allocated list element 921
.\n"); 922
70 return RESULT_FAILED; 923
71 } 924
72 925
73 newElement->pActivity = actv; 926
74 while (curr != NULL) { 927
75 if (!isActivityBeforeOrEqual(curr->pActivity, actv)) 928
76 break; 929
77 prev=curr, curr=curr->next; 930
78 } 931
79 932
80 newElement->next = curr; 933
81 if (prev == NULL) { 934
82 list->head = newElement; 935
83 list->tail = newElement; 936
84 } else 937
85 prev->next = newElement; 938
86 939
87 return RESULT_OK; 940
88 } 941
89 942
90 void printActivity(Activity *actv, FILE *f) { 943
91 fprintf(f, "%02d:%02d-%02d:%02d ", 944
92 actv->startTime.hours, actv->startTime.minutes, 945
93 actv->endTime.hours, actv->endTime.minutes); 946
94 fputs(actv->description, f); 947
95 fputc(’\n’, f); 948
96 } 949
97 950
98 int freeList (List *list) { 951
99 while (list->head != NULL) { 952
100 ListElement *curr = list->head; 953
101 list->head = list->head->next; 954
102 if (curr->pActivity != NULL) 955
103 MEM_FREE(curr->pActivity); 956
104 MEM_FREE(curr); 957
105 } 958
106 return RESULT_OK; 959
107 } 960
108 961
109 int printList (List *list, FILE *outputFile) { 962
110 ListElement *p = list->head; 963
111 while (p != NULL) { 964
112 printActivity(p->pActivity, outputFile); 965
113 p = p->next; 966
114 } 967
115 return RESULT_OK; 968
116 } 969
28
117 970
118 int isOverlapped(Activity *a1, Activity *a2) { 971
119 return 972
120 (isTimeBeforeOrEqual(a1->startTime,a2->startTime) && 973
121 isTimeBefore(a2->startTime, a1->endTime)) 974
122 || 975
123 (isTimeBeforeOrEqual(a2->startTime,a1->startTime) && 976
124 isTimeBefore(a1->startTime, a2->endTime)); 977
125 } 978
126 979
127 int removeOverlappedActivities(List *list, FILE *output) { 980
128 ListElement *pi=list->head, *pj=NULL; 981
129 while (pi != NULL) { 982
130 pj = pi->next; 983
131 while (pj != NULL) { 984
132 if (isOverlapped(pi->pActivity, pj->pActivity)) { 985
133 pi->next = pj->next; 986
134 printActivity(pj->pActivity, output); 987
135 if (pj->pActivity != NULL) 988
136 MEM_FREE(pj->pActivity); 989
137 MEM_FREE(pj); 990
138 pj = pi->next; 991
139 } else { 992
140 pj=pj->next; 993
141 } 994
142 } 995
143 pi = pi->next; 996
144 } 997
145 return RESULT_OK; 998
146 } 9991000
29
Listing 1.28: Drugo rešenje -funkcija main1001
1 int main () { 1002
2 List *activityList = createList(); 1003
3 Activity *currActivity; 1004
4 int status = EXIT_SUCCESS; 1005
5 1006
6 FILE *scheduleFile = fopen("kalendar.txt", "r"); 1007
7 FILE *overlapingFile = fopen("preklapanja.txt", "w"); 1008
8 FILE *debugFile = fopen("debug.txt", "w"); 1009
9 1010
10 if (scheduleFile == NULL) { 1011
11 printf("Cannot open input file."); 1012
12 status = EXIT_FAILURE; 1013
13 goto ON_ERROR; 1014
14 } 1015
15 if (overlapingFile == NULL) { 1016
16 printf("Cannot open output file."); 1017
17 status = EXIT_FAILURE; 1018
18 goto ON_ERROR; 1019
19 } 1020
20 if (debugFile == NULL) { 1021
21 printf("Cannot open debug file."); 1022
22 status = EXIT_FAILURE; 1023
23 goto ON_ERROR; 1024
24 } 1025
25 1026
26 while ( (currActivity = readActivity(scheduleFile)) != NULL ) { 1027
27 if (insertActivity(activityList, currActivity) != RESULT_OK) { 1028
28 printf("Could not insert activity into the list of activities.\n"); 1029
29 goto ON_ERROR; 1030
30 } 1031
31 } 1032
32 1033
33 printList(activityList, debugFile); 1034
34 if (removeOverlappedActivities(activityList, overlapingFile) != RESULT_OK) { 1035
35 printf("Could not remove overlapped activities from the list of activities 1036
.\n"); 1037
36 goto ON_ERROR; 1038
37 } 1039
38 printList(activityList, debugFile); 1040
39 1041
40 ON_ERROR: // Resource deallocation. 1042
41 if (scheduleFile != NULL) fclose(scheduleFile); 1043
42 if (overlapingFile != NULL) fclose(overlapingFile); 1044
43 if (debugFile != NULL) fclose(debugFile); 1045
44 freeList(activityList); 1046
45 1047
46 return status; 1048
47 } 10491050
30