This shows you the differences between two versions of the page.
|
pp:26:teme:racket-supermarket [2026/03/12 09:48] mihaela.balint |
pp:26:teme:racket-supermarket [2026/03/26 08:55] (current) mihaela.balint |
||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | ===== Etapa 2 ===== | + | ===== Etapa 4 ===== |
| - | Etapa 2 își propune exploatarea faptului că funcțiile sunt valori de ordinul întâi. Veți defini funcții curry, veți abstractiza funcții cu implementări similare, și veți folosi funcționale - atât implementări proprii, cât și funcționalele predefinite în Racket. Vă încurajăm să valorificați oportunitățile de utilizare a funcțiilor anonime și funcționalelor, inclusiv când enunțul nu impune acest lucru. | + | 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. |
| - | În această etapă, numărul de case din magazin nu mai este fixat. Avem: | + | Din nou, rezolvarea etapei începe cu implementarea TDA-ului ''queue'' în fișierul **queue.rkt**. |
| - | * o listă fast-counters de case care acceptă doar clienți care au cumpărat maxim ITEMS produse | + | |
| - | * o listă slow-counters de case deschise tuturor clienților | + | 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ă: |
| - | Pentru ca în viitor să putem determina ordinea ieșirii clienților din magazin, introducem un nou câmp în structura counter: | + | |
| <code scheme> | <code scheme> | ||
| - | (define-struct counter (index tt et queue)) | + | (define-struct queue (left right size-l size-r)) |
| </code> | </code> | ||
| - | * ''et'' | + | * o adăugare în coadă este o adăugare în stiva ''right'' (ca înainte) |
| - | * vine de la "exit time", și reprezintă timpul rămas până când primul client din coadă va părăsi această casă | + | * o extragere din coadă este o extragere din stiva ''left'' (ca înainte) |
| - | * depinde de numărul de produse cumpărate de acest client (1 produs = 1 minut) și de eventualele întârzieri suferite de casă | + | * 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**: | |
| - | Simulatorul trebuie să modeleze atât situațiile de la etapa anterioară, cât și două noi situații: | + | * mutăm "în mod leneș" toate elementele din ''right'' în ''left'' |
| - | * când cel mai avansat client (din punct de vedere al exit time-ului) părăsește magazinul | + | * 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) |
| - | * când este necesară deschiderea unor noi case, pentru a micșora media timpilor totali de așteptare | + | |
| - | + | Veți redefini interfața din etapa 3. Noile implementări depind de implementarea funcției de rotație: | |
| - | Inițial, veți adapta o serie de funcții de la etapa 1 la noua reprezentare (adică la numărul variabil de case și la prezența câmpului ''et'' în structură). | + | |
| - | + | ||
| - | Apoi, funcțiile principale pe care va trebui să le implementați sunt: | + | |
| <file> | <file> | ||
| - | (update f counters index) | + | (rotate left right Acc) |
| </file> | </file> | ||
| - | * update aplică transformarea ''f'' casei din ''counters'' care are indexul ''index'', și întoarce lista ''counters'' actualizată | + | * ''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: | Exemplu: | ||
| <code scheme> | <code scheme> | ||
| - | (update (λ (C) (struct-copy counter C [tt 0])) | + | (rotate (stream-cons 1 (stream-cons 2 (stream-cons 3 empty-stream))) |
| - | (list (counter 1 2 2 '()) (counter 2 5 5 '())) | + | '(7 6 5 4) |
| - | 2) | + | empty-stream) |
| </code> | </code> | ||
| - | => | + | => |
| + | ''%%#<stream>%%'' \\ | ||
| + | Mai precis, rezultatul este de forma: | ||
| <code scheme> | <code scheme> | ||
| - | (list (counter 1 2 2 '()) (counter 2 0 5 '())) | + | (stream-cons 1 |
| + | (rotate (stream-cons 2 (stream-cons 3 empty-stream)) | ||
| + | '(6 5 4) | ||
| + | (stream-cons 7 empty-stream))) | ||
| </code> | </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> | ||
| - | (remove-first-from-counter C) | ||
| - | </file> | ||
| - | * remove-first-from-counter scoate prima persoană din coada casei ''C'' | ||
| - | * ''tt''-ul și ''et''-ul casei ''C'' trebuie ajustate în consecință | ||
| - | * orice întârziere avea casa, ea dispare | ||
| - | * dispar produsele clientului care pleacă (și minutele asociate acestora) | ||
| - | * nicio altă casă nu este afectată (este ca și cum ar fi trecut timpul doar pe la casa ''C''; acest lucru se va schimba în etapa 3) | ||
| - | Exemplu: | + | Exemplu pentru ''ITEMS = 5'': |
| <code scheme> | <code scheme> | ||
| - | (remove-first-from-counter (counter 1 50 5 '((ana . 3) (leo . 35) (mia . 10)))) | + | (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> | </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> | <code scheme> | ||
| - | (counter 1 45 35 '((leo . 35) (mia . 10))) | + | (list |
| + | '((1 . mia) (2 . ana)) | ||
| + | (cons 2 (queue #<stream> '() 1 0)) | ||
| + | (cons 3 (queue #<stream> '() 3 0))) | ||
| </code> | </code> | ||
| - | |||
| - | <file> | ||
| - | (serve requests fast-counters slow-counters) | ||
| - | </file> | ||
| - | * serve primește o listă de cereri (așezări la coadă, întârzieri, ieșiri de la casă, ajustări ale numărului de case) și le tratează în ordine, în sensul că actualizează casele din ''fast-counters'' și ''slow-counters'' pe măsură ce situația lor evoluează | ||
| - | |||
| - | Exemplu (pentru ''ITEMS = 5''): | ||
| - | <code scheme> | ||
| - | (serve '((ana 8) (mia 2) (mara 14) (ion 7) (remove-first) (ensure 5) (remove-first)) | ||
| - | (list (empty-counter 1) (empty-counter 2)) | ||
| - | (list (empty-counter 3) (empty-counter 4))) | ||
| - | </code> | ||
| - | |||
| - | * observăm că avem două case fast (pentru simplitate le numim ''C1'' și ''C2'') și două case slow (le numim ''C3'' și ''C4'') | ||
| - | * primele 4 cereri distribuie cei 4 clienți astfel: | ||
| - | * ''ana'' la ''C3'' (prima casă slow cu ''tt=0'') => ''%%C3 = (counter 3 8 8 '((ana . 8)))%%'' | ||
| - | * ''mia'' la ''C1'' (prima casă fast cu ''tt=0'') => ''%%C1 = (counter 1 2 2 '((mia . 2)))%%'' | ||
| - | * ''mara'' la ''C4'' (casa slow cu ''tt'' minim) => ''%%C4 = (counter 4 14 14 '((mara . 14)))%%'' | ||
| - | * ''ion'' la ''C3'' (casa slow cu ''tt'' minim) => ''%%C3 = (counter 3 15 8 '((ana . 8) (ion . 7)))%%'' | ||
| - | * remove-first scoate cel mai avansat client: | ||
| - | * cel mai avansat client este ''mia'' (''et=2'') | ||
| - | * ea este scoasă de la ''C1'' => ''%%C1 = (counter 1 0 0 '())%%'' (observați ''tt'' și ''et'') | ||
| - | * ensure compară media timpilor totali cu ''5'': | ||
| - | * ''tt1 + tt2 + tt3 + tt4 = 0 + 0 + 15 + 14 = 29'' => ''ttmed = 29 / 4 > 5'' | ||
| - | * se adaugă o casă slow goală (''C5'') => ''ttmed = 29 / 5 > 5'' | ||
| - | * se adaugă o casă slow goală (''C6'') => ''ttmed = 29 / 6 ≤ 5'' (deci putem trece la cererea următoare) | ||
| - | * remove-first scoate cel mai avansat client: | ||
| - | * 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'') | ||