This shows you the differences between two versions of the page.
|
programare:laboratoare:lab12 [2020/01/26 00:45] george.muraru [Noţiuni teoretice] |
programare:laboratoare:lab12 [2025/12/14 02:36] (current) darius.neatu [PCLP Laborator12: Optimizarea programelor cu operații pe biți] |
||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | ===== Operaţii cu fişiere. Aplicaţii folosind fişiere. ===== | + | ===== PCLP Laborator12: Optimizarea programelor cu operații pe biți ===== |
| - | **Responsabil:** | + | **Responsabili:** |
| - | * [[neatudarius@gmail.com|Darius Neațu (2016-2018)]] | + | * [[neatudarius@gmail.com|Darius Neațu (2019 - Prezent)]] |
| - | * [[mihaela.vasile@gmail.com|Mihaela Vasile (2015)]] | + | * [[ion_dorinel.filip@cti.pub.ro|Dorinel Filip (2019 - Prezent)]] |
| + | * [[rares96cheseli@gmail.com|Rareș Cheșeli (2017 - 2018)]] | ||
| - | [[https://we.tl/t-U1WwFpnaFU|Probleme]] | + | ==== Obiective ==== |
| - | **Ultima modificare:** **09.12.2018** | + | Î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 | ||
| - | În urma parcurgerii acestui laborator studentul va fi capabil să: | + | <note warning> |
| - | * lucreze cu fişiere text (deschidere, închidere, citire, scriere) | + | * 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. |
| - | * înteleaga un fişier binar şi să lucreze cu el; | + | * 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! |
| - | * să se poziţioneze in interiorul unui fişier; | + | </note> |
| - | * poată determina poziţia în cadrul unui fişier; | + | |
| - | * înteleagă diferenţa între organizarea internă a fişierelor pe sistemele de operare Linux şi Windows. | + | |
| - | ==== Noţiuni teoretice ==== | + | ==== Motivație ==== |
| + | Î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. | ||
| - | === Introducere === | + | 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). |
| - | Un fişier este o structură dinamică, situată în memoria secundară (pe disk-uri). Limbajul C permite operarea cu fişiere: | + | Î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. |
| - | * de **tip text** - un astfel de fişier conţine o succesiune de **linii**, separate prin new line ('\n') | + | |
| - | * de **tip binar** - un astfel de fişier conţine o succesiune de octeti, **fără nici o structură**. | + | |
| - | Prelucrarea unui fişier presupune asocierea acestuia cu un canal de I/E (numit flux sau stream). Există trei canale predefinite, care se deschid automat la lansarea unui program: | + | În acest laborator vom vorbi despre altă metrică de măsurare a performanței unui program, mai exact despre ** memoria folosită ** de un program. |
| - | * **stdin** - fişier de intrare, text, este intrarea standard - tastatura | + | |
| - | * **stdout** - fişier de iesire, text, este ieşirea standard - ecranul monitorului. | + | |
| - | * **stderr** – fişier de iesire, text, este ieşirea standard unde sunt scris mesajele de eroare - ecran. | + | |
| - | Pentru a prelucra un fişier, trebuie parcurse următoarele etape: | + | 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. |
| - | * **se defineşte** o variabilă de tip **FILE*** pentru accesarea fişierului; **FILE** este un tip structură definit în <stdio.h>, care conţine informaţii referitoare la fişier şi la tamponul de transfer de date între memoria centrală şi fişier (adresa, lungimea tamponului, modul de utilizare a fişierului, indicator de sfârsit, de poziţie în fişier). Puteți citi mai multe [[http://stackoverflow.com/questions/5672746/what-exactly-is-the-file-keyword-in-c | aici ]]. | + | |
| - | * **se deschide fişieru**l pentru un anumit **mod de acces**, folosind funcţia de bibliotecă **fopen**, care realizează şi asocierea între variabila fişier şi numele extern al fişierului | + | 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). |
| - | * **se prelucrează fişierul** în citire/scriere cu **funcţiile specifice** | + | |
| - | * **se închide fişierul** folosind funcţia de bibliotecă **fclose** | + | |
| - | === Funcții === | + | ==== Dimensiunea tipurilor implicite în C. Calculul memoriei unui program ==== |
| - | Mai jos se prezintă restul funcţiilor de prelucrare a fişierelor. Pentru documentația oficială puteți citi [[http://www.cplusplus.com/reference/cstdio/ | aici]]. | + | |
| - | == fopen == | + | În [[https://ocw.cs.pub.ro/courses/programare/laboratoare/lab02|laboratorul 2]] au fost prezentate tipurile de date implicite din C și dimensiunea acestora. |
| + | |||
| + | Pentru a afla ** dimensiunea în bytes ** a unei variabile se poate folosi operatorul **sizeof**. | ||
| + | |||
| + | <spoiler Exemplu> | ||
| + | Fie codul din următorul cod. | ||
| <code c> | <code c> | ||
| - | FILE *fopen(const char *filename, const char *mod); | ||
| - | </code> | ||
| - | deschide fişierul cu numele **filename** pentru acces de tip **mod**. | + | #include <stdio.h> |
| - | Returnează **pointer la fişier** sau **NULL** dacă fişierul nu poate fi deschis; valoarea returnată este memorată în variabila fişier, care a fost declarată pentru accesarea lui. | + | int main() { |
| + | // 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)); | ||
| - | Modul de deschidere poate fi: | + | short int xs; |
| - | * "**r**" - **readonly** , este permisă doar citirea dintr-un fişier existent | + | printf("sizeof(unsigned short int) = %ld B\n", sizeof(unsigned short int)); |
| - | * "**w**" - **write**, crează un nou fişier, sau dacă există deja, distruge vechiul continut | + | printf("sizeof(short int) = %ld B\n", sizeof(short int)); |
| - | * "**a**" - **append**, deschide pentru scriere un fişier existent ( scrierea se va face în continuarea | + | printf("sizeof(xs) = %ld B\n", sizeof(xs)); |
| - | informaţiei deja existente în fişier, deci pointerul de acces se plasează la sfârşitul fişierului ) | + | |
| - | * "**+**" - permite scrierea şi citirea - **actualizare** (ex: "r+", "w+", "a+"). Cursorul de fi. Întâi trebuie repoziţionat cursorul de acces printr-un apel la **fseek**. | + | |
| - | * "**b**" - specifică fişier de tip **binar** | + | int xi; |
| - | * "**t**" - specifică fişier de tip **text** (implicit), la care se face automat conversia CR-LF("\n\f") în sau din CR ('\n'). | + | 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)); | ||
| - | == fclose == | + | // afiseaza dimensiunea unor tablouri cu dimensiune cunoscuta |
| - | <code c> | + | char vc[100]; |
| - | int fclose(FILE *pFile); | + | short int vs[100]; |
| - | </code> | + | 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; | ||
| + | } | ||
| - | **închide fişierul** asociat cu variabila **pFile** şi eliberează zona tampon; returnează 0 la succes, EOF (end of file) la eroare | ||
| - | == fseek == | ||
| - | <code c> | ||
| - | int fseek(FILE *pFile, long offset, int whence); | ||
| </code> | </code> | ||
| - | **repoziţionează pointerul** asociat fişierului **pFile**; offset - numărul de octeţi între poziţia dată de whence şi noua poziţie. | + | În urma executării acestui program pe o arhitectură de **32 biți** (ceea ce folosim la PC) vom vedea următorul rezultat. |
| - | whence - are una din cele trei valori posibile: | + | <code> |
| - | * SEEK_SET = 0 - Căutarea se face de la începutul fişierului | + | |
| - | * SEEK_CUR = 1 - Căutare din poziţia curentă | + | |
| - | * SEEK_END = 2 - Căutare de la sfârşitul fişierului | + | |
| - | == ftell == | + | sizeof(unsigned char) = 1 B |
| - | <code c> | + | sizeof(char) = 1 B |
| - | long ftell(FILE *pFile); | + | sizeof(xc) = 1 B |
| - | </code> | + | |
| - | **întoarce poziţia curentă** în cadrul fișierului asociat cu pFile. | + | sizeof(unsigned short int) = 2 B |
| + | sizeof(short int) = 2 B | ||
| + | sizeof(xs) = 2 B | ||
| - | == fgetpos == | + | sizeof(unsigned int) = 4 B |
| - | <code c> | + | sizeof(int) = 4 B |
| - | int fgetpos(FILE *pFile, fpos_t *ptr); | + | sizeof(xi) = 4 B |
| - | </code> | + | |
| - | această funcţie **memorează poziţia curentă** în variabila ptr în cadrul fişierului asociat cu pFile (ptr va putea fi folosit ulterior cu funcţia fsetpos). | + | sizeof(vc) = 100 B |
| + | sizeof(vs) = 200 B | ||
| + | sizeof(vi) = 400 B | ||
| - | == fsetpos == | ||
| - | <code c> | ||
| - | int fsetpos(FILE *pFile, const fpos_t *ptr); | ||
| </code> | </code> | ||
| - | această funcţie **setează poziţia curentă** în fişierul asociat cu pFile la valoarea ptr, obţinută anterior prin funcţia fgetpos. | + | </spoiler> |
| - | == feof == | + | <note> |
| - | <code c> | + | 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. |
| - | int feof(FILE *fis); | + | |
| - | </code> | + | |
| - | returnează 0 dacă nu s-a detectat **sfârşit de fişier** la ultima operaţie de citire, respectiv o valoare nenulă (adevărată) pentru sfârşit de fişier. | + | |
| - | == freopen == | + | **sizeof** poate fi folosit **și** pentru măsurarea dimesiunii unui vector / matrice alocat(a) static. |
| - | <code c> | + | |
| - | FILE* freopen(const char *filename, const char *mode, FILE *fp); | + | |
| - | </code> | + | |
| - | se închide fişierul fp, se deschide fişierul cu numele filename în modul mode şi acesta se asociază la fp; se întoarce fp sau NULL în caz de eroare. | + | |
| - | == fflush == | + | Memoria totală folosită de un program poate fi calculată ca **suma** tuturor dimensiunilor ocupate de variabilele din program. |
| - | <code c> | + | |
| - | int fflush(FILE *fp); | + | |
| - | </code> | + | |
| - | Această funcţie se utilizează pentru fişierele deschise pentru scriere şi are ca efect scrierea în fişier a datelor din bufferul asociat acestuia, care înca nu au fost puse în fişier. | + | |
| - | === Citirea şi scrierea în/din fişiere === | + | 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. |
| - | Citirea/scrierea în fişiere se poate face în două moduri (în funcție de tipul fişierului): în mod text sau în mod binar. Principalele diferenţe dintre cele două moduri sunt: | ||
| - | * în modul text, la sfarsitul fişierului se pune un caracter suplimentar, care indică sfârşitul de fişier. În DOS şi Windows se utilizează caracterul cu codul ASCII 26 (Ctrl-Z), iar în Unix se utilizează caracterul cu codul ASCII 4. Dacă citim un fişier în mod text, citirea se va opri la intâlnirea acestui caracter, chiar dacă mai există şi alte caractere după el. În modul binar nu există caracter de sfârşit de fişier (mai precis, caracterul cu codul 26, respectiv 4, este tratat la fel ca şi celelalte caractere). | ||
| - | * în DOS şi Windows, în modul text, sfârşitul de linie este reprezentat prin două caractere, CR (Carriage Return, cod ASCII 13) şi LF (Line Feed, cod ASCII 10). Atunci când în modul text scriem un caracter '\n' (LF) în fişier, acesta va fi convertit într-o secventă de 2 caractere CR şi LF. Când citim în mod text dintr-un fişier, secvenţa CR, LF este convertită într-un '\n' (LF). În Unix, sfârşitul de linie este reprezentat doar prin caracterul LF. În mod binar, atât în DOS-Windows cât şi în Unix, sfârşitul de linie este reprezentat doar prin caracterul LF. | ||
| - | Modul binar se utilizează de obicei pentru a scrie în fişier datele exact aşa cum sunt reprezentate în memorie (cu functiile **fread**, **fwrite**) - de exemplu pentru un număr intreg se va scrie reprezentarea internă a acestuia, pe 2 sau pe 4 octeti. | + | <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> | ||
| - | Modul text este utilizat mai ales pentru scrierea cu format (cu funcţiile fprintf, fscanf) - în cazul acesta pentru un număr întreg se vor scrie caracterele ASCII utilizate pentru a reprezenta cifrele acestuia (adică un şir de caractere cum ar fi "1" sau "542"). | + | Vom descoperi mai multe în următoare laboratoare. 8-) |
| + | </note> | ||
| - | === Citire/scriere cu format === | + | ==== Operatori pe biți în C ==== |
| - | <code c> | + | 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]]). |
| - | int fprintf(FILE *fp, const char *format, ...); | + | |
| - | int fscanf(FILE *fp, const char *format, ...); | + | |
| - | </code> | + | |
| - | Funcţiile sunt utilizate pentru citire/scriere în mod text şi sunt asemănătoare cu printf/scanf (diferenţa fiind că trebuie dat pointerul la fişier ca prim parametru). | + | În tabelul următor reamintim operatorii limbajului C care sunt folosiți la nivel de bit. |
| - | == Exemplu 1 == | + | ^ Operator ^ Descriere ^ Asociativitate ^ |
| - | Să presupunem că avem următorul fișier care pe prima linie conține un număr natural nenul n, iar pe a doua linie se află n numere întregi reprezentând elementele unui vector. Se cere citirea vectorului, dublarea fiecarui element, apoi salvarea rezultatelor în fișierul "gigel.out". | + | | **%%~%%** | 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 | | ||
| - | <code c> | + | <note warning> |
| - | gigel.in | + | Trebuie avută în vedere precedenţa operatorilor pentru obţinerea rezultatelor dorite! |
| - | 5 | + | |
| - | 1 3 -1 6 7 | + | |
| - | </code> | + | |
| - | Rulați și înțelegeți următorul cod C. | + | 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. |
| - | <code c> | + | </note> |
| - | #include <stdio.h> | + | |
| - | #define NMAX 100 | + | |
| - | int main() { | + | == Bitwise NOT == |
| - | // numele fisierului de intrare | + | <spoiler Semnificație> |
| - | char input_filename[] = "gigel.in"; | + | ** 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 ^ | ||
| - | // deschidere fisier de intrare pentru | + | Evident putem extinde această operație și la nivel de număr. Operația se aplică separat pentru fiecare rang binar. |
| - | // citire (r) in modul text (t) | + | ^ ^ $b_2$ ^ $b_1$ ^ $b_0$ ^ |
| - | FILE *in = fopen(input_filename, "rt"); | + | ^ x = 3 ^ 0 ^ 1 ^ 1 ^ |
| + | ^ ~x = 4 ^ 1 ^ 0 ^ 0 ^ | ||
| + | </spoiler> | ||
| - | // verific daca fisierul a fost deschis cu succes | + | == Bitwise AND == |
| - | // altfel opresc executia (in cazul acestei probleme) | + | <spoiler Semnificație> |
| - | if (in == NULL) { | + | ** Bitwise AND ** (**ȘI pe biți**) este operația la nivel de bit care următorul tabel de adevăr. |
| - | fprintf(stderr, "ERROR: Can't open file %s", input_filename); | + | ^ x ^ y ^ x & y ^ |
| - | return -1; | + | ^ 0 ^ 0 ^ 0 ^ |
| - | } | + | ^ 0 ^ 1 ^ 0 ^ |
| + | ^ 1 ^ 0 ^ 0 ^ | ||
| + | ^ 1 ^ 1 ^ 1 ^ | ||
| - | int n, v[NMAX], i; // numarul de elemente && vectorul | + | Evident putem extinde această operație și la nivel de număr. Operația se aplică separat pentru fiecare rang binar. |
| + | ^ ^ $b_2$ ^ $b_1$ ^ $b_0$ ^ | ||
| + | ^ x = 3 ^ 0 ^ 1 ^ 1 ^ | ||
| + | ^ y = 7 ^ 1 ^ 1 ^ 1 ^ | ||
| + | ^ x & y = 3 ^ 0 ^ 1 ^ 1 ^ | ||
| + | </spoiler> | ||
| - | // citesc n din fisier | + | == Bitwise OR == |
| - | fscanf(in, "%d", &n); | + | <spoiler Semnificație> |
| - | //citesc tabloul | + | ** Bitwise OR ** (**SAU pe biți**) este operația la nivel de bit care următorul tabel de adevăr. |
| - | for (i = 0; i < n; ++i) { | + | ^ x ^ y ^ x %%|%% y ^ |
| - | fscanf(in, "%d", &v[i]); | + | ^ 0 ^ 0 ^ 0 ^ |
| - | } | + | ^ 0 ^ 1 ^ 1 ^ |
| + | ^ 1 ^ 0 ^ 1 ^ | ||
| + | ^ 1 ^ 1 ^ 1 ^ | ||
| - | // deoarece stiu sigur ca nu mai am nimic de citit | + | Evident putem extinde această operație și la nivel de număr. Operația se aplică separat pentru fiecare rang binar. |
| - | // pot inchide fisierul de intrare | + | ^ ^ $b_2$ ^ $b_1$ ^ $b_0$ ^ |
| - | fclose(in); | + | ^ x ^ 0 ^ 1 ^ 1 ^ |
| + | ^ y ^ 1 ^ 0 ^ 1 ^ | ||
| + | ^ x %%|%% y = 7 ^ 1 ^ 1 ^ 1 ^ | ||
| + | </spoiler> | ||
| - | // dublez elementele din vector | + | == Bitwise XOR == |
| - | for (i = 0; i < n; ++i) { | + | <spoiler Semnificație> |
| - | v[i] <<= 1; | + | ** Bitwise XOR ** (**SAU-EXCLUSIV pe biți**) este operația la nivel de bit care următorul tabel de adevăr. |
| - | } | + | ^ x ^ y ^ x %%^%% y ^ |
| + | ^ 0 ^ 0 ^ 0 ^ | ||
| + | ^ 0 ^ 1 ^ 1 ^ | ||
| + | ^ 1 ^ 0 ^ 1 ^ | ||
| + | ^ 1 ^ 1 ^ 0 ^ | ||
| - | // deschid fisierul pentru a scrie rezultatele | + | Evident putem extinde această operație și la nivel de număr. Operația se aplică separat pentru fiecare rang binar. |
| - | char output_filename[] = "gigel.out"; | + | ^ ^ $b_2$ ^ $b_1$ ^ $b_0$ ^ |
| - | // deschid pentru scriere (w) in modul text (t) | + | ^ x = 3 ^ 0 ^ 1 ^ 1 ^ |
| - | FILE *out = fopen(output_filename, "wt"); | + | ^ y = 5 ^ 1 ^ 0 ^ 1 ^ |
| + | ^ x %%^%% y = 6 ^ 1 ^ 1 ^ 0 ^ | ||
| + | </spoiler> | ||
| - | // verific daca fisierul a fost deschis cu succes | + | == Bit LOGICAL SHIFT == |
| - | // altfel opresc executia (in cazul acestei probleme) | + | Î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. |
| - | if (out == NULL) { | + | |
| - | fprintf(stderr, "ERROR: Can't open file %s", output_filename); | + | |
| - | return -1; | + | |
| - | } | + | |
| - | // scriu n si vectorul in fisier | + | == LEFT SHIFT == |
| - | fprintf(out, "%d\n", n); | + | <note> |
| - | for (i = 0; i < n; ++i) { | + | Efectul unei deplasări la stânga cu un rang binar este echivalent cu înmulțirea cu 2 a numărului din baza 10. |
| - | fprintf(out, "%d ", v[i]); | + | Dacă rezultatul nu are loc pe tipul de date folosit, atunci se pot pierde din biți! |
| - | } | + | |
| - | fprintf(out, "\n"); | + | |
| - | // inchid fisierul de iesire | + | Se poate deduce următoarea relație: $ n << k = n * 2^k $. |
| - | fclose(out); | + | </note> |
| - | return 0; | + | <spoiler Exemplu> |
| - | } | + | Fie un exemplu de deplasarea la stânga, pentru un număr ** pe 3 biți **. |
| - | </code> | + | ^ ^ $b_2$ ^ $b_1$ ^ $b_0$ ^ |
| + | ^ x = 3 ^ 0 ^ 1 ^ 1 ^ | ||
| + | ^ x << 1 = 6 ^ 1 ^ 1 ^ 0 ^ | ||
| + | ^ x << 2 = 8 ^ 1 ^ 0 ^ 0 ^ | ||
| + | </spoiler> | ||
| - | Pentru exemplul de fișier de intrare de mai sus, rezultatul este următorul. | + | == RIGHT SHIFT == |
| - | <code c> | + | <note> |
| - | gigel.out | + | 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. |
| - | 5 | + | |
| - | 2 6 -2 12 14 | + | |
| - | </code> | + | |
| - | == Exemplu 2 == | + | Se poate deduce următoarea relație: $ n >> k = [n / 2^k ] $. |
| - | Fie un fișier text cu un nume dat. Scrieți o funcție care calculează numărul de bytes (dimensiune fișierului). | + | </note> |
| - | <code c> | + | <spoiler Exemplu> |
| - | #include <stdio.h> | + | 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> | ||
| - | int sizeof_file(char *filename) { | ||
| - | // deschidere fisier de intrare pentru | ||
| - | // citire (r) in modul text (t) | ||
| - | FILE *file = fopen(filename, "rt"); | ||
| - | // verific daca fisierul a fost deschis cu succes | ||
| - | // altfel opresc executia | ||
| - | if (file == NULL) { | ||
| - | return -1; // nu am putut calcula dimensiunea | ||
| - | } | ||
| - | // ma pozitionez la sfarsit | ||
| - | fseek(file, 0, SEEK_END); | ||
| - | // acum cursorul este dupa ultimul caracter | ||
| - | // deci ftell imi va spune pozitia, care este echivalenta cu numarul de bytes | ||
| - | int bytes_count = ftell(file); | ||
| - | // inchid fisierul | + | ==== Lucrul cu măști ==== |
| - | fclose(file); | + | Având la dispoziție operațiile prezentate mai sus, putem răspunde la următoarele întrebări. |
| - | return bytes_count; | + | * Cum verificăm dacă ** bitul i ** dintr-un număr n este ** setat **? |
| - | } | + | * 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ă **. | ||
| - | int main() { | ||
| - | // numele fisierului de intrare | ||
| - | char filename[] = "gigel.in"; | ||
| - | int sz = sizeof_file(filename); | ||
| - | if (sz < 0) { | ||
| - | // afisez la stderr | ||
| - | fprintf(stderr, "ERROR: Can't open file %s", filename); | ||
| - | return -1; | ||
| - | } | ||
| - | // afisez la stdout rezultatul | ||
| - | printf("fisierul %s are %d bytes\n", filename, sz); | ||
| - | // pot folosi tot fprintf pentru a afisa la stdout | + | === Cum verificăm dacă bitul i dintr-un număr n este setat? === |
| - | fprintf(stdout, "fisierul %s are %d bytes\n", filename, sz); | + | <note> |
| + | Detectarea bitului: | ||
| - | return 0; | + | * 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> | ||
| + | |||
| + | |||
| + | <spoiler Implementare C> | ||
| + | <code C> | ||
| + | |||
| + | |||
| + | // is_set | ||
| + | // 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; | ||
| } | } | ||
| + | ... | ||
| + | if (is_set(mybyte, i)) { | ||
| + | printf("bitul %d din byteul %d este setat!\n", i, mybyte); | ||
| + | } else { | ||
| + | printf("bitul %d din byteul %d NU este setat!\n", i, mybyte); | ||
| + | } | ||
| + | ... | ||
| </code> | </code> | ||
| + | </spoiler> | ||
| - | === Citire/scriere fără conversie === | + | <spoiler Explicație pas cu pas> |
| - | <code c> | + | Această întrebare ne oferă valoarea bitului i. |
| - | size_t fread(void *ptr, size_t size, size_t nrec, FILE *fp); | + | |
| - | size_t fwrite(const void *ptr, size_t size, size_t nrec, FILE *fp); | + | |
| - | </code> | + | |
| - | Cu aceste funcţii lucrăm cand deschidem fişierul în mod binar; citirea/scrierea se face fără nici un fel de conversie sau interpretare. Se lucrează cu "înregistrări", adică zone compacte de memorie: funcţia fread citeşte nrec înregistrări începănd de la poziţia curentă din fişierul fp, o înregistrare având dimensiunea size. Acestea sunt depuse în tabloul ptr. "Înregistrările" pot fi asociate cu structurile din C - adică în mod uzual, tabloul ptr este un tablou de structuri (dar în loc de structuri putem avea şi tipuri simple de date). | + | Dacă "valoarea este 1", atunci vom spune că "bitul este setat". |
| + | Dacă "valoarea este 0", atunci vom spune că "bitul nu este "setat". | ||
| - | == Exemplu 3 == | ||
| - | Să reluăm problema de la Exemplul 1. Să presupunem că rezolvăm aceeași problemă, doar ca fișierul de intrare este binar: primii 4 bytes din fișier reprezintă numarul n (numărul de elemente); următorii 4*n bytes reprezintă vectorul. | ||
| - | Luăm ca exemplul fișierul text care are conținutul. | + | Pentru a ** verifica ** valoarea bitului i din numărul n, practic noi ar trebui să privim numărul astfel: |
| - | <code c> | + | |
| - | gigel.in | + | ^ ^ $b_7$ ^ $b_6$ ^ ... ^ $b_i$ ^ ... ^ $b_1$ ^ $b_0$ ^ |
| - | 3 | + | ^ n ^ * ^ * ^ ... ^ ? ^ ... ^ * ^ * ^ |
| - | 1 2 3 | + | |
| - | </code> | + | unde ***** înseamnă ** don't care ** (de la PL), |
| - | Fișierul binar echivalent este "gigel_in.bin". | + | iar **?** este valoarea pe care o cautăm. |
| - | <code c> | + | |
| - | gigel_in.bin | + | 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). |
| - | 0300 0000 0100 0000 0200 0000 0300 0000 | + | ^ ^ $b_7$ ^ ... ^ $b_{i+1}$ ^ $b_i$ ^ $b_{i-1}$ ^ ... ^ $b_0$ ^ ^ |
| - | </code> | + | ^ n ^ * ^ ... ^ * ^ ? ^ * ^ ... ^ * ^ ^ |
| - | <note warning> | + | ^ mask ^ $m_7$ ^ ... ^ $m_{i+1}$ ^ $m_i$ ^ $m_{i-1}$ ^ ... ^ $m_0$ ^ op ^ |
| - | * Fișierul poate fi descărcat de [[http://ocw.cs.pub.ro/courses/_media/programare/teme_2016/lab11_gigel_in.zip | aici]]. | + | ^ n op mask ^ 0 ^ ... ^ 0 ^ ? ^ 0 ^ ... ^ 0 ^ ^ |
| - | * Pentru a putea vizualiza continutul mai usor, acesta poate fi deschis cu [[https://www.sublimetext.com/ | Sublime]]. Observați reprezentarea în baza 16 a numerelor. Fiecare grup de câte 2 cifre reprezintă un octet. 4 astfel de grupuri formează un int: "0300 0000" semnifică numărul 3. | + | |
| - | * Atenție la [[https://en.wikipedia.org/wiki/Endianness || Endianness]]! | + | **op** este o operație, iar **mask** un număr. Să analizăm cine pot fi **op** și **biții din mască** ($m_i$). |
| + | |||
| + | Dorim ca: | ||
| + | * $ ? \ \ op \ \ m_i = \ ? $ , adică operația op aplicată pe $?$ și $m_i$, va avea mereu ca rezultat pe $?$ | ||
| + | * $ * \ \ op \ \ m_j = 0 $ (unde i != j), adică operația op aplicată pe orice valoare și $m_j$, va da 0 | ||
| + | |||
| + | Observăm că: | ||
| + | * 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> | ||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | === Cum setăm (valoarea devine 1) bitul i dintr-un număr n? === | ||
| + | <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$ ^ ^ | ||
| + | |||
| + | |||
| + | * explicație: | ||
| + | * orice valoare are avea bitul $*$ va fi suprascris cu 1 | ||
| + | * ceilalti biți vor fi copiați | ||
| </note> | </note> | ||
| - | Următoarea sursă C citește vectorul din fișierul "gigel_in.bin", dublează elementele și apoi scrie rezultatul în "gigel_out.bin". | ||
| - | <code c> | ||
| - | #include <stdio.h> | ||
| - | #define NMAX 100 | ||
| - | int main() { | + | <spoiler Implementare C> |
| - | // numele fisierului de intrare | + | <code C> |
| - | char input_filename[] = "gigel_in.bin"; | + | |
| - | FILE *in; | + | |
| - | // incerc sa dechid pentru citire (r) | + | // set |
| - | // fisierul in mod binar (b) | + | // byte - byte de intrare pentru care vreau sa setez un bit |
| - | // observati alt mod de a scrie deschiderea && verificarea | + | // i - indexul bitului din byte |
| - | if ((in = fopen(input_filename, "rb")) == NULL) { | + | // @return - noul byte |
| - | fprintf(stderr, "Can't open %s", input_filename); | + | char set(char byte, int i) { |
| - | return -1; | + | int mask = (1 << i); |
| - | } | + | return (byte | mask); |
| + | } | ||
| - | // numarul de elemente, vectorul, variabila de iterare | + | ... |
| - | int n, v[NMAX], i; | + | mybyte = set(mybyte, i); |
| + | ... | ||
| + | |||
| + | </code> | ||
| + | </spoiler> | ||
| - | // doresc sa citesc tinand cont de: | + | <spoiler Explicație pas cu pas> |
| - | // voi stoca in n (deci specific adresa lui n ca la scanf => &n) | + | Dorim să facem următoarea operație: schimba doar bitul i in 1, iar pe ceilalți lasă-i neschimbați. |
| - | // valoarea citita are dimensiunea unui int => sizeof(int) | + | ^ ^ $b_7$ ^ ... ^ $b_{i+1}$ ^ $b_i$ ^ $b_{i-1}$ ^ ... ^ $b_0$ ^ ^ |
| - | // citesc o singura variabila => 1 | + | ^ n ^ $n_7$ ^ ... ^ $n_{i+1}$ ^ * ^ $n_{i-1}$ ^ ... ^ $n_0$ ^ ^ |
| - | // voi citi din fisierul asociat cu variabila in | + | ^ mask ^ $m_7$ ^ ... ^ $m_{i+1}$ ^ $m_i$ ^ $m_{i-1}$ ^ ... ^ $m_0$ ^ op ^ |
| - | fread(&n, sizeof(int), 1, in); | + | ^ n op mask ^ $n_7$ ^ ... ^ $n_{i+1}$ ^ 1 ^ $n_{i-1}$ ^ ... ^ $n_0$ ^ ^ |
| - | // fread permite citirea unui vector printr-un singur apel | + | **op** este o operație, iar **mask** un număr. Să analizăm cine pot fi **op** și **biții din mască** ($m_i$). |
| - | // doresc sa citesc tinand cont de: | + | |
| - | // voi stoca in v (care este adresa primului element) | + | |
| - | // o valoare citita are dimensiunea unui int => sizeof(int) | + | |
| - | // citesc n valori | + | |
| - | // voi citi din fisierul asociat cu variabila in | + | |
| - | fread(v, sizeof(int), n, in); | + | |
| - | // pot inchide fisierul | + | Dorim ca: |
| - | fclose(in); | + | * $ * \ \ 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$ | ||
| - | // dublez elementele din vector | + | Observăm că: |
| - | for (i = 0; i < n; ++i) { | + | * 1 este elementul care poate "umple" un bit prin **SAU**, ceea ce verifică $ * | 1 = 1 $, oricare ar fi * un bit |
| - | v[i] <<= 1; | + | * 0 este elementul neutru pentru **SAU**, ceea ce verifică $ n_j | 0 = n_j $, oricare are fi $n_j$ un bit |
| - | } | + | </spoiler> |
| - | // salvez rezultatul in fisierul out | ||
| - | // numele fisierului de intrare | ||
| - | char output_filename[] = "gigel_out.bin"; | ||
| - | FILE *out; | ||
| - | // incerc sa dechid pentru scriere (w) | ||
| - | // fisierul in mod binar (b) | ||
| - | // observati alt mod de a scrie deschiderea && verificarea | ||
| - | if ((out = fopen(output_filename, "wb")) == NULL) { | ||
| - | fprintf(stderr, "Can't open %s", output_filename); | ||
| - | return -1; | ||
| - | } | ||
| - | // doresc sa scriu tinand cont de: | ||
| - | // voi scrie valoare de la adresa lui n (&n) | ||
| - | // valoarea scrisa are dimensiunea unui int => sizeof(int) | ||
| - | // scriu o singura variabila => 1 | ||
| - | // voi scrie in fisierul asociat cu variabila out | ||
| - | fwrite(&n, sizeof(int), 1, out); | ||
| - | // fwrite permite scrierea unui vector printr-un singur apel | ||
| - | // doresc sa scriu tinand cont de: | ||
| - | // voi scrie elemente incepand de la adresa indicata de v | ||
| - | // o valoare scrisa are dimensiunea unui int => sizeof(int) | ||
| - | // scriu n valori | ||
| - | // voi scrie in fisierul asociat cu variabila out | ||
| - | fwrite(v, sizeof(int), n, out); | ||
| - | // inchid fisierul | + | === Cum resetăm (valoarea devine 0) bitul i dintr-un număr n? === |
| - | fclose(out); | + | <note> |
| + | Resetarea bitului: | ||
| - | return 0; | + | * pas 1 / 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 ^ $1 $ ^ ... ^ $1 $ ^ $0 $ ^ $1 $ ^ ... ^ $1 $ ^ op ^ | ||
| + | ^ n op mask ^ $n_7$ ^ ... ^ $n_{i+1}$ ^ 0 ^ $n_{i-1}$ ^ ... ^ $n_0$ ^ ^ | ||
| + | |||
| + | |||
| + | * explicație: | ||
| + | * orice valoare are avea bitul $*$ va fi suprascris cu 0 | ||
| + | * ceilalti biți vor fi copiați | ||
| + | |||
| + | </note> | ||
| + | |||
| + | |||
| + | <spoiler Implementare C> | ||
| + | <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> | ||
| - | Rezultatul îl putem vizualiza tot cu Sublime. | + | </spoiler> |
| - | <code c> | + | |
| - | gigel_out.bin | + | |
| - | 0300 0000 0200 0000 0400 0000 0600 0000 | + | <spoiler Explicație pas cu pas> |
| + | Dorim să facem următoarea operație: schimba doar bitul i in 0, 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}$ ^ 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$). | ||
| + | |||
| + | Dorim ca: | ||
| + | * $ * \ \ op \ \ m_i = 0 $, adică operația op aplicată pe $*$ (orice) și $m_i$, va avea mereu ca rezultat pe $0$ | ||
| + | * $ 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ă: | ||
| + | * 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> | ||
| + | |||
| + | |||
| + | ==== Exerciții ==== | ||
| + | |||
| + | ==Precizari generale== | ||
| + | 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 == | ||
| + | Să se verifice folosind operații pe biți că un număr natural n e par. | ||
| + | <code C> | ||
| + | int is_even(int n); | ||
| </code> | </code> | ||
| - | <note warning> | ||
| - | * În acest exemplu am pus extensia "bin" pentru a sugera faptul că fișierele sunt binare. Acest lucru este irelevant pentru funcțiile fread și fwrite. | ||
| - | </note> | ||
| - | === Citire/scriere la nivel de caracter === | + | == 1. Calcul putere a lui 2 (0p) == |
| + | 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? | ||
| - | <code c> | + | == 2. Negarea biților unui număr n (0p) == |
| - | int fgetc(FILE *fp); // întoarce următorul caracter din fişier, EOF la sfârşit de fişier | + | 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> | </code> | ||
| + | Răspundeți la întrebarea: **are sens** să scriem o funcție? | ||
| - | <code c> | + | == 3. Afișarea biților unui număr n (0p)== |
| - | char* fgets(char *s, int n, FILE *fp); // întoarce următoarele n caractere de la pointer sau pâna la sfârşitul de linie | + | Să se scrie o funcție care să afișeze toți biții unui număr întreg pe 32 biți. |
| + | <code C> | ||
| + | void print_bits(int n); | ||
| </code> | </code> | ||
| - | <code c> | + | |
| - | int fputc(int c, FILE *fp); //pune caracterul c in fişier | + | == 4. Verificare că un număr este putere al lui 2 (0p)== |
| + | 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> | </code> | ||
| - | <code c> | + | <spoiler Hint> |
| - | int ungetc(int c, FILE *fp); // pune c în bufferul asociat lui fp (c va fi următorul caracter citit din fp) | + | Analizați reprezentarea în baza 2 a lui n (ex. n = 16 si n = 5). |
| + | </spoiler> | ||
| + | |||
| + | |||
| + | |||
| + | |||
| + | \\ | ||
| + | Implementați invidual următoarea problemă. | ||
| + | |||
| + | |||
| + | == 5. bitset (10p )== | ||
| + | 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. | ||
| + | |||
| + | Pentru eficientă, vectorul S va conține date de tipul **unsigned char** (reamintim ca **sizeof(unsigned char) == 1 byte** adică **8 biți**). | ||
| + | |||
| + | <note> | ||
| + | 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: | ||
| + | |||
| + | <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> | ||
| - | ==== Exercitii laborator CB/CD ==== | + | Cele două linii de mai sus vor fi puse imediat dupa includerea directivelor header! |
| - | Codul sursa se gaseste [[http://swarm.cs.pub.ro/~gmuraru/PC/lab11.c|aici]], iar fisierul text necesar rularii programului este [[http://swarm.cs.pub.ro/~gmuraru/PC/lab11.in|aici]]. | + | </note> |
| - | Primul exercitiu presupune modificarea/adaugarea de instructiuni unui cod existent pentru a realiza anumite lucruri. In momentul actual programul citeste dintr-un fisier text mai multe date si le afiseaza la output. | ||
| - | Cerinte: | ||
| - | *Observati care este diferenta de size intre structuri, daca se foloseste pragma si daca nu se foloseste. | ||
| - | *Sa se populeze o structura de tipul 'Group' folosind fisierul binar de [[http://swarm.cs.pub.ro/~gmuraru/PC/lab11.bin|aici]], iar apoi aceste date sa se scrie intr-un fisier text. Datele se citesc in ordine din fisierul binar (adica prima data numarul de persoane apoi persoanele efective). | ||
| - | **Următoarele două probleme vă vor fi date de asistent în cadrul laboratorului.** | + | === 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> | ||
| + | Există un [[https://ocw.cs.pub.ro/courses/programare/laboratoare/lab12-bitset-example | exemplu]] detaliat care vă explică cum functionează aceastea. | ||
| - | **Următoarele două probleme vă vor fi date de asistent în cadrul laboratorului.** | + | Treceți la subpunctul următor abia după ce v-ați asigurat că acestea funcționează. |
| + | </note> | ||
| - | [[https://drive.google.com/drive/folders/1qB6EZLGVubKbuTXMtMue06egH_8fo25M|Checker laborator 12]] | + | * **adăugarea** unui element în mulțime |
| + | <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> | ||
| + | // delete_from_set(s, n) - scoate numarul n din multime s | ||
| + | void delete_from_set(SET s, unsigned int n); | ||
| + | </code> | ||
| - | <hidden> | + | * **verificarea** faptului că un element n **aparține** unei mulțimi |
| - | Link direct către lista completă de probleme: | + | <code C> |
| - | [[https://docs.google.com/document/d/1tmi_z18Wmn5YUgAHmemUToyTs7bjQjJBVgiYXMn4wq4/edit?usp=sharing|aici]] | + | // is_in_set(s, n) - returneaza 1 daca n este in s, 0 altfel |
| - | Link catre teste: [[https://drive.google.com/drive/folders/1r7EM0GF-e_gnVTsNrPXNP-YPkE6Ndxyc|aici]] | + | int is_in_set(SET s, unsigned int n); |
| - | Link catre checker: [[https://drive.google.com/open?id=12M2b-RTWi24GjchgSX9mRJVR46cfkBJE|aici]] | + | </code> |
| - | </hidden> | + | |
| + | * **ș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> | ||
| - | ==== Exerciţii de Laborator ==== | + | * verificarea faptului că mulțimea este **vidă** |
| - | - [2p] Generati un **fisier binar** care sa contina un numar n de numere intregi. n se va citi de la tastatura si cele n numere se vor genera random (hint: **rand**). In fisierul de output, numit ''gigel.bin'', scrieti **doar** cele n numere intregi generate. Pentru a va asigura ca in fisier ati scris ce ati dorit, puteti sa afisati numerele pe ecran, apoi sa deschideti fisierul in Sublime (vedeti exemplul din laborator) si sa comparati numerele afisate cu ce ati scris in fisier. | + | <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> | ||
| - | - [2p] Se da un **fisier binar** cu un numar nedeterminat de numere intregi (puteti folosi fisierul generat de voi de la exercitiul anterior). Să se calculeze si sa se afiseze pe ecran suma numerelor **impare** din fişier. Numerele **pare** vor fi citite si apoi afisate in **fisierul text** ''gigel.txt''. | + | * 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> | ||
| - | - [3p] Să se scrie un program pentru crearea unui **fişier binar**, având articole structuri (de tip student) cu următoarele câmpuri:<code> | + | * o funcție care **să afișeze** pe ecran elementele care se află într-o mulțime |
| - | nume student | + | <code C> |
| - | şir de maxim 30 de caractere (alocat static) | + | // print_set(s) - functia printeaza elementele multimii s |
| - | data nastere | + | void print_set(SET s); |
| - | o structură având câmpurile de tip int: zi,lună,an | + | </code> |
| - | medie | + | |
| - | o valoare reala, de tip float. | + | Urmăriți acest [[https://ocw.cs.pub.ro/courses/programare/laboratoare/lab12-bitset-example | exemplu cu bitset]] pentru a înțelege 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> | ||
| - | Pentru a demonstra ca programul vostru functioneaza corect, rulati-l si scrieti cateva astfel de structuri in fisier (ex. un vector cu 10 studenti), apoi scrieti o functie care deschide fisierul creat, citeste structura cu structura din fisier si afiseaza pe ecran ce a citit. | ||
| - | - [3p] Realizati un alt program in care rezolvati exercitiul anterior pentru cazul in care ** nume student** este un ** sir de caractere alocat dinamic **. | + | <spoiler Hint> |
| + | Analizați reprezentarea în baza 2 a lui n. | ||
| + | </spoiler> | ||
| - | |||
| - | |||
| - | ==== Bonus ==== | ||
| - | - [3p] Se dau n nume de fișiere citite de la tastatură, fiecare fișier binar conține un vector de numere intregi sortat crescător (nu se cunosc dimensiunile vectorilor). Se cere să salveze în fișierul ''sortare.out'' (de tip text) vectorul sortat crescător obținut prin interclasarea celor n vectori. | + | ==== Probleme de interviu ==== |
| + | Pentru cei interesați, recomandăm rezolvarea și următoarelor probleme, care sunt des întâlnite la interviuri. | ||
| + | <note warning> | ||
| + | Atenție! Problemele din această categorie au un nivel de dificultate ridicat, peste cel cerut la cursul de PC. | ||
| - | <hidden> | + | Recomandăm totuși rezolvarea acestor probleme pentru cei care doresc să aprofundeze lucrul cu operații pe biți. |
| - | - [2p] Într-un fişier există cuvinte, câte unul pe fiecare rând. Să se verifice dacă cuvintele de pe poziţii consecutive sunt rime. (dacă ultimele p caractere ale celor 2 cuvinte coincid, unde p este prima valoare citită din acelaşi fişier). | + | </note> |
| - | <file - rime.in> 2 | + | |
| - | ferit | + | |
| - | indraznit | + | |
| - | indraznet | + | |
| - | istet | + | |
| - | descurcaret | + | |
| - | </file> | + | |
| - | <file - rime.out> DA | + | |
| - | NU | + | |
| - | DA | + | |
| - | DA | + | |
| - | </file> | + | |
| - | <file > Explicaţie: | + | |
| - | Ultimele 2 litere ale cuvintelor "ferit" si "indraznit" | + | |
| - | coincid, pe când "indraznit" cu "indraznet" nu coincid. | + | |
| - | </file> | + | |
| - | </hidden> | + | |
| - | <hidden> | + | <spoiler Swap bits> |
| - | - [1p] Citiţi din fişierul ''lab11p2.bin'' (prezent in {{:programare:laboratoare:lab11.zip|arhiva laboraturlui}}) şi afişati rezultatele intr-un fişier text, a cărui denumire va fi ''lab11p2.txt''. | + | 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. |
| - | [3p] Ştergeţi toate apariţiile unui cuvânt dintr-un fişier text. Numele fișierului și cuvântul care se şterge se citesc de la tastatură. Programul vostru trebuie să suprascrie fișierul inițial. | + | |
| - | - Rezolvaţi următoarele cerinţe: | + | Exemplu: n = 2863311530 = > m = 1431655765 |
| - | </hidden> | + | |
| + | <note> | ||
| + | Hint: Reprezentarea numerelor în baza 2 ([http://www.binaryhexconverter.com/decimal-to-binary-converter | convertor]]). | ||
| + | </note> | ||
| + | |||
| + | </spoiler> | ||
| + | |||
| + | <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: | ||
| + | n = 5 și sirul [1, 4, 4, 1, 5] | ||
| + | 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> | ||
| - | <hidden> | + | </spoiler> |
| - | - [3p] Să se scrie un program care determină toate secvenţele binare de lungime n, fiecare din ele conţinând nu mai mult de k cifre de 1. | + | |
| - | - **Intrare**: Numerele naturale n, 1<n<20, şi k, k<=n, se citesc din fişierul in.txt. | + | |
| - | - **Ieşire**: fiecare linie a fişierului text out.txt va conţine câte o secvenţă binară distinctă, ce corespunde condiţiilor din enunţul problemei. | + | |
| - | </hidden> | + | |
| - | ==== Referinţe ==== | + | <spoiler Sushi> |
| - | * [[http://crasseux.com/books/ctutorial/Input-and-output.html#Input%20and%20output|The GNU Programming Tools - Input and Output]] | + | Enunt: [[http://www.infoarena.ro/problema/sushi | sushi ]] |
| + | </spoiler> | ||
| - | [[https://drive.google.com/open?id=1mWuEO8gdQTTfi_BahavCrwsVdiyBZWX_|Probleme laborator 14:00 - 16:00]] | ||