This shows you the differences between two versions of the page.
|
pp:26:teme:racket-supermarket [2026/03/18 11:18] mihaela.balint |
pp:26:teme:racket-supermarket [2026/03/26 08:55] (current) mihaela.balint |
||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | ===== Etapa 3 ===== | + | ===== Etapa 4 ===== |
| - | Această etapă își propune să sublinieze importanța abstractizării. Va trebui să vă definiți propriul TDA (tip de date abstract) și să oferiți o interfață completă (un set de constructori și operatori) prin care utilizatorul poate manipula valorile tipului, independent de implementarea din spate. Apoi, este esențial ca voi înșivă să folosiți TDA-ul doar prin intermediul interfeței (în etapa 4 veți înțelege și mai bine de ce este esențial). | + | 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. |
| - | Veți începe rezolvarea etapei prin a implementa TDA-ul ''%%queue%%'' în fișierul **queue.rkt**. | + | Din nou, 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ă: | + | 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 lisp> | + | <code scheme> |
| (define-struct queue (left right size-l size-r)) | (define-struct queue (left right size-l size-r)) | ||
| </code> | </code> | ||
| - | * left, right | + | * o adăugare în coadă este o adăugare în stiva ''right'' (ca înainte) |
| - | * sunt stive (last-in-first-out, implementate ca liste Racket) | + | * o extragere din coadă este o extragere din stiva ''left'' (ca înainte) |
| - | * fiecare adăugare în coadă va fi o adăugare în stiva right | + | * 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ă |
| - | * ex adăugare: \\ adaug 1 => ''%%right = '(1)%%'', \\ adaug 2 => ''%%right = '(2 1)%%'' | + | * când o operație ''enqueue'' sau ''dequeue'' produce situația ''%%size(left) = size(right) - 1%%'', aplicăm o **rotație**: |
| - | * fiecare scoatere din coadă va fi o scoatere din stiva left (când left este vidă, va trebui mai întâi să mutăm toate elementele din right în left, apoi să scoatem din left) | + | * mutăm "în mod leneș" toate elementele din ''right'' în ''left'' |
| - | * mutarea este rezultatul unor operații pop (din right) + push (în left) repetate | + | * 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) |
| - | * 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 scoatere: \\ scot din coada cu ''%%left = '()%%'', ''%%right = '(2 1)%%'' => \\ ''%%left = '(1 2)%%'', ''%%right = '()%%'' (după mutarea elementelor) => \\ ''%%left = '(2)%%'', ''%%right = '()%%'' (după scoaterea primului element) | + | Veți redefini interfața din etapa 3. Noile implementări depind de implementarea funcției de rotație: |
| - | * observați că primul element adăugat este primul element scos (first-in-first-out) | + | |
| - | * size-l, size-r | + | |
| - | * sunt numere naturale, reprezentând numărul de elemente din cele 2 stive | + | |
| - | + | ||
| - | Sarcina voastră este să implementați interfața TDA-ului queue: | + | |
| <file> | <file> | ||
| - | empty-queue : -> queue (constructor nular pentru o coadă goală) | + | (rotate left right Acc) |
| - | 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 scoatere din coadă) | + | |
| - | top : queue -> Elem (operatorul de vizualizare a elementului din vârful cozii) | + | |
| </file> | </file> | ||
| - | | + | * ''rotate'' calculează (cu evaluare întârziată) rezultatul ''%%left ++ (reverse right)%%'' |
| - | Această reprezentare pentru coadă asigură cost amortizat O(1) pentru operațiile de enqueue și dequeue. Vom folosi această reprezentare pentru câmpul queue al structurii counter, întrucât este mai eficientă decât reprezentarea cu liste Racket din etapele 1 și 2. | + | * 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'' | ||
| - | După ce ați finalizat implementarea TDA-ului, continuați dezvoltarea simulatorului în fișierul **supermarket.rkt**. (Observație: pe vmchecker veți încărca o arhivă .zip cu fișierele queue.rkt și supermarket.rkt.) | ||
| - | |||
| - | În primul rând, va trebui să adaptați o serie de funcții 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) - TDA-ul implementat de voi; este o coincidență că numele câmpului coincide cu numele tipului, în niciun caz nu era necesar acest lucru). | ||
| - | |||
| - | În plus față de etapa anterioară, în această etapă simulatorul trebuie să modeleze trecerea timpului. Până acum, 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 diversele evenimente este firesc să treacă timp, timp în care clienții avansează la case și, la un moment dat, părăsesc supermarketul. | ||
| - | |||
| - | Exceptând adaptările menționate, 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ă doar 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 în tt-ul și et-ul pe care casa ar trebui să le aibă după trecerea numărului dat de minute | ||
| - | * queue-ul casei nu se modifică, pentru că intenția este de a nu folosi niciodată această funcție pentru a avansa cu un număr de minute mai mare decât timpul până la ieșirea primului client din coadă | ||
| - | | ||
| Exemplu: | Exemplu: | ||
| - | <code lisp> | + | <code scheme> |
| - | ((pass-time-through-counter 5) | + | (rotate (stream-cons 1 (stream-cons 2 (stream-cons 3 empty-stream))) |
| - | (counter 1 | + | '(7 6 5 4) |
| - | 12 | + | empty-stream) |
| - | 7 | + | |
| - | (make-queue '() '((ada . 7)) 0 1))) | + | |
| </code> | </code> | ||
| => | => | ||
| - | ''%%(counter 1 7 2 (queue '() '((ada . 7)) 0 1))%%'' | + | ''%%#<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ă. | ||
| - | <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: | + | Exemplu pentru ''ITEMS = 5'': |
| - | <code lisp> | + | <code scheme> |
| - | (serve '((ana 14) (mia 2) 5 (ion 7) (delay 1 2) 7) | + | (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 1)) | ||
| (list (empty-counter 2) (empty-counter 3))) | (list (empty-counter 2) (empty-counter 3))) | ||
| - | </code> pentru ITEMS = 5: | + | </code> |
| - | * observăm că avem o casă fast (o vom numi C1) și 2 case slow (le vom numi C2 și C3) | + | * avem o casă fast (o numim ''C1'') și două case slow (le numim ''C2'' și ''C3'') |
| - | * primele 2 cereri distribuie cei 2 clienți astfel: | + | * când ilustrăm starea caselor: |
| - | * ana la C2 => ''%%C2 = (counter 2 14 14 (queue '() '((ana . 14)) 0 1))%%'' | + | * o casă este o colecție de ''index'', ''tt'', ''et'' și ''queue'' (dar puteți modifica structura, dacă doriți) |
| - | * mia la C1 => ''%%C1 = (counter 1 2 2 (queue '() '((mia . 2)) 0 1))%%'' | + | * vizualizăm elementele fluxurilor între acolade (în loc să scriem ''#<stream>'', ceea ce nu este tocmai informativ) |
| - | * apoi trec 5 minute, după care situația trebuie să fie: | + | * primele două cereri distribuie cei doi clienți astfel: |
| - | * mia a ieșit de la C1, care a rămas goală => ''%%C1 = (counter 1 0 0 (queue '() '() 0 0))%%'' | + | * ''ana'' la ''C2'' => ''%%C2 = (counter 2 7 7 (queue {(ana . 7)} '() 1 0))%%'' |
| - | * la C2 au trecut 5 minute => ''%%C2 = (counter 2 9 9 (queue '() '((ana . 14)) 0 1))%%'' | + | * ''mia'' la ''C1'' => ''%%C1 = (counter 1 2 2 (queue {(mia . 2)} '() 1 0))%%'' |
| - | * C3 a rămas cum era: goală și neîntârziată => ''%%C3 = (counter 3 0 0 (queue '() '() 0 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)%%'' |
| - | * au părăsit supermarketul, în ordine: ''%%'((1 . mia))%%'' (mia de la C1) | + | * apoi trec ''5'' minute, după care situația este: |
| - | * ion se așază la C3 => ''%%C3 = (counter 3 7 7 (queue '() '((ion . 7)) 0 1))%%'' | + | * ''mia'' a ieșit de la ''C1'', care a rămas goală => ''%%C1 = (counter 1 0 0 (queue {} '() 0 0))%%'' |
| - | * C1 este întârziată cu 2 minute => ''%%C1 = (counter 1 2 2 (queue '() '() 0 0))%%'' | + | * la ''C2'' au trecut ''5'' minute => ''%%C2 = (counter 2 2 2 (queue {(ana . 7)} '() 1 0))%%'' |
| - | * apoi trec 7 minute, după care situația trebuie să fie: | + | * ''C3'' a rămas cum era: goală și neîntârziată => ''%%C3 = (counter 3 0 0 (queue {} '() 0 0))%%'' |
| - | * întârzierea de la C1 s-a consumat => ''%%C1 = (counter 1 0 0 (queue '() '() 0 0))%%'' | + | * următoarele două cereri distribuie cei doi clienți astfel: |
| - | * la C2 au trecut 7 minute => ''%%C2 = (counter 2 2 2 (queue '() '((ana . 14)) 0 1))%%'' | + | * ''ion'' la ''C3'' => ''%%C3 = (counter 3 8 8 (queue {(ion . 8)} '() 1 0))%%'' |
| - | * ion a ieșit de la C3, care a rămas goală => ''%%C3 = (counter 3 0 0 (queue '() '() 0 0))%%'' | + | * ''dan'' la ''C2'' => ''%%C2 = (counter 2 8 2 (queue {(ana . 7)} '((dan . 6)) 1 1))%%'' |
| - | * au părăsit supermarketul, în ordine:: ''%%'((1 . mia) (3 . ion))%%'' | + | * ''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> | ||