Tema constă în definirea și utilizarea heap-urilor de împerechere și 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 14.03, 21.03, 28.03, 04.04).
Rezolvările tuturor etapelor pot fi trimise până în ziua laboratorului 6, dar orice exercițiu trimis după deadline (și până în ziua laboratorului 6) se punctează cu jumătate din punctaj. Orice exercițiu trimis după ziua laboratorului 6 nu se mai punctează deloc. Nota finală pe etapă 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 dezvolta 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 heap-urile de împerechere (pe care le vom numi PH, conform prescurtării termenului englez “pairing heap”) și să vină cu exemple de rulare a funcțiilor mai complexe din schelet. Dacă preferați, puteți rezolva tema utilizând doar indicațiile din schelet.
În prima etapă vă veți familiariza cu structura și reprezentarea heap-urilor de împerechere (PH) în Racket, și veți implementa un max-heap de împerechere împreună cu operațiile sale uzuale.
Un PH este un heap n
-ar eficient și simplu de implementat. În Racket, vom reprezenta un PH vid ca pe o listă vidă, iar un PH nevid ca pe o listă de n
elemente, primul element fiind valoarea din rădăcină, iar următoarele n-1
elemente fiind PH-uri - fiii nodului rădăcină. Veți implementa următorii constructori și operatori ai acestui tip:
empty-ph
: creează un PH vid(val->ph val)
: creează un PH care este un singur nod cu valoarea val(ph-empty? ph)
: verifică dacă un PH este vid(ph-root ph)
: întoarce valoarea din rădăcină, sau false dacă nu există o asemenea valoare întrucât argumentul este un PH vid(ph-subtrees ph)
: întoarce lista fiilor rădăcinii (o listă de PH-uri), sau false dacă argumentul este un PH vid(merge ph1 ph2)
: reunește PH-urile ph1 și ph2 - în cazul unui max-PH, aceasta înseamnă că PH-ul cu rădăcina mai mică devine cel mai din stânga fiu al celui cu rădăcina mai mare, ca în figura de mai jos: (merge '(8 (2) (5) (7 (3))) '(12 (6) (11 (8) (10 (4) (1) (2)))))
12 > 8
, primul arbore devine cel mai din stânga fiu al celui de-al doilea, producând rezultatul '(12 (8 (2) (5) (7 (3))) (6) (11 (8) (10 (4) (1) (2))))
(list->ph lst)
: inserează toate valorile din lista lst, de la dreapta la stânga, într-un PH vid(list->ph '(2 5 8 7 3))
'(3)
'(7 (3))
'(8 (7 (3)))
'(8 (5) (7 (3)))
'(8 (2) (5) (7 (3)))
(two-pass-merge-LR phs)
: reunește toate PH-urile din lista phs, conform unui protocol în 2 pași, ca în figura de mai jos: (two-pass-merge-LR '((8 (2)) (12) (14 (11 (8))) (5 (4)) (11 (10 (9)))))
'(12 (8 (2)))
'(14 (5 (4)) (11 (8)))
'(11 (10 (9)))
(ultima valoare rămâne ca atare, întrucât nu are cu cine să se unească)'(14 (12 (8 (2))) (5 (4)) (11 (8)))
⇒ '(14 (11 (10 (9))) (12 (8 (2))) (5 (4)) (11 (8)))
(two-pass-merge-RL phs)
: la fel, însă ambii pași se realizează de la dreapta la stânga(two-pass-merge-RL '((8 (2)) (12) (14 (11 (8))) (5 (4)) (11 (10 (9)))))
'(8 (2))
(prima valoare rămâne ca atare, întrucât nu are cu cine să se unească) '(14 (12) (11 (8)))
'(11 (5 (4)) (10 (9)))
'(14 (11 (5 (4)) (10 (9))) (12) (11 (8)))
⇒ '(14 (8 (2)) (11 (5 (4)) (10 (9))) (12) (11 (8)))
(tournament-merge phs)
: reprezintă un alt protocol de reuniune a PH-urilor din lista phs - se realizează primul pas de la two-pass-merge-LR asupra listei phs, apoi din nou asupra listei rezultate anterior, și tot așa până rămâne un singur PH(tournament-merge '((8 (2)) (12) (14 (11 (8))) (5 (4)) (11 (10 (9)))))
'(12 (8 (2)))
'(14 (5 (4)) (11 (8)))
'(11 (10 (9)))
(ca la two-pass-merge-LR)'(14 (12 (8 (2))) (5 (4)) (11 (8)))
'(11 (10 (9)))
'(14 (11 (10 (9))) (12 (8 (2))) (5 (4)) (11 (8)))
(ph-del-root ph)
: întoarce PH-ul obținut în urma ștergerii rădăcinii lui ph - fiii nodului șters sunt reuniți prin two-pass-merge-LR (vezi figura anterioară)În rezolvare, veți exersa lucrul cu:
În această etapă veți abstractiza operatorii tipului PH astfel încât aceștia să poată manipula min-PH-uri, max-PH-uri, și în general PH-uri bazate pe orice relație de ordine între valorile stocate în noduri.
merge
, veți defini o funcție mai generală merge-f
care compară rădăcinile a două PH-uri după un criteriu primit ca parametru. Din această funcție veți deriva tipuri particulare de merge:merge-max
- pentru max-PH-urimerge-min
- pentru min-PH-urimerge
cerute explicit în schelet sau pe post de funcții ajutătoare pentru alte funcțiimerge
este apelată direct sau indirect de alți operatori ai tipului PH, și aceștia vor trebui abstractizați - adică modificați să primească tipul de merge
ca parametru.
În etapele următoare veți folosi heap-uri de împerechere pentru a prelucra filme. De aceea, în partea a doua a acestei etape definim structura movie
, care stochează informații despre un film în 5 câmpuri intitulate name
, rating
, genre
, duration
, others
. Fișierul tutorial.rkt vă oferă exemple de definire și manipulare a structurilor.
Aveți apoi de implementat o serie de funcții dedicate filmelor:
(lst->movie lst)
- constructor al structurii movie
care primește ca parametru o listă cu 5 valori, nu 5 valori separate(mark-as-seen m)
- adaugă informația 'seen la începutul câmpului (listei) others
a filmului m
(mark-as-seen-from-list movies seen)
- în lista de filme movies
, marchează ca văzute filmele din lista de nume seen
(mark-as-seen-from-list (list (make-movie 'a 9.3 'drama '(2 12) '()) (make-movie 'b 8.2 'comedy '(1 56) '(feel-good)) (make-movie 'c 8.8 'drama '(1 44) '(old)) (make-movie 'd 8.0 'thriller '(2 25) '()) (make-movie 'e 8.1 'action '(2 19) '(sequel))) '(a c))
others
al filmelor 'a și 'c ⇒ '((movie 'a 9.3 'drama '(2 12) '(seen))
(movie 'b 8.2 'comedy '(1 56) '(feel-good))
(movie 'c 8.8 'drama '(1 44) '(seen old))
(movie 'd 8.0 'thriller '(2 25) '())
(movie 'e 8.1 'action '(2 19) '(sequel)))
(extract-seen movies)
- extrage numele filmelor văzute din lista de filme movies
(extract-seen (list (make-movie 'a 9.3 'drama '(2 12) '(seen)) (make-movie 'b 8.2 'comedy '(1 56) '(feel-good)) (make-movie 'c 8.8 'drama '(1 44) '(legal seen old)) (make-movie 'd 8.0 'thriller '(2 25) '()) (make-movie 'e 8.1 'action '(2 19) '(sequel))))
'(a c)
(întrucât se cere o listă de nume, nu o listă de filme)'seen
poate apărea pe orice poziție în câmpul others
(rating-stats movies)
- calculează o pereche cu rating-ul mediu al filmelor văzute, respectiv al celor nevăzute (prin convenție, dacă nu există filme de un anumit fel, rating-ul mediu al acestora este 0
)(rating-stats (list (make-movie 'a 9.3 'drama '(2 12) '(seen)) (make-movie 'b 8.2 'comedy '(1 56) '(feel-good)) (make-movie 'c 8.8 'drama '(1 44) '(legal seen old)) (make-movie 'd 8.0 'thriller '(2 25) '()) (make-movie 'e 8.1 'action '(2 19) '(sequel))))
'(9.05 . 8.1)
(observați că rezultatul este o pereche cu punct, nu o listă)(extract-name-rating movies)
- transformă o listă de filme într-o listă de perechi între numele și rating-ul filmului, renunțând la celelalte informații(extract-name-rating (list (make-movie 'a 9.3 'drama '(2 12) '(seen)) (make-movie 'b 8.2 'comedy '(1 56) '(feel-good)) (make-movie 'c 8.8 'drama '(1 44) '(legal seen old)) (make-movie 'd 8.0 'thriller '(2 25) '()) (make-movie 'e 8.1 'action '(2 19) '(sequel))))
'((a . 9.3) (b . 8.2) (c . 8.8) (d . 8.0) (e . 8.1))
(make-rating-ph movies)
- construiește un max-PH care conține perechi nume-rating corespunzătoare filmelor din lista movies
, ordonate după rating(make-rating-ph (list (make-movie 'a 9.3 'drama '(2 12) '(seen)) (make-movie 'b 8.2 'comedy '(1 56) '(feel-good)) (make-movie 'c 8.8 'drama '(1 44) '(legal seen old)) (make-movie 'd 8.0 'thriller '(2 25) '()) (make-movie 'e 8.1 'action '(2 19) '(sequel))))
'e
⇒ '((e . 8.1))
'd
, care devine fiul lui 'e
, pentru că are rating mai mic ⇒ '((e . 8.1) ((d . 8.0)))
'c
, care devine părintele lui 'e
, pentru că are rating mai mare ⇒ '((c . 8.8) ((e . 8.1) ((d . 8.0))))
'b
, care devine fiul lui 'c
, pentru că are rating mai mic ⇒ '((c . 8.8) ((b . 8.2)) ((e . 8.1) ((d . 8.0))))
'a
, care devine părintele lui 'c
, pentru că are rating mai mare ⇒ '((a . 9.3) ((c . 8.8) ((b . 8.2)) ((e . 8.1) ((d . 8.0)))))
(before? a b L)
- întoarce true dacă și numai dacă a = b sau a apare înaintea lui b în lista L(make-genre-ph movies genres)
- construiește un PH care conține filme din lista movies
, ordonate după preferințele exprimate în lista de genuri genres
- genul unui nod copil nu poate să apară înaintea genului nodului părinte în lista genres
(make-genre-ph (list (make-movie 'a 9.3 'drama '(2 12) '(seen)) (make-movie 'b 8.2 'comedy '(1 56) '(feel-good)) (make-movie 'c 8.8 'drama '(1 44) '(legal seen old)) (make-movie 'd 8.0 'thriller '(2 25) '()) (make-movie 'e 8.1 'action '(2 19) '(sequel))) '(drama comedy action))
'e
'd
, care devine fiul lui 'e
, întrucât 'action
apare în lista de preferințe iar 'thriller
nu'c
, care devine părintele lui 'e
, întrucât 'drama
precede pe 'action
'b
, care devine fiul lui 'c
, întrucât 'drama
precede pe 'comedy
'a
, care devine fiul lui 'c
, întrucât inserția este un merge între rezultatul anterior și noua valoare, iar la valori egale (ambele filme au genul 'drama
) funcția before?
întoarce true, preferând primul root (același principiu pe care îl folosea și funcția merge
)(list (movie 'c 8.8 'drama '(1 44) '(legal seen old)) (list (movie 'a 9.3 'drama '(2 12) '(seen))) (list (movie 'b 8.2 'comedy '(1 56) '(feel-good))) (list (movie 'e 8.1 'action '(2 19) '(sequel)) (list (movie 'd 8.0 'thriller '(2 25) '()))))
Exercițiile valorifică faptul că, în programarea funcțională, funcțiile sunt valori de ordinul întâi. Scopul etapei este consolidarea cunoștințelor legate de:
Baremul depunctărilor posibile în etapa 2 este:
merge-max
, merge-min
, merge-max-rating
care nu sunt definite point-free prin aplicația parțială a lui merge-f
lst->movie
, mark-as-seen-from-list
, extract-seen
, rating-stats
, extract-name-rating
, before?
rezolvate fără a folosi funcționale (conform cerinței) în locul recursivității expliciteÎn această etapă veți implementa două aplicații ale heap-urilor de împerechere:
Mai precis, veți implementa particularizări ale celor două aplicații dedicate filmelor, așa cum au fost acestea definite în etapa 2. Observați că la începutul fișierului etapa3.rkt apare linia (require “etapa2.rkt”)
, ceea ce înseamnă că va fi necesar să aduceți rezolvarea etapei 2 în același folder în care rezolvați etapa 3, pentru a beneficia de funcțiile implementate anterior.
Scopul etapei este lucrul cu expresii de legare statică a variabilelor:
Aveți de implementat următoarele funcții:
best-k
- o funcție care primește un criteriu de comparație op
, o listă de filme movies
și un număr k
, într-o ordine și grupare definite de voi (de aceea nu am specificat antetul funcției), și determină primele k
filme din movies
conform criteriului op
movies
și a criteriului op
k
extrageri succesive ale rădăcinii sale best-k
nu este punctată de checker, însă pe baza ei trebuie obținute funcțiile best-k-rating
și best-k-duration
, care sunt punctatebest-k-rating
- determină cele mai bune k
filme dintr-o listă, din punct de vedere al rating-ului(best-k-rating (list (make-movie 'a 9.3 'drama '(2 12) '(seen)) (make-movie 'b 8.2 'comedy '(1 56) '(feel-good)) (make-movie 'c 8.8 'drama '(1 44) '(legal seen old)) (make-movie 'd 8.0 'thriller '(2 25) '()) (make-movie 'e 8.1 'action '(2 19) '(sequel))) 4)
(list (movie 'a 9.3 'drama '(2 12) '(seen)) (movie 'c 8.8 'drama '(1 44) '(legal seen old)) (movie 'b 8.2 'comedy '(1 56) '(feel-good)) (movie 'e 8.1 'action '(2 19) '(sequel)))
best-k-duration
- determină cele mai scurte k
filme dintr-o listă(best-k-duration (list (make-movie 'a 9.3 'drama '(2 12) '(seen)) (make-movie 'b 8.2 'comedy '(1 56) '(feel-good)) (make-movie 'c 8.8 'drama '(1 44) '(legal seen old)) (make-movie 'd 8.0 'thriller '(2 25) '()) (make-movie 'e 8.1 'action '(2 19) '(sequel))) 3)
(list (movie 'c 8.8 'drama '(1 44) '(legal seen old)) (movie 'b 8.2 'comedy '(1 56) '(feel-good)) (movie 'a 9.3 'drama '(2 12) '(seen)))
(update-pairs p pairs)
- șterge rădăcina PH-ului primei perechi care satisface predicatul p
pairs
conține un nume de film și un max-PH de rating-uri acordate filmului de diverși utilizatoripairs
nemodificatăp
, se întoarce lista pairs
nemodificată(update-pairs (λ (p) #t) '((a 10 (9) (10 (8) (9 (7) (9 (8))))) (b 10 (9) (6) (8) (9) (8) (7 (7))) (c 10 (8) (10 (8) (7) (9) (8) (6)))))
'((a 10 (9) (8) (9 (7) (9 (8)))) ; aici a fost ștearsă rădăcina 10 (b 10 (9) (6) (8) (9) (8) (7 (7))) (c 10 (8) (10 (8) (7) (9) (8) (6))))
(best-k-ratings-overall pairs k)
- determină cele mai bune k
recenzii (în pereche cu filmul căruia i-au fost acordate), pe baza unei liste pairs
de tipul celei de mai sus (perechi (nume-film . PH-cu-rating-uri-film))pairs
- îl vom numi PH-ul de rădăcini(best-k-ratings-overall '((a 10 (9) (10 (8) (9 (7) (9 (8))))) (b 10 (9) (6) (8) (9) (8) (7 (7))) (c 10 (8) (10 (8) (7) (9) (8) (6)))) 7)
'((c . 10) (a . 10) (c . 10) (a . 10) (b . 10) (a . 9) (b . 9))
Baremul depunctărilor posibile în etapa 3 este:
best-k-rating
, best-k-duration
care nu sunt definite ca aplicații ale lui best-k
best-k
, update-pairs
, best-k-ratings-overall
rezolvate fără a folosi named let conform enunțuluiCând interesează percepția “tipică” a utilizatorilor asupra unui film, mediana rating-urilor poate fi mai relevantă decât media, cu precădere când lista de rating-uri conține valori extreme, care pot distorsiona semnificativ media. Întrucât rating-urile pentru un film sosesc în flux continuu, pe măsură ce filmul înregistrează noi vizionări, și valoarea medianei trebuie actualizată frecvent. În loc să recalculăm această valoare de fiecare dată când filmul primește o nouă recenzie, ea poate fi menținută în mod dinamic, cu ajutorul a două heap-uri de împerechere:
(nume-film delta max-ph min-ph)
, unde delta
reprezintă diferența dintre dimensiunea lui max-ph
și dimensiunea lui min-ph
(0 sau 1)În etapa 4 veți implementa acest algoritm pentru calculul dinamic al medianei, pe baza unui flux de recenzii modelat ca flux Racket.
Noutatea etapei constă în lucrul cu fluxuri:
Este important sa discerneți între situațiile în care operați cu liste și cele în care operați cu fluxuri:
Aveți de implementat următoarele funcții:
(add-rating quad rating)
- primește un cvartet tip (nume-film delta max-ph min-ph)
și un rating, și întoarce cvartetul actualizat prin inserarea rating-ului în PH-ul aferent și reechilibrarea PH-urilor(add-rating '(a 0 (4) (7)) 5)
'(a -1 (4) (5 (7)))
'(a 1 (5 (4)) (7))
(reviews->quads reviews)
- transformă un flux de recenzii (recenzie = pereche (nume-film . rating)) într-un flux de stadii (stadiu = listă de cvartete: câte un cvartet pentru fiecare film care a primit recenzii până în acest moment)(reviews->quads (stream '(a . 4) '(b . 1) '(a . 2)))
4
pentru filmul 'a
⇒ 'a
, în care doar max-PH-ul conține valoarea 4
⇒ '((a 1 (4) ()))
este primul stadiu de evoluție (o listă care conține un singur cvartet)1
pentru filmul 'b
⇒ 'b
, în care doar max-PH-ul conține valoarea 1
⇒ '((b 1 (1) ()) (a 1 (4) ()))
este al doilea stadiu de evoluție (o listă cu două cvartete)2
pentru filmul 'a
⇒ 'a
prin adăugarea rating-ului 2
⇒ '((b 1 (1) ()) (a 0 (2) (4)))
este al treilea stadiu de evoluție (quads->medians quads)
- transformă un flux de stadii reprezentate folosind cvartete într-un flux de stadii reprezentate folosind mediane(quads->medians (reviews->quads (stream '(a . 4) '(b . 1) '(a . 2))))
'((a 1 (4) ()))
devine lista de perechi '((a . 4))
'a
, mediana coincide cu singurul rating primit)'((b 1 (1) ()) (a 1 (4) ()))
devine lista de perechi '((b . 1) (a . 4))
'((b 1 (1) ()) (a 0 (2) (4)))
devine lista de perechi '((b . 1) (a . 3))
'a
, mediana este media celor 2 rating-uri, conform formulei pentru număr par de rating-uri)Baremul depunctărilor posibile în etapa 4 este:
quads->medians
folosește recursivitate explicită sau nu folosește o funcțională pe fluxuri