This shows you the differences between two versions of the page.
pp:22:teme:haskell-graph-zoo [2022/04/12 11:15] bot.pp |
pp:22:teme:haskell-graph-zoo [2022/05/04 14:49] (current) bot.pp |
||
---|---|---|---|
Line 2: | Line 2: | ||
* Data publicării: 12.04.2022 | * Data publicării: 12.04.2022 | ||
- | * Data ultimei modificări: 12.04.2022 | + | * Data ultimei modificări: 04.05.2022 |
+ | * Deadline hard: ziua laboratorului 10 | ||
* [[https://curs.upb.ro/2021/mod/forum/view.php?id=208281|Forum temă]] | * [[https://curs.upb.ro/2021/mod/forum/view.php?id=208281|Forum temă]] | ||
* [[https://vmchecker.cs.pub.ro/ui/#PP|vmchecker]] | * [[https://vmchecker.cs.pub.ro/ui/#PP|vmchecker]] | ||
Line 28: | Line 29: | ||
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 02.05, 09.05, 16.05). | 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 02.05, 09.05, 16.05). | ||
- | **Rezolvările tuturor etapelor pot fi trimise până în ziua laboratorului 10**, dar orice exercițiu trimis după deadline se punctează cu **jumătate** din punctaj. 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). | + | **Rezolvările tuturor etapelor pot fi trimise până în ziua laboratorului 10** (**deadline hard pentru toate etapele**). Orice exercițiu trimis după un **deadline soft** se punctează cu **jumătate** din punctaj. Cu alte cuvinte, 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. | În fiecare etapă, veți folosi ce ați învățat în săptămâna anterioară pentru a dezvolta aplicația. | ||
Line 49: | Line 50: | ||
* **evaluare leneșă**, implicită în Haskell, pentru decuplarea conceptuală a construcției unei structuri de parcurgerea acesteia. | * **evaluare leneșă**, implicită în Haskell, pentru decuplarea conceptuală a construcției unei structuri de parcurgerea acesteia. | ||
- | În schelet veți găsi două module: | + | În **schelet** veți găsi două module: |
* ''%%StandardGraph%%'': conține **reprezentarea** grafului orientat și funcții de **acces și manipulare**: | * ''%%StandardGraph%%'': conține **reprezentarea** grafului orientat și funcții de **acces și manipulare**: | ||
Line 71: | Line 72: | ||
Este suficient ca arhiva pentru **vmchecker** să conțină modulele ''%%StandardGraph%%'' și ''%%Algorithms%%''. | Este suficient ca arhiva pentru **vmchecker** să conțină modulele ''%%StandardGraph%%'' și ''%%Algorithms%%''. | ||
+ | |||
+ | ===== Etapa 2 ===== | ||
+ | |||
+ | În această etapă: | ||
+ | |||
+ | * veți lucra cu o altă reprezentare a grafurilor orientate, pe care o vom denumi //**constructivă**//, sau //**algebrică**// (explicațiile urmează) | ||
+ | * veți **redefini** funcțiile de acces și manipulare din etapa 1 (din modulul ''%%StandardGraph%%''), pentru a opera pe noua reprezentare | ||
+ | * pentru bonus, veți începe să implementați o funcționalitate de **compactare** a reprezentării unui graf, continuată în etapa 3. | ||
+ | |||
+ | Funcțiile din modulul ''%%Algorithms%%'' ar trebui să funcționeze **neschimbate** în etapa 2, cu toate că accentul nu mai cade pe ele acum. | ||
+ | |||
+ | Construcțiile și mecanismele noi de limbaj pe care le veți exploata în rezolvare, pe lângă cele din etapa 1, sunt: | ||
+ | |||
+ | * **tipurile de date utilizator**. | ||
+ | |||
+ | Ce putem înțelege prin reprezentare **algebrică** a grafurilor? Ori de cât ori auzim aceste termen, ne putem gândi la construirea unor obiecte mai complexe din altele mai simple, în baza unor operații de îmbinare. De exemplu, plecând de la numerele **0 și 1**, și utilizând operațiile de **adunare și înmulțire**, putem genera **toate numerele naturale**. | ||
+ | |||
+ | Similar, putem elabora o abordare **constructivă** a grafurilor orientate, pornind de la următoarele mecanisme (vedeți [[https://eprints.ncl.ac.uk/file_store/production/239461/EF82F5FE-66E3-4F64-A1AC-A366D1961738.pdf|articolul]] din secțiunea de Referințe dacă doriți să aprofundați subiectul): | ||
+ | |||
+ | * graful **vid**, fără noduri și fără arce, denumit ''%%Empty%%'' | ||
+ | * graful cu un **singur nod**, denumit ''%%Node x%%'', unde ''%%x%%'' este eticheta nodului | ||
+ | * graful obținut prin **reunirea** nodurilor și a arcelor din alte două grafuri, denumit ''%%Overlay g1 g2%%'', unde ''%%g1%%'' și ''%%g2%%'' sunt alte grafuri | ||
+ | * graful obținut prin **reunirea** nodurilor și a arcelor din alte două grafuri, precum și prin **conectarea exhaustivă** a tuturor nodurilor din primul graf cu cele din al doilea, denumit ''%%Connect g1 g2%%'', unde ''%%g1%%'' și ''%%g2%%'' sunt alte grafuri. | ||
+ | |||
+ | Ideile de mai sus se pot traduce direct într-un **tip de date utilizator**, unde ''%%a%%'' este tipul **etichetelor** nodurilor: | ||
+ | |||
+ | <code> | ||
+ | data AlgebraicGraph a | ||
+ | = Empty | ||
+ | | Node a | ||
+ | | Overlay (AlgebraicGraph a) (AlgebraicGraph a) | ||
+ | | Connect (AlgebraicGraph a) (AlgebraicGraph a) | ||
+ | |||
+ | </code> | ||
+ | Mai jos, sunt **exemplificate** mai multe grafuri care utilizează această reprezentare, pentru o înțelegere mai bună: | ||
+ | |||
+ | * ''%%Overlay (Node 2) (Node 3)%%'' este un graf foarte simplu, care conține doar nodurile 2 și 3, fără niciun arc. | ||
+ | * ''%%Connect (Node 2) (Node 3)%%'' conține nodurile 2 și 3, precum și arcul (2, 3). | ||
+ | * ''%%Connect (Node 1) (Overlay (Node 2) (Node 3))%%'' conține nodurile 1, 2 și 3, și arcele (1, 2) și (1, 3), pentru că nodul 1 trebuie conectat cu fiecare dintre nodurile 2 și 3. | ||
+ | * ''%%Connect (Node 1) (Connect (Node 2) (Node 3))%%'', conține nodurile 1, 2 și 3, și arcele (1, 2), (1, 3) și (2, 3). | ||
+ | * ''%%Connect (Node 1) (Connect (Node 2) (Connect (Node 3) (Node 4)))%%'' conține nodurile 1, 2, 3 și 4, și arcele (1, 2), (1, 3), (1, 4), (2, 3), (2, 4) și (3, 4), întrucât fiecare nod trebuie conectat cu **toate** arcele care îi urmează. | ||
+ | |||
+ | Din exemplele de mai sus, transpar două **avantaje** importante ale acestei reprezentări algebrice, care îi **lipsesc** reprezentării standard din etapa 1: | ||
+ | |||
+ | * Se **împiedică** direct prin construcție definirea unor grafuri inconsistente, fără a fi necesare verificări suplimentare. De exemplu, în etapa 1, puteam defini un graf prin mulțimea de noduri [1] și mulțimea de arce [(1, 2)], fără a preciza nodul 2 în mulțimea de noduri. În reprezentarea algebrică este **imposibilă** definirea unui astfel de graf. | ||
+ | * Anumite grafuri dense, ca în ultimul exemplu de mai sus, care necesită un **spațiu pătratic** în raport cu numărul de noduri în cazul enumerării explicite a arcelor, pot fi reprezentate în **spațiu liniar** în cazul algebric. | ||
+ | |||
+ | În baza celui de-al doilea avantaj de mai sus, se poate pune problema **compactării** reprezentării unui graf, analizând relațiile pe care nodurile le au cu celelalte noduri. Spre exemplu, pentru unul dintre grafurile exemplificate mai sus, este dată și o reprezentare alternativă (a doua), mai lungă. | ||
+ | |||
+ | * ''%%Connect (Node 1) (Overlay (Node 2) (Node 3))%%'' | ||
+ | * ''%%Overlay (Connect (Node 1) (Node 2)) (Connect (Node 1) (Node 3))%%''. | ||
+ | |||
+ | Ca fapt divers, echivalența celor două reprezentări de mai sus poate fi înțeleasă ca **distributivitate** a lui ''%%Connect%%'' față de ''%%Overlay%%''. | ||
+ | |||
+ | Compactarea reprezentării grafului se bazează pe conceptul de [[https://en.wikipedia.org/wiki/Modular_decomposition|descompunere modulară]]. Pe scurt, un **modul** este o mulțime de noduri, care **toate au aceeași mulțime de out-neighbors** și **aceeași mulțime de in-neighbors** dacă ne uităm **doar în afara modulului**, cu toate că cele două mulțimi pot fi diferite. Nodurile din **interiorul** unui modul pot fi conectate **oricum**. Se poate demonstra că, în cazul a **două module** disjuncte, dacă există un arc orientat între un nod din primul modul și un nod din al doilea, atunci există arce cu orientarea respectivă între **toate** nodurile din primul modul și **toate** nodurile din al doilea (exact ce exprimă ''%%Connect%%''). | ||
+ | |||
+ | În graful de mai jos, observăm următoarele: | ||
+ | |||
+ | * La cel mai înalt nivel, se disting două module, ''%%M1%%'' și ''%%M2%%'', întrucât avem arce de la fiecare nod din ''%%M1%%'' către fiecare (unicul) nod din ''%%M2%%''. Toate nodurile din ''%%M1%%'' au aceeași mulțime de out-neighbors [5], și aceeași mulțime de in-neighbors, [], **în exteriorul** lui ''%%M1%%''. Aceeași proprietate se respectă banal și pentru nodul 5 din ''%%M2%%''. | ||
+ | * Dacă ne concentrăm acum doar asupra lui ''%%M1%%'', observăm că acesta poate fi la rândul său descompus în două module, ''%%M3%%'' și ''%%M4%%'', întrucât avem arce de la fiecare nod din ''%%M3%%'' către fiecare nod din ''%%M4%%''. Arcul (1, 2) nu este relevant, pentru că nodurile din interiorul unui modul pot fi conectate oricum. | ||
+ | * Mulțimea [1, 3] **nu** ar putea constitui un modul, pentru că există arcul (1, 2), dar nu și arcul (3, 2). | ||
+ | |||
+ | {{ :pp:22:teme:haskell:graph.png }} | ||
+ | |||
+ | Orice graf are două descompuneri modulare **banale**: | ||
+ | |||
+ | * un singur modul cu întregul graf | ||
+ | * câte un modul pentru fiecare nod, | ||
+ | |||
+ | dar acestea sunt neinteresante. Pe noi ne interesează descompunerile nebanale, dacă acestea există, care contribuie la compactarea reprezentării grafului. De exemplu, cea mai compactă reprezentare a grafului din imagine este: | ||
+ | |||
+ | <code> | ||
+ | Connect (Connect (Connect (Node 1) (Node 2)) | ||
+ | (Overlay (Node 3) (Node 4))) | ||
+ | (Node 5) | ||
+ | |||
+ | </code> | ||
+ | Veți implementa aspecte legate de descompunerea modulară parțial ca bonus, în cadrul etapelor 2 și 3. Deși există [[https://www.sciencedirect.com/science/article/pii/S0166218X04002458|algoritmi eficienți (liniari)]] pentru determinarea descompunerii modulare, aceștia sunt destul de complicați, astfel că vom utiliza o abordare mai simplă bazată pe forță brută. Cu alte cuvinte, vom genera **toate partițiile** mulțimii de noduri, și apoi le vom filtra pentru a obține modulele. | ||
+ | |||
+ | În etapa 2, **scheletul** conține următoarele module: | ||
+ | |||
+ | * ''%%StandardGraph%%'': implementat în etapa 1. Reprezentarea originală este de **pereche** de mulțimi de noduri, respectiv arce. Ca încălzire: | ||
+ | * Gândiți-vă cum se poate **redefini** tipul ''%%StandardGraph%%'' ca **tip de date utilizator** (''%%data%%''), în locul sinonimului de tip pereche. | ||
+ | * Funcțiile ''%%nodes%%'' și ''%%edges%%'' pot fi definite direct drept **câmpuri** în cadrul tipului. | ||
+ | * Funcția ''%%fromComponents%%'' trebuie și ea redefinită. | ||
+ | * Schițați în comentarii redefinirea celor patru entități de mai sus. Această parte nu este testată automat, dar o veți **prezenta**. | ||
+ | * Nu este necesar să reimplementați celelalte funcții din acest modul. | ||
+ | * ''%%AlgebraicGraph%%'': conține **reprezentarea algebrică** a grafurilor, descrisă mai sus | ||
+ | * Tipul ''%%AlgebraicGraph%%'' este cel prezentat mai sus. | ||
+ | * Mai departe, veți implementa **aceleași funcții** ca în modulul ''%%StandardGraph%%'', cu excepția ''%%fromComponents%%'', dar de data aceasta vor opera pe reprezentarea algebrică. | ||
+ | * Toate funcțiile vor fi implementate **CU recursivitate explicită**. | ||
+ | * Veți observa că implementările funcțiilor ''%%removeNode%%'', ''%%splitNode%%'' și ''%%mergeNodes%%'' respectă un **tipar similar**, pe care vom căuta să îl generalizăm în etapa 3. | ||
+ | * **ATENȚIE!** Funcțiile de mai sus trebuie să opereze **direct pe reprezentarea algebrică** din această etapă. **NU este permisă** convertirea în reprezentarea din etapa 1 și utilizarea funcțiilor de acolo. | ||
+ | * ''%%Modular%%'': conține momentan doar câteva funcții pentru determinarea **descompunerii modulare** a grafului, dar va fi îmbogățit în etapa 3: | ||
+ | * Funcția ''%%mapSingle%%'' este asemănătoare cu ''%%map%%'', în sensul că aplică o funcție asupra fiecărui element al unei liste, dar aplicarea se face asupra unui **singur element** din listă la un moment dat, celelalte rămânând nemodificate. | ||
+ | * Funcția ''%%partitions%%'' generează toate partițiile unei liste. | ||
+ | |||
+ | ===== Etapa 3 ===== | ||
+ | |||
+ | În această etapă: | ||
+ | |||
+ | * veți continua să lucrați cu reprezentarea **algebrică** a grafurilor, cu care v-ați familiarizat în etapa 2 | ||
+ | * veți înzestra reprezentarea cu funcționalități standard (de exemplu, de descriere sub formă de șir de caractere sau de verificare a egalității), utilizând instanțe de **clase** | ||
+ | * veți defini anumite **funcționale pe grafuri**, pentru a observa cum funcționalele pe liste pot fi **generalizate**, întrucât ele surprind tipare universale de prelucrare a structurilor | ||
+ | * veți **redefini** anumite funcții din etapa 2 pentru a utiliza funcționalele de mai sus | ||
+ | * în cea mai mare parte pentru bonus, veți continua să implementați funcționalitatea de **compactare** a reprezentării unui graf, începută în etapa 2. | ||
+ | |||
+ | Construcțiile și mecanismele noi de limbaj pe care le veți exploata în rezolvare, pe lângă cele din etapa 2, sunt: | ||
+ | |||
+ | * **polimorfismul ad-hoc** și **clasele**. | ||
+ | |||
+ | Ca încălzire, amintiți-vă că ați operat până acum cu **două reprezentări** ale grafurilor, ''%%StandardGraph%%'' și ''%%AlgebraicGraph%%'', și că ați implementat același set de funcții de acces și manipulare pentru amândouă (''%%nodes%%'', ''%%edges%%'' etc.). Gândiți-vă cum ați putea **generaliza** această interfață de lucru cu grafuri în Haskell: | ||
+ | |||
+ | * Cum ați defini o **clasă** care să surprindă conceptul de graf orientat? Ați parametriza-o cu **tipul concret** (de exemplu, ''%%AlgebraicGraph a%%'') sau cu **constructorul de tip** în sine (de exemplu, ''%%AlgebraicGraph%%'')? | ||
+ | * Cum ar arăta **tipul** funcției ''%%nodes%%'' dacă ar fi definită în interiorul acestei clase? | ||
+ | |||
+ | Schițați răspunsul în comentarii în vederea **prezentării** (partea aceasta nu este testată automat). | ||
+ | |||
+ | În etapa 3, **scheletul** conține următoarele module: | ||
+ | |||
+ | * ''%%AlgebraicGraph%%'' | ||
+ | * Funcțiile ''%%nodes%%'', ''%%edges%%'', ''%%outNeighbors%%'', ''%%inNeighbors%%'' sunt cele din etapa 2. | ||
+ | * Instanța clasei ''%%Num%%'' permite interpretarea unei **expresii aritmetice** ca un graf algebric, în care literalii numerici sunt etichete de noduri, iar adunarea și înmulțirea reprezintă operațiile ''%%Overlay%%'', respectiv ''%%Connect%%''. De exemplu, graful din diagrama de mai sus poate fi reprezentat prin expresia aritmetică ''%%((1*2) * (3+4)) * 5%%''. | ||
+ | * Instanța clasei ''%%Show%%'' permite descrierea grafului sub forma unui **șir de caractere**, utilizând perspectiva aritmetică de mai sus. | ||
+ | * Instanța clasei ''%%Eq%%'' permite verificarea corectă a **egalității** dintre două grafuri algebrice, ținând cont că același graf conceptual poate avea două descrieri simbolice diferite. | ||
+ | * Funcția ''%%extend%%'' permite **elaborarea** unui graf, prin atașarea unor subgrafuri oarecare în locul nodurilor, în baza unei funcții de corespondență. Funcția ''%%extend%%'' constituie **baza implementării** tuturor operațiilor de mai jos. | ||
+ | * Funcția familiară ''%%splitNode%%'' va fi **reimplementată** utilizând ''%%extend%%''. | ||
+ | * Instanța clasei ''%%Functor%%'', prin operația ''%%fmap%%'', **generalizează** funcționala ''%%map%%'' de pe liste pe grafuri, permițând **aplicarea unei funcții** pe toate etichetele nodurilor dintr-un graf. Implementarea va utiliza ''%%extend%%''. | ||
+ | * Funcția familiară ''%%mergeNodes%%'' va fi **reimplementată** utilizând ''%%fmap%%''. | ||
+ | * Funcționala ''%%filterGraph%%'' **generalizează** funcționala ''%%filter%%'' de pe liste pe grafuri, pentru a **păstra** doar nodurile ale căror etichete satisfac o proprietate. Implementarea va utiliza ''%%extend%%''. | ||
+ | * Funcția familiară ''%%removeNode%%'' va fi **reimplementată** utilizând ''%%filterGraph%%''. | ||
+ | * ''%%Modular%%'' | ||
+ | * Funcțiile ''%%mapSingle%%'' și ''%%partitions%%'' sunt cele din etapa 2. | ||
+ | * Funcția ''%%isModule%%'' verifică dacă o mulțime candidat de noduri constituie într-adevăr un **modul** (vedeți explicațiile din etapa 2). | ||
+ | * Funcția ''%%isModularPartition%%'' verifică dacă o partiție candidat este într-adevăr **modulară**, i.e. dacă toate **submulțimile** sunt module. | ||
+ | * Funcția ''%%maximalModularPartition%%'' determină cea mai acoperitoare partiție, pornind de la lista tuturor partițiilor mulțimii de noduri (vedeți explicațiile din comentarii). | ||
+ | * Funcția ''%%modularlyDecompose%%'' este **deja implementată**, și vă permite să puneți cap la cap funcțiile de mai sus. | ||
===== Precizări ===== | ===== Precizări ===== | ||
* Încercați să folosiți pe cât posibil funcții **predefinite** din modulele [[https://hackage.haskell.org/package/base-4.16.1.0/docs/Data-List.html|Data.List]] și [[https://hackage.haskell.org/package/containers-0.6.5.1/docs/Data-Set.html|Data.Set]]. Este foarte posibil ca o funcție de prelucrare de care aveți nevoie să fie deja definită aici. | * Încercați să folosiți pe cât posibil funcții **predefinite** din modulele [[https://hackage.haskell.org/package/base-4.16.1.0/docs/Data-List.html|Data.List]] și [[https://hackage.haskell.org/package/containers-0.6.5.1/docs/Data-Set.html|Data.Set]]. Este foarte posibil ca o funcție de prelucrare de care aveți nevoie să fie deja definită aici. | ||
- | * Exploatați cu încredere //**pattern matching**//, **''%%case%%''** și **gărzi**, în locul //if//-urilor imbricate. | + | * Ca sugestie, exploatați cu încredere //**pattern matching**//, **''%%case%%''** și **gărzi**, în locul //if//-urilor imbricate. |
* Pentru **rularea testelor**, încărcați în interpretor modulul ''%%TestGraph%%'' și evaluați ''%%checkAll%%''. | * Pentru **rularea testelor**, încărcați în interpretor modulul ''%%TestGraph%%'' și evaluați ''%%checkAll%%''. | ||
Line 81: | Line 219: | ||
* [[https://ocw.cs.pub.ro/courses/_media/pp/22/teme/haskell/etapa1.zip|Schelet etapa 1]] | * [[https://ocw.cs.pub.ro/courses/_media/pp/22/teme/haskell/etapa1.zip|Schelet etapa 1]] | ||
+ | * [[https://ocw.cs.pub.ro/courses/_media/pp/22/teme/haskell/etapa2.zip|Schelet etapa 2]] | ||
+ | * [[https://ocw.cs.pub.ro/courses/_media/pp/22/teme/haskell/etapa3.zip|Schelet etapa 3]] | ||
+ | |||
+ | ===== Referințe ===== | ||
+ | |||
+ | * [[https://eprints.ncl.ac.uk/file_store/production/239461/EF82F5FE-66E3-4F64-A1AC-A366D1961738.pdf|Algebraic Graphs with Class (Functional Pearl)]] | ||
+ | |||
+ | ===== Changelog ===== | ||
+ | |||
+ | * 04.05 (14:50) - Clarificare deadline hard | ||
+ | * 01.05 (19:40) | ||
+ | * Etapa 2 - Flexibilizare testPartitions pentru a nu ține cont de ordinea elementelor și a submulțimilor. | ||
+ | * Etapa 3 - Flexibilizare testMaximalModularPartition pentru a ține cont de ambele descompuneri valide de la ultimul subtest. | ||
+ | * 29.04 (11:35) - Etapa 3 - Publicare | ||
+ | * 19.04 (16:30) - Etapa 2 - Publicare | ||
+ | * 17.04 (18:54) - Etapa 1 - Actualizare checker, subtest 10 de la bfs, care apela dfs în loc de bfs. | ||
+ | * 17.04 (12:47) - Etapa 1 - Actualizare checker cu ordinea corectă a valorilor testate și corecte. În versiunea anterioară erau inversate, lucru ce ducea la mesaje confuze în cazul testelor picate. | ||