Tema constă într-o aplicație care simulează fluxul clienților pe la casele unui supermarket.
Tema este împărțită î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 23.03, 30.03, 06.04, 13.04).
Rezolvările tuturor etapelor pot fi trimise până în ziua laboratorului 6, dar orice exercițiu trimis după deadline se punctează cu jumătate din punctaj. Nota finală pe temă se calculează conform formulei: n = (n1 + n2) / 2 (n1 = nota obținută înainte de deadline; n2 = nota obținută după deadline). Când toate submisiile sunt înainte de deadline, 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 simulatorul (ori ca performanță, ori ca număr de situații pe care este capabil să le modeleze).
Pentru fiecare etapă veți primi un schelet de cod (dar rezolvarea se bazează în mare măsură pe rezolvările anterioare). Intenția este să puteți rezolva tema utilizând doar indicațiile din schelet (fără a fi necesar să citiți enunțul). Enunțul încearcă să lămurească aspectele care poate nu sunt clare tuturor doar din schelet.
În această etapă presupunem că supermarket-ul 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 doar 2 tipuri de situații:
Funcțiile principale pe care va trebui să le implementați sunt:
(add-to-counter C name n-items)
(add-to-counter C2 'ana 12)
va determina adăugarea perechii '(ana . 12)
în câmpul queue al lui C2(min-tt counters)
(min-tt (list (counter 1 10 '()) (counter 2 12 '((ana . 12)))))
⇒ '(1 . 10)
(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:
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.
În această etapă, numărul de case din supermarket nu mai este fixat. Vom avea:
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:
(define-struct counter (index tt et queue))
În această etapă, simulatorul trebuie să modeleze atât situațiile de la etapa anterioară, cât și 2 noi situații:
Î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:
(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:
(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)))
pentru ITEMS = 5:
C3 = (counter 3 8 8 '((ana . 8)))
C1 = (counter 1 2 2 '((mia . 2)))
C4 = (counter 4 14 14 '((mara . 14)))
C3 = (counter 3 15 8 '((ana . 8) (ion . 7)))
C1 = (counter 1 0 0 '())
(observați tt și et)C3 = (counter 3 7 7 '((ion . 7)))
(observați tt și et)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).
Veți începe rezolvarea etapei prin a implementa TDA-ul 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))
right = '(1)
, right = '(2 1)
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)left = '()
, right = '(2 1)
⇒ left = '(1 2)
, right = '()
(după mutarea elementelor) ⇒ left = '(2)
, right = '()
(după scoaterea primului element)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 scoatere din coadă) top : queue -> Elem (operatorul de vizualizare a elementului din vârful cozii)
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.
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:
(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)
requests
în care elementele sunt:Exemplu:
(serve '((ana 14) (mia 2) 5 (ion 7) (delay 1 2) 7) (list (empty-counter 1)) (list (empty-counter 2) (empty-counter 3)))
pentru ITEMS = 5:
C2 = (counter 2 14 14 (queue '() '((ana . 14)) 0 1))
C1 = (counter 1 2 2 (queue '() '((mia . 2)) 0 1))
C1 = (counter 1 0 0 (queue '() '() 0 0))
C2 = (counter 2 9 9 (queue '() '((ana . 14)) 0 1))
C3 = (counter 3 0 0 (queue '() '() 0 0))
'((1 . mia))
(mia de la C1)C3 = (counter 3 7 7 (queue '() '((ion . 7)) 0 1))
C1 = (counter 1 2 2 (queue '() '() 0 0))
C1 = (counter 1 0 0 (queue '() '() 0 0))
C2 = (counter 2 2 2 (queue '() '((ana . 14)) 0 1))
C3 = (counter 3 0 0 (queue '() '() 0 0))
'((1 . mia) (3 . ion))
În această etapă veți observa 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 la etapa anterioară (folosind valorile de tip coadă doar prin intermediul interfeței), vă veți bucura de faptul că nu trebuie să modificați nimic în fișierul supermarket.rkt pentru a lucra cu această nouă reprezentare. Volumul de lucru este diminuat (relativ la etapele precedente), dar vă încurajăm să folosiți timpul suplimentar pentru a înțelege în profunzime felul în care ne ajută fluxurile.
Veți începe rezolvarea etapei prin a reimplementa TDA-ul queue
în fișierul queue.rkt.
Din motive de performanță explicate în detaliu în schelet, vom reprezenta câmpul left al structurii queue ca flux (spre deosebire de reprezentarea ca listă din etapa anterioară). Reamintim definiția structurii (care nu s-a modificat):
(define-struct queue (left right size-l size-r))
Sarcina voastră este să reimplementați interfața TDA-ului queue (aceleași funcții pe care le-ați implementat în etapa 3). Aceasta depinde 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, după cum știm din comportamentul constructorului stream-cons, apelul recursiv al funcției rotate este întârziat.
Dacă ulterior vom accesa restul acestui flux, vom evalua noul apel al lui rotate și vom obține un rezultat de forma (stream-cons 2 (rotate ....))
, etc.
După ce ați finalizat implementarea TDA-ului, continuați dezvoltarea simulatorului în fișierul supermarket.rkt.
Simulatorul trebuie să modeleze toate situațiile de la etapa anterioară, plus situația în care o casă este închisă.
Exemplu:
(serve '((ana 7) (mia 2) 5 (ion 8) (close 2) (delay 1 10) (dan 2) 3 (ensure 5)) (list (empty-counter 1)) (list (empty-counter 2) (empty-counter 3)))
pentru ITEMS = 5:
C2 = (counter 2 7 7 (queue {(ana . 7)} '() 1 0))
C1 = (counter 1 2 2 (queue {(mia . 2)} '() 1 0))
C1 = (counter 1 0 0 (queue {} '() 0 0))
C2 = (counter 2 2 2 (queue {(ana . 7)} '() 1 0))
C3 = (counter 3 0 0 (queue {} '() 0 0))
C3 = (counter 3 8 8 (queue {(ion . 8)} '() 1 0))
C1 = (counter 1 10 10 (queue {} '() 0 0))
C3 = (counter 3 10 8 (queue {(ion . 8)} '((dan . 2)) 1 1))
C1 = (counter 1 7 7 (queue {} '() 0 0))
C2 = (counter 2 0 0 (queue {} '() 0 0))
C3 = (counter 3 7 5 (queue {(ion . 8)} '((dan . 2)) 1 1))
Tema noastră-i
Gata astfel.
De vă place paradigma,
V-așteptăm la Tema Haskell!
(define Tema1 (list etapa1 etapa2 etapa3 etapa4)) (map close Tema1)