Table of Contents

La cumpărături

Descriere generală și organizare

Tema constă într-o aplicație care simulează fluxul clienților pe la casele unui magazin, și constă în 4 etape:

Așa cum se poate observa, ziua deadline-ului variază în funcție de semigrupa în care sunteți repartizați. Restanțierii care refac tema și nu refac laboratorul beneficiază de ultimul deadline (deci vor avea deadline-uri în zilele de 20.03, 27.03, 03.04, 10.04).

Rezolvările tuturor etapelor pot fi trimise până la ora 08:00 în ziua laboratorului 7, dar orice exercițiu trimis după deadline-ul soft și înainte de deadline-ul hard (care este dimineața zilei laboratorului 7) se punctează cu jumătate din punctaj. Orice exercițiu trimis după deadline-ul hard nu se mai punctează deloc. Nota finală pe etapă se calculează conform formulei: n = (n1 + n2) / 2 (n1 = nota obținută înainte de deadline-ul soft; n2 = nota obținută între deadline-ul soft și cel hard). Când toate submisiile sunt înainte de deadline-ul soft, nota pe ultima submisie este și nota finală (întrucât n1 = n2).

În fiecare etapă, veți folosi ce ați învățat în săptămâna anterioară pentru a perfecționa aplicația.

Pentru fiecare etapă veți primi un schelet de cod (dar rezolvarea se bazează în mare măsură pe rezolvările anterioare). Enunțul din această pagină este menit să descrie pe scurt aplicația și să ofere exemple de rulare a funcțiilor mai complexe. Dacă preferați, puteți rezolva tema utilizând doar indicațiile din schelet.

Etapa 1

În această etapă presupunem că magazinul are fix 4 case (“counters” în engleză): C1, C2, C3, C4. Fiecare casă este reprezentată ca o structură:

(define-struct counter (index tt queue))

Deși nu am studiat structuri la curs sau laborator, utilizarea lor este simplă (și ne ajută să avem un cod mai lizibil). Aveți aici un tutorial foarte scurt cu tot ce vă trebuie pe partea de structuri și pattern matching.

Statutul caselor diferă astfel:

În această etapă, simulatorul trebuie să modeleze două situații:

Funcțiile principale pe care va trebui să le implementați sunt:

(min-tt counters)
(add-to-counter C name n-items)
(serve requests C1 C2 C3 C4)

Exemplu:

(serve '((ana 12) (delay 1 5) (mia 2)) C1 C2 C3 C4)

unde presupunem că C1-C4 sunt în prezent lipsite de clienți, iar ITEMS = 5:

Etapa 2

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.

În această etapă, numărul de case din magazin nu mai este fixat. Avem:

Pentru ca în viitor să putem determina ordinea ieșirii clienților din magazin, introducem un nou câmp în structura counter:

(define-struct counter (index tt et queue))

Simulatorul trebuie să modeleze atât situațiile de la etapa anterioară, cât și două noi situații:

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:

(update f counters index)

Exemplu:

(update (λ (C) (struct-copy counter C [tt 0]))
        (list (counter 1 2 2 '()) (counter 2 5 5 '()))
        2)

(list (counter 1 2 2 '()) (counter 2 0 5 '()))
(remove-first-from-counter C)

Exemplu:

(remove-first-from-counter (counter 1 50 5 '((ana . 3) (leo . 35) (mia . 10))))

(counter 1 45 35 '((leo . 35) (mia . 10)))
(serve requests fast-counters slow-counters)

Exemplu (pentru ITEMS = 5):

(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)))

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ă:

(define-struct queue (left right size-l size-r))

Sarcina voastră este să implementați interfața TDA-ului queue:

  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)

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:

(pass-time-through-counter minutes)

Exemplu:

((pass-time-through-counter 5) 
 (counter 1 
          12 
          7 
          (make-queue '() '((ada . 7)) 0 1)))

(counter 1 7 2 (queue '() '((ada . 7)) 0 1))
(serve requests fast-counters slow-counters)

Exemplu pentru ITEMS = 5:

(serve '((ana 14) (mia 2) 5 (ion 7) (delay 1 2) 7)
       (list (empty-counter 1))
       (list (empty-counter 2) (empty-counter 3)))

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ă:

(define-struct queue (left right size-l size-r))

Veți redefini interfața din etapa 3. Noile implementări depind de implementarea funcției de rotație:

(rotate left right Acc)

Exemplu:

(rotate (stream-cons 1 (stream-cons 2 (stream-cons 3 empty-stream)))
        '(7 6 5 4)
        empty-stream)

#<stream>
Mai precis, rezultatul este de forma:

(stream-cons 1 
             (rotate (stream-cons 2 (stream-cons 3 empty-stream))
                     '(6 5 4)
                     (stream-cons 7 empty-stream)))

ș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:

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:

(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)))

Rezultat final:

(list
 '((1 . mia) (2 . ana))
 (cons 2 (queue #<stream> '() 1 0))
 (cons 3 (queue #<stream> '() 3 0)))

Precizări

Resurse

Changelog

Referinţe