This is an old revision of the document!


Racket: Heap-uri de împerechere

  • Data publicării: 01.03.2025
  • Data ultimei modificări: 01.03.2025 (changelog)
  • Tema (o arhivă .zip cu toate fișierele .rkt folosite în etapa curentă) se va încărca pe vmchecker

Descriere generală și organizare

Tema constă în definirea și utilizarea heap-urilor de împerechere și este împărțită în 4 etape:

  • una pe care o veți rezolva după laboratorul 2 (cu deadline în ziua laboratorului 3, la ora 23:59)
  • una pe care o veți rezolva după laboratorul 3 (cu deadline în ziua laboratorului 4, la ora 23:59)
  • una pe care o veți rezolva după laboratorul 4 (cu deadline în ziua laboratorului 5, la ora 23:59)
  • una pe care o veți rezolva după laboratorul 5 (cu deadline în ziua laboratorului 6, la ora 23:59)

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.

Etapa 1

Î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:
    • Un posibil apel Racket corespunzător imaginii este:
      (merge '(8 (2) (5) (7 (3)))
             '(12 (6) (11 (8) (10 (4) (1) (2)))))
      • Cum 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))))
  • (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:
  • (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))
      • se inserează 3 în vid ⇒ '(3)
      • se inserează 7 în rezultat ⇒ '(7 (3))
      • se inserează 8 în rezultat ⇒ '(8 (7 (3)))
      • se inserează 5 în rezultat ⇒ '(8 (5) (7 (3)))
      • se inserează 2 în rezultat ⇒ '(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:
    • ex:
      (two-pass-merge-LR '((8 (2))   
                           (12)
                           (14 (11 (8)))   
                           (5 (4))   
                           (11 (10 (9)))))
      • 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)))
  • (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)))))
      • 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
    • ex:
      (tournament-merge '((8 (2))   
                          (12)   
                          (14 (11 (8)))   
                          (5 (4))   
                          (11 (10 (9)))))
      • 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ă)

În rezolvare, veți exersa lucrul cu:

  • liste și operatorii acestora (întrucât fiecare PH este reprezentat ca listă)
  • funcții recursive pe stivă, respectiv pe coadă (observați tipul de recursivitate al fiecărei funcții implementate, și atenție la cazurile în care vi se solicită un anumit tip de implementare - chiar dacă obțineți punctaj pe checker, punctajul va fi anulat în cazul în care funcțiile nu sunt implementate conform cerințelor)
  • operatori condiționali, operatori logici și valori boolene

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. 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:
      (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))
      • 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:
      (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)
      • 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:
      (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
    • ex:
      (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
    • ordinea de inserare a valorilor în PH este de la dreapta listei către stânga
    • ex:
      (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))))
      • 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:
      (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))
      • 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)
      • (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:

  • 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

Precizări

  • Scheletul fiecărei etape va conține unul sau mai multe fișiere .rkt în care trebuie să lucrați, plus fișierul checker.rkt pe care îl veți folosi doar pentru testare (rulând codul fără să îl modificați).
  • Fiecare etapă (o arhivă .zip cu fișierele în care ați lucrat, plus eventualele fișiere care sunt solicitate de acestea cu “require”) se va încărca pe vmchecker. Testele de vmchecker sunt aceleași cu cele din checker.rkt.
  • În fiecare etapă veți avea de implementat o serie de funcții, în sprijinul cărora vă puteți defini oricând funcții ajutătoare (dacă nu se interzice asta în mod explicit). Atunci când există restricții asupra implementării funcției din cerință, aceleași restricții trebuie respectate și de eventualele funcții ajutătoare definite de voi.
  • Dacă doriți să rezolvați exerciții din etapa curentă care depind de exerciții din etapele anterioare pe care nu le-ați rezolvat, puteți semnala acest lucru responsabilului de temă, care vă va pune la dispoziție o rezolvare pentru acele exerciții astfel încât să puteți continua tema. Odată ce alegeți această variantă, renunțați la dreptul de a mai trimite cu întârziere etapa pentru care ați solicitat parțial sau integral rezolvarea. Puteți solicita rezolvări doar pentru exercițiile din etapele anterioare, nu și pentru cele din etapa curentă. Dacă implementați un exercițiu din etapa curentă pe baza unui alt exercițiu din etapa curentă pe care nu l-ați rezolvat, se va lua în calcul punctajul dat de checker, chiar dacă implementarea ar funcționa în caz că exercițiul nerezolvat ar funcționa.
  • Tema este o temă de programare funcțională - pentru care folosim Racket. Racket este un limbaj multiparadigmă, care conține și elemente “ne-funcționale” (de exemplu proceduri cu efecte laterale), pe care nu este permis să le folosiți în rezolvare.
  • 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ț.
  • 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

Changelog

  • 01.03 (ora 01:45) - Am publicat etapa 1.

Referinţe

pp/25/teme/racket-ph.1741353251.txt.gz · Last modified: 2025/03/07 15:14 by mihaela.balint
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0