This shows you the differences between two versions of the page.
pp:25:teme:racket-ph [2025/02/27 12:12] mihaela.balint [Etapa 1] |
pp:25:teme:racket-ph [2025/03/21 12:16] (current) mihaela.balint [Racket: Heap-uri de împerechere] |
||
---|---|---|---|
Line 1: | Line 1: | ||
====== Racket: Heap-uri de împerechere ====== | ====== Racket: Heap-uri de împerechere ====== | ||
- | * Data publicării: 28.02.2025 | + | * Data publicării: 01.03.2025 |
- | * Data ultimei modificări: 28.02.2025 ([[pp:25:teme:racket-ph#changelog]]) | + | * Data ultimei modificări: 21.03.2025 ([[pp:25:teme:racket-ph#changelog]]) |
* Tema (o arhivă .zip cu toate fișierele .rkt folosite în etapa curentă) se va încărca pe [[https://vmchecker.cs.pub.ro/ui/#PP|vmchecker]] | * Tema (o arhivă .zip cu toate fișierele .rkt folosite în etapa curentă) se va încărca pe [[https://vmchecker.cs.pub.ro/ui/#PP|vmchecker]] | ||
===== Descriere generală și organizare ===== | ===== Descriere generală și organizare ===== | ||
Line 31: | Line 31: | ||
* ''(ph-subtrees ph)'': întoarce lista fiilor rădăcinii (o listă de PH-uri), sau false dacă 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: {{ :pp:25:teme:racket:merge.png?400 }} | * ''(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: {{ :pp:25:teme:racket:merge.png?400 }} | ||
- | * Un posibil apel Racket corespunzător imaginii este <code scheme> | + | * Un posibil apel Racket corespunzător imaginii este: <code scheme> |
(merge '(8 (2) (5) (7 (3))) | (merge '(8 (2) (5) (7 (3))) | ||
'(12 (6) (11 (8) (10 (4) (1) (2))))) | '(12 (6) (11 (8) (10 (4) (1) (2))))) | ||
Line 38: | Line 38: | ||
* ''(ph-insert val ph)'': inserează valoarea val în PH-ul ph - inserția este de fapt un merge între ph și un PH care conține doar valoarea val, ca în figura de mai jos: {{ :pp:25:teme:racket:insert.png?400 }} | * ''(ph-insert val ph)'': inserează valoarea val în PH-ul ph - inserția este de fapt un merge între ph și un PH care conține doar valoarea val, ca în figura de mai jos: {{ :pp:25:teme:racket:insert.png?400 }} | ||
* ''%%(list->ph lst)%%'': inserează toate valorile din lista lst, de la dreapta la stânga, într-un PH vid | * ''%%(list->ph lst)%%'': inserează toate valorile din lista lst, de la dreapta la stânga, într-un PH vid | ||
- | * ex: ''%%(list->ph '(2 5 8 7 3))%%'' | + | * ex: <code scheme> |
+ | (list->ph '(2 5 8 7 3)) | ||
+ | </code> | ||
* se inserează 3 în vid => ''%%'(3)%%'' | * se inserează 3 în vid => ''%%'(3)%%'' | ||
* se inserează 7 în rezultat => ''%%'(7 (3))%%'' | * se inserează 7 în rezultat => ''%%'(7 (3))%%'' | ||
Line 53: | Line 55: | ||
</code> | </code> | ||
* se reunesc PH-urile două câte două, de la stânga la dreapta => \\ ''%%'(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ă) | * se reunesc PH-urile două câte două, de la stânga la dreapta => \\ ''%%'(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ă) | ||
- | * se reunesc rezultatele, de la stânga la dreapta => ''%%'(14 (12 (8 (2))) (5 (4)) (11 (8)))%%'' => ''%%'(14 (11 (10 (9))) (12 (8 (2))) (5 (4)) (11 (8)))%%'' | + | * se reunesc rezultatele, de la stânga la dreapta => \\ ''%%'(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 phs)'': la fel, însă ambii pași se realizează de la dreapta la stânga | ||
- | * ex: ''%%(two-pass-merge-RL '((8 (2)) (12) (14 (11 (8))) (5 (4)) (11 (10 (9)))))%%'' | + | * ex: <code scheme> |
- | * se reunesc PH-urile două câte două, de la dreapta la stânga => ''%%'(8 (2))%%'', ''%%'(14 (12) (11 (8)))%%'', ''%%'(11 (5 (4)) (10 (9)))%%'' (prima valoare rămâne ca atare, întrucât nu are cu cine să se unească) | + | (two-pass-merge-RL '((8 (2)) |
- | * se reunesc rezultatele, de la dreapta la stânga => ''%%'(14 (11 (5 (4)) (10 (9))) (12) (11 (8)))%%'' => ''%%'(14 (8 (2)) (11 (5 (4)) (10 (9))) (12) (11 (8)))%%'' | + | (12) |
+ | (14 (11 (8))) | ||
+ | (5 (4)) | ||
+ | (11 (10 (9))))) | ||
+ | </code> | ||
+ | * se reunesc PH-urile două câte două, de la dreapta la stânga => \\ ''%%'(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)))%%'' | ||
+ | * se reunesc rezultatele, de la dreapta la stânga => \\ ''%%'(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 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 | ||
- | * ex: ''%%(tournament-merge '((8 (2)) (12) (14 (11 (8))) (5 (4)) (11 (10 (9)))))%%'' | + | * ex: <code scheme> |
- | * se reunesc PH-urile două câte două, de la stânga la dreapta => ''%%'(12 (8 (2)))%%'', ''%%'(14 (5 (4)) (11 (8)))%%'', ''%%'(11 (10 (9)))%%'' (ca la two-pass-merge-LR) | + | (tournament-merge '((8 (2)) |
- | * se reunesc rezultatele două câte două, de la stânga la dreapta => ''%%'(14 (12 (8 (2))) (5 (4)) (11 (8)))%%'', ''%%'(11 (10 (9)))%%'' | + | (12) |
- | * se reunesc rezultatele două câte două, de la stânga la dreapta => ''%%'(14 (11 (10 (9))) (12 (8 (2))) (5 (4)) (11 (8)))%%'' | + | (14 (11 (8))) |
+ | (5 (4)) | ||
+ | (11 (10 (9))))) | ||
+ | </code> | ||
+ | * se reunesc PH-urile două câte două, de la stânga la dreapta => \\ ''%%'(12 (8 (2)))%%'' \\ ''%%'(14 (5 (4)) (11 (8)))%%'' \\ ''%%'(11 (10 (9)))%%'' (ca la two-pass-merge-LR) | ||
+ | * se reunesc rezultatele două câte două, de la stânga la dreapta => \\ ''%%'(14 (12 (8 (2))) (5 (4)) (11 (8)))%%'' \\ ''%%'(11 (10 (9)))%%'' | ||
+ | * se reunesc rezultatele două câte două, de la stânga la dreapta => \\ ''%%'(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ă) | * ''(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ă) | ||
Line 71: | Line 85: | ||
==== Depunctări generate de nerespectarea cerințelor din enunț ==== | ==== Depunctări generate de nerespectarea cerințelor din enunț ==== | ||
- | Unele exerciții vă cer să folosiți un anumit tip de recursivitate. Fiecare restricție încălcată atrage după sine o depunctare de 10p. | + | * Unele exerciții vă cer să folosiți un anumit tip de recursivitate. Fiecare restricție încălcată atrage după sine o depunctare de 10p. Când funcția testată de checker apelează funcții ajutătoare, atunci restricțiile se răsfrâng asupra funcțiilor ajutătoare. |
+ | * Folosiți interfața (constructorii și operatorii) tipului PH, nu funcții echivalente pentru liste care fac codul mai puțin lizibil și greu de modificat în cazul în care modalitatea de implementare a PH-urilor se schimbă. Deși încălcarea acestei reguli nu se depunctează, este un aspect important pe care îl vom dezvolta în capitolul despre abstractizare. | ||
+ | |||
+ | ===== Etapa 2 ===== | ||
+ | |||
+ | Î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. | ||
+ | * Cum criteriul de ordonare este folosit doar în funcția ''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-uri | ||
+ | * ''merge-min'' - pentru min-PH-uri | ||
+ | * alte variante de ''merge'' cerute explicit în schelet sau pe post de funcții ajutătoare pentru alte funcții | ||
+ | * Cum funcția ''merge'' 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'' | ||
+ | * ex: <code scheme> | ||
+ | (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)) | ||
+ | </code> | ||
+ | * se adaugă informația 'seen în câmpul ''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'' | ||
+ | * ex: <code scheme> | ||
+ | (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)))) | ||
+ | </code> | ||
+ | * => ''%%'(a c)%%'' (întrucât se cere o listă de nume, nu o listă de filme) | ||
+ | * observați că informația '''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'') | ||
+ | * ex: <code scheme> | ||
+ | (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)))) | ||
+ | </code> | ||
+ | * => ''%%'(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 | ||
+ | * ex: <code scheme> | ||
+ | (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)))) | ||
+ | </code> | ||
+ | * => ''%%'((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 | ||
+ | * ordinea de inserare a valorilor în PH este de la dreapta listei către stânga | ||
+ | * ex: <code scheme> | ||
+ | (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)))) | ||
+ | </code> | ||
+ | * se inserează '''e'' => \\ ''%%'((e . 8.1))%%'' | ||
+ | * se inserează '''d'', care devine fiul lui '''e'', pentru că are rating mai mic => \\ ''%%'((e . 8.1) ((d . 8.0)))%%'' | ||
+ | * se inserează '''c'', care devine părintele lui '''e'', pentru că are rating mai mare => \\ ''%%'((c . 8.8) ((e . 8.1) ((d . 8.0))))%%'' | ||
+ | * se inserează '''b'', care devine fiul lui '''c'', pentru că are rating mai mic => \\ ''%%'((c . 8.8) ((b . 8.2)) ((e . 8.1) ((d . 8.0))))%%'' | ||
+ | * se inserează '''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 | ||
+ | * nu este necesar ca a și b să apară în lista L | ||
+ | * dacă apare doar a, rezultatul este true | ||
+ | * dacă apare doar b sau nu apare niciuna (și nici nu sunt egale), rezultatul este false | ||
+ | * ''(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'' | ||
+ | * ca de obicei, inserarea se face de la dreapta la stânga | ||
+ | * ex: <code scheme> | ||
+ | (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)) | ||
+ | </code> | ||
+ | * se inserează '''e'' | ||
+ | * se inserează '''d'', care devine fiul lui '''e'', întrucât '''action'' apare în lista de preferințe iar '''thriller'' nu | ||
+ | * se inserează '''c'', care devine părintele lui '''e'', întrucât '''drama'' precede pe '''action'' | ||
+ | * se inserează '''b'', care devine fiul lui '''c'', întrucât '''drama'' precede pe '''comedy'' | ||
+ | * se inserează '''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'') | ||
+ | * => <code scheme> | ||
+ | (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) '())))) | ||
+ | </code> | ||
+ | |||
+ | 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: | ||
+ | * funcționale (anumite sarcini impun lucrul cu funcționale în locul utilizării recursivității explicite) | ||
+ | * funcții anonime (deși aveți libertate cu privire la utilizarea lor, vă recomandăm să dați funcții anonime ca parametri pentru funcționale atunci când funcțiile respective nu mai sunt necesare altundeva) | ||
+ | * funcții curry și uncurry (veți folosi mecanismul de curry-ing pentru a deriva "cu ușurință" diverse tipuri de merge dintr-o funcție mai generală) | ||
+ | |||
+ | ==== Depunctări generate de nerespectarea cerințelor din enunț ==== | ||
+ | Baremul depunctărilor posibile în etapa 2 este: | ||
+ | * -5p*n: unde n = numărul de funcții dintre ''merge-max'', ''merge-min'', ''merge-max-rating'' care nu sunt definite point-free prin aplicația parțială a lui ''merge-f'' | ||
+ | * -10p*n: unde n = numărul de funcții dintre ''%%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 | ||
+ | |||
+ | ===== Etapa 3 ===== | ||
+ | |||
+ | În această etapă veți implementa două aplicații ale heap-urilor de împerechere: | ||
+ | * extragerea primelor k elemente dintr-o listă, conform unui anumit criteriu de sortare | ||
+ | * interclasarea valorilor conținute în mai multe PH-uri | ||
+ | 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**: | ||
+ | * let sau let* - pentru evitarea calculelor duplicate | ||
+ | * named let - pentru implementarea ad-hoc a proceselor recursive, fără a apela la funcții ajutătoare | ||
+ | |||
+ | 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'' | ||
+ | * rezultatul trebuie obținut folosind un PH construit pe baza listei ''movies'' și a criteriului ''op'' | ||
+ | * odată inițializat acest PH, lista finală rezultă din ''k'' extrageri succesive ale rădăcinii sale | ||
+ | * funcția ''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 punctate | ||
+ | * ''best-k-rating'' - determină cele mai bune ''k'' filme dintr-o listă, din punct de vedere al rating-ului | ||
+ | * ex: <code scheme> | ||
+ | (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) | ||
+ | </code> | ||
+ | * => <code scheme> | ||
+ | (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))) | ||
+ | </code> | ||
+ | * ''best-k-duration'' - determină cele mai scurte ''k'' filme dintr-o listă | ||
+ | * ex: <code scheme> | ||
+ | (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) | ||
+ | </code> | ||
+ | * => <code scheme> | ||
+ | (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))) | ||
+ | </code> | ||
+ | * ''(update-pairs p pairs)'' - șterge rădăcina PH-ului primei perechi care satisface predicatul ''p'' | ||
+ | * fiecare pereche din ''pairs'' conține un nume de film și un max-PH de rating-uri acordate filmului de diverși utilizatori | ||
+ | * dacă nu sunt stocate rating-uri pentru filmul din perechea vizată (PH-ul asociat este vid), atunci se întoarce lista ''pairs'' nemodificată | ||
+ | * dacă nicio pereche nu satisface predicatul ''p'', se întoarce lista ''pairs'' nemodificată | ||
+ | * ex: <code scheme> | ||
+ | (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))))) | ||
+ | </code> | ||
+ | * toate perechile satisfac predicatul, așadar se va șterge rădăcina PH-ului primei perechi | ||
+ | * întrucât perechile au pe a doua poziție un PH, adică o listă, ele nu se afișează ca perechi cu punct, ci ca liste care au pe prima poziție numele filmului, iar restul listei corespunde PH-ului | ||
+ | * => <code scheme> | ||
+ | '((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)))) | ||
+ | </code> | ||
+ | * ''(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)) | ||
+ | * rezultatul este o listă de perechi (nume-film . rating) și se obține prin interclasarea PH-urilor corespunzătoare fiecărui film, astfel: | ||
+ | * se inițializează un nou PH cu perechi (nume-film . rating) corespunzătoare rădăcinilor fiecărui PH din ''pairs'' - îl vom numi PH-ul de rădăcini | ||
+ | * invariant: rădăcina PH-ului de rădăcini corespunde celei mai bune recenzii per total | ||
+ | * la fiecare iterație, se extrage această rădăcină și se aduce în PH-ul de rădăcini următorul cel mai bun rating corespunzător filmului care tocmai a fost extras (pentru a menține invariantul) | ||
+ | * ex: <code scheme> | ||
+ | (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) | ||
+ | </code> | ||
+ | * se inițializează PH-ul de rădăcini cu perechile '(a . 10), '(b . 10), '(c . 10) => \\ una dintre ele devine rădăcină, de exemplu '(c . 10) | ||
+ | * în urma extragerii lui '(c . 10), se aduce un alt '(c . 10) în locul acestuia (întrucât mai există un rating 10 pentru filmul c) => \\ avem aceleași valori în PH-ul de rădăcini, iar noua rădăcină este, de exemplu, '(a . 10) | ||
+ | * se extrage '(a . 10), în locul său fiind adus un nou '(a . 10), iar noua rădăcină este '(c . 10) | ||
+ | * se extrage '(c . 10), în locul său fiind adus un '(c . 9), iar noua rădăcină este '(a . 10) | ||
+ | * se extrage '(a . 10), în locul său fiind adus un '(a . 9), iar noua rădăcină este '(b . 10) | ||
+ | * se extrage '(b . 10), în locul său fiind adus un '(b . 9), iar noua rădăcină este '(a . 9) | ||
+ | * se extrage '(a . 9), în locul său fiind adus un nou '(a . 9), iar noua rădăcină este '(b . 9) | ||
+ | * extragerea lui '(b . 9) este a șaptea extragere => \\ ''%%'((c . 10) (a . 10) (c . 10) (a . 10) (b . 10) (a . 9) (b . 9))%%'' | ||
+ | * Observație: checker-ul acceptă și alte rezultate corecte (aceleași rating-uri dar în pereche cu alte filme, cât timp perechile respective corespund unor recenzii din input) | ||
+ | |||
+ | ==== Depunctări generate de nerespectarea cerințelor din enunț ==== | ||
+ | Baremul depunctărilor posibile în etapa 3 este: | ||
+ | * -5p*n: unde n = numărul de funcții dintre ''best-k-rating'', ''best-k-duration'' care nu sunt definite ca aplicații ale lui ''best-k'' | ||
+ | * -20p*n: unde n = numărul de funcții dintre ''best-k'', ''update-pairs'', ''best-k-ratings-overall'' rezolvate fără a folosi named let conform enunțului | ||
+ | * -0p: Vă încurajăm să folosiți let și let* pentru a evita calculele duplicate, chiar dacă nu există depunctări referitoare la acest aspect. | ||
+ | |||
+ | ===== Etapa 4 ===== | ||
+ | |||
+ | Câ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: | ||
+ | * un max-PH care reține jumătatea cu cele mai mici rating-uri | ||
+ | * un min-PH care reține jumătatea cu cele mai mari rating-uri | ||
+ | * cele două PH-uri trebuie să rămână echilibrate: în cel mai nebalansat caz, max-PH va conține o valoare mai mult decât min-PH | ||
+ | * fiecare nouă recenzie implică inserarea noului rating în PH-ul aferent (cel cu rating-uri mai mari sau mai mici, după caz) și reechilibrarea celor două PH-uri | ||
+ | * pentru a implementa cu ușurință acest proces, la un anumit moment t unui film îi va corespunde un cvartet ''(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) | ||
+ | * mediana va fi: | ||
+ | * media rădăcinilor celor 2 PH-uri, pentru număr par de recenzii | ||
+ | * rădăcina lui max-PH, pentru număr impar de recenzii | ||
+ | |||
+ | Î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**: | ||
+ | * Recenziile care intră în sistem sunt modelate ca flux | ||
+ | * Stadiile de evoluție ale medianelor sunt modelate ca flux | ||
+ | * Fiecare nouă recenzie corespunde unui moment de timp t, iar pentru fiecare moment de timp t vom depune un "stadiu" în fluxul rezultat - informația despre cât erau medianele filmelor la momentul t | ||
+ | Este important sa discerneți între situațiile în care operați cu liste și cele în care operați cu fluxuri: | ||
+ | * Intrările și ieșirile algoritmului sunt fluxuri - reflectă o evoluție temporală potențial infinită | ||
+ | * Fiecare element din fluxul rezultat este o listă - este necesară o listă pentru că interesează "starea" tuturor filmelor la un anumit moment t | ||
+ | |||
+ | 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 | ||
+ | * ex: <code scheme> | ||
+ | (add-rating '(a 0 (4) (7)) 5) | ||
+ | </code> | ||
+ | * întrucât 5 > 4 (rating > root(max-ph)), 4 este inserat în min-ph => \\ ''%%'(a -1 (4) (5 (7)))%%'' | ||
+ | * întrucât delta < 0, root(min-ph) este mutat în max-ph => \\ ''%%'(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) | ||
+ | * ex: <code scheme> | ||
+ | (reviews->quads (stream '(a . 4) '(b . 1) '(a . 2))) | ||
+ | </code> | ||
+ | * la început nu avem informații despre niciun film | ||
+ | * la momentul t = 1, avem de adăugat rating-ul ''4'' pentru filmul '''a'' => \\ se creează un cvartet pentru filmul '''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) | ||
+ | * la momentul t = 2, avem de adăugat rating-ul ''1'' pentru filmul '''b'' => \\ se creează un cvartet pentru filmul '''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) | ||
+ | * la momentul t = 3, avem de adăugat rating-ul ''2'' pentru filmul '''a'' => \\ se actualizează cvartetul pentru filmul '''a'' prin adăugarea rating-ului ''2'' => \\ ''%%'((b 1 (1) ()) (a 0 (2) (4)))%%'' este al treilea stadiu de evoluție | ||
+ | * rezultatul apelului este un flux care conține cele 3 liste de mai sus (cele 3 stadii) | ||
+ | * ''%%(quads->medians quads)%%'' - transformă un flux de stadii reprezentate folosind cvartete într-un flux de stadii reprezentate folosind mediane | ||
+ | * ex: <code scheme> | ||
+ | (quads->medians (reviews->quads (stream '(a . 4) '(b . 1) '(a . 2)))) | ||
+ | </code> | ||
+ | * lista de cvartete ''%%'((a 1 (4) ()))%%'' devine lista de perechi ''%%'((a . 4))%%'' \\ (pentru filmul '''a'', mediana coincide cu singurul rating primit) | ||
+ | * lista de cvartete ''%%'((b 1 (1) ()) (a 1 (4) ()))%%'' devine lista de perechi ''%%'((b . 1) (a . 4))%%'' | ||
+ | * lista de cvartete ''%%'((b 1 (1) ()) (a 0 (2) (4)))%%'' devine lista de perechi ''%%'((b . 1) (a . 3))%%'' \\ (pentru '''a'', mediana este media celor 2 rating-uri, conform formulei pentru număr par de rating-uri) | ||
+ | * rezultatul apelului este un flux care conține cele 3 liste de perechi de mai sus | ||
+ | |||
+ | ==== Depunctări generate de nerespectarea cerințelor din enunț ==== | ||
+ | Baremul depunctărilor posibile în etapa 4 este: | ||
+ | * -20p: dacă faceți conversii din liste în fluxuri sau din fluxuri în liste, în loc să operați direct cu interfața pentru fluxuri (când creați/manipulați fluxuri) și cu interfața pentru liste (când creați/manipulați liste) | ||
+ | * -20p: dacă funcția ''%%quads->medians%%'' folosește recursivitate explicită sau nu folosește o funcțională pe fluxuri | ||
===== Precizări ===== | ===== Precizări ===== | ||
Line 82: | Line 354: | ||
* Pentru fiecare etapă, checker-ul vă oferă un punctaj între 0 și 120 de puncte. Nota 100 corespunde punctajului maxim pe etapă, iar orice surplus se transformă într-un bonus proporțional. | * Pentru fiecare etapă, checker-ul vă oferă un punctaj între 0 și 120 de puncte. Nota 100 corespunde punctajului maxim pe etapă, iar orice surplus se transformă într-un bonus proporțional. | ||
* Veți prezenta tema asistentului, care poate modifica punctajul dat de checker dacă observă nereguli precum răspunsuri hardcodate, proceduri cu efecte laterale, implementări neconforme cu restricțiile din enunț. | * Veți prezenta tema asistentului, care poate modifica punctajul dat de checker dacă observă nereguli precum răspunsuri hardcodate, proceduri cu efecte laterale, implementări neconforme cu restricțiile din enunț. | ||
+ | * Temele implementează o politică de **zero toleranță pentru copiere**. Cât timp respectați politica de colaborare și utilizare a internetului descrisă în regulamentul materiei, nu aveți motive de îngrijorare. Contăm pe voi să nu recurgeți la mijloace nepermise, și vă stăm la dispoziție pentru orice neclaritate. | ||
===== Resurse ===== | ===== Resurse ===== | ||
+ | |||
+ | * {{:pp:25:teme:racket:etapa1.zip|etapa 1}} | ||
+ | * {{:pp:25:teme:racket:etapa2.zip|etapa 2}} | ||
+ | * {{:pp:25:teme:racket:etapa3.zip|etapa 3}} | ||
+ | * {{:pp:25:teme:racket:etapa4.zip|etapa 4}} | ||
/* | /* | ||
Line 93: | Line 371: | ||
===== Changelog ===== | ===== Changelog ===== | ||
- | * 28.02 (ora 20:45) - Am publicat etapa 1. | + | * 21.03 (ora 12:16) - Am publicat etapa 4. |
+ | * 15.03 (ora 08:45) - Am publicat etapa 3. | ||
+ | * 07.03 (ora 15:15) - Am publicat etapa 2. | ||
+ | * 01.03 (ora 01:45) - Am publicat etapa 1. | ||
===== Referinţe ===== | ===== Referinţe ===== |