Edit this page Backlinks This page is read only. You can view the source, but not change it. Ask your administrator if you think this is wrong. ====== TODO ====== Scopul laboratorului: * recapitularea conceptelor învățate * programarea cu o structură funcțională * înțelegerea conceptului de "leftist heap" ===== Priority Queue ===== O [[http://pages.cs.wisc.edu/~vernon/cs367/notes/11.PRIORITY-Q.html|coadă de priorități ("priority queue")]] este o structură de date în care putem stoca elemente și din care îl putem scoate pe cel cu prioritatea cea mai mare. Spre deosebire de o coadă normală, care respectă principiul First In, First Out, elementele unei cozi de priorități sunt scoase în ordinea //priorității//, o proprietate a obiectelor conținute. Operațiile neceseare implementării unei cozi de priorități sunt: * ''empty'' - care creează o coadă goală de priorități * ''isEmpty'' - verifică dacă există elemente în coadă * ''insert'' - inserează un element într-o coadă * ''top'' - returnează elementul cu prioritatea maximă * ''delete'' - scoate elementul cu prioritatea maximă din coadă Coada de priorități este o structură abstractă, ce poate avea diverse implementări care diferă prin complexitatea diverselor operații (e.g. ''insert'', ''delete''). Un exemplu naiv este implementarea unei cozi de priorități folosind o listă. Avem două posibilități: să introducem complexitatea determinării priorității în funcția ''top'', sau în ''insert''. Putem pune mereu un element la începutul listei (astfel ''insert'' e echivalent cu '':''). Atunci când avem nevoie de cel cu prioritatea cea mai mare, pornim o căutare liniară prin elementele listei. Similar pentru ștergere. Avem astfel complexitățile: | Funcție | Complexitate | | ''isEmpty'' | ''O(1)'' | | ''insert'' | ''O(1)'' | | ''top'' | ''O(n)'' | | ''delete'' | ''O(n)'' | Alternativ, simplificând funcția ''top'', ne asigurăm că elementele sunt mereu //ordonate// în listă (astfel, ''top'' este echivalent cu ''head''). Inserarea unui element într-o listă ordonată se face în timp liniar. Avem astfel complexitățile: | Funcție | Complexitate | | ''isEmpty'' | ''O(1)'' | | ''insert'' | ''O(n)'' | | ''top'' | ''O(1)'' | | ''delete'' | ''O(1)'' | Implementarea cu liste nu este ideală și putem obține performanțe mai bune. ==== Binary Heap ==== Un [[https://en.wikipedia.org/wiki/Binary_heap|binary heap]] este un arbore binar cu următoarele două proprietăți: * pentru orice nod, valoarea asociată este **mai mare sau egală** cu valorile copiiilor * arborele este [[https://en.wikipedia.org/wiki/Binary_tree#Types_of_binary_trees|complet]]: fiecare nivel este plin, cu excepția ultimului, pe care nodurile sunt cât mai la stânga ==== Leftist Heap === Un [[https://en.wikipedia.org/wiki/Leftist_tree|leftist heap]] este ===== newtype ===== În [[pp:l04|laboratorul de tipuri]], ați învățat să definiți noi tipuri de date în Haskell, folosind două metode oferite de cuvintele cheie ''data'' și ''type'': * ''data'' este folosit pentru a crea tipuri algebrice complexe, cu un număr arbitrar de constructori care pot avea, la rândul lor, un număr arbitrar de parametrii * ''type'' este folosit pentru a crea //un alias de tip//, în scopul clarității (e.g. ''type String = [Char]'') Cuvântul cheie ''newtype'' poate fi folosit pentru a crea un nou tip cu următoarele constrângeri: * tipul are un singur constructor * constructorul are un singur argument <code haskell> -- este foarte comun ca tipul și constructorul să aibă același nume -- amintiți-vă de record syntax, getPair este o funcție autogenerată -- cu tipul "Pair a b -> (a, b)" newtype Pair a b = Pair { getPair :: (a, b) } </code> Obținem astfel un nou tip, echivalent cu perechea din Haskell, dar //distinct//: ''Pair a b'' și ''(a, b)'' **nu sunt interschimbabile**: <code> Prelude> :t fst fst :: (a, b) -> a Prelude> fst (Pair (1, 3)) <interactive>:17:6: error: • Couldn't match expected type ‘(a, b0)’ with actual type ‘Pair Integer Integer’ • In the first argument of ‘fst’, namely ‘(Pair (1, 3))’ In the expression: fst (Pair (1, 3)) In an equation for ‘it’: it = fst (Pair (1, 3)) • Relevant bindings include it :: a (bound at <interactive>:17:1) Prelude> </code> Astfel, ''newtype'' ajută atât la claritatea codului, cât și la type-safety. ==== newtype vs type ==== ''type'' creează //sinonime de tip// perfect interschimbabile. Având definiția ''type String = [Char]'' putem să trimitem un ''String'' oricărei funcții care așteaptă ''[Char]'' și vice-versa: <code> Prelude> :t id id :: a -> a Prelude> (id :: String -> [Char]) ("asdf" :: [Char]) "asdf" Prelude> (id :: [Char] -> [Char]) ("asdf" :: String) "asdf" Prelude> </code> Scopul lui ''type'' este de a ajuta programtorul, crescând lizibilitatea codului. De exemplu, având definiția ''type FilePath = String'', devine mai clar ce face funcția: <code> *Main> :t combine combine :: FilePath -> FilePath -> FilePath </code> ==== newtype vs data ==== Sintactic și semantic, funcționalitatea ''newtype'' poate fi înlocuită de ''data'', întrucât ''data'' este un fel mai general de a defini tipuri (număr arbitrar de constructori, număr arbitrar de argumente): <code haskell> data Pair a b = Pair { getPair :: (a, b) } </code> Diferența este însă una de //performanță//: tocmai din cauza constrângerilor lui ''newtype'', compilatorul poate să elimine orice overhead de împachetare/despachetare la runtime. Avem astfel tipuri diferite și putem beneficia de verificarea sistemului de tipuri, fără dezvantajul împachetării/despachetării constructorilor la rulare. ==== Înrolarea în clase ==== Pentru că tipurile definite de ''type'' sunt sinonime perfecte acestea au aceeași înrolare într-o clasă anume, deci același comportament. De exemplu, nu putem avea formate diferite de afișare pentru un ''String'' și un ''[Char]''. Însă, drept consecință a faptului că ''newtype'' definește **un tip nou**, putem scrie, pentru perechea definită mai sus: <code haskell> instance (Show a, Show b) => Show (Pair a b) where show (Pair a b) = "<" ++ show a ++ "," ++ show b ++ ">" </code> <code> *Main> (7, 27) (7,27) *Main> Pair 7 27 <7,27> </code> ===== Exerciții ===== - Folosiți liste Haskell pentru a implementa o coadă de priorități. - implementați o coadă de priorități care scoate valoarea //minimă// - implementați o coadă de priorități care scoate valoarea //maximă// - Folosiți arbori binari pentru a implementa un leftist heap <note tip> Clasa ''PQueue'' conține unele funcții cu implementări default. Considerați înlocuirea acestora cu implementări particularizate. </note> ===== Recommended Reading ===== * [[http://typeocaml.com/2015/03/12/heap-leftist-tree/|Heap - Leftist Tree (discuție și implementare în OCaml, puteți ignora codul în sine)]] * [[http://learnyouahaskell.com/types-and-typeclasses#typeclasses-101|Learn you a Haskell for Great Good - Chapter 2: Types and Typeclasses#Typeclasess 101]]\\ * [[http://learnyouahaskell.com/making-our-own-types-and-typeclasses#typeclasses-102|Learn you a Haskell for Great Good - Chapter 8: Making Our Own Types and Typeclasses#Typeclasess 102]]\\ * [[http://book.realworldhaskell.org/read/using-typeclasses.html|Real World Haskell - Chapter 6: Using Typeclasses]]\\ * [[https://www.haskell.org/tutorial/stdclasses.html|A Gentle Introduction to Haskell - Chapter 8: Standard Haskell Classes]]