This shows you the differences between two versions of the page.
|
pp:26:teme:racket-supermarket [2026/03/12 09:06] mihaela.balint created |
pp:26:teme:racket-supermarket [2026/03/26 08:55] (current) mihaela.balint |
||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | ===== Etapa 2 ===== | + | ===== Etapa 4 ===== |
| - | Această etapă își propune exploatarea faptului că funcțiile sunt valori de ordinul întâi. Va trebui să folosiți funcții curry și să abstractizați funcții cu implementări similare. De asemenea, vă încurajăm să valorificați oportunitățile de utilizare a funcțiilor anonime și funcționalelor, deși 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 supermarket nu mai este fixat. Vom avea: | + | 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 etapa următoare să putem determina ordinea ieșirii clienților din supermarket, introducem acum un nou câmp în structura counter: | + | <code scheme> |
| - | <code lisp> | + | (define-struct queue (left right size-l size-r)) |
| - | (define-struct counter (index tt et queue)) | + | |
| </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 casa respectivă | + | * 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**: | |
| - | În această etapă, simulatorul trebuie să modeleze atât situațiile de la etapa anterioară, cât și 2 noi situații: | + | * mutăm "în mod leneș" toate elementele din ''right'' în ''left'' |
| - | * situația în care cel mai avansat client (din punct de vedere al exit time-ului) părăsește supermarket-ul | + | * 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) |
| - | * situația în care 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: | |
| - | În primul rând, va trebui să adaptați o serie de funcții de la etapa 1 astfel încât ele să țină cont de noua reprezentare (adică de numărul variabil de case și de prezența câmpului ''%%'et%%'' în structura de tip casă). | + | |
| - | + | ||
| - | Exceptând aceste adaptări, 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 lisp> | + | <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> | ||
| - | => | + | => |
| - | ''%%(list (counter 1 2 2 '()) (counter 2 0 5 '()))%%'' | + | ''%%#<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. | ||
| - | <file> | + | După ce ați finalizat implementarea TDA-ului, continuați implementarea în fișierul **etapa4.rkt**. |
| - | (remove-first-from-counter C) | + | |
| - | </file> | + | Față de etapa anterioară, simulatorul tratează două cereri noi: |
| - | * remove-first-from-counter scoate prima persoană din coada casei C | + | * ''(close index)'' solicită închiderea casei cu indexul ''index'', și redistribuirea clienților din coadă (cu excepția primului) |
| - | * tt-ul și et-ul casei C trebuie ajustate în consecință | + | * ''(open index)'' solicită deschiderea casei cu indexul ''index'' |
| - | * orice întârziere avea casa, ea dispare | + | 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ă. |
| - | * 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: | + | |
| - | <code lisp> | + | Exemplu pentru ''ITEMS = 5'': |
| - | (remove-first-from-counter (counter 1 50 5 '((ana . 3) (leo . 35) (mia . 10)))) | + | <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> | </code> | ||
| - | => | ||
| - | ''%%(counter 1 45 35 '((leo . 35) (mia . 10)))%%'' | ||
| - | |||
| - | <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: | ||
| - | <code lisp> | ||
| - | (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> pentru ITEMS = 5: | ||
| - | * observăm că avem 2 case fast (pentru simplitate le vom numi C1 și C2) și 2 case slow (le vom numi 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 caută cel mai avansat client pentru a-l scoate de la casă | ||
| - | * 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 caută cel mai avansat client pentru a-l scoate de la casă | ||
| - | * 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) | ||