This shows you the differences between two versions of the page.
|
pp:26:teme:racket-counters [2026/03/13 08:10] mihaela.balint [Changelog] |
pp:26:teme:racket-counters [2026/03/27 23:12] (current) mihaela.balint [Changelog] |
||
|---|---|---|---|
| Line 175: | Line 175: | ||
| * cel mai avansat client este ''ana'' (''et=8'') | * cel mai avansat client este ''ana'' (''et=8'') | ||
| * ea este scoasă de la ''C3'' => ''%%C3 = (counter 3 7 7 '((ion . 7)))%%'' (observați ''tt'' și ''et'') | * ea este scoasă de la ''C3'' => ''%%C3 = (counter 3 7 7 '((ion . 7)))%%'' (observați ''tt'' și ''et'') | ||
| + | |||
| + | ===== Etapa 3 ===== | ||
| + | |||
| + | Etapa 3 subliniază importanța abstractizării. Vă veți defini propriul TDA (tip de date abstract) cu o interfață completă (un set de constructori și operatori) prin care utilizatorul poate manipula valorile tipului, independent de implementarea din spate. Apoi, voi înșivă trebuie să folosiți TDA-ul doar prin intermediul interfeței (aspect esențial pentru o dezvoltare facilă în etapa 4). | ||
| + | |||
| + | Rezolvarea etapei începe cu implementarea TDA-ului ''queue'' în fișierul **queue.rkt**. | ||
| + | |||
| + | Acest tip reprezintă o coadă (first-in-first-out) ca pe o structură: | ||
| + | <code scheme> | ||
| + | (define-struct queue (left right size-l size-r)) | ||
| + | </code> | ||
| + | * ''left'', ''right'' | ||
| + | * sunt stive (last-in-first-out, implementate ca liste Racket) | ||
| + | * o adăugare în coadă este o adăugare în stiva ''right'' | ||
| + | * ex adăugare: \\ adaug 1 => ''%%right = '(1)%%'', \\ adaug 2 => ''%%right = '(2 1)%%'' | ||
| + | * o extragere din coadă este o extragere din stiva ''left'' (când ''left'' este vidă, mutăm toate elementele din right în left, apoi extragem din left) | ||
| + | * mutarea este rezultatul unor operații ''pop'' (din ''right'') + ''push'' (în ''left'') repetate | ||
| + | * ex mutare: \\ mut în ''%%left = '()%%'' din ''%%right = '(2 1)%%'' => \\ ''%%left = '(2)%%'', ''%%right = '(1)%%'' (primul ''pop'' din ''right'' îl extrage pe ''2'' și îi face ''push'' în ''left'') => \\ ''%%left = '(1 2)%%'', ''%%right = '()%%'' (apoi ''pop'' din ''right'' îl extrage pe ''1'' și îi face ''push'' în ''left'') | ||
| + | * ex extragere: \\ extrag din coada cu ''%%left = '()%%'', ''%%right = '(2 1)%%'' => \\ ''%%left = '(1 2)%%'', ''%%right = '()%%'' (după mutarea elementelor) => \\ ''%%left = '(2)%%'', ''%%right = '()%%'' (după extragerea primului element) | ||
| + | * observați că primul element adăugat este primul element extras (first-in-first-out) | ||
| + | * ''size-l'', ''size-r'' | ||
| + | * sunt numere naturale, reprezentând numărul de elemente din cele două stive | ||
| + | |||
| + | Sarcina voastră este să implementați interfața TDA-ului queue: | ||
| + | <file> | ||
| + | empty-queue : -> queue (constructor nular pentru o coadă goală) | ||
| + | queue-empty? : queue -> Bool (operator care verifică dacă o coadă este goală) | ||
| + | enqueue : Elem x queue -> queue (operatorul de adăugare în coadă) | ||
| + | dequeue : queue -> queue (operatorul de extragere din coadă) | ||
| + | top : queue -> Elem (operatorul de vizualizare a elementului din vârful cozii) | ||
| + | </file> | ||
| + | | ||
| + | Această reprezentare asigură cost amortizat ''O(1)'' pentru operațiile de ''enqueue'' și ''dequeue''. Vom folosi acest TDA pentru câmpul ''queue'' al structurii ''counter'', optimizând reprezentarea cu liste Racket din etapele 1 și 2. | ||
| + | |||
| + | După ce ați finalizat implementarea TDA-ului, continuați implementarea în fișierul **etapa3.rkt**. | ||
| + | |||
| + | Mai întâi, adaptați funcțiile de la etapa 2 astfel încât ele să țină cont de noua reprezentare (în care câmpul ''queue'' din structura ''counter'' este de tip coadă (''queue''); este o coincidență că numele câmpului coincide cu numele tipului, nu o necesitate). | ||
| + | |||
| + | În această etapă simulatorul modelează, în plus, trecerea timpului. Anterior, simulatorul trata așezările la cozi, întârzierile și deschiderile de noi case ca și cum s-ar produce în ordine, dar la un același moment de timp. Acest lucru nu corespunde realității - între evenimente este firesc să treacă timp, timp în care clienții avansează la case și, la un moment dat, părăsesc magazinul. | ||
| + | |||
| + | Funcțiile principale pe care va trebui să le implementați sunt: | ||
| + | <file> | ||
| + | (pass-time-through-counter minutes) | ||
| + | </file> | ||
| + | * este o funcție curry (aplicată parțial pe un număr de minute, va aștepta un al doilea argument de tip ''counter'') | ||
| + | * odată ce și-a primit (pe rând) argumentele, ''pass-time-through-counter'' actualizează ''tt''-ul și ''et''-ul casei pentru a reflecta trecerea numărului dat de minute | ||
| + | * câmpul ''queue'' nu se modifică, deoarece intenția este să folosim această funcție doar cu timpi mai mici sau egali cu timpul până la ieșirea primului client din coadă | ||
| + | | ||
| + | Exemplu: | ||
| + | <code scheme> | ||
| + | ((pass-time-through-counter 5) | ||
| + | (counter 1 | ||
| + | 12 | ||
| + | 7 | ||
| + | (make-queue '() '((ada . 7)) 0 1))) | ||
| + | </code> | ||
| + | => | ||
| + | <code scheme> | ||
| + | (counter 1 7 2 (queue '() '((ada . 7)) 0 1)) | ||
| + | </code> | ||
| + | |||
| + | <file> | ||
| + | (serve requests fast-counters slow-counters) | ||
| + | </file> | ||
| + | * ''serve'' actualizează casele din ''fast-counters'' și ''slow-counters'' pe măsură ce situația lor evoluează, pe baza listei ''requests'' în care elementele sunt: | ||
| + | * cereri (așezări la coadă, întârzieri, ajustări ale numărului de case) \\ sau | ||
| + | * timpi care trec între cereri | ||
| + | |||
| + | Exemplu pentru ''ITEMS = 5'': | ||
| + | <code scheme> | ||
| + | (serve '((ana 14) (mia 2) 5 (ion 7) (delay 1 2) 7) | ||
| + | (list (empty-counter 1)) | ||
| + | (list (empty-counter 2) (empty-counter 3))) | ||
| + | </code> | ||
| + | |||
| + | * observăm că avem o casă fast (o numim ''C1'') și două case slow (le numim ''C2'' și ''C3'') | ||
| + | * primele două cereri distribuie cei doi clienți astfel: | ||
| + | * ''ana'' la ''C2'' => ''%%C2 = (counter 2 14 14 (queue '() '((ana . 14)) 0 1))%%'' | ||
| + | * ''mia'' la ''C1'' => ''%%C1 = (counter 1 2 2 (queue '() '((mia . 2)) 0 1))%%'' | ||
| + | * apoi trec ''5'' minute, după care situația este: | ||
| + | * ''mia'' a ieșit de la ''C1'', care a rămas goală => ''%%C1 = (counter 1 0 0 (queue '() '() 0 0))%%'' | ||
| + | * la ''C2'' au trecut ''5'' minute => ''%%C2 = (counter 2 9 9 (queue '() '((ana . 14)) 0 1))%%'' | ||
| + | * ''C3'' a rămas cum era: goală și neîntârziată => ''%%C3 = (counter 3 0 0 (queue '() '() 0 0))%%'' | ||
| + | * au părăsit magazinul, în ordine: ''%%'((1 . mia))%%'' (''1'' reprezintă indexul casei de la care a ieșit ''mia'') | ||
| + | * ''ion'' se așază la ''C3'' => ''%%C3 = (counter 3 7 7 (queue '() '((ion . 7)) 0 1))%%'' | ||
| + | * ''C1'' este întârziată cu ''2'' minute => ''%%C1 = (counter 1 2 2 (queue '() '() 0 0))%%'' | ||
| + | * apoi trec ''7'' minute, după care situația este: | ||
| + | * întârzierea de la ''C1'' s-a consumat => ''%%C1 = (counter 1 0 0 (queue '() '() 0 0))%%'' | ||
| + | * la ''C2'' au trecut ''7'' minute => ''%%C2 = (counter 2 2 2 (queue '() '((ana . 14)) 0 1))%%'' | ||
| + | * ''ion'' a ieșit de la ''C3'', care a rămas goală => ''%%C3 = (counter 3 0 0 (queue '() '() 0 0))%%'' | ||
| + | * au părăsit magazinul, în ordine:: ''%%'((1 . mia) (3 . ion))%%'' | ||
| + | |||
| + | ===== Etapa 4 ===== | ||
| + | |||
| + | Etapa 4 oferă un exemplu interesant de utilizare a fluxurilor. Veți reimplementa TDA-ul ''queue'' pentru a obține un plus de performanță și, cu condiția să fi respectat bariera de abstractizare în etapa 3, funcțiile implementate anterior vor funcționa fără modificări pe noua reprezentare. | ||
| + | |||
| + | Din nou, rezolvarea etapei începe cu implementarea TDA-ului ''queue'' în fișierul **queue.rkt**. | ||
| + | |||
| + | Din motive de performanță detaliate în schelet, reținem câmpul ''left'' al structurii ''queue'' ca flux (în contrast cu reprezentarea ca listă din etapa 3). Definiția structurii nu se modifică: | ||
| + | <code scheme> | ||
| + | (define-struct queue (left right size-l size-r)) | ||
| + | </code> | ||
| + | * o adăugare în coadă este o adăugare în stiva ''right'' (ca înainte) | ||
| + | * o extragere din coadă este o extragere din stiva ''left'' (ca înainte) | ||
| + | * după fiecare operație ''enqueue'' sau ''dequeue'' trebuie menținut invariantul ''%%size(left) ≥ size(right)%%''; astfel, niciun ''dequeue'' nu va găsi stiva ''left'' vidă | ||
| + | * când o operație ''enqueue'' sau ''dequeue'' produce situația ''%%size(left) = size(right) - 1%%'', aplicăm o **rotație**: | ||
| + | * mutăm "în mod leneș" toate elementele din ''right'' în ''left'' | ||
| + | * ce înseamnă "leneș": elementele vor fi mutate, de fapt, unul câte unul, pe măsură ce extragem elemente din ''left'', nu toate deodată (dacă s-ar muta deodată nu am rezolva problema complexității, ci doar am deplasa-o asupra altor operații) | ||
| + | | ||
| + | Veți redefini interfața din etapa 3. Noile implementări depind de implementarea funcției de rotație: | ||
| + | <file> | ||
| + | (rotate left right Acc) | ||
| + | </file> | ||
| + | * ''rotate'' calculează (cu evaluare întârziată) rezultatul ''%%left ++ (reverse right)%%'' | ||
| + | * rotația se efectuează doar atunci când ''%%size(left) = size(right) - 1%%'', așadar găsește un număr echilibrat de elemente în cele două stive | ||
| + | * la fiecare extragere din ''left'', extragem ("pop") și un element din ''right'' pe care îl adăugăm ("push") în acumulatorul ''Acc'' | ||
| + | * când ''left'' devine goală, ''right'' conține un singur element (''%%size(left) = size(right) - 1%%''), iar ''Acc'' conține toate elementele aflate inițial în ''right'', în ordine inversă; acum adăugăm elementul din ''right'' la începutul ''Acc'' (în timp ''O(1)''), și acesta este exact conținutul cu care trebuie să reinițializăm stiva ''left'' | ||
| + | |||
| + | Exemplu: | ||
| + | <code scheme> | ||
| + | (rotate (stream-cons 1 (stream-cons 2 (stream-cons 3 empty-stream))) | ||
| + | '(7 6 5 4) | ||
| + | empty-stream) | ||
| + | </code> | ||
| + | => | ||
| + | ''%%#<stream>%%'' \\ | ||
| + | Mai precis, rezultatul este de forma: | ||
| + | <code scheme> | ||
| + | (stream-cons 1 | ||
| + | (rotate (stream-cons 2 (stream-cons 3 empty-stream)) | ||
| + | '(6 5 4) | ||
| + | (stream-cons 7 empty-stream))) | ||
| + | </code> | ||
| + | și, conform comportamentului constructorului ''stream-cons'', apelul recursiv al funcției ''rotate'' este întârziat. | ||
| + | Când accesăm restul acestui flux (de exemplu, la ''dequeue''), evaluăm apelul întârziat, obținând un rezultat de forma ''%%(stream-cons 2 (rotate ....))%%'', etc. | ||
| + | |||
| + | După ce ați finalizat implementarea TDA-ului, continuați implementarea în fișierul **etapa4.rkt**. | ||
| + | |||
| + | Față de etapa anterioară, simulatorul tratează două cereri noi: | ||
| + | * ''(close index)'' solicită închiderea casei cu indexul ''index'', și redistribuirea clienților din coadă (cu excepția primului) | ||
| + | * ''(open index)'' solicită deschiderea casei cu indexul ''index'' | ||
| + | Apare distincția între case deschise și case închise: în această etapă, cererile de tip "așezare la o casă", respectiv "ensure" iau în considerare doar casele deschise. Modul în care reprezentați starea caselor (deschisă/închisă) este la alegerea voastră. | ||
| + | |||
| + | |||
| + | Exemplu pentru ''ITEMS = 5'': | ||
| + | <code scheme> | ||
| + | (serve '((ana 7) (mia 2) 5 (ion 8) (dan 6) (close 2) (delay 1 15) (ema 2) (open 2) 2 (geo 5) (close 1) (ensure 7)) | ||
| + | (list (empty-counter 1)) | ||
| + | (list (empty-counter 2) (empty-counter 3))) | ||
| + | </code> | ||
| + | * avem o casă fast (o numim ''C1'') și două case slow (le numim ''C2'' și ''C3'') | ||
| + | * când ilustrăm starea caselor: | ||
| + | * o casă este o colecție de ''index'', ''tt'', ''et'' și ''queue'' (dar puteți modifica structura, dacă doriți) | ||
| + | * vizualizăm elementele fluxurilor între acolade (în loc să scriem ''#<stream>'', ceea ce nu este tocmai informativ) | ||
| + | * primele două cereri distribuie cei doi clienți astfel: | ||
| + | * ''ana'' la ''C2'' => ''%%C2 = (counter 2 7 7 (queue {(ana . 7)} '() 1 0))%%'' | ||
| + | * ''mia'' la ''C1'' => ''%%C1 = (counter 1 2 2 (queue {(mia . 2)} '() 1 0))%%'' | ||
| + | * obs: în etapa trecută ''ana'' și ''mia'' apăreau ca elemente în stiva ''right''; acum ele sunt în stiva ''left'', deoarece s-a efectuat o rotație, necesară pentru menținerea invariantului ''%%size(left) ≥ size(right)%%'' | ||
| + | * apoi trec ''5'' minute, după care situația este: | ||
| + | * ''mia'' a ieșit de la ''C1'', care a rămas goală => ''%%C1 = (counter 1 0 0 (queue {} '() 0 0))%%'' | ||
| + | * la ''C2'' au trecut ''5'' minute => ''%%C2 = (counter 2 2 2 (queue {(ana . 7)} '() 1 0))%%'' | ||
| + | * ''C3'' a rămas cum era: goală și neîntârziată => ''%%C3 = (counter 3 0 0 (queue {} '() 0 0))%%'' | ||
| + | * următoarele două cereri distribuie cei doi clienți astfel: | ||
| + | * ''ion'' la ''C3'' => ''%%C3 = (counter 3 8 8 (queue {(ion . 8)} '() 1 0))%%'' | ||
| + | * ''dan'' la ''C2'' => ''%%C2 = (counter 2 8 2 (queue {(ana . 7)} '((dan . 6)) 1 1))%%'' | ||
| + | * ''C2'' se închide => ''ana'' rămâne la ''C2'', iar ''dan'' se mută la ''C3'' | ||
| + | * => ''%%C2 = (counter 2 2 2 (queue {(ana . 7)} '() 1 0))%%'' și nu mai primește clienți | ||
| + | * => ''%%C3 = (counter 3 14 8 (queue {(ion . 8)} '((dan . 6)) 1 1))%%'' | ||
| + | * ''C1'' este întârziată cu ''15'' minute => ''%%C1 = (counter 1 15 15 (queue {} '() 0 0))%%'' | ||
| + | * ''ema'' se așază la ''C3'' => ''%%C3 = (counter 3 16 8 (queue {(ion . 8) <flux-neevaluat-care-va-produce-dan-și-ema>} '() 3 0))%%'' | ||
| + | * ''C2'' are ''tt'' mai mic, însă ''C2'' este închisă, deci se alege între ''C1'' și ''C3'' | ||
| + | * ''C2'' se deschide, fără să producă alte modificări | ||
| + | * apoi trec ''2'' minute, după care situația este: | ||
| + | * întârzierea de la ''C1'' s-a consumat parțial => ''%%C1 = (counter 1 13 13 (queue {} '() 0 0))%%'' | ||
| + | * ''ana'' a ieșit de la ''C2'' => ''%%C2 = (counter 2 0 0 (queue {} '() 0 0))%%'' | ||
| + | * la ''C3'' au trecut ''2'' minute => ''%%C3 = (counter 3 14 6 (queue {(ion . 8) <flux...>} '() 3 0))%%'' | ||
| + | * ''geo'' se așază la ''C2'' => ''%%C2 = (counter 2 5 5 (queue {(geo . 5)} '() 1 0))%%'' | ||
| + | * ''C1'' se închide, fără să producă alte modificări | ||
| + | * ''ensure'' compară media timpilor totali ai caselor deschise cu ''7'' | ||
| + | * ''tt2'' + ''tt3'' = ''5'' + ''14'' = ''19'' => ''tt-mediu = 19 / 2 > 7'' | ||
| + | * ''tt1'' nu participă la medie întrucât ''C1'' este închisă | ||
| + | * se adaugă o casă slow goală (''C4'') => ''tt-mediu = 19 / 3 ≤ 7'' (deci ne oprim aici cu adăugarea) | ||
| + | Rezultat final: | ||
| + | <code scheme> | ||
| + | (list | ||
| + | '((1 . mia) (2 . ana)) | ||
| + | (cons 2 (queue #<stream> '() 1 0)) | ||
| + | (cons 3 (queue #<stream> '() 3 0))) | ||
| + | </code> | ||
| Line 190: | Line 379: | ||
| ===== Resurse ===== | ===== Resurse ===== | ||
| + | * {{:pp:26:teme:racket:etapa-4.zip|etapa 4}} | ||
| + | * {{:pp:26:teme:racket:etapa-3.zip|etapa 3}} | ||
| * {{:pp:26:teme:racket:etapa-2.zip|etapa 2}} | * {{:pp:26:teme:racket:etapa-2.zip|etapa 2}} | ||
| * {{:pp:26:teme:racket:etapa-1.zip|etapa 1}} | * {{:pp:26:teme:racket:etapa-1.zip|etapa 1}} | ||
| Line 196: | Line 387: | ||
| ===== Changelog ===== | ===== Changelog ===== | ||
| + | * 27.03 (ora 23:12) - Am publicat etapa 4. | ||
| + | * 19.03 (ora 10:32) - Am publicat etapa 3. | ||
| * 13.03 (ora 08:10) - Am publicat etapa 2. | * 13.03 (ora 08:10) - Am publicat etapa 2. | ||
| * 09.03 (ora 15:30) - Am publicat etapa 1. | * 09.03 (ora 15:30) - Am publicat etapa 1. | ||