This shows you the differences between two versions of the page.
programare:laboratoare:lab07 [2022/04/01 11:35] cristian.olaru1612 |
programare:laboratoare:lab07 [2025/10/15 18:31] (current) darius.neatu [Referinţe] |
||
---|---|---|---|
Line 1: | Line 1: | ||
+ | ===== PCLP Laborator07: Prelucrarea şirurilor de caractere. Funcţii. Aplicaţii ===== | ||
- | ===== Optimizarea programelor folosind operaţii pe biţi ===== | ||
- | **Responsabili:** | + | **Resposabili**: |
- | * [[neatudarius@gmail.com|Darius Neațu (CA 2019-2021)]] | + | * [[ion_dorinel.filip@cti.pub.ro|Dorinel Filip (2016-2020)]] |
- | * [[ion_dorinel.filip@cti.pub.ro|Dorinel Filip (CA 2019-2021)]] | + | * [[neatudarius@gmail.com|Darius Neațu (2019-2020)]] |
- | * [[rares96cheseli@gmail.com|Rareș Cheșeli (2018, 2017)]] | + | * [[emil.racec@gmail.com|Emil Racec (2012)]] |
+ | * [[alina.g.simion@gmail.com|Alina Simion (2008)]] | ||
+ | * [[stefan.bucur@gmail.com|Ştefan Bucur (2006)]] | ||
- | ==== Obiective ==== | ||
- | În urma parcurgerii acestui laborator, studentul va fi capabil: | + | ==== Obiective ==== |
- | * să înțeleagă conceptele legate de operații pe biți | + | |
- | * să utilizeze operații pe biți pentru optimizare (când este cazul) | + | |
- | * să înteleagă mai multe despre organizarea datelor în memorie | + | |
- | * să gestioneze mai bine memoria folosită într-un anumit program C | + | |
- | * să implementeze o structură de date in C | + | |
- | <note warning> | + | În urma parcurgerii acestui laborator studentul va fi capabil: |
- | * Laboratorul curent introduce mai multe noțiuni. Pe multe dintre acestea le veți mai întâlni și la alte cursuri din următorii ani, precum: IOCLA, CN/CN2, AA/PA, PC, PM, SO. | + | |
- | * Secțiunile ** Studiu de caz ** și ** Probleme de interviu ** nu sunt obligatorii, dar recomandăm parcurgerea întregului material pentru o viziune de ansamblu mai bună. Atenție! Problemele de interviu sunt mult peste nivelul așteptat la această materie! | + | |
- | </note> | + | |
- | ==== Motivație ==== | + | * să declare şi să folosească şiruri de caractere |
- | În cadrul acestui articol vă vom prezenta câteva metode prin care programele se pot optimiza dacă utilizăm eficient operaţiile pe biţi. Un lucru foarte important de reținut este că nu întotdeauna putem folosi aceste operații pentru optimizare, iar laboratorul are ca scop ilustrarea câtorva **exemple**, pe care le puteți întâlni și pe viitor. | + | * să folosească funcţiile de manipulare a şirurilor de caractere din libraria string.h |
- | Veți învăța în anul 2 la Analiza Algoritmilor despre complexitatea unui algoritm. Vom considera momentan că un algoritm este mai rapid decât altul dacă are mai puțini pași (exemplu un for cu n = 100 de pași este mai rapid decât un for cu 1000 de pași). | + | ==== Noţiuni teoretice ==== |
- | În exemplul anterior **performanța** se referă la timp (dacă executăm mai puține instrucțiuni într-un program, ne așteptăm să se termine mai repede). Acest aspect va fi abordat pe larg la materiile AA și PA. | ||
- | În acest laborator vom vorbi despre altă metrică de măsurare a performanței unui program, mai exact despre ** memoria folosită ** de un program. | ||
- | De ce este **important** și acest aspect? Dacă din punct de vedere al timpului de execuție, sunt situații în care putem aștepta mai mult timp pentru a se termina programul, din punctul de vedere al memoriei folosite avem o limitare exactă. Un exemplu simplu este calculatorul nostru, care are 4GB/8GB/16GB. Dacă mașina noastră are X GB RAM, dintre care o parte importantă o ocupă sistemul de operare, asta înseamnă că într-un anumit program nu putem folosi o cantitate nelimitată de RAM (mai multe detalii la CN2, SO). Pentru **simplitate**, momentan presupunem că programul nostru nu poate rula pe o mașină cu X GB, dacă are nevoie de mai mult de X GB. | + | === Şiruri de caractere === |
- | + | ||
- | Dacă ajungem într-o astfel de situație în mod evident trebuie să schimbăm ceva, însă de multe ori putem păstra algoritmul și să facem câteva modificări în implementare, care exploatează anumite abilități ale limbajului C (ex. operații pe biți). | + | |
- | ==== Dimensiunea tipurilor implicite în C. Calculul memoriei unui program ==== | + | Un **caracter** se declară în C de forma: ''char a=%%'a'%%''; Pentru inițializarea lui, se observă că am pus un caracter între apostroafe. |
- | În [[https://ocw.cs.pub.ro/courses/programare/laboratoare/lab02|laboratorul 2]] au fost prezentate tipurile de date implicite din C și dimensiunea acestora. | + | Un **şir de caractere** presupune practic un vector de caractere, terminat prin caracterul ''\0''. |
- | Pentru a afla ** dimensiunea în bytes ** a unei variabile se poate folosi operatorul **sizeof**. | + | Compilatorul folosește în mod implicit această reprezentare, astfel încât cea mai simplă declarație este ''char c[]="cuvant"''; Observăm aici folosirea ghilimelelor în locul apostroafelor. Acecastă instrucțiune va aloca un spațiu de ''7'' octeți pe care va reprezenta șirul de caractere 'cuvant' (care are ''6'' caractere). |
- | <spoiler Exemplu> | + | Dacă dorim să alocăm un spațiu de memorie mai mare (pentru a putea folosi variabila pentru a stoca șiruri de caractere mai lungi), putem folosi o declarație de tipul ''char c[10] = "cuvant";''. Astfel am alocat spațiu suficient pentru un șir de ''9'' caractere. |
- | Fie codul din următorul cod. | + | |
- | <code c> | + | |
- | #include <stdio.h> | + | <note important> |
- | + | Deși o inițializare de tipul ''char c[6] = "cuvant"'', în care spațiul alocat este egal cu numărul de caractere, nu va determina compilatorul să genereze un warning/o eroare, acest lucru poate avea rezultate neașteptate dacă în memorie - la finalul șirului - nu se află (întâmplător) valoarea 0 binar. | |
- | int main() { | + | </note> |
- | // afiseaza dimensiunea tipurilor si a unor variabile de un anumit tip | + | |
- | char xc; | + | |
- | printf("sizeof(unsigned char) = %ld B\n", sizeof(unsigned char)); | + | |
- | printf("sizeof(char) = %ld B\n", sizeof(char)); | + | |
- | printf("sizeof(xc) = %ld B\n", sizeof(xc)); | + | |
- | + | ||
- | short int xs; | + | |
- | printf("sizeof(unsigned short int) = %ld B\n", sizeof(unsigned short int)); | + | |
- | printf("sizeof(short int) = %ld B\n", sizeof(short int)); | + | |
- | printf("sizeof(xs) = %ld B\n", sizeof(xs)); | + | |
- | + | ||
- | int xi; | + | |
- | printf("sizeof(unsigned int) = %ld B\n", sizeof(unsigned int)); | + | |
- | printf("sizeof(int) = %ld B\n", sizeof(int)); | + | |
- | printf("sizeof(xi) = %ld B\n", sizeof(xi)); | + | |
- | + | ||
- | // afiseaza dimensiunea unor tablouri cu dimensiune cunoscuta | + | |
- | char vc[100]; | + | |
- | short int vs[100]; | + | |
- | int vi[100]; | + | |
- | printf("sizeof(vc) = %ld B\n", sizeof(vc)); | + | |
- | printf("sizeof(vs) = %ld B\n", sizeof(vs)); | + | |
- | printf("sizeof(vi) = %ld B\n", sizeof(vi)); | + | |
- | + | ||
- | return 0; | + | |
- | } | + | |
+ | Cum s-a prezentat anterior, o variabilă vector conţine adresa de început a vectorului(adresa primei componente a vectorului), şi de aceea este echivalentă cu un pointer la tipul elementelor din vector. Deci declaraţiile de mai jos vor declara fiecare cate un şir de caractere: | ||
+ | <code c> | ||
+ | char a[5]; | ||
+ | char *b="unsir"; | ||
+ | char *c; | ||
</code> | </code> | ||
- | În urma executării acestui program pe o arhitectură de **32 biți** (ceea ce folosim la PC) vom vedea următorul rezultat. | + | Diferenţa majoră dintre între ele este însa că primele două declaraţii vor aloca 5 pozitii în memorie, pe când ultima nu va aloca nici o zona de memorie, necesitând sa fie ulterior alocată, folosind funcţiile de alocare dinamică (''malloc(), calloc(), realloc()''), prezentate în laboratorul anterior. |
- | <code> | + | Între prima și a 2-a declarație, diferența este mai subtilă și constră în faptul că declarația ''char *b="unsir"'' va determina compilatorul să plaseze șirul respectiv într-o zonă de memorie asupra căreia nu avem drepturi de scriere, deci orice încercare de a modifica acel șir - pe Linux - va genera, cel mai probabil, o eroare de tipul ''segmentation fault''. |
- | sizeof(unsigned char) = 1 B | + | Un mic exemplu de citire a unui şir, caracter cu caracter pana la întâlnirea caracterului ''-'': |
- | sizeof(char) = 1 B | + | |
- | sizeof(xc) = 1 B | + | |
- | sizeof(unsigned short int) = 2 B | + | <code c> |
- | sizeof(short int) = 2 B | + | #include <stdio.h> |
- | sizeof(xs) = 2 B | + | #include <string.h> |
- | + | ||
- | sizeof(unsigned int) = 4 B | + | |
- | sizeof(int) = 4 B | + | |
- | sizeof(xi) = 4 B | + | |
- | + | ||
- | sizeof(vc) = 100 B | + | |
- | sizeof(vs) = 200 B | + | |
- | sizeof(vi) = 400 B | + | |
+ | #define N 30 | ||
+ | |||
+ | int main () { | ||
+ | char str[N], c; | ||
+ | int n = 0; | ||
+ | |||
+ | do { | ||
+ | scanf("%c", &c); | ||
+ | if (c == '-') { | ||
+ | break; | ||
+ | } | ||
+ | str[n++] = c; | ||
+ | } while(1); | ||
+ | |||
+ | str[n] = '\0'; // setam terminatorul de șir | ||
+ | printf("%s", str); | ||
+ | |||
+ | return 0; | ||
+ | } | ||
</code> | </code> | ||
- | </spoiler> | ||
- | <note> | + | Pentru citirea si afisarea unui şir de caractere se poate folosi flagul '**s**' la citirea cu ''scanf'' sau afişarea cu ''printf''. De asemenea biblioteca ''stdio.h'' defineşte funcţiile ''gets()'' şi ''puts()'' pentru lucrul cu şiruri de caractere. |
- | Putem afla dimensiunea unui tip de date / unei variabile de un anumit tip la ** compile time ** folosind operatorul sizeof care returnează dimensiunea în bytes a parametrului dat. | + | |
- | **sizeof** poate fi folosit **și** pentru măsurarea dimesiunii unui vector / matrice alocat(a) static. | + | * ''gets(zona)'' -– citeşte de la terminalul standard un şir de caractere terminat cu linie noua (enter). |
+ | * Funcţia are ca parametru adresa zonei de memorie în care se introduc caracterele citite. | ||
+ | * Funcţia returneaza adresa de început a zonei de memorie. | ||
+ | * ''puts(zona)'' -- afişeaza la terminalul standard şirul de caractere din zona data ca parametru, până la caracterul terminator de şir (\0), în locul căruia va afișa caracterul sfârșit de linie. | ||
+ | * Funcţia are ca parametru adresa zonei de memorie de unde începe afişarea caracterelor. | ||
+ | * Funcţia returneaza codul ultimului caracter (diferit de \0) din şirul de caractere afişat şi -1 dacă a aparut o eroare. | ||
- | Memoria totală folosită de un program poate fi calculată ca **suma** tuturor dimensiunilor ocupate de variabilele din program. | + | <note important> |
- | + | Funcţia ''gets()'' va citi de la tastatura câte caractere sunt introduse, chiar daca şirul declarat are o lungime mai mică. Presupunem un şir declarat: ''char a[]=”unsir”'' , care va avea deci 5 caractere. Citind un şir de lungime mai mare ca 5 de la tastatura, în şirul a, la afişare vom vedea ca s-a reţinut tot sirul (nu doar primele 5 caractere)! . Nimic deosebit până acum. Dar dacă luăm în considerare că citirea caracterelor auxiliare se face în continuare în zona de memorie, ne punem problema ce se va suprascrie?! | |
- | De obicei, ne interesează să știm ** ordinul de mărime ** al spațiului de memorie alocat, astfel, de cele mai multe ori, putem contoriza doar tablourile. | + | Raspunsul este: nu se ştie... poate nimic important pentru programul nostru, poate ceva ce il va bloca sau duce la obţinerea de date eronate. |
- | + | ||
- | + | ||
- | <note warning> | + | |
- | Un caz special îl poate reprezenta recursivitatea! Punerea parametrilor pe stivă de un număr foarte mare de ori, este echivalent cu declararea unui tablou de valori pe stivă. Aceste variabile nu pot fi neglijate în calculul memoriei! | + | |
</note> | </note> | ||
- | Vom descoperi mai multe în următoare laboratoare. 8-) | + | Pentru a evita aceasta se recomandă utilizarea ''fgets()''. |
- | </note> | + | |
- | ==== Operatori pe biți în C ==== | + | * ''fgets(zona, lung_zona, stdin)'' -– citeşte de la ''stdin'' un şir de caractere terminat printr-o linie nouă dacă lungimea lui este mai mică decat ''lung_zona'' sau primele ''lung_zona - 1'' caractere în caz contrar. Parametrii sunt: zona de memorie, lungimea maxima admisă a şirului, şi terminalul standard de intrare. În cazul în care şirul dorit are lungime mai mică decât cea maximă, înaintea terminatorului de şir (\0), în zona de memorie va fi reţinut şi enter-ul dat(\n). |
- | Operatorii limbajului C pot fi unari, binari sau ternari, fiecare având o precedenţă şi o asociativitate bine definite (vezi [[https://ocw.cs.pub.ro/courses/programare/laboratoare/lab02 | lab02]]). | + | === Funcţii din <string.h> === |
- | În tabelul următor reamintim operatorii limbajului C care sunt folosiți la nivel de bit. | + | Pentru manipularea şirurilor de caractere în limbajul C se folosesc funcţii declarate în fişierul <string.h>. Vom încerca să le detaliem putin pe cele mai des folosite: |
- | ^ Operator ^ Descriere ^ Asociativitate ^ | + | == strlen() == |
- | | **%%~%%** | Complement faţă de 1 pe biţi | dreapta-stânga | | + | |
- | | **%%<<%% si %%>>%%** | Deplasare stânga/dreapta a biţilor | stânga-dreapta | | + | |
- | | **&** | ŞI pe biţi | stânga-dreapta | | + | |
- | | **^** | SAU-EXCLUSIV pe biţi | stânga-dreapta | | + | |
- | | **|** | SAU pe biţi | stânga-dreapta | | + | |
- | | **&=** și **|=** | Atribuire cu ŞI/SAU | dreapta-stânga | | + | |
- | | **^=** | Atribuire cu SAU-EXCLUSIV | dreapta-stânga | | + | |
- | | **%%<<=%%** şi **%%>>=%%** | Atribuire cu deplasare de biţi | dreapta-stânga | | + | |
- | <note warning> | + | <code c> |
- | Trebuie avută în vedere precedenţa operatorilor pentru obţinerea rezultatelor dorite! | + | size_t strlen(const char *str); |
+ | </code> | ||
- | Dacă nu sunteți sigur de precendența unui operator, folosiți o pereche de paranteze rotunde în plus în expresia voastră! Nu exagerați cu parantezele, codul poate deveni ilizibil. | + | Returneaza lungimea unui şir dat ca parametru. (numarul de caractere până la întalnirea terminatorului de şir: \0) |
- | </note> | + | |
- | == Bitwise NOT == | + | **Exemplu:** |
- | <spoiler Semnificație> | + | |
- | ** Bitwise NOT ** (**complement față de 1**) este operația la nivel de bit care următorul tabel de adevăr. | + | |
- | ^ x ^ ~x ^ | + | |
- | ^ 0 ^ 1 ^ | + | |
- | ^ 1 ^ 0 ^ | + | |
- | Evident putem extinde această operație și la nivel de număr. Operația se aplică separat pentru fiecare rang binar. | + | <code c> |
- | ^ ^ $b_2$ ^ $b_1$ ^ $b_0$ ^ | + | #include <stdio.h> |
- | ^ x = 3 ^ 0 ^ 1 ^ 1 ^ | + | #include <string.h> |
- | ^ ~x = 4 ^ 1 ^ 0 ^ 0 ^ | + | |
- | </spoiler> | + | |
- | == Bitwise AND == | + | #define N 256 |
- | <spoiler Semnificație> | + | |
- | ** Bitwise AND ** (**ȘI pe biți**) este operația la nivel de bit care următorul tabel de adevăr. | + | int main () { |
- | ^ x ^ y ^ x & y ^ | + | char text[N]; |
- | ^ 0 ^ 0 ^ 0 ^ | + | printf("Introduceti un text: "); |
- | ^ 0 ^ 1 ^ 0 ^ | + | gets(text); |
- | ^ 1 ^ 0 ^ 0 ^ | + | printf("Textul are %u caractere.\n", strlen(text)); |
- | ^ 1 ^ 1 ^ 1 ^ | + | return 0; |
+ | } | ||
+ | </code> | ||
- | Evident putem extinde această operație și la nivel de număr. Operația se aplică separat pentru fiecare rang binar. | + | **Iesire:** |
- | ^ ^ $b_2$ ^ $b_1$ ^ $b_0$ ^ | + | |
- | ^ x = 3 ^ 0 ^ 1 ^ 1 ^ | + | |
- | ^ y = 7 ^ 1 ^ 1 ^ 1 ^ | + | |
- | ^ x & y = 3 ^ 0 ^ 1 ^ 1 ^ | + | |
- | </spoiler> | + | |
- | == Bitwise OR == | + | <code> |
- | <spoiler Semnificație> | + | Introduceti un text: just testing |
- | ** Bitwise OR ** (**SAU pe biți**) este operația la nivel de bit care următorul tabel de adevăr. | + | Textul are 12 caractere. |
- | ^ x ^ y ^ x %%|%% y ^ | + | </code> |
- | ^ 0 ^ 0 ^ 0 ^ | + | |
- | ^ 0 ^ 1 ^ 1 ^ | + | |
- | ^ 1 ^ 0 ^ 1 ^ | + | |
- | ^ 1 ^ 1 ^ 1 ^ | + | |
- | Evident putem extinde această operație și la nivel de număr. Operația se aplică separat pentru fiecare rang binar. | + | == memset() == |
- | ^ ^ $b_2$ ^ $b_1$ ^ $b_0$ ^ | + | |
- | ^ x ^ 0 ^ 1 ^ 1 ^ | + | |
- | ^ y ^ 1 ^ 0 ^ 1 ^ | + | |
- | ^ x %%|%% y = 7 ^ 1 ^ 1 ^ 1 ^ | + | |
- | </spoiler> | + | |
- | == Bitwise XOR == | + | <code c> |
- | <spoiler Semnificație> | + | void* memset(void *ptr, int val, size_t num); |
- | ** Bitwise XOR ** (**SAU-EXCLUSIV pe biți**) este operația la nivel de bit care următorul tabel de adevăr. | + | </code> |
- | ^ x ^ y ^ x %%^%% y ^ | + | |
- | ^ 0 ^ 0 ^ 0 ^ | + | |
- | ^ 0 ^ 1 ^ 1 ^ | + | |
- | ^ 1 ^ 0 ^ 1 ^ | + | |
- | ^ 1 ^ 1 ^ 0 ^ | + | |
- | Evident putem extinde această operație și la nivel de număr. Operația se aplică separat pentru fiecare rang binar. | + | În zona de memorie dată de pointerul ptr, sunt setate primii num octeți la valoarea dată de val. Pentru șiruri de caractere - în care fiecare element ocupă 1 octet - aceasta are ca rezultat înlocuirea primelor 'num' valori cu cea dată ca argument. |
- | ^ ^ $b_2$ ^ $b_1$ ^ $b_0$ ^ | + | |
- | ^ x = 3 ^ 0 ^ 1 ^ 1 ^ | + | |
- | ^ y = 5 ^ 1 ^ 0 ^ 1 ^ | + | |
- | ^ x %%^%% y = 6 ^ 1 ^ 1 ^ 0 ^ | + | |
- | </spoiler> | + | |
- | == Bit LOGICAL SHIFT == | + | Funcţia returnează şirul ptr. |
- | În C sunt definite doar shiftări logice. Acestea pot fi la stânga (%%<<%%) sau la dreapta (%%>>%%), reprezentând deplasarea in binar a cifrelor și completarea pozițiilor "golite" cu zerouri. | + | |
- | == LEFT SHIFT == | + | **Exemplu:** |
- | <note> | + | |
- | Efectul unei deplasări la stânga cu un rang binar este echivalent cu înmulțirea cu 2 a numărului din baza 10. | + | |
- | Dacă rezultatul nu are loc pe tipul de date folosit, atunci se pot pierde din biți! | + | |
- | Se poate deduce următoarea relație: $ n << k = n * 2^k $. | + | <code c> |
- | </note> | + | #include <stdio.h> |
+ | #include <string.h> | ||
+ | |||
+ | int main () { | ||
+ | char str[] = "nu prea vreau vacanta!"; | ||
+ | memset(str, '-', 7); | ||
+ | puts(str); | ||
+ | return 0; | ||
+ | } | ||
+ | </code> | ||
- | <spoiler Exemplu> | + | **Iesire:** |
- | Fie un exemplu de deplasarea la stânga, pentru un număr ** pe 3 biți **. | + | |
- | ^ ^ $b_2$ ^ $b_1$ ^ $b_0$ ^ | + | |
- | ^ x = 3 ^ 0 ^ 1 ^ 1 ^ | + | |
- | ^ x << 1 = 6 ^ 1 ^ 1 ^ 0 ^ | + | |
- | ^ x << 2 = 8 ^ 1 ^ 0 ^ 0 ^ | + | |
- | </spoiler> | + | |
- | == RIGHT SHIFT == | + | <code> |
- | <note> | + | ------- vreau vacanta! |
- | Efectul unei deplasări la dreapta cu un rang binar este echivalent cu împărțirea întreagă la 2 a numărului din baza 10. | + | </code> |
- | Se poate deduce următoarea relație: $ n >> k = [n / 2^k ] $. | + | <note important> |
- | </note> | + | Ca programatori, veți întâlni adesea situații în care ''memset'' este folosit pentru inițializarea de vectori de diverse tipuri. |
- | <spoiler Exemplu> | + | Atunci când scrieți sau evaluați cod care face acest lucru trebuie să aveți în vedere că memset face scrierea valorii primite pe fiecare **octet** (fără a ține cont de dimensiunea reperezentării tipului de date). Următorul cod arată cum putem folosi memset pentru a inițializa un vector de int la 0 folosind memset, respectiv care ar fi rezultatul dacă am încerca să inițializăm vectorul cu o altă valoare: |
- | Fie un exemplu de deplasarea la dreapta, pentru un număr ** pe 3 biți **. | + | |
- | ^ ^ $b_2$ ^ $b_1$ ^ $b_0$ ^ | + | |
- | ^ x = 3 ^ 0 ^ 1 ^ 1 ^ | + | |
- | ^ x >> 1 = 1 ^ 0 ^ 0 ^ 1 ^ | + | |
- | ^ x >> 2 = 0 ^ 0 ^ 0 ^ 0 ^ | + | |
- | </spoiler> | + | |
+ | <code c> | ||
+ | #include <stdio.h> | ||
+ | #include <string.h> | ||
+ | #define SIZE 2 | ||
+ | int main(void) | ||
+ | { | ||
+ | int a[SIZE], b[SIZE], i; | ||
+ | memset(a, 0, SIZE * sizeof(int)); | ||
+ | memset(b, 5, SIZE * sizeof(int)); | ||
- | ==== Lucrul cu măști ==== | + | for(i = 0; i < SIZE; i++) |
- | Având la dispoziție operațiile prezentate mai sus, putem răspunde la următoarele întrebări. | + | printf("%d ", a[i]); |
- | * Cum verificăm dacă ** bitul i ** dintr-un număr n este ** setat **? | + | printf("\n"); |
- | * Cum ** setăm bitul i ** dintr-un număr n? | + | |
- | * Cum ** resetăm bitul i ** dintr-un număr n? | + | |
- | + | ||
- | Pentru a răspunde ușor, pentru fiecare întrebare vom aplica o operație pe biți între n și o valoarea numită ** mască **. | + | |
+ | for(i = 0; i < SIZE; i++) | ||
+ | printf("%d ", b[i]); | ||
+ | return 0; | ||
+ | } | ||
+ | </code> | ||
+ | Rezultatul este... | ||
+ | <code> | ||
+ | 0 0 | ||
+ | 84215045 84215045 | ||
+ | </code> | ||
+ | rezultatul neașteptat de pe a 2-a linie provenind din faptul că fiecare octet al int-urilor a fost setat la 5, și nu valoarea întregii structuri. | ||
+ | Este de menționat faptul că utilizarea ''memset'' în astfel de situații **nu** este recomandată. | ||
- | === Cum verificăm dacă bitul i dintr-un număr n este setat? === | ||
- | <note> | ||
- | Detectarea bitului: | ||
- | |||
- | * pas 1: se aplică următoarea operația ** x = n & mask **, unde ** mask = (1 << i) ** | ||
- | |||
- | ^ ^ $b_7$ ^ ... ^ $b_{i+1}$ ^ $b_i$ ^ $b_{i-1}$ ^ ... ^ $b_0$ ^ ^ | ||
- | ^ n ^ * ^ ... ^ * ^ ? ^ * ^ ... ^ * ^ ^ | ||
- | ^ mask ^ 0 ^ ... ^ 0 ^ 1 ^ 0 ^ ... ^ 0 ^ op ^ | ||
- | ^ x ^ 0 ^ ... ^ 0 ^ ? ^ 0 ^ ... ^ 0 ^ ^ | ||
- | |||
- | |||
- | * pas 2: deoarece **?** poate avea două valori, x poate fi $0$ sau $2^i$ | ||
- | * dacă ** x == 0 **, atunci ** bitul i este 0 ** | ||
- | * dacă ** x > 0 **, atunci ** bitul i este 1 ** | ||
</note> | </note> | ||
+ | == memmove() == | ||
- | <spoiler Implementare C> | + | <code c> |
- | <code C> | + | void* memmove(void *destination, const void *source, size_t num); |
+ | </code> | ||
+ | Copiază un număr de num caractere de la sursă, la zona de memorie indicată de destinaţie. Copierea are loc ca şi cum ar exista un buffer intermediar, deci sursa si destinatia se pot suprapune. Funcţia nu verifică terminatorul de şir la sursă, copiază mereu num bytes, deci pentru a evita depăsirea trebuie ca dimensiunea sursei sa fie mai mare ca num. Funcţia returnează destinaţia. | ||
- | // is_set | + | **Exemplu:** |
- | // byte - byte de intrare pentru care vreau sa verific un bit | + | |
- | // i - indexul bitului din byte | + | |
- | // @return - 1, daca bitul este 1 | + | |
- | // 0, daca bitul este 0 | + | |
- | int is_set(char byte, int i) { | + | |
- | int mask = (1 << i); | + | |
- | return (byte & mask) != 0; | + | |
- | } | + | |
- | ... | + | <code c> |
- | if (is_set(mybyte, i)) { | + | #include <stdio.h> |
- | printf("bitul %d din byteul %d este setat!\n", i, mybyte); | + | #include <string.h> |
- | } else { | + | |
- | printf("bitul %d din byteul %d NU este setat!\n", i, mybyte); | + | int main () { |
+ | char str[] = "memmove can be very useful......"; | ||
+ | memmove(str + 20, str + 15, 11); | ||
+ | puts(str); | ||
+ | return 0; | ||
} | } | ||
- | ... | ||
</code> | </code> | ||
- | </spoiler> | ||
- | <spoiler Explicație pas cu pas> | + | **Iesire:** |
- | Această întrebare ne oferă valoarea bitului i. | + | <code> |
+ | memmove can be very very useful. | ||
+ | </code> | ||
- | Dacă "valoarea este 1", atunci vom spune că "bitul este setat". | + | == memcpy() == |
- | Dacă "valoarea este 0", atunci vom spune că "bitul nu este "setat". | + | <code c> |
+ | void* memcpy(void *destination, const void *source, size_t num); | ||
+ | </code> | ||
+ | Copiază un număr de num caractere din şirul sursă in şirul destinaţie. Funcţia returnează şirul destinaţie. | ||
- | Pentru a ** verifica ** valoarea bitului i din numărul n, practic noi ar trebui să privim numărul astfel: | + | **Exemplu:** |
- | ^ ^ $b_7$ ^ $b_6$ ^ ... ^ $b_i$ ^ ... ^ $b_1$ ^ $b_0$ ^ | + | <code c> |
- | ^ n ^ * ^ * ^ ... ^ ? ^ ... ^ * ^ * ^ | + | #include <stdio.h> |
+ | #include <string.h> | ||
- | unde ***** înseamnă ** don't care ** (de la PL), | + | #define N 40 |
- | iar **?** este valoarea pe care o cautăm. | + | |
- | Deci am vrea să facem, după cum am zis mai sus, o operație de tipul "scoate" doar bitul i din număr, iar în rest lasă 0 (pentru a evidenția bitul nostru). | + | int main () { |
- | ^ ^ $b_7$ ^ ... ^ $b_{i+1}$ ^ $b_i$ ^ $b_{i-1}$ ^ ... ^ $b_0$ ^ ^ | + | char str1[] = "Exemplu"; |
- | ^ n ^ * ^ ... ^ * ^ ? ^ * ^ ... ^ * ^ ^ | + | char str2[N]; |
- | ^ mask ^ $m_7$ ^ ... ^ $m_{i+1}$ ^ $m_i$ ^ $m_{i-1}$ ^ ... ^ $m_0$ ^ op ^ | + | char str3[N]; |
- | ^ n op mask ^ 0 ^ ... ^ 0 ^ ? ^ 0 ^ ... ^ 0 ^ ^ | + | memcpy(str2, str1, strlen(str1) + 1); // + 1 este necesar pentru a copia și terminatorul de șir |
+ | memcpy(str3, "un sir", 7); | ||
+ | printf("str1: %s\nstr2: %s\nstr3: %s\n", str1, str2, str3); | ||
+ | return 0; | ||
+ | } | ||
+ | </code> | ||
- | **op** este o operație, iar **mask** un număr. Să analizăm cine pot fi **op** și **biții din mască** ($m_i$). | + | **Iesire:** |
- | Dorim ca: | + | <code> |
- | * $ ? \ \ op \ \ m_i = \ ? $ , adică operația op aplicată pe $?$ și $m_i$, va avea mereu ca rezultat pe $?$ | + | str1: Exemplu |
- | * $ * \ \ op \ \ m_j = 0 $ (unde i != j), adică operația op aplicată pe orice valoare și $m_j$, va da 0 | + | str2: Exemplu |
+ | str3: un sir | ||
+ | </code> | ||
- | Observăm că: | + | == strcpy() == |
- | * 1 este elementul neutru pentru **ȘI**, ceea ce verifică ** ? & 1 = ? **, oricare are fi ? un bit | + | |
- | * 0 este elementul care poate "șterge" un bit prin **Și**, ceea ce verifică ** * & 0 = 0 **, oricare ar fi * un bit | + | |
- | </spoiler> | + | <code c> |
+ | char* strcpy(char *destination, const char *source); | ||
+ | </code> | ||
+ | Copiază şirul sursă in şirul destinaţie. Şirul destinaţie va fi suprascris. Funcţia asigură plasarea terminatorului de şir în şirul destinaţie după copiere. Funcţia returneaza şirul destinaţie. | ||
+ | == strncpy() == | ||
+ | <code c> | ||
+ | char* strncpy(char *destination, const char *source, size_t num); | ||
+ | </code> | ||
+ | Asemeni cu ''strcpy()'', dar in loc de a fi copiată toata sursa sunt copiate doar primele //num// caractere. | ||
- | === Cum setăm (valoarea devine 1) bitul i dintr-un număr n? === | + | **Exemplu:** |
- | <note> | + | |
- | Setarea bitului: | + | |
- | * pas 1: se aplică următoarea operația ** n = n | mask **, unde ** mask = (1 << i) ** | + | |
- | + | ||
- | ^ ^ $b_7$ ^ ... ^ $b_{i+1}$ ^ $b_i$ ^ $b_{i-1}$ ^ ... ^ $b_0$ ^ ^ | + | |
- | ^ n ^ $n_7$ ^ ... ^ $n_{i+1}$ ^ * ^ $n_{i-1}$ ^ ... ^ $n_0$ ^ ^ | + | |
- | ^ mask ^ $0 $ ^ ... ^ $0 $ ^ $1 $ ^ $0 $ ^ ... ^ $0 $ ^ op ^ | + | |
- | ^ n op mask ^ $n_7$ ^ ... ^ $n_{i+1}$ ^ 1 ^ $n_{i-1}$ ^ ... ^ $n_0$ ^ ^ | + | |
+ | <code c> | ||
+ | #include <stdio.h> | ||
+ | #include <string.h> | ||
- | * explicație: | + | #define N 40 |
- | * orice valoare are avea bitul $*$ va fi suprascris cu 1 | + | |
- | * ceilalti biți vor fi copiați | + | |
- | </note> | + | int main () { |
- | + | char str1[] = "Exemplu"; | |
- | <spoiler Implementare C> | + | char str2[N]; |
- | <code C> | + | char str3[N]; |
- | + | strcpy(str2, str1); | |
- | // set | + | strncpy(str3, "un sir", 2); |
- | // byte - byte de intrare pentru care vreau sa setez un bit | + | str3[2] = '\0'; |
- | // i - indexul bitului din byte | + | printf("str1: %s\nstr2: %s\nstr3: %s\n", str1, str2, str3); |
- | // @return - noul byte | + | return 0; |
- | char set(char byte, int i) { | + | |
- | int mask = (1 << i); | + | |
- | return (byte | mask); | + | |
} | } | ||
- | |||
- | ... | ||
- | mybyte = set(mybyte, i); | ||
- | ... | ||
- | | ||
</code> | </code> | ||
- | </spoiler> | ||
- | <spoiler Explicație pas cu pas> | + | **Iesire:** |
- | Dorim să facem următoarea operație: schimba doar bitul i in 1, iar pe ceilalți lasă-i neschimbați. | + | |
- | ^ ^ $b_7$ ^ ... ^ $b_{i+1}$ ^ $b_i$ ^ $b_{i-1}$ ^ ... ^ $b_0$ ^ ^ | + | |
- | ^ n ^ $n_7$ ^ ... ^ $n_{i+1}$ ^ * ^ $n_{i-1}$ ^ ... ^ $n_0$ ^ ^ | + | |
- | ^ mask ^ $m_7$ ^ ... ^ $m_{i+1}$ ^ $m_i$ ^ $m_{i-1}$ ^ ... ^ $m_0$ ^ op ^ | + | |
- | ^ n op mask ^ $n_7$ ^ ... ^ $n_{i+1}$ ^ 1 ^ $n_{i-1}$ ^ ... ^ $n_0$ ^ ^ | + | |
- | **op** este o operație, iar **mask** un număr. Să analizăm cine pot fi **op** și **biții din mască** ($m_i$). | + | <code> |
+ | str1: Exemplu | ||
+ | str2: Exemplu | ||
+ | str3: un | ||
+ | </code> | ||
- | Dorim ca: | + | == strcat() == |
- | * $ * \ \ op \ \ m_i = 1 $, adică operația op aplicată pe $*$ (orice) și $m_i$, va avea mereu ca rezultat pe $1$ | + | |
- | * $ n_j \ \ op \ \ m_j = n_j $ (unde i != j), adică operația $op$ aplicată pe $n_j$ și $m_j$, va da $n_j$ | + | |
- | Observăm că: | + | <code c> |
- | * 1 este elementul care poate "umple" un bit prin **SAU**, ceea ce verifică $ * | 1 = 1 $, oricare ar fi * un bit | + | char* strcat(char *destination, const char *source); |
- | * 0 este elementul neutru pentru **SAU**, ceea ce verifică $ n_j | 0 = n_j $, oricare are fi $n_j$ un bit | + | </code> |
- | </spoiler> | + | |
+ | Concatenenaza şirul sursă la şirul destinaţie. Funcţia returnează şirul destinaţie. | ||
+ | <note critical> | ||
+ | Șirul destinație trebuie să aibă suficientă memorie alocată pentru a a acomoda șirul rezultat. | ||
+ | </note> | ||
+ | == strncat() == | ||
+ | <code c> | ||
+ | char* strncat(char *destination, const char *source, size_t num); | ||
+ | </code> | ||
+ | Asemeni cu ''strcat()'', dar în loc de a fi concatenată toată sursa sunt concatenate **cel mult** primele //num// caractere din șirul sursa (aceasta putând fii și mai scurt). | ||
- | === Cum resetăm (valoarea devine 0) bitul i dintr-un număr n? === | + | **Exemplu:** |
- | <note> | + | |
- | Resetarea bitului: | + | |
- | * pas 1 / 1: se aplică următoarea operația ** n = n & mask **, unde ** mask = ~(1 << i) ** | + | <code c> |
+ | #include <stdio.h> | ||
+ | #include <string.h> | ||
- | ^ ^ $b_7$ ^ ... ^ $b_{i+1}$ ^ $b_i$ ^ $b_{i-1}$ ^ ... ^ $b_0$ ^ ^ | + | #define N 80 |
- | ^ n ^ $n_7$ ^ ... ^ $n_{i+1}$ ^ * ^ $n_{i-1}$ ^ ... ^ $n_0$ ^ ^ | + | |
- | ^ mask ^ $1 $ ^ ... ^ $1 $ ^ $0 $ ^ $1 $ ^ ... ^ $1 $ ^ op ^ | + | |
- | ^ n op mask ^ $n_7$ ^ ... ^ $n_{i+1}$ ^ 0 ^ $n_{i-1}$ ^ ... ^ $n_0$ ^ ^ | + | |
- | + | int main () { | |
- | * explicație: | + | char str[N]; |
- | * orice valoare are avea bitul $*$ va fi suprascris cu 0 | + | strcpy(str, "ana "); |
- | * ceilalti biți vor fi copiați | + | strcat(str, "are "); |
- | + | strcat(str, "mere "); | |
- | </note> | + | puts(str); |
- | + | strncat(str, "si pere si prune", 7); | |
- | + | puts(str); | |
- | <spoiler Implementare C> | + | return 0; |
- | <code C> | + | |
- | + | ||
- | // reset | + | |
- | // byte - byte de intrare pentru care vreau sa resetez un bit | + | |
- | // i - indexul bitului din byte | + | |
- | // @return - noul byte | + | |
- | char reset(char byte, int i) { | + | |
- | int mask = ~(1 << i); | + | |
- | return (byte & mask); | + | |
} | } | ||
- | |||
- | ... | ||
- | mybyte = reset(mybyte, i); | ||
- | ... | ||
- | | ||
</code> | </code> | ||
- | </spoiler> | ||
+ | **Iesire:** | ||
- | <spoiler Explicație pas cu pas> | + | <code> |
- | Dorim să facem următoarea operație: schimba doar bitul i in 0, iar pe ceilalți lasă-i neschimbați. | + | ana are mere |
- | ^ ^ $b_7$ ^ ... ^ $b_{i+1}$ ^ $b_i$ ^ $b_{i-1}$ ^ ... ^ $b_0$ ^ ^ | + | ana are mere si pere |
- | ^ n ^ $n_7$ ^ ... ^ $n_{i+1}$ ^ * ^ $n_{i-1}$ ^ ... ^ $n_0$ ^ ^ | + | </code> |
- | ^ mask ^ $m_7$ ^ ... ^ $m_{i+1}$ ^ $m_i$ ^ $m_{i-1}$ ^ ... ^ $m_0$ ^ op ^ | + | |
- | ^ n op mask ^ $n_7$ ^ ... ^ $n_{i+1}$ ^ 0 ^ $n_{i-1}$ ^ ... ^ $n_0$ ^ ^ | + | |
- | **op** este o operație, iar **mask** un număr. Să analizăm cine pot fi **op** și **biții din mască** ($m_i$). | + | == strcmp() == |
- | Dorim ca: | + | <code c> |
- | * $ * \ \ op \ \ m_i = 0 $, adică operația op aplicată pe $*$ (orice) și $m_i$, va avea mereu ca rezultat pe $0$ | + | int strcmp(const char *str1, const char *str2); |
- | * $ n_j \ \ op \ \ m_j = n_j $ (unde i != j), adică operația $op$ aplicată pe $n_j$ și $m_j$, va da $n_j$ | + | </code> |
- | Observăm că: | + | Compară şirul //str1// cu şirul //str2//, verificându-le caracter cu caracter. Valoarea returnată este 0 daca cele şiruri sunt identice, mai mare ca 0 daca str1 este "mai mare"(alfabetic) şi mai mic ca 0 altfel. |
- | * 0 este elementul care poate "șterge" un bit prin **ȘI**, ceea ce verifică $ * & 0 = 0 $, oricare ar fi * un bit | + | |
- | * 1 este elementul neutru pentru **ȘI**, ceea ce verifică $ n_j | 1 = n_j $, oricare are fi $n_j$ un bit | + | |
- | </spoiler> | + | |
+ | **Exemplu:** | ||
- | ==== Exerciții ==== | + | <code c> |
- | [[https://drive.google.com/drive/u/1/folders/1qB6EZLGVubKbuTXMtMue06egH_8fo25M|Checker laborator 7 CB\CD]] | + | #include <stdio.h> |
- | [[https://drive.google.com/open?id=1oBRfwcYfuosDaBRkeq7f_XmQYayOqbZq | Teste Problema 1]] | + | #include <string.h> |
- | [[https://drive.google.com/open?id=16kc4UllXMXb62KYIyZ8AndclaRjBTQ0Q | Teste Problema 2]] | + | |
- | [[https://drive.google.com/open?id=1kO2oT9cF9Oih0Jav00Ji9gHuhSnsMo5R | Teste Problema 3]] | + | #define N 80 |
- | [[https://drive.google.com/open?id=1MVOlZdZv7aZ3DAdMRn2QQL2DkjkCb6Xh | Teste Problema 4]] | + | |
- | [[https://drive.google.com/open?id=1TsXI7HkbLOR2kwM0lLe-1z3qcSs3eT0z | Teste Problema 5]] | + | |
- | [[https://drive.google.com/open?id=1QjYcHcDSApXZ1ZTloxqibOAU9GSckaxE | Teste Problema 6]] | + | |
- | ==Precizari CB\CD== | + | int main () { |
+ | char cuv[] = "rosu"; | ||
+ | char cuv_citit[N]; | ||
+ | do { | ||
+ | printf ("Ghiceste culoarea..."); | ||
+ | gets(cuv_citit); | ||
+ | } while (strcmp(cuv,cuv_citit) != 0); | ||
+ | |||
+ | puts("OK"); | ||
+ | |||
+ | return 0; | ||
+ | } | ||
+ | </code> | ||
+ | În situația în care șirurile au lungimi diferite, ultima comparație se face între \0 și caracterul de pe aceași poziție din șirul mai lung, | ||
- | * Arhivele 4, 5, 6 testeaza reuniunea, intersectia respectiv diferenta seturilor. | + | == strchr() == |
- | * Intrarea corespunde functiei set_read (n, urmat de n elemente) | + | |
- | * Ref corespunde functiei set_print aplicata pe setul obtinut (cardinalul setului, urmat pe urmatoarea linie de elementele din set) | + | |
- | * Arhiva 7 corespunde problemei de bonus B1 (.ref contine rezultatul pentru get_lsb urmat de rezultatul pentru get_msb) | + | <code c> |
- | * Arhiva 8 corespunde problemei B2. | + | char* strchr(const char *str, int character); |
+ | </code> | ||
+ | Caută caracterul //character// în şirul //str// şi returnează un pointer la //prima// sa apariţie sau ''NULL'' dacă acesta nu a fost găsit.. | ||
+ | == strrchr()== | ||
+ | <code c> | ||
+ | char* strrchr(const char *str, int character); | ||
+ | </code> | ||
+ | Caută caracterul //character// în şirul //str// şi returnează un pointer la //ultima// sa apariţie sau ''NULL'' dacă acesta nu există în șir. | ||
- | ==Precizari generale== | + | == strstr() == |
- | Rezolvați împreună cu asistentul pe tablă, exercițiile 0-4, apoi rezolvați invidual exercițiul 5. | + | |
- | == 0: Verificare că un număr e par == | + | <code c> |
- | Să se verifice folosind operații pe biți că un număr natural n e par. | + | char* strstr(const char *str1, const char *str2); |
- | <code C> | + | |
- | int is_even(int n); | + | |
</code> | </code> | ||
- | == 1. Calcul putere a lui 2 (0p) == | + | Caută şirul //str2// în şirul //str1// şi //returnează un pointer la prima sa apariţie//, sau //''NULL''// dacă nu a fost găsit. |
- | Să se scrie o funcție care să calculeze $2^n$, unde $ n <= 30 $. | + | |
- | <code C> | + | |
- | int pow2(int n); | + | |
- | </code> | + | |
- | Răspundeți la întrebarea: **are sens** să scriem o funcție? | + | |
- | == 2. Negarea biților unui număr n (0p) == | + | == strdup() == |
- | Să se scrie o funcție care să nege biții unui număr n (32 biți). | + | |
- | <code C> | + | |
- | int flip_bits(int n); | + | |
- | </code> | + | |
- | Răspundeți la întrebarea: **are sens** să scriem o funcție? | + | |
- | == 3. Afișarea biților unui număr n (0p)== | + | <code c> |
- | Să se scrie o funcție care să afișeze toți biții unui număr întreg pe 32 biți. | + | char* strdup(const char *str); |
- | <code C> | + | |
- | void print_bits(int n); | + | |
</code> | </code> | ||
+ | Realizează un duplicat al şirului //str//, pe care îl şi returnează. Spațiul de memorie necesar copiei este alocată dinamic, fiind responsabilitatea noastră să o dealocăm (așa cum am s-a prezentat laboratorul anterior). | ||
- | == 4. Verificare că un număr este putere al lui 2 (0p)== | + | **Exemplu:** |
- | Să se scrie o funcție care verifică dacă un număr întreg n pe 32 biți este puterea a lui 2. Funcția va returna 1 dacă n este putere a lui 2, 0 altfel. | + | |
- | <code C> | + | |
- | int is_power2(int n); | + | |
- | </code> | + | |
- | <spoiler Hint> | + | <code c> |
- | Analizați reprezentarea în baza 2 a lui n (ex. n = 16 si n = 5). | + | #include <stdio.h> |
- | </spoiler> | + | #include <string.h> |
- | + | #define N 80 | |
- | + | ||
- | + | int main () { | |
- | \\ | + | char str[N] = "salut", *d; |
- | Implementați invidual următoarea problemă. | + | |
- | + | d = strdup(str); | |
- | + | if(d == NULL) { | |
- | == 5. bitset (10p )== | + | printf("Eroare!\n"); |
- | O mulțime de numere întregi poate fi reprezentată astfel: spunem că un număr **i ** aparține unei mulțimi S dacă bit-ul al i-lea din vectorul S are valoarea 1. | + | return -1; |
- | + | } | |
- | Pentru eficientă, vectorul S va conține date de tipul **unsigned char** (reamintim ca **sizeof(unsigned char) == 1 byte** adică **8 biți**). | + | |
- | + | puts(d); | |
- | <note> | + | free(d); |
- | Pentru a folosi cu ușurință același cod facând schimbări minime (de exemplu schimbăm dimensiunea maximă a unei mulțimi), putem să ne definim propriul tip astfel: | + | |
- | + | return 0; | |
- | <code C> | + | } |
- | #define SET_SIZE 100 // aceasta este o macrodefiniție (momentan o putem privi ca pe O CONSTANTA CARE ARE VALOAREA 100 | + | |
- | typedef unsigned char SET[SET_SIZE]; // definesc tipul SET, care este un vector cu maxim 100 de elemente de tip unsigned char | + | |
</code> | </code> | ||
- | |||
- | Cele două linii de mai sus vor fi puse imediat dupa includerea directivelor header! | ||
- | |||
- | </note> | ||
- | |||
- | |||
- | |||
- | === 5.1 === | ||
- | |||
- | Implementați următoarele funcții. Realizați un program în C prin care să demonstrați că funcțiile implementate funcționează. | ||
<note> | <note> | ||
- | Există un [[https://ocw.cs.pub.ro/courses/programare/laboratoare/lab07-bitset-example | exemplu]] detaliat care vă explică cum functionează aceastea. | + | ''strdup(..)'' va aloca întotdeuna ''strlen() + 1'' octeți pentru destinație, indiferent de dimensiunea memoriei alocate pentru sursă. |
- | + | ||
- | Treceți la subpunctul următor abia după ce v-ați asigurat că acestea funcționează. | + | |
</note> | </note> | ||
- | * **adăugarea** unui element în mulțime | + | == strtok() == |
- | <code C> | + | |
- | // insert_in_set(s, n) - adauga numarul n in multimea s | + | |
- | void insert_in_set(SET s, unsigned int n); | + | |
- | </code> | + | |
- | * **ștergerea** unui element din mulțime | + | <code c> |
- | <code C> | + | char* strtok(char *str, const char *delimitators); |
- | // delete_from_set(s, n) - scoate numarul n din multime s | + | |
- | void delete_from_set(SET s, unsigned int n); | + | |
- | </code> | + | |
- | + | ||
- | * **verificarea** faptului că un element n **aparține** unei mulțimi | + | |
- | <code C> | + | |
- | // is_in_set(s, n) - returneaza 1 daca n este in s, 0 altfel | + | |
- | int is_in_set(SET s, unsigned int n); | + | |
- | </code> | + | |
- | + | ||
- | * **ștergerea** tuturor elementelor din mulțime | + | |
- | <code C> | + | |
- | // delete_all_from_set(s) - elimina toate elementele din multime | + | |
- | void delete_all_from_set(SET s); | + | |
- | </code> | + | |
- | + | ||
- | * calcularea **cardinalul** unei mulțimi | + | |
- | <code C> | + | |
- | // card_set(s) - returneaza cardinalul multimii s | + | |
- | int card_set(SET s); | + | |
- | </code> | + | |
- | + | ||
- | * verificarea faptului că mulțimea este **vidă** | + | |
- | <code C> | + | |
- | // is_empty_set(s) - verifica daca multimea este sau nu goala | + | |
- | // returneaza 1 daca este, 0 daca nu este | + | |
- | int is_empty_set(SET s); | + | |
- | </code> | + | |
- | + | ||
- | * o funcție care **să citească de la tastatură** o mulțime | + | |
- | <code C> | + | |
- | // read_set(s) - functia citeste numarul n de elemente care se afla in a | + | |
- | // apoi citeste cele n numere si le insereaza in a | + | |
- | // va returna numarul n citit (numarul de elemente) | + | |
- | int read_set(SET s); | + | |
- | </code> | + | |
- | + | ||
- | * o funcție care **să afișeze** pe ecran elementele care se află într-o mulțime | + | |
- | <code C> | + | |
- | // print_set(s) - functia printeaza elementele multimii s | + | |
- | void print_set(SET s); | + | |
- | </code> | + | |
- | + | ||
- | Urmăriți acest [[https://ocw.cs.pub.ro/courses/programare/laboratoare/lab07-bitset-example | exemplu cu bitset]] pentru a înțele cum funcționeazăaceste operații. | + | |
- | + | ||
- | === 5.2 === | + | |
- | Realizati un program care, utilizând metodele defite anterior, citește 2 mulțimi A (n și B și afișează: $ A U B, A ∩ B, A - B, B - A $. | + | |
- | + | ||
- | Pentru a realiza acest lucru, va trebui să implementați următoarele funcții: | + | |
- | * **reuniunea** a două mulțimi (**1p**) | + | |
- | <code C> | + | |
- | // c = a U b | + | |
- | void merge_set(SET a, SET b, SET c); | + | |
- | </code> | + | |
- | + | ||
- | * **intersecția** a două mulțimi (**1p**) | + | |
- | <code C> | + | |
- | // c = a n b | + | |
- | void intersect_set(SET a, SET b, SET c); | + | |
- | </code> | + | |
- | + | ||
- | * **diferența** a două mulțimi (**1p**) | + | |
- | <code C> | + | |
- | // c = a \ b | + | |
- | void diff_set(SET a, SET b, SET c); | + | |
- | </code> | + | |
- | + | ||
- | În final va trebui sa creați o funcție ** main ** și să faceți un program care rezolvă cerința folosind funcțiile implementate. | + | |
- | + | ||
- | ==== B1. Optimizations ==== | + | |
- | O să învățați pe viitor că nu toate înstrucțiunile sunt la fel din punct de vedere al timpului de execuției. Ca un punct de start, gândiți-vă că dacă ați face pe foaie o adunare sau o înmultire, durează mai mult să înmulțiți decât să adunați. Această dificulate o are și procesorul din laptopul vostru! | + | |
- | + | ||
- | Pentru a-l ajuta și a face programul mai rapid, putem înlocui operații costisitoare (** * **, ** / **) cu altele mai puțin costistoare (** + **, ** - **). Cele mai rapide instrucțiuni sunt cele care lucrează direct pe biți (deoarece numerele sunt stocate în memorie în binar, deci este modul natural de lucru pentru calculator). | + | |
- | + | ||
- | Acest exercițiu vă propune să aplicați aceste optimizări pe codul vostru! | + | |
- | + | ||
- | <spoiler Hint> | + | |
- | Pentru a completa acest bonus, **NU** aveți voie să folosiți operatorii ** /, *, % ** ! Încercați să folosiți operații pe biți! | + | |
- | </spoiler> | + | |
- | + | ||
- | ==== B2. MSB/LSB ==== | + | |
- | Să se scrie o câte o funcție pentru aflarea MSB(Most significant bit), respectiv LSB(Least significant bit), pentru un număr n pe 32 biți. | + | |
- | <code C> | + | |
- | int get_lsb(int n); | + | |
- | int get_msb(int n); | + | |
</code> | </code> | ||
- | <spoiler Hint> | + | Funcţia are rolul de a împarţi şirul //str// în tokens(subşiruri separate de orice caracter aflat în lista de delimitatori), prin apelarea ei succesivă. |
- | Analizați reprezentarea în baza 2 a lui n. | + | |
- | </spoiler> | + | |
+ | La primul apel, parametrul //str// trebuie sa fie un şir de caractere, ce urmează a fi împartit. Apelurile urmatoare, vor avea în loc de //str//, ''NULL'' conţinuând împarţirea aceluiaşi şir. | ||
+ | Funcţia va returna la fiecare apel un token(un subsir), ignorând caracterele cu rol de separator aflate în şirul de delimitatori. O dată terminat şirul, funcţia va returna ''NULL''. | ||
- | ==== Probleme de interviu ==== | + | <note important> |
- | Pentru cei interesați, recomandăm rezolvarea și următoarelor probleme, care sunt des întâlnite la interviuri. | + | Implementarea curentă din ''<string.h>'' nu permite folosirea ''strtok()'' în paralel pe mai mult de un şir. |
- | <note warning> | + | |
- | Atenție! Problemele din această categorie au un nivel de dificultate ridicat, peste cel cerut la cursul de PC. | + | |
- | + | ||
- | Recomandăm totuși rezolvarea acestor probleme pentru cei care doresc să aprofundeze lucrul cu operații pe biți. | + | |
</note> | </note> | ||
- | <spoiler Swap bits> | + | **Exemplu:** |
- | Se dă un număr n natural pe 32 de biți. Se cere să se calculeze numărul obținut prin interschimbarea biților de rang par cu cel de rang impar. | + | |
- | Exemplu: n = 2863311530 = > m = 1431655765 | + | <code c> |
+ | #include <stdio.h> | ||
+ | #include <string.h> | ||
+ | |||
+ | int main () { | ||
+ | char str[] = "- Uite, asta e un sir."; | ||
+ | char *p; | ||
+ | p = strtok(str, " ,.-"); | ||
+ | /* separa sirul in "tokeni" si afiseaza-i pe linii separate. */ | ||
+ | while (p != NULL) { | ||
+ | printf("%s\n", p); | ||
+ | p = strtok(NULL, " ,.-"); | ||
+ | } | ||
+ | |||
+ | return 0; | ||
+ | } | ||
+ | </code> | ||
- | <note> | + | **Iesire:** |
- | Hint: Reprezentarea numerelor în baza 2 ([http://www.binaryhexconverter.com/decimal-to-binary-converter | convertor]]). | + | |
- | </note> | + | |
- | </spoiler> | + | <code> |
+ | Uite | ||
+ | asta | ||
+ | e | ||
+ | un | ||
+ | sir | ||
+ | </code> | ||
- | <spoiler Element unic din șir> | ||
- | Fie un șir cu ** 2n + 1 ** numere întregi, dintre care n numere apar de câte 2 ori, iar unul singur este unic. | ||
- | Să se gasească elementul unic. | ||
- | Exemplu: | + | ==== Exerciții ==== |
- | n = 5 și sirul [1, 4, 4, 1, 5] | + | Exercițiile pentru laborator se găsesc pe [[https://acs-pclp.github.io/laboratoare/07 | PCLP Laborator07: Prelucrarea şirurilor de caractere. Funcţii. Aplicaţii]]. |
- | Numărul unic este 5. | + | |
- | <note> | + | |
- | Hint: Încercați să nu folosiți tablouri. | + | |
- | </note> | + | |
- | Follow-up 1: Șirul are $2*n + (2 * p + 1)$ numere. Se sție că un singur număr apare de un număr impar de ori (2p + 1), iar celelalte apar de un număr par de ori. Cum găsiți numărul care apare de un număr impar de ori? | ||
- | |||
- | Exemplu: | ||
- | n = 5 și sirul [1, 1, 4, 4, 4, 4, 5, 5, 5] | ||
- | Răspunsul este 5. | ||
- | |||
- | Follow-up 2: Șirul are $2n + 2$ numere, n numere apar de câte 2 ori, iar 2 numere sunt unice. Cum găsiți cele 2 numer unice? | ||
- | |||
- | Exemplu: | ||
- | n = 5 și sirul [1, 4, 4, 1, 5, 6] | ||
- | Numărele unice sunt 5 și 6. | ||
- | |||
- | TODO: sursă | ||
- | </spoiler> | ||
- | |||
- | <spoiler Căutare binară pe biți> | ||
- | Realizează o funcție de căutare pe binară, utilizând operații pe biți pentru optimizarea acestei implementări. | ||
- | |||
- | Follow up: Puteți găsi alt algoritm care să nu se bazeze pe împarțirea vectorului în două și compararea elementului din mijloc cu cel cautat? | ||
- | |||
- | <note> | ||
- | Hint: | ||
- | [[http://www.infoarena.ro/problema/cautbin | caut bin]] și [[http://www.infoarena.ro/multe-smenuri-de-programare-in-cc-si-nu-numai | Multe "smenuri" de programare in C/C++... si nu numai!]] | ||
- | </note> | ||
- | |||
- | </spoiler> | ||
- | |||
- | |||
- | |||
- | <spoiler Jocul NIM> | ||
- | Se dau n grămezi, fiecare conţinând un anumit număr de pietre. Doi jucători vor începe să ia alternativ din pietre, astfel: la fiecare pas, jucătorul aflat la mutare trebuie să îndepărteze un număr nenul de pietre dintr-o singură grămadă. Câştigătorul este cel care ia ultima piatră. | ||
- | Să se determine dacă jucătorul care ia primele pietre are strategie sigură de câştig. | ||
- | |||
- | Exemple | ||
- | n = 4, gramezi = [1 3 5 7], raspuns = NU | ||
- | n = 3, gramezi = [4 8 17], raspuns = DA | ||
- | |||
- | <note> | ||
- | Hint: [[http://www.infoarena.ro/problema/nim | nim]] | ||
- | </note> | ||
- | </spoiler> | + | ==== Referinţe ==== |
- | <spoiler Sushi> | + | * [[http://en.wikipedia.org/wiki/String_(computer_science)|Wikipedia - String(Computer science)]] |
- | Enunt: [[http://www.infoarena.ro/problema/sushi | sushi ]] | + | * [[http://en.wikipedia.org/wiki/String.h|Wikipedia - C string handling]] |
- | </spoiler> | + | |