# NeprocProg Cure [TOC] ## Prolog ### Topologické uspořádání grafu Zdroj: [MFF Forum: Zkoužka 4.9.2020](http://forum.matfyz.info/viewtopic.php?f=169&t=12095) Je dán orientovaný graf G pomocí seznamů sousedů. Zjistěte, jestli lze graf G topologicky uspořádat a pokud ano, vydejte seznam vrcholů v topologickém pořadí. Příklad: ```prolog ?- topo([a-[],b-[a,c],c-[a],d-[a,c]],Usp). Usp = [b,d,c,a] ``` 1) Definujte příslušný predikát topo/2 v jazyce Prolog. 2) Odhadněte časovou složitost vašeho řešení. Odhad zdůvodněte. 3) Jsou některé z vašich predikátů koncově rekurzivní ? Pokud ano, vysvětlete, které to jsou, a jaký to má význam. Pokud ne, vysvětlete, zdali by se dal některý takto upravit. Řešení: ```prolog remove_vertex([], _, []). remove_vertex([ Vertex-_ | Graph ], Vertex, Out) :- remove_vertex(Graph, Vertex, Out), !. remove_vertex([ V-Ns | Graph ], Vertex, [ V-NewNs | Ans ]) :- remove_vertex(Graph, Vertex, Ans), ( member(Vertex, Ns) -> select(Vertex, Ns, NewNs) ; NewNs = Ns ). topo(Graph, Usp) :- topo_(Graph, [], Usp). topo_([], Acc, Acc). topo_(Graph, Acc, Out) :- member(Min-[], Graph), remove_vertex(Graph, Min, NewGrap), topo_(NewGrap, [Min | Acc], Out). ``` ### Diskrepanční vrstvy Zdroj: [MFF Forum: Zkoužka 4.9.2020](http://forum.matfyz.info/viewtopic.php?f=169&t=12095) Napište predikát ``diskr/2``, který dostane binární strom (s konstruktory ``t/3`` a ``nil/0``) a vrátí seznam seznamů vrcholů stromu, kde v jednom vnitřním seznamu jsou všechny vrcholy, ke kterým se při průchodu od kořene dostaneme se stejným počtem kroků doprava. Vnější seznam je od nejlevější vrstvy, na pořadí ve vnitřních seznamech nezáleží. Příklad: ```prolog ?- diskr(t( t(t(nil,a,nil),b,t(nil,c,nil)), d, t(t(nil,e,t(nil,f,nil)), g, t(nil,h,t(nil,i,nil)) )), V). V = [[a,b,d],[c,g,e],[f,h],[i]] ``` 1. Definujte příslušný predikát ``diskr/2.`` 2. Je ve vašem řešení použit řez (!) nebo negace? Pokud ano, změní se něco, když řez / negaci vypustíme? Pokud ne, dal by se řez / negace někde smysluplně využít? 3. Lze u predikátu ``diskr/2`` obrátit směr výpočtu? Podrobněji: dle příkladu předpokládáme volání diskr(+,-). Bude fungovat i volání diskr(-, +), tj. zadáme seznam diskrepančních vrstev, a na výstupu obdržíme strom? Vysvětlete. Řešení: ```prolog diskr(Tree, V) :- diskr_(Tree, 0, NodeRightCount), collect(NodeRightCount, 0, V). diskr_(nil, _, []). diskr_(t(Left, Node, Right), RightCount, NodeRightCount) :- diskr_(Left, RightCount, LAns), NewCount is RightCount + 1, diskr_(Right, NewCount, RAns), append(LAns, [Node-RightCount | RAns], NodeRightCount). is_count(Count, _-Count). get_value(Value-_, Value). collect([], _, []). collect(Pairs, Count, Ans) :- include(is_count(Count), Pairs, RightCount), ( RightCount \= [] -> maplist(get_value, RightCount, Vals), NewCount is Count + 1, collect(Pairs, NewCount, TmpAns), Ans = [Vals | TmpAns] ; Ans = [] ). % test data test_tree( t( t( t(nil, a, nil), b, t(nil, c, nil) ), d, t( t( nil, e, t(nil, f, nil) ), g, t( nil, h, t(nil, i, nil) ) ) ) ). ``` ### Generování binárních stromů Zdroj: [MFF Forum: Zkouška 16. 7. 2020](http://forum.matfyz.info/viewtopic.php?f=169&t=12089) Cílem úlohy je definovat predikát allTrees, který pro daný seznam hladin vygeneruje všechny možné binární stromy. - Hladinou rozumíme seznam prvků, které se nacházejí ve stejné hloubce - Můžete předpokládat, že každá hladina má nanejvýš dvojnásobek prvků předchozí hladiny (ale může jich mít méně). - Hladiny vygenerovaného stromu musejí odpovídat hladinám specifikovaných ve vstupním seznamu. Např. pro seznam ``[[1],[2,3],[4]]`` dostaneme následující 4 stromy: ```none 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 ``` 1. Popište zvolenou reprezentaci binárních stromů. 2. Definujte predikát ``allTrees/2``. 3. Stručně vysvětlete, proč je vaše definice korektní. 4. Lze vaší definici použít opačným směrem? Tj. nalezne váš predikát seznam hladin pokud specifikujete pouze výsledný strom? Vysvětlete. Řešení: ```prolog level_to_forest([], [], []). level_to_forest([X | Xs], [Left, Right | Rest], [t( Left, X, Right ) | Ans]) :- level_to_forest( Xs, Rest, Ans ). level_to_forest([X | Xs], [Left | Rest], [t( Left, X, nil ) | Ans]) :- level_to_forest( Xs, Rest, Ans ). level_to_forest([X | Xs], [Right | Rest], [t( nil, X, Right ) | Ans]) :- level_to_forest( Xs, Rest, Ans ). level_to_forest([X | Xs], Trees, [t( nil, X, nil ) | Ans]) :- level_to_forest( Xs, Trees, Ans ). all_trees(Levels, SingleTree) :- reverse(Levels, ReversedLevels), Forest = [], all_trees(ReversedLevels, Forest, [SingleTree]). all_trees([], Forest, Forest). all_trees([Level | Levels], Forest, Ans) :- level_to_forest(Level, Forest, NewForest), all_trees(Levels, NewForest, Ans). ``` ### Bipartitní rozklad grafu Zdroj: [MFF Forum: Zkouška 16. 7. 2020](http://forum.matfyz.info/viewtopic.php?f=169&t=12089) Je zadán neorientovaný graf *G* a množina vrcholů *M*. Zjistěte, zda *M* a doplněk *M* tvoří bipartitní rozklad grafu *G* (tj. každá hrana grafu má právě jeden koncový vrchol v množině *M*). Pokud ano, vydejte druhou množinu rozkladu. ```prolog ?- bip([a-[c,d], b-[d], c-[a], d-[a,b]], [a,b], D). D = [c,d] ?- bip([a-[c,d], b-[d], c-[a], d-[a,b]], [b,c], D). false ``` 1. Definujte predikát ``bip/3``. 2. Napište o jednotlivých predikátech ve vašem řešení, zda jsou koncově rekurzivní. Řešení: ```prolog % collect_nodes(+Graph, -Nodes) is true when Nodes are all the nodes in Graph % in sorted order. collect_nodes(Graph, Nodes) :- collect_nodes(Graph, [], NodesDup), sort(NodesDup, Nodes), !. collect_nodes([], Acc, Acc). collect_nodes([Node-Neighbours | Ns], Acc, Ans) :- append([Node | Neighbours], Acc, NewAcc), collect_nodes(Ns, NewAcc, Ans). % difference(+List1, +List2, -List3) is true if List3 contains all nodes of List1 % except the elements in List2 difference(List, [], List). difference(List, [X | Xs], Out) :- exclude(=(X), List, Tmp), difference(Tmp, Xs, Out), !. bip(Graph, V, U) :- collect_nodes(Graph, Nodes), difference(Nodes, V, U), is_bipartite(Graph, V, U). is_bipartite([], _, _). is_bipartite([Node-Neighbours | NNs], V, U) :- ( member(Node, V) -> Partition = V ; Partition = U ), member(Node, Partition), maplist(does_not_contain(Partition), Neighbours), is_bipartite(NNs, V, U), !. does_not_contain(List, X) :- \+ member(X, List). ``` ### Problém truhláře Zdroj: [MFF Forum: Zkouška 24. 6. 2020](http://forum.matfyz.info/viewtopic.php?f=169&t=12078) Truhlář má dostatek trámů délky ``D`` a seznam ``Xs`` délek trámů, které potřebuje nařezat. V seznamu ``Xs`` se délky mohou opakovat. Cílem problému je sestavit predikát ``rezy(+D, +Xs, -N, -Vss)``, který - rozdělí požadované délky do skupin, které se mají nařezat z jednoho trámu - truhlář přitom používá hladový algoritmus, tj. pro každou délku použije první trám, z něhož lze ještě požadovanou délku odřezat - vrátí celkový počet řezaných trámů N - a seznam seznamů Vss (délky N), jehož každý prvek reprezentuje dělení jednoho trámu (případný zbytek se neuvádí). ```prolog ?- rezy(5,[3,2,2,2,2,1,4], N, V). N=4, V=[[3,2],[2,2,1],[2],[4]] ``` 1. Definujte predikát ``rezy/4.`` Definice případných pomocných predikátů prosím opatřete vysvětlujícím komentářem. 2. Je některý z vašich predikátů koncově rekurzivní? Pokud ano, vysvětlete, který to je a jaký to má význam. 3. Pokud ne, dal by se některý takto upravit? Odpověď prosím zdůvodněte. Řešení: ```prolog second(_-X, X). rezy(Length, Xs, N, Vss) :- rezy_(Length, Xs, [], TRVss), maplist(second, TRVss, RVss), maplist(reverse, RVss, Vss), length(Vss, N), !. rezy_(_, [], Acc, Acc). rezy_(Length, [ X | Xs ], Acc, Ans) :- greedy_extend_cut(Length, Acc, X, NewAcc), rezy_(Length, Xs, NewAcc, Ans). greedy_extend_cut(_, [], NextCut, [ NextCut-[NextCut] ]). greedy_extend_cut(MaxLength, [ Total-Cuts | Cs ], NextCut, Ans) :- NextCut =< MaxLength, Free is MaxLength - Total, ( NextCut =< Free -> NewTotal is Total + NextCut, Ans = [ NewTotal-[NextCut | Cuts ] | Cs ] ; greedy_extend_cut(MaxLength, Cs, NextCut, Ans_), Ans = [ Total-Cuts | Ans_] ), !. ``` ### Systém různých reprezentantů Zdroj: [MFF Forum: Zkouška 24. 6. 2020](http://forum.matfyz.info/viewtopic.php?f=169&t=12078) Je zadán seznam množin ``Mss``. Chceme všemi možnými způsoby vybrat a vrátit v seznamu reprezentanty daných množin v odpovídajícím pořadí s podmínkou, že konkrétní reprezentanti v jednom výběru jsou různí. Příklad: ```prolog ?- reprezentanti([[1],[1,2,3],[1,3,4]], R). R = [[1,2,3],[1,2,4],[1,3,4]] ``` 1. Sestavte predikát ``reprezentanti(+Mss, -Rss)``. 2. Stručně vysvětlete, proč je vaše definice korektní. 3. Je ve vašem programu použit řez ``(!)`` ? Jde o řez červený (mění deklarativní význam programu) či zelený (nemění d.v.)? Pokud ne, je řez nezbytný pro definici některého vestavěného predikátu / operátoru, který jste ve vašem řešení použili? Jde o řez červený (mění deklarativní význam programu) či zelený (nemění d.v.)? ```prolog cons(X, Xs, [X | Xs]). extend_one(X, Ys, Ans) :- exclude(member(X), Ys, Tmp), maplist(cons(X), Tmp, Ans). extend_with([], _, Acc, Acc). extend_with([X | Xs], Ys, Acc, Ans) :- extend_one(X, Ys, Tmp), append(Tmp, Acc, NewAcc), extend_with(Xs, Ys, NewAcc, Ans). reprezentanti(Xss, Ans) :- reprezentanti(Xss, [[]], Ans). reprezentanti([], Acc, Acc). reprezentanti([Xs | Xss], Acc, Ans) :- extend_with(Xs, Acc, [], NewAcc), reprezentanti(Xss, NewAcc, Ans). ``` ### Hammerstein Zdroj: [MFF Forum: Zkouška 12. 6. 2020 (Dvořák, Hric)](http://forum.matfyz.info/viewtopic.php?f=169&t=12066) Profesor Hammerstein definoval predikat ``setrid/2`` takto: ```prolog % setrid(+Xs,-Ys) :- Ys je seznam přirozených čísel ze seznamu Xs setříděný % vzestupně setrid(Xs,Ys) :- append(A,[H1,H2|B],Xs), H1 > H2, !, append(A,[H2,H1|B],Xs1), setrid(Xs1,Ys). ``` zapomněl však na klauzuli, která definuje bázi rekurze. 1. Doplňte jednu (opravdu jen jednu) chybějící klauzuli za uvedené pravidlo tak, aby výsledná procedura korektně setřídila vstupní seznam přirozených čísel. Na výstupu bychom měli obdržet jen jediné řešení. 2. V definici pravidla je použit řez (!). Jde o zelený (nemění deklarativní význam) či červený řez (mění d.v.) ? Vysvětlete! Obsahuje některá z vašich klauzulí, (doplněná v(a) nebo (b)) zelený či červený řez? 3. Jaký známý třídící algoritmus výše uvedený kód implementuje? Pokud neznáte název, můžete alespoň slovně popsat, jak ``setrid/2`` funguje. 4. *VOLITELNE*: Lze u procedury ``setrid/2`` obrátit směr výpočtu? ```prolog setrid(-Xs,+Ys) :- Xs je seznam přirozených čísel ze seznamu Ys setříděný vzestupně ``` Pokud ne, šel by kód jednoduše upravit tak, aby se výsledný predikát (pojmenovaný třeba ``setrid2/2``) dal korektně volat oběma způsoby? Řešení: ```prolog % setrid(+Xs,-Ys) :- Ys je seznam přirozených čísel ze seznamu Xs setříděný % vzestupně % Bubble sort setrid(Xs, Ys) :- append(A, [H1,H2|B], Xs), H1 > H2, !, % cerveny append(A, [H2,H1|B], Xs1), setrid(Xs1, Ys). setrid(Xs, Xs). ``` ### Cestovatel Zdroj: [MFF Forum: Zkouška 12. 6. 2020 (Dvořák, Hric)](http://forum.matfyz.info/viewtopic.php?f=169&t=12066) Do země Mobilia, v níž je každý občan vybaven chytrým telefonem, přicestoval Cestovatel, nakažený virovým onemocněním. Všichni ostatní byli přitom ještě zdraví. Můžeme předpokládat, že virus se přenese z jedné osoby na druhou, pokud spolu strávili ve vzdálenosti menší než 2m alespoň čas ``K``, kde ``K`` je známá kritická hodnota. Díky chytrým telefonům máme pro každého občana Mobilie seznam záznamů jeho kontaktů, kde každý takový záznam pro osobu ``A`` obsahuje identifikaci osoby ``B``, která se k němu přiblížila do vzdálenosti ``< 2m`` čas setkání a délku setkání. Cílem je sestavit program, který na základě takových záznamů vrátí seznam infikovaných osob. 1. V jazyce Prolog popište datovou strukturu pro reprezentaci jednoho záznamu kontaktu občana Mobilie popsaného výše. 2. V jazyce Prolog navrhněte reprezentaci položek VstupníhoSeznamu, přičemž každá položka bude obsahovat indentifikaci občana Mobilie a seznam záznamů jeho kontaktů. 3. Sestavte predikát ``inf/4``, který obdrží ```none VstupníSeznam identifikaci Cestovatele kritickou hodnotu K ``` a vrátí seznam infikovaných. U každého pomocného predikátu prosím v poznámce popište jeho význam. *Volitelné:* výstupní seznam můžete uspořádat dle délky kontaktu s infikovanými do nerostoucí posloupnosti. 4. Odhadněte časovou složitost vašeho řešení. 5. Je některý z vašich predikátů koncově rekurzivní ? Pokud ano, vysvětlete, jaký to má význam. Pokud ne , dal by se některý takto upravit? Řešení: ```prolog % contact(ID, Time, Length) % [ID1-[ contact(...), ... ], ID2-[ contact(...), ... ], ...] lookup_contact([ID-Contacts | _], ID, Contacts) :- !. lookup_contact([ _ | Cs ], ID, Contacts) :- lookup_contact(Cs, ID, Contacts). was_infected(TimeInfected, K, contact(_, Time, Length)) :- Time >= TimeInfected, Length >= K. get_id_time_pair(contact(ID, Time), ID-Time). get_infected(ContactsList, K, ID, TimeInfected, Infected) :- lookup_contact(ContactsList, ID, Contacts), include(was_infected(TimeInfected, K), Contacts, InfectedCs), maplist(get_id_time_pair, InfectedCs, Infected). contained_in(InfectedIDs, ID-_) :- member(ID, InfectedIDs). first(X-_, X). inf(ContactsList, TravellerID, K, Infected) :- TimeInfected = 0, ToProcess = [ TravellerID-TimeInfected ], Acc = [ ], inf(ContactsList, K, ToProcess, Acc, Infected). inf(_, _, [], Acc, Acc). inf(ContactsList, K, [ ID-TimeInfected | Ps ], Acc, Ans) :- get_infected(ContactsList, K, ID, TimeInfected, Infected), exclude(contained_in(Acc), Infected, NewInfected), append(Ps, NewInfected, NewToProcess), maplist(first, NewInfected, NewIDs), append(NewIDs, Acc, NewAcc), inf(ContactsList, K, NewToProcess, NewAcc, Ans). ``` ### Generování hodnot výrokových proměnných Zdroj: [MFF Forum: 10. 9. 2019 - Dvořák](http://forum.matfyz.info/viewtopic.php?f=169&t=11977) Definujte binární predikát ``aspon2/2``, který - obdrží seznam výrokových proměnných (reprezentovaných atomy), v němž je každá proměnná ohodnocena hodnotou true nebo false - vrátí seznam všech takových ohodnocení týchž proměnných, v němž se každé ohodnocení bude od vstupního lišit v hodnotách alespoň 2 proměnných. Příklad: ```prolog ?- aspon2([x1-true, x2-false, y-true], V). V = [ [x1-false, x2-true, y-true], [x1-false, x2-false, y-false], [x1-true, x2-true, y-false], [x1-false, x2-true, y-false] ] ``` Řešení: ```prolog cons(X, Xs, [X | Xs]). % diff(Modified, Original, N). diff_ord([], [], 0). diff_ord([_-V | T1], [_-not(V) | T2], AnsN) :- diff_ord(T1, T2, N), AnsN is N + 1. diff_ord([_-V | T1], [_-V | T2], N) :- diff_ord(T1, T2, N). diff_less_then_2(Original, Modified) :- diff_ord(Original, Modified, N), N < 2. aspon_2(Values, X) :- subset_change(Values, All), exclude(diff_less_then_2(Values), All, X). subset_change([], [[]]). subset_change([Var-Value | Values], Ans) :- subset_change(Values, Tmp), maplist(cons(Var-Value), Tmp, NotChanged), maplist(cons(Var-not(Value)), Tmp, Changed), append(NotChanged, Changed, Ans). ``` ### Trojúhelníky v grafu Zdroj: [MFF Forum: 10. 9. 2019 - Dvořák](http://forum.matfyz.info/viewtopic.php?f=169&t=11977) Graf je zadán jako seznam svých vrcholů se seznamy sousedů (viz příklad). Definujte binární predikát ``troj(+Graf, -SeznamTrojuhelniku)`` který k takovému grafu vrátí seznam všech jeho trojúhelníků. Ve výsledném seznamu by se každý trojúhelník měl vyskytovat právě jednou (``t(a,b,c)``, ``t(b,c,a)`` a ``t(c,a,b)`` jsou stejné trojúhelníky). Příklad: ```prolog ?- troj([a-[b,c,d],b-[a,c],c-[a,b,d],d-[a,c],e-[]], S). S = [t(a,b,c), t(a,c,d)] ``` Řešení: ```prolog is_edge(Graph, From, To) :- member(From-Neighbours, Graph), member(To, Neighbours). neighbours(Graph, Node, Neighbours) :- member(Node-Neighbours, Graph). first(X-_, X). lift_list(X, [X]). all_paths(_, 0, []). all_paths(Graph, N, Paths) :- maplist(first, Graph, Nodes), maplist(lift_list, Nodes, Acc), all_paths(Graph, N, 1, Acc, Paths). all_paths(_, N, N, Acc, Acc) :- !. all_paths(Graph, MaxLen, Len, Paths, Ans) :- extend_paths(Graph, Paths, NewAcc), NewLen is Len + 1, all_paths(Graph, MaxLen, NewLen, NewAcc, Ans), !. extend_paths(Graph, Paths, Ans) :- extend_paths(Graph, Paths, [], Ans). push(Xs, X, [X | Xs]). extend_paths(_, [], Acc, Acc). extend_paths(Graph, [ Path | Ps ], Acc, Ans) :- Path = [ N | _ ], neighbours(Graph, N, Neighbours), maplist(push(Path), Neighbours, Tmp), append(Tmp, Acc, NewAcc), extend_paths(Graph, Ps, NewAcc, Ans). is_triangle([A, B, C, A]) :- A \= B, B \= C, A \= C. to_triangle([A, B, C, A], t(A, B, C)). is_congruent(t(A, B, C), t(A, B, C)). is_congruent(t(A, B, C), t(A, C, B)). is_congruent(t(A, B, C), t(B, A, C)). is_congruent(t(A, B, C), t(B, C, A)). is_congruent(t(A, B, C), t(C, A, B)). is_congruent(t(A, B, C), t(C, B, A)). deduplicate(Triangles, Ans) :- deduplicate(Triangles, [], Ans). deduplicate([], Acc, Acc). deduplicate([ Triangle | Ts ], Acc, Ans) :- exclude(is_congruent(Triangle), Ts, Filtered), deduplicate(Filtered, [Triangle | Acc], Ans). troj(Graph, Triangles) :- all_paths(Graph, 4, Paths), include(is_triangle, Paths, Filtered), maplist(to_triangle, Filtered, Tmp), deduplicate(Tmp, Triangles). ``` ### Generování výrokových formulí Zdroj: [MFF Forum: 24.6.2019 (Dvořák, Hric)](http://forum.matfyz.info/viewtopic.php?f=169&t=11969) Formule výrokového počtu jsou sestavené z (výrokových) proměnných ve funktoru ``var/1`` a logických spojek negace, konjunkce a disjunkce (bez konstant). Dále máte dány v argumentech predikátu ``gen/3`` číslo ``k`` pro velikost formule a seznam jmen proměnných. Generujte backtrackingem všechny logické formule (každou jednou), které obsahují proměnné ze seznamu a ve kterých je počet spojek a výskytů proměnných dohromady právě ``k``. Definujte predikát ``gen(+K, +Jmena, -Fle)``. Na pořadí generovaných formulí nezáleží, ale měli byste vygenerovat každou právě jednou. K řešení není potřeba predikát ``=../2`` (univ). Příklad: ```prolog ?- gen(4,[p],F). F = not(not(not(var(p)))); F = not(and(var(p),var(p))); F = not(or(var(p),var(p))); F = and(not(var(p)),var(p)); F = and(var(p),not(var(p))); F = or(not(var(p)),var(p)); F = or(var(p),not(var(p))); false. ``` Řešení: ```prolog gen(K, Vars, F) :- length(Slots, K), gen_(Slots, Vars, F). gen_([_], Vars, var(V)) :- member(V, Vars). gen_([_ | Ss], Vars, not(F)) :- gen_(Ss, Vars, F). gen_([_ | Ss], Vars, Ans) :- append(Left, Right, Ss), gen_(Left, Vars, F1), gen_(Right, Vars, F2), ( Ans = and(F1, F2) ; Ans = or(F1, F2) ). ``` ### Koncepty Zdroj: [MFF Forum: 24.6.2019 (Dvořák, Hric)](http://forum.matfyz.info/viewtopic.php?f=169&t=11969) Jeden objekt je zadán uspořádaným seznamem dvojic klíč-hodnota. Na vstupu máte seznam objektů. Napište proceduru ``koncept/2``, která vyrobí nejmenší koncept zahrnující všechny vstupní objekty. Koncept je seznam dvojic ``klíč-seznam_hodnot``. Koncept zahrnuje objekt, pokud koncept má všechny klíče objektu a v seznamu hodnot příslušného klíče u konceptu je obsažena hodnota klíče u objektu. Pokud objekt nějaký klíč konceptu nemá, bude v seznamu hodnot konceptu hodnota ``nedef``. Příklad: ```prolog ?- koncept([ [barva-modra, motor-diesel, pocet_kol-6], [barva-bila, motor-plyn, pocet_mist-40], [motor-elektro, pocet_mist-5] ], Koncept). Koncept = [ barva-[modra,bila,nedef], motor-[diesel,plyn,elektro], pocet_kol-[6,nedef], pocet_mist-[40,5,nedef] ] ``` Řešení: ```prolog collect_attributes(Objects, Attributes) :- collect_attributes(Objects, [], Attributes). collect_attributes([], Acc, Ans) :- sort(Acc, Ans). collect_attributes([ Object | Os ], Acc, Ans) :- collect_attributes_one(Object, Attrs), append(Attrs, Acc, NewAcc), collect_attributes(Os, NewAcc, Ans). collect_attributes_one(Object, Attributes) :- collect_attributes_one(Object, [], Attributes). collect_attributes_one([], Acc, Acc). collect_attributes_one([Key-_ | Ps], Acc, Ans) :- collect_attributes_one(Ps, [ Key | Acc ], Ans). koncept(Objects, Concepts) :- collect_attributes(Objects, Attrs), koncept(Objects, Attrs, [], Concepts). koncept([], _, Concepts, Concepts). koncept([ Object | Os ], Attrs, Concepts, Ans ) :- extend_concepts(Attrs, Object, Concepts, NewConcepts), koncept( Os, Attrs, NewConcepts, Ans ). extend_concepts([], _, Concepts, Concepts). extend_concepts([ Attr | Attrs ], Objects, Concepts, Ans) :- ( select(Attr-Value_, Objects, RestObjects) -> Value = Value_, NewObjects = RestObjects ; Value = nedef, NewObjects = Objects ), extend_concept(Attr-Value, Concepts, NewConcepts), extend_concepts(Attrs, NewObjects, NewConcepts, Ans). extend_concept(Attr-Value, Concepts, NewConcepts) :- ( select(Attr-Values, Concepts, RestConcepts) -> set_add(Values, Value, NewValues), NewConcept = Attr-NewValues, NewConcepts = [ NewConcept | RestConcepts ] ; NewValues = [ Value ], NewConcept = Attr-NewValues, NewConcepts = [ NewConcept | Concepts ] ). set_add(List, Element, Out) :- ( member(Element, List) -> Out = List ; Out = [ Element | List ] ). ``` ### Překrytí segmentů Zdroj: [MFF Forum: Zkouška 10.6.2019 (Dvořák + Hric)](http://forum.matfyz.info/viewtopic.php?f=169&t=11954) Máte dány dva řetězce, u kterých nevíte jejich vzájemnou orientaci. Najděte a vydejte v seznamu všechna jejich vzájemná neprázdná překrytí. Příklad: ```prolog ?- prekryti([a,t,c,t,c],[c,t,c,c], V). V = [a,t,c,t,c,t,c,c],[a,t,c,t,c,c],[a,t,c,t,c,c,t,c]] ``` Řešení: ```prolog id_or_reverse(X, X). id_or_reverse(X, Y) :- reverse(X, Y), X \= Y. prekryti(Xs, Ys, Out) :- prekryti_(Xs, Ys, [], Out). prekryti_(Xs, Ys, Acc, Out) :- is_prekryti(Xs, Ys, P), \+ member(P, Acc), prekryti_(Xs, Ys, [P | Acc], Out), !. prekryti_(_, _, Acc, Acc) :- !. is_prekryti(Xs, Ys, Out) :- id_or_reverse(Xs, X), id_or_reverse(Ys, Y), append(_, BodyTailX, X), append(HeadBodyY, TailY, Y), BodyTailX = HeadBodyY, BodyTailX \= [], append(X, TailY, Out). ``` ### Neporovnatelné prvky částečně uspořádané množiny Zdroj: [MFF Forum: Zkouška 10.6.2019 (Dvořák + Hric)](http://forum.matfyz.info/viewtopic.php?f=169&t=11954) Částečně uspořádaná množina je popsána seznamem termů tvaru ``x -> y`` s významem x pokrývá y (tj. ``x > y`` a současně ``x ≥ z ≥ y`` implikuje ``x = z`` nebo ``y = z``). Definujte predikát ``nepor/2``, který k takto zadané množině vrátí seznam všech dvojic vzájemně neporovnatelných prvků (tj. dvojic ``x``,``y`` takových, že neplatí ``x ≥ y`` ani ``x ≤ y``). Příklad: ```prolog ?- nepor([a->b, a->c, b->d, e->f], N). N = [a-e,a-f,b-c,b-e,b-f,c-d,c-e,c-f,d-e,d-f] ``` Řešení: ```prolog ge(_, X, X). ge(R, X, Y) :- member(X -> Y, R). ge(R, X, Y) :- member(X -> Z, R), ge(R, Z, Y). collect_variables(Rel, Vars) :- collect_variables_(Rel, [], Vars). collect_variables_( [], Acc, Ans) :- sort(Acc, Ans). collect_variables_( [X -> Y | Rs], Acc, Ans) :- collect_variables_(Rs, [X, Y | Acc], Ans). pair(X, Y, X-Y). pairs(Vars, Pairs) :- select(Var, Vars, RestVars), !, maplist(pair(Var), RestVars, Tmp), pairs(RestVars, Ans), append(Tmp, Ans, Pairs). pairs([], []). is_nepor(R, X-Y) :- \+ ge(R, X, Y), \+ ge(R, Y, X). nepor(Rel, Out) :- collect_variables(Rel, Vars), pairs(Vars, Pairs), include(is_nepor(Rel), Pairs, Out). ``` ### Lexikograficky předchozí permutace Zdroj: [MFF Forum: Zkouška 21.6.2018](http://forum.matfyz.info/viewtopic.php?f=169&t=11747) Nalezněte lexikograficky předchozí permutaci. Pokud neexistuje tak ``false``. Řešení: ```prolog ?- prev([1,2,6,3,4,5,7],V). V = [1,2,5,7,6,4,3] ``` ```prolog find_longest_ascending([], [], []). find_longest_ascending([X], [X], []). find_longest_ascending([X1, X2 | Xs], [X1], [X2 | Xs]) :- X1 > X2, !. find_longest_ascending([X1, X2 | Xs], [ X1 | Ans ], Rest) :- X1 < X2, find_longest_ascending([X2 | Xs], Ans, Rest). replace([], _, _, []). replace([X | Xs], X, Y, [Y | Xs]) :- !. replace([R | Xs], X, Y, [R | Ans]) :- replace(Xs, X, Y, Ans). prev(Perm, Prev) :- find_longest_ascending(Perm, Asc, Rest), reverse(Asc, Rev), member(X, Rev), Y is X - 1, member(Y, Rest), !, replace(Asc, X, Y, NewAsc), replace(Rest, Y, X, NewRest), reverse(NewRest, FinalRest), append(NewAsc, FinalRest, Prev). ``` ### Frekvence Zdroj: [MFF Forum: Zkouška 26.6.2018](http://forum.matfyz.info/viewtopic.php?f=169&t=11756) Definujte predikát ``frekv/2``, který obdrží seznam konstant a vrátí frekvence dvojic za sebou jdoucích konstant. Výstupní reprezentaci si zvolte a popište pro vzorový vstup. ```prolog ?- frekv([a,b,a,b,c], P). P = [f(a-b,2), f(b-a,1), f(b-c,1)] ``` Řešení: ```prolog frekv(List, Freq) :- frekv_(List, [], Freq), !. frekv_([], Acc, Acc). frekv_([_], Acc, Acc). frekv_([ X1, X2 | Xs ], Freq, Ans) :- increase_frequency(X1-X2, Freq, NewFreq), frekv_([ X2 | Xs ], NewFreq, Ans). increase_frequency(X-Y, Freq, [f(X-Y, NewN) | Rest]) :- select(f(X-Y, N), Freq, Rest), NewN is N + 1, !. increase_frequency(X-Y, Freq, [f(X-Y, 1) | Freq]). ``` ### Časové ohodnocení DFS Zdroj: [MFF Forum: Zkouška 26.6.2018](http://forum.matfyz.info/viewtopic.php?f=169&t=11756) Je dán orientovaný acyklický graf (DAG) o ``n`` vrcholech pomocí seznamu sousedů. Procedura ``dfs/2`` projde graf do hloubky a přidá k vrcholům časy otevření a uzavření v rozsahu od ``1`` do ``2n``. Na pořadí vrcholů na výstupu nezáleží: Definujte predikát ``dfs/2`` a napište konkrétní výstup vašeho programu na vzorovém grafu z příkladu níže. Příklad: ```prolog ?- dfs([c-[d], a-[b,c], b-[d,e], d-[], e-[]], V). V = [v(a,1,10,[b,c]), v(c,2,5,[d]), v(d,3,4,[]), v(b,6,9,[e]), v(e,7,8,[])] ``` Řešení: ```prolog dfs(Graph, Out) :- member(Start-_, Graph), Stack = [ Start ], Opened = [], Closed = [], Time = 1, dfs_(Graph, Stack, Time, Opened, Closed, Out). dfs_(_, [ ], _, _, Out, Out). dfs_(Graph, [ Vertex | Vs ], Time, Opened, Closed, Out) :- member(t(Vertex, _, _), Closed), dfs_(Graph, Vs, Time, Opened, Closed, Out), !. dfs_(Graph, [ Vertex | Vs ], Time, Opened, Closed, Out) :- NewTime is Time + 1, ( select(Vertex-InTime, Opened, NewOpened) -> dfs_( Graph, Vs, NewTime, NewOpened, [t(Vertex, InTime, Time) | Closed], Out ) ; member(Vertex-Neigbours, Graph), append(Neigbours, [Vertex | Vs], NewStack), dfs_( Graph, NewStack, NewTime, [ Vertex-Time | Opened ], Closed, Out ) ). ``` ### Splay Zdroj: [MFF Forum: Zkouška 6. 6. 2016 (Dvořák, Hric)](http://forum.matfyz.info/viewtopic.php?f=169&t=10961) Naprogramujte predikát ``splay(+Hodnota, +BinarniVyhledavaciStrom, -Vysledek)``, který provede funkci ``splay`` (přesune daný vrchol až do kořene pomoci rotací) na ``Hodnotu``. Pokud ``Hodnota`` ve stromě není, pak se splay provede na bezprostredního předchůdce/následníka. ```prolog TestTree = tree( tree( tree( tree(null, 1, null), 2, tree(null, 3, null) ), 4, tree(null, 5, null) ), 6, tree( tree(null, 7, null), 8, tree(null, 9, null) ) ). ``` Řešení: ```prolog splay(X, T, T) :- T = tree(_, X, _), !. splay(X, T, Out) :- T = tree(Left, Y, Right), ( X < Y -> splay(X, Left, Ans), Ans = tree(LeftAns, Z, RightAns), Out = tree(LeftAns, Z, tree(RightAns, Y, Right)) ; splay(X, Right, Ans), Ans = tree(LeftAns, Z, RightAns), Out = tree(tree(Left, Y, LeftAns), Z, RightAns) ), !. splay(_, T, T) :- T = tree(null, _, null). ``` ### Skládání konstantních úseků Zdroj: [MFF Forum: Zkouška 6. 6. 2016 (Dvořák, Hric)](http://forum.matfyz.info/viewtopic.php?f=169&t=10961) Na vstupu máme seznam po částech konstantních funkcí ``Fs``, kde každá funkce je ve tvaru ``DelkaUseku-Hodnota``. Všechny funkce začinají v ``0`` a po konci posledního useku pokračují hodnotou ``0``. Máme vytvořit nejmenší novou funkci takovu, že v každém bodě je větší rovna všem zadaným funkcím. Příklad: Dvě funkce: první má na intervalu ``[0, 2)`` hodnotu 5, na intervalu ``[2, 4)`` hodnotu 3 a na intervalu ``[4, inf)`` hodnotu 0. Druhá má na intervalu ``[0, 3)`` hodnotu 4 a na intervalu ``[3, inf)`` hodnotu 0. Vysledkem je funkce ``[2-5, 1-4, 1-3]``. ```prolog ?- combine([[2-5, 2-3], [3-4]], G) G = [2-5, 1-4, 1-3] ``` Řešení: ```prolog combine([], []). combine([ Base | Fs ], G) :- combine_(Fs, Base, G). combine_([], Base, Base). combine_([F | Fs], Base, Out) :- extend_base(Base, F, NewBase), combine_(Fs, NewBase, Out), !. extend_base(Base, [], Out) :- merge_adjecent(Base, Out). extend_base([], F, F). extend_base([ Length-BaseVal | BLVs ], [ Length-FVal | FLVs], Out) :- extend_base(BLVs, FLVs, Ans), ( BaseVal > FVal -> Out = [Length-BaseVal | Ans] ; Out = [Length-FVal | Ans] ). extend_base([ BaseLength-BaseVal | BLVs ], [ FLength-FVal | FLVs], Out) :- ( BaseLength < FLength -> Remainder is FLength - BaseLength, extend_base( [ BaseLength-BaseVal | BLVs ], [ BaseLength-FVal, Remainder-FVal | FLVs], Out ) ; Remainder is BaseLength - FLength, extend_base( [ FLength-BaseVal, Remainder-BaseVal | BLVs ], [ FLength-FVal | FLVs], Out ) ). merge_adjecent([], []). merge_adjecent([P], [P]). merge_adjecent([Length1-Val, Length2-Val | LVs], Out) :- NewLength is Length1 + Length2, merge_adjecent([NewLength-Val | LVs], Out), !. merge_adjecent([X, Y | LVs], [X | Out]) :- merge_adjecent([Y | LVs], Out). ``` ### Kružnice v grafu Zdroj: [MFF Forum: Zkouška 22.6.](http://forum.matfyz.info/viewtopic.php?f=169&t=11412) Máme daný orientovaný graf reprezentovaný jako ``[vrchol-[seznam sousedů]|...]``, zjistěte, zda v něm je orientovaná kružnice, a pokud ano, vraťte vrcholy nějaké takové kružnice v tom pořadí, jak jsou na kružnici. Chce se polynomiální řešení. Příklad: ```prolog ?- cycle([a-[b,c,d],b-[c],c-[a,b,d],d-[a,c],e-[]], C) C = [a, c, b] ``` Řešení: ```prolog cycle(Graph, Cycle) :- member(Start-_, Graph), Stack = [Start], Path = [], cycle_(Graph, Stack, Path, Cycle), !. cycle_(Graph, [ Vertex | Vs ], Path, Out) :- ( member(Vertex, Path) -> append(Cs, [Vertex | _], Path), Out = [Vertex | Cs] ; member(Vertex-Neighbours, Graph), append(Neighbours, Vs, NewStack), cycle_(Graph, NewStack, [Vertex | Path], Out) ). ``` ### Vypustění nejvýše dvou prvků Zdroj: [MFF Forum: Zkouska 20.9.2017](http://forum.matfyz.info/viewtopic.php?f=169&t=11466) Definujte predikát ``dif/2``, který obdrží seznam ``S``, a vrátí seznam všech seznamů, které obdržíte z ``S`` vypuštěním nejvýše 2 prvků. Pořadí prvků ve výstupních seznamech se nemění. Příklad: ```prolog ?- dif([a,b,c],D). D=[[a,b,c], [b,c], [a,c], [a,b], [a], [c]] ``` Řešení: ```prolog smaller_than(N, Xs) :- length(Xs, K), K < N. dif(List, Out) :- length(List, N), MinSize is N - 2, dif_(List, Ans), exclude(smaller_than(MinSize), Ans, Out). cons(X, Xs, [X | Xs]). dif_([], [[]]). dif_([X | Xs], Out) :- dif_(Xs, Tmp), maplist(cons(X), Tmp, Appended), append(Appended, Tmp, Out). ``` ### Vrcholové pokrytí minimální k inkluzi Zdroj: [MFF Forum: Zkouska 20.9.2017](http://forum.matfyz.info/viewtopic.php?f=169&t=11466) Je zadán neorientovaný graf ``G`` a jeden jeho vrchol ``v``. Definujte predikát ``pokryti/3,`` který postupně vrátí všechna vrcholová pokrytí grafu ``G``, která obsahují vrchol ``v`` a jsou minimální vzhledem k inkluzi. Množina vrcholů ``V`` grafu je vrcholovým pokrytím, pokud každá hrana má alespoň jeden vrchol v množině ``V``. 1. Na příkladě popište, jakou reprezentaci grafu budete používat. 2. Definujte predikát ```pokryti(+Graf, +Vrchol, -VPokrytí)``` kde Graf je zadán v reprezentaci popsané v **1.)**. Řešení: ```prolog collect_nodes(Graph, Nodes) :- collect_nodes(Graph, [], NodesDup), sort(NodesDup, Nodes), !. collect_nodes([], Acc, Acc). collect_nodes([Node-Neighbours | Ns], Acc, Ans) :- append([Node | Neighbours], Acc, NewAcc), collect_nodes(Ns, NewAcc, Ans). covers(Cover, U, V) :- member(U, Cover); member(V, Cover). is_cover([], _). is_cover([ Vertex-Neighbours | Rest ], Cover) :- maplist(covers(Cover, Vertex), Neighbours), is_cover(Rest, Cover). pokryti(Graph, Vertex, Cover) :- collect_nodes(Graph, Nodes), select(Vertex, Nodes, Rest), !, pokryti_(Graph, Vertex, Rest, Cover). pokryti_(Graph, Vertex, [], [Vertex]) :- is_cover(Graph, [Vertex]). pokryti_(Graph, Vertex, Nodes, Cover) :- ( select(_, Nodes, Rest), is_cover(Graph, [Vertex | Rest]) -> pokryti_(Graph, Vertex, Rest, Cover) ; Cover = [Vertex | Nodes] ). ``` ### Rozděl Zdroj: [MFF Forum: Zkouška 13. 9. 2017](http://forum.matfyz.info/viewtopic.php?f=169&t=11457) Napište predikát ``rozdel(+Mnozina,-Rozdeleni)``, který rozdělí množinu na neprázdné podmnožiny. Všechny možnosti rozdělení pak vrátí spojené v jednom seznamu. Příklad: ```prolog ?- rozdel([a,b,c],X). X = [[a, b, c], [[a, b], [c]], [[a], [b, c]], [[a, c], [b]], [[a], [b], [c]]]. ``` Řešení: ```prolog is_subset([], [], []). is_subset([ X | Xs ], [ X | Ys ], Zs) :- is_subset( Xs, Ys, Zs ). is_subset([ X | Xs ], Ys, [ X | Zs ]) :- is_subset( Xs, Ys, Zs ). rozdel_nedet([], []). rozdel_nedet([ X | Xs ], Division) :- is_subset(Xs, S, Rest), rozdel_nedet(Rest, Ds), Division = [ [ X | S ] | Ds]. rozdel(List, Divisions) :- rozdel_(List, [], Divisions). rozdel_(List, Acc, Divisions) :- rozdel_nedet(List, Division), \+ member(Division, Acc), rozdel_(List, [ Division | Acc ], Divisions), !. rozdel_(_, Acc, Acc). ``` ### Nezávislé množiny Zdroj: [MFF Forum: Zkouška 13. 9. 2017](http://forum.matfyz.info/viewtopic.php?f=169&t=11457) Napište predikát ``nez(+Graf,+Vrchol.-NezMn)``, který vezme graf a jeden jeho zadaný vrchol a postupně vydává všechny jeho největší nezávislé množiny obsahující daný vrchol. Příklad: ```prolog nez(g([a,b,c,d,e],[a-b,b-c,b-d,c-d]),a,X). X=[a,c,e]; X=[a,d,e]. ``` Řešení: ```prolog is_edge(g(_, Edges), U, V) :- member(U-V, Edges), !; member(V-U, Edges), !. is_not_edge(Graph, U, V) :- \+ is_edge(Graph, U, V). is_independent(_, []) :- !. is_independent(Graph, [ V | Vs ]) :- maplist(is_not_edge(Graph, V), Vs), is_independent(Graph, Vs). is_subset([], []). is_subset([ X | Xs ], [ X | Ys ]) :- is_subset(Xs, Ys). is_subset([_ | Xs], Ys) :- is_subset(Xs, Ys). nez(Graph, Vertex, MaxIndSet) :- g(Vertices, _) = Graph, length(Vertices, N), nez_(Graph, N, MaxIndSet), !, member(Vertex, MaxIndSet). nez_(Graph, N, IndSet) :- N > 0, Graph = g(Vertices, _), length(IndSet, N), is_subset(Vertices, IndSet), is_independent(Graph, IndSet). nez_(Graph, N, IndSet) :- NewN is N - 1, NewN > 0, nez_(Graph, NewN, IndSet). ``` ### Cykly délky alespoň N Zdroj: [MFF Forum: Zkouška 6. 6. 2017](http://forum.matfyz.info/viewtopic.php?f=169&t=11380) Na vstupu máme graf reprezentovaný jako ```prolog graf(SeznamVrcholu, SeznamHran) ``` (bylo ale dovoleno si reprezentaci grafu změnit) a číslo ``N``. Máme určit, jestli v grafu existuje cyklus délky alespoň ``N``. Pokud ano, program alespoň jeden takový cyklus vypíše, pokud ne, vrátí fail. *Pozn.: Problém je NP-úplný, tzn. očekává se řešení typu hrubá síla.* Řešení: ```prolog subsets([], []). subsets([ H | T ], [ H | Out ]) :- subsets(T, Out). subsets([ _ | T], Out) :- subsets(T, Out). is_edge(graph(_, Edges), U, V) :- member(U-V, Edges), !; member(V-U, Edges), !. is_path(Graph, [], Start, End) :- is_edge(Graph, Start, End). is_path(Graph, Vertices, Start, End) :- select(Vertex, Vertices, Rest), is_edge(Graph, Start, Vertex), is_path(Graph, Rest, Vertex, End). is_cycle(Graph, Vertices) :- select(Start, Vertices, Rest), is_path(Graph, Rest, Start, Start), !. cycle_n(Graph, N, Cycle) :- graph(Vertices, _) = Graph, length(Vertices, K), K >= N, !, between(N, K, N_), length(Cycle, N_), subsets(Vertices, Cycle), is_cycle(Graph, Cycle), !. ``` ### Termy Zdroj: [MFF Forum: Zkouška 29.5.2017](http://forum.matfyz.info/viewtopic.php?f=169&t=11357) Sestavte predikát ``termy/1``, který postupně vrací termy složené z funktorů ``bin/2``, ``un/1`` a ``const/0``. Výstupem bude tedy korektně sestavený term. Predikát by měl postupně vrátit všechna řešení, sice v libovolném pořadí, ovšem každé právě jednou. Příklad: ```prolog ?- termy(V). V=const; V=un(const); V=bin(const,const); V=un(un(const)); V=un(bin(const,const)); V=bin(un(const),un(const)); ``` Řešení: ```prolog termy(V) :- length(Slots, _), termy_(Slots, V). termy_([_], const). termy_([_ | Slots], un(T)) :- termy_(Slots, T). termy_([_ | Slots], bin(T1, T2)) :- append(S1, S2, Slots), termy_(S1, T1), termy_(S2, T2). ``` ### !! Porovnání multimnožin Zdroj: [MFF Forum: Zkouška 29.5.2017](http://forum.matfyz.info/viewtopic.php?f=169&t=11357) Multimnožinu lze specifikovat seznamem termů ``Prvek-Pocet``. Sestavte predikát ``mensi/2``, který porovná multimnožiny ``A`` a ``B`` následovně: - ``mensi(A,B)`` je ``true`` právě tehdy, pokud v ``B`` existuje nějaký prvek, co není v ``A`` takový, že je větší než všechny prvky z ``A``, které nejsou v ``B``. ```prolog ?- mensi([c-3,b-2,a-1],[d-1,b-3]) true ?- mensi([c-3,b-2,a-1],[c-1,b-3]) fail ``` Řešení: ??? ### Plánování výroby Zdroj: [MFF Forum: Zkouška 13. 6. 2016 (Dvořák, Hric)](http://forum.matfyz.info/viewtopic.php?f=169&t=10963) Napište predikát, který naplánuje pokud možno optimální (nutné použít nějakou jednoduchou heuristiku) rozvrh výroby na strojích. Na vstupu je seznam délek operací (např. ``[3,3,2,6,4]``) a maximální čas běhu (např. ``10``). Operace je možné plánovat na paralelně běžící stroje, chceme, aby celkový počet potřebných strojů byl co nejmenší. Výstupem má být nějaké optimální rozložení operací pro jednotlivé stroje (např. ``[[3,3,2],[6,4]]``, což znamená, že použijeme dva stroje, první z nich vykoná operace trvající ``3``, ``3`` a ``2`` úseky, druhý operace trvající ``6`` a ``4`` časové úseky, obojí se vejde do limitu ``10`` časových úseků / stroj). Řešení: ```prolog sum(List, Sum) :- sum_(List, 0, Sum). sum_([], Acc, Acc). sum_([X | Xs], Acc, Out) :- NewAcc is Acc + X, sum_(Xs, NewAcc, Out). plan(Times, MaxTime, Plan) :- msort(Times, TimesSorted), plan_(TimesSorted, MaxTime, [], Plan). plan_([], _, Plan, Plan). plan_([T | Ts], MaxTime, Plan, Out) :- extend_plan(Plan, MaxTime, T, NewPlan), plan_(Ts, MaxTime, NewPlan, Out). extend_plan([], MaxTime, T, [[T]]) :- MaxTime >= T. extend_plan([ P | Ps ], MaxTime, T, Out) :- sum(P, PlanTime), Free is MaxTime - PlanTime, ( Free >= T -> Out = [ [ T | P ] | Ps ] ; extend_plan(Ps, MaxTime, T, Ans), Out = [ P | Ans ] ). ``` ### Listy stromu podle počtu kroků vpravo Zdroj: [MFF Forum: Zkouška 13. 6. 2016 (Dvořák, Hric)](http://forum.matfyz.info/viewtopic.php?f=169&t=10963) Máte zadaný binární strom (klasická ``tree(vlevo, hodnota, vpravo)`` notace). Roztřiďte vrcholy podle toho, kolikrát musíme jít doprava, než je objevíme. Řešení: ```prolog group_by_right_turns(null, []). group_by_right_turns(tree(Left, Val, Right), Out) :- group_by_right_turns(Left, LeftAns), group_by_right_turns(Right, RightAns), merge_(LeftAns, [ [Val] | RightAns], Out). merge_(Xs, [], Xs). merge_([], Ys, Ys). merge_([X | Xs], [ Y | Ys], [ Z | Ans ]) :- append(X, Y, Z), merge_(Xs, Ys, Ans). ``` ### Maximální párování dle inkluze Zdroj: [MFF Forum: Zkouška 28.6.2016 - Dvořák, Hric](http://forum.matfyz.info/viewtopic.php?f=169&t=10993) Napište predikát ``parovani(+G, +H, -P)``, který bere neorientovaný graf ``G`` bez smyček (tj. reflexivních hran) zadaný jako seznam následníků, hranu ``H`` v podobě ``(v1-v2)`` a vydá co do inkluze maximální párování obsahující zadanou hranu ``H`` (pozor: nikoli největší párování, ale pouze maximální co do inkluze). Například: ```prolog ?- parovani([a-[b,c,d],b-[a,c],c-[a,b,d],d-[a,c],e-[]],a-d,P) P = [a-d,b-c]. ``` Řešení: ```prolog parovani(Graph, Edge, MaxMatching) :- Edge = U-V, select(U-_, Graph, Tmp), select(V-_, Tmp, RestOfGraph), parovani_(RestOfGraph, [U, V], Ans), MaxMatching = [ Edge | Ans ]. parovani_([], _, []). parovani_(Graph, Taken, [ U-V | Ans ]) :- select(U-Neighbours, Graph, RestOfGraph), member(V, Neighbours), \+ member(V, Taken), parovani_(RestOfGraph, [U, V | Taken], Ans), !. parovani_(_, _, []). ``` ### Generování všech možných výrazů Zdroj: [MFF Forum: Zkouška 30. 05. 2016 (Dvořák, Hric)](http://forum.matfyz.info/viewtopic.php?f=169&t=10934) Na vstupu dostaneme posloupnost čísel a číslo ``V``. Máme vrátit všechny možné matematické výrazy, které lze z dané posloupnosti postavit pomocí operátorů ``+``, ``-``, ``*``, ``//`` a ``závorek``, a jejichž hodnota je ``V``. Výraz musí využít všechna zadaná čísla, a jejich pořadí nesmí měnit. Dále si máme dávat pozor, abychom ve výrazu nedělili nulou. Řešení: ```prolog gen_expr(List, V, Expr) :- gen_expr_(List, Expr), V is Expr. gen_expr_([Expr], Expr). gen_expr_(Xs, Expr) :- select(X, Xs, Ys), select(Y, Ys, Zs), !, ( E = X + Y ; E = X - Y ; E = X * Y ; Denom is Y, Denom \= 0, E = X // Y ), gen_expr_([E | Zs], Expr). ``` ### !! Zlepšení řezu Zdroj: [MFF Forum: Zkouška 19.06.2015 - Dvořák, Hric](http://forum.matfyz.info/viewtopic.php?f=169&t=10536) Napište predikát ``zlepsirez(+Graf, +Vrcholy1, +Vrcholy2, -OutV)``, který pro zadaný ohodnocený neorientovaný graf ``Graf`` a řez (definovaný pomocí dvou disjunktních množin vrcholů ``Vrcholy1`` a ``Vrcholy2``) najde vrchol, který když přesuneme do opačné skupiny vrcholů řezu, tak dostaneme řez s lepší cenou. ### Ohodnocení stromu post- a pre-order Zdroj: [MFF Forum: Zkouška 2. 6. 2015 (Dvořák, Hric)](http://forum.matfyz.info/viewtopic.php?f=169&t=10479) Definujte predikát ``transverse(+Strom,-OhodnocenýStrom)``, který zkopíruje strukturu stromu ``Strom`` do ``OhodnocenýStrom`` s tím, že ke každému vrcholu přidá číslo ``N``, které znamená pořadí v preOrder průchodu a číslo ``M``, které znamená pořadí v postOrder průchodu. Ideálně jedním průchodem stromem. Příklad ```prolog ?- transverse(t(t(nil,l,nil),v,t(nil,p,nil)),X). X = t(t(nil,l-2-1,nil),v-1-3,t(nil,p-3-2,nil)) ``` Řešení: ```prolog transverse(Tree, Out) :- transverse(Tree, 0, 0, _, _, Out), !. transverse(nil, PreOrder, PostOrder, PreOrder, PostOrder, nil). transverse(Tree, PreOrderIn, PostOrderIn, PreOrderOut, PostOrderOut, Out) :- Tree = t(Left, Val, Right), NewPreOrder is PreOrderIn + 1, transverse( Left, NewPreOrder, PostOrderIn, PreOrderOutLeft, PostOrderOutLeft, LeftAns ), transverse( Right, PreOrderOutLeft, PostOrderOutLeft, PreOrderOutRight, PostOrderOutRight, RightAns ), PreOrderOut = PreOrderOutRight, PostOrderOut is PostOrderOutRight + 1, Out = t(LeftAns, Val-NewPreOrder-PostOrderOut, RightAns). ``` ### Rotace seznamu Zdroj: [MFF Forum: Zkouška 25. 5. 2014 (Dvořák, Hric)](http://forum.matfyz.info/viewtopic.php?f=169&t=10462) - napište predikát ``rotace/2`` na rotování seznamu. Nesmíte použít žádné pomocné predikáty. (V lineárním čase) (pouze 3 verze) - v konstantním čase, jakou potřebujete strukturu? Ukažte na ``[1,2,3]`` - napište ``rotace/2`` (pouze 2 verze) v konstantním čase Řešení: ```prolog rotace1([], []). rotace1([X | Xs], Out) :- rotace1_(Xs, X, Out). rotace1_([], X, [X]). rotace1_([Y | Ys], X, [ Y | Out ]) :- rotace1_(Ys, X, Out). append_dif(X-Xs, Y-X, Y-Xs). rotace2(P-[ X | Xs ], Out) :- append_dif(P-Xs, NewP-[ X | NewP ], Out). ``` ### Izomorfizmus bin. stromů s popisem Zdroj: [MFF Forum: Zkouška 12.7.2021](http://forum.matfyz.info/viewtopic.php?f=169&t=12193) Jsou zadány dva binární (zakořeněné) stromy ``S`` a ``T`` s ohodnocenými vrcholy, přičemž ohodnocení vrcholů se může opakovat. Definujte predikát ``iso/3``, který zjistí, zdali jsou tyto stromy isomorfní a vydá popis transformace. Volání je ``iso(+S,+T, -Popis)``, kde ve třetím argumentu bude popis. Popis je strom stejného tvaru jako ``S`` a ve vrcholech má boolovské hodnoty ``true`` a ``false``. Hodnota ``true`` ve vrcholu znamená, že se děti vrcholu v ``S`` mají přehodit, abychom dostali ``T``. Dva binární stromy jsou isomofní, pokud lze jeden získat z druhého permutací dětí libovolných vrcholů stromu, tj. vyměněním nebo nevyměněním podstromů vrcholu. 1. Navrhněte reprezentaci binárního (zakořeněného) stromu s ohodnocenými vrcholy v jazyce Prolog. Vaši reprezentaci ukažte na příkladě. 2. Definujte predikát ``iso/3``. 3. Je některý z predikátů, které ve vašem řešení používáte (ať už vámi definovaných či knihovních), nedeterministický? Je predikát ``iso/3`` nedeterministický? Lze ho zdeterminičtit (a jak?), pokud nám stačí nejvýš jedno řešení? Příklad: ```none S= d T= d Popis= t /---\ /---\ /---\ b e e b f t / \ / \ / \ / \ / \ / \ a c f g g f a c f f f f ``` ```prolog S = t( t( t(nil, a, nil), b, t(nil, c, nil) ), d, t( t(nil, f, nil), e, t(nil, g, nil) ) ). T = t( t( t(nil, g, nil), e, t(nil, f, nil) ), d, t( t(nil, a, nil), b, t(nil, c, nil) ) ). ``` Řešení: ```prolog iso(TreeA, TreeB, Transform) :- transform(TreeA, Transform, TreeB), !. transform(nil, nil, nil). transform(t(Left, Val, Right), t(TransLeft, Bool, TransRight), TreeOut) :- transform(Left, TransLeft, LeftOut), transform(Right, TransRight, RightOut), ( Bool = false, TreeOut = t(LeftOut, Val, RightOut) ; Bool = true, TreeOut = t(RightOut, Val, LeftOut) ). ``` ### FirstFit Dostanete informaci o obsazené paměti jako seznam dvojic ``zacatek-konec`` o jednotlivých obsazených úsecích. Úseky jsou v seznamu uspořádány vzestupné a nenavazují bezprostředně na sebe (tj. navazující úseky se spojí) a tyto invarianty chcete udržovat. Dále dostanete seznam délek úseků, které máte naalokovat. Napište predikát ```prolog firstFit(+Aalokovat, +Obsazeno, -Umisteni, -ObsszenoO) ``` , který naalokuje postupně všechny požadavky z ``Alokovat`` metodou firstFit, tj. alokuje na první místo, kde se úsek vejde a tím ho obsadí. Vydejte nový popis obsazených úseků, ve tvaru splňujicím invariant, a popis umístění jako seznam dvojic ``delkaUseku-umisteni`` ve stejném pořadíjako v ``Alokova``. Příklad: ```prolog ?- firstFit([100,117,501, 10-50, 1P0-150, 250-1001, U, O). U = [100-150,10-50,50-100], O = [0-60, 100-150] ``` Řešení: ```prolog first_fit([], Obsazeno, [], Obsazeno). first_fit([H | T], Obsazeno, [ H-U | UmistnenoAns], ObsazenoOut) :- first_fit_one(H, Obsazeno, 0-0, U, ObsazenoTmp), first_fit(T, ObsazenoTmp, UmistnenoAns, ObsazenoOut), !. first_fit_one(Size, [], LastFrom-LastTo, LastTo, [LastFrom-NewTo]) :- NewTo is LastTo + Size. first_fit_one(Size, [From-To | Rest], LastFrom-LastTo, LastTo, ObsazenoOut) :- Free is From - LastTo, Free >= Size, ObsazenoOut = ObsazenoOut_, ( Free is Size -> ObsazenoOut_ = [ LastFrom-To | Rest] ; NewTo is LastTo + Size, ObsazenoOut_ = [ LastFrom-NewTo, From-To | Rest] ). first_fit_one(Size, [From-To | Rest], LastFrom-LastTo, OutPos, ObsazenoOut) :- Free is From - LastTo, Free < Size, first_fit_one(Size, Rest, From-To, OutPos, ObsazenoAns), ObsazenoOut = ObsazenoOut_, ( 0 is LastTo -> ObsazenoOut_ = ObsazenoAns ; ObsazenoOut_ = [LastFrom-LastTo | ObsazenoAns] ). ``` ### Otočení v sekvenci Na vstupu je daný seznam ``S`` nějakých položek, například RNA bází. Chcete vydat seznam seznamú položek ``Vs`` jako seznam výsledků, který vznikne otočením nějaké souvislé části ``S`` délky aspoň ``2`` všemi možnými zpüsoby. Napište predikát ``otoceni(+S, -Vs)``. Přiklad: ```Prolog ?- otoceni([ a, c, g, t], Vs). Vs = [[c, a, g, t], [g, c, a, t], [t, g, c, a], [a, t, g, c], [a, c, t, g]] ``` Řešení: ```prolog je_otoceni(List, Out) :- append(Front, MidBack, List), append(Mid, Back, MidBack), length(Mid, N), N >= 2, reverse(Mid, MidRev), append(MidRev, Back, Tmp), append(Front, Tmp, Out). otoceni(List, Out) :- otoceni_(List, [], Out). otoceni_(Xs, Acc, Out) :- je_otoceni(Xs, P), \+ member(P, Acc), otoceni_(Xs, [P | Acc], Out), !. otoceni_(_, Acc, Acc) :- !. ``` ## Haskell ### Největší součet souvislé podposloupnosti Zdroj: [MFF Forum: Zkoužka 4.9.2020](http://forum.matfyz.info/viewtopic.php?f=169&t=12095) 1. Pro zadanou posloupnost čísel najděte spojitý úsek, jehož součet je největší. Vydejte souřadnice začátku a konce úseku a dosažený součet. ```haskell soucty :: Num a => [a] → (Int, Int, a) ``` Pokuste se o nějakou optimalizaci, tj. nepočítejte součty hrubou silou (zcela samostatně). Příklad: (indexováno od 0) ```haskell > soucty [-1,1,2,3,-4] (1,3,6) ``` 2. Jaký význam má část ``Num a =>`` v definici funkce soucty ? Proč tam musí být? 3. Uveďte dvě možné konkrétní hodnoty proměnné a z typu funkce soucty. 4. Lze definovat ``Num a``taky pro uživatelské typy nebo musíme použít pouze předdefinované/vestavěné? Lze naši funkci soucty použít pro nějaký uživatelský typ na místě ``a`` ? (Proč ano/ne?) Řešení: ```haskell scan :: (b -> a -> b) -> b -> [a] -> [b] scan _ acc [] = [acc] scan f acc (x : xs) = acc : scan f (f acc x) xs soucty :: (Num a, Ord a) => [a] -> (Int, Int, a) soucty [] = (0, 0, 0) soucty xs = foldr partSum (0, 0, 0) [ (from, to - 1, cumsum !! to - cumsum !! from) | from <- [0 .. length xs] , to <- [from + 1 .. length xs] ] where cumsum = scan (+) 0 xs partSum :: Ord b => (a, a, b) -> (a, a, b) -> (a, a, b) partSum a@(_, _, x) b@(_, _, y) | x > y = a | otherwise = b ``` ### Rekonstrukce binárního stromu Zdroj: [MFF Forum: Zkoužka 4.9.2020](http://forum.matfyz.info/viewtopic.php?f=169&t=12095) Binární vyhledávací strom je zadán jako seznam hodnot vrcholů v pořadí preorder. Definujte funkci ```haskell readBt :: Ord a => [a] -> Bt a ``` která ze zadaného seznamu zrekonstruuje původní strom typu ```haskell data Bt a = Void | Node (Bt a) a (Bt a) ``` Připomeňme, že v binárním vyhledávacím stromu platí pro každý vrchol ``v``, že všechny hodnoty v levém, resp. pravém podstromu v jsou menší, resp. větší nežli hodnota ``v``. Odtud plyne, že původní strom je zadaným seznamem určen jednoznačně. Příklad: ```haskell > readBt [5, 2, 4, 9] Node (Node Void 2 (Node Void 4 Void)) 5 (Node Void 9 Void) ``` 1. Definujte funkci ``readBt``. 2. Je ve vašem řešení použita nějaká funkce vyššího řádu (funkce s funkcionálními argumenty)? Pokud ne, dala by se zde nějaká smysluplně použít? 3. Je ve vašem řešení použita notace stručných seznamů (list comprehension), tj. ``[... | ...]`` ? Pokud ne, dala by se zde smysluplně použít? Řešení: ```haskell data Bt a = Void | Node (Bt a) a (Bt a) deriving (Eq, Show) readBt :: Ord a => [a] -> Bt a readBt [] = Void readBt (x : xs) = Node leftAns x rightAns where left = takeWhile (<= x) xs right = dropWhile (<= x) xs leftAns = readBt left rightAns = readBt right ``` ### Rostoucí posloupnosti Zdroj: [MFF Forum: Zkouška 16. 7. 2020](http://forum.matfyz.info/viewtopic.php?f=169&t=12089) Cílem je definovat funkci ``ascending,`` která na vstupu obdrží seznam hodnot (libovolného typu) a vrátí zpět seznam posloupností, který splňuje: - každá posloupnost je striktně rostoucí a nelze ji zleva ani zprava prodloužit - sloučením všech posloupností dostaneme vstupní seznam Příklad: ```haskell ghci> ascending [1,2,3,4,3,2,1,2] [[1,2,3,4],[3],[2],[1,2]] ghci> let x = [1,2,3,1,2,3] in concat (ascending x) == x True ``` 1. Definujte typovou signaturu funkce ``ascending``. 2. Definujte vlastní funkci. 3. Jak byste zobecnili tuto funkci tak, aby ji bylo možné použít s libovolným porovnávacím operátorem? 4. Bude vaše definice fungovat i na nekonečných seznamech? Pokud ano, vysvětlete proč. Pokud ne, dala by se vaše definice takto upravit? Zdůvodněte proč. Řešení: ```haskell ascending :: Ord a => [a] -> [[a]] ascending [] = [] ascending xs = ys : ascending zs where (ys, zs) = takeAscending (<) xs takeAscending :: Ord a => (a -> a -> Bool) -> [a] -> ([a], [a]) takeAscending cmp [] = ([], []) takeAscending cmp [x] = ([x], []) takeAscending cmp (x1 : x2 : xs) | cmp x1 x2 = (x1 : asc, rest) | otherwise = ([x1], x2 : xs) where (asc, rest) = takeAscending cmp (x2 : xs) ``` ### Stromové operace Zdroj: [MFF Forum: Zkouška 16. 7. 2020](http://forum.matfyz.info/viewtopic.php?f=169&t=12089) 1. Definujte datový typ pro binární stromy. - Hodnoty jsou uloženy ve vnitřních uzlech. - Pokuste se o co nejobecnější definici. - Nezapomeňte na reprezentaci prázdného stromu. 2. Definujte funkci ``replicateT``. Výsledkem ``replicateT n a`` je binární strom, který obsahuje ``n`` kopií hodnoty ``a``. - Výsledný strom by měl mít minimální možnou hloubku. Např. strom ``replicateT 7 a`` by měl mít hloubku 3. 3. Definujte funkci ``zipWithT`` jako zobecnění funkce ``zipWith``. ``zipWithT f t1 t2`` sloučí prvky stromů ``t1`` a ``t2`` na stejných pozicích pomocí funkce f. - Pokud nemá nějaký prvek z jednoho stromu odpovídající prvek na stejné pozici v druhém stromě, tak jej do výsledného stromu nepřidávejte. Např. pro prázdný strom empty by mělo platit ``zipWithT f t empty == empty`` a ``zipWithT f empty t == empty``. 4. Pomocí ``replicateT`` a ``zipWithT`` definujte funkci ``cut``. Funkce ``cut n t`` odstraní ze stromu ``t`` všechny vrcholy, jejichž hloubka je ostře větší než ``n``. Řešení: ```haskell data Tree a = Null | Tree (Tree a) a (Tree a) deriving (Eq, Show) replicateT :: Int -> a -> Tree a replicateT 0 _ = Null replicateT n val = Tree leftAns val rightAns where leftN = (n - 1) `div` 2 rightN = n - 1 - leftN leftAns = replicateT leftN val rightAns = replicateT rightN val zipWithT :: (a -> b -> c) -> Tree a -> Tree b -> Tree c zipWithT _ Null _ = Null zipWithT _ _ Null = Null zipWithT f (Tree leftA a rightA) (Tree leftB b rightB) = Tree leftAns (f a b) rightAns where leftAns = zipWithT f leftA leftB rightAns = zipWithT f rightA rightB cut :: Int -> Tree a -> Tree a cut n tree = zipWithT const tree (replicateT ((2 ^ n) - 1) undefined) ``` ### Klouzavé průměry Zdroj: [MFF Forum: Zkouška 24. 6. 2020](http://forum.matfyz.info/viewtopic.php?f=169&t=12078) Cílem je definovat binární funkci klouzave, která - obdrží na vstupu posloupnost čísel a přirozené číslo ``n`` - a vrátí posloupnost klouzavých průměrů řádu ``n``, tj. aritmetických průměrů ``n`` sousedních prvků. Příklad: ```haskell klouzave [1.5, 2.5, 3.5, 4.5, 5.5] 3 [2.5,3.5,4.5] ``` 1. Definujte typovou signaturu funkce ``klouzave`` 2. Definujte vlastní funkci s explicitním využitím rekurze 3. Sestavte alternativní definici, tentokráte bez explicitního použití rekurze, přitom můžete využívat libovolné knihovní funkce z přiloženého seznamu. 4. Vyhýbá se alespoň jedna z vašich definic opakovaným výpočtům? Pokud ne, dala by se takto upravit? Zdůvodněte. 5. Bude některá z vašich definic fungovat i na nekonečných seznamech? Pokud ano, vysvětlete proč. Pokud ne, dala by se některá z vašich definic takto upravit? Zdůvodněte. ```haskell take 5 $ klouzave [1..] 10 [5.5,6.5,7.5,8.5,9.5] ``` Řešení: ```haskell klouzave :: [Double] -> Int -> [Double] klouzave _ 0 = [] klouzave [] _ = [] klouzave xs@(first : _) n | length window < n = [] | otherwise = klouzave' window mean n rest where window = take n xs mean = sum window / fromIntegral n rest = drop n xs klouzave' :: [Double] -> Double -> Int -> [Double] -> [Double] klouzave' _ mean _ [] = [mean] klouzave' (w : ws) mean n (x : xs) = mean : klouzave' (ws ++ [x]) newMean n xs where newMean = (mean * fromIntegral n - w + x) / fromIntegral n klouzave2 :: [Double] -> Int -> [Double] klouzave2 xs n = [ sum window / fromIntegral n | d <- [0 .. length xs - n] , let tail = drop d xs , let window = take n tail , length window >= n ] ``` ### Stromovy fold Zdroj: [MFF Forum: Zkouška 24. 6. 2020](http://forum.matfyz.info/viewtopic.php?f=169&t=12078) Cílem toho problému je zobecnit funkce ``foldr`` / ``foldl`` na obecné kořenové stromy. 1. Definujte datový typ pro reprezentaci obecných kořenových stromů s ohodnocenými vrcholy: - snažte se o co nejobecnější definici - nezapomeňte na reprezentaci prázdného stromu 2. Funkce ``foldl`` a ``foldr`` zobecněte na funkci ``foldT``, která bude - namísto seznamu - procházet stromem ve vaší reprezentaci popsané v **1.**. 3. Pomocí funkce fold definujte funkci ``arita,`` která vrátí ``aritu`` (tj. maximální počet dětí přes všechny vrcholy) zadaného kořenového stromu. 4. Pomocí funkce ``foldT`` definujte funkci ``pdc``, která vrátí průměrnou délku cesty z kořene do listu (tj. součet délek všech cest z kořene do listu / počet listů). Řešení: ```haskell -- NOTE: Tree a [] is invalid data Tree a = Null | Tree a [Tree a] deriving (Eq, Show) foldT :: (a -> [b] -> b) -> b -> Tree a -> b foldT _ acc Null = acc foldT f acc (Tree val ts) = f val bs where bs = map (foldT f acc) ts arita :: Tree a -> Int arita = foldT (\_ bs -> max (length bs) (maximum bs)) 0 pdc :: Tree a -> Double pdc tree = sumOfLengths / leafsN where (sumOfLengths, leafsN) = foldT step (0, 1) tree step _ bs = (sum $ map ((+ 1) . fst) bs, sum $ map snd bs) testTree = Tree 1 [ Tree 1 [Null] , Null , Tree 2 [Tree 4 [Tree 5 [Null], Tree 6 [Null]]] , Tree 3 [Null, Null, Null, Null] ] ``` ### Deleni stromu Zdroj: [MFF Forum: Zkouška 12. 6. 2020 (Dvořák, Hric)](http://forum.matfyz.info/viewtopic.php?f=169&t=12066) Rozdělte zadaný binární vyhledávací strom ``T`` na ``n+1`` binárních vyhledávacích stromů ``T_0, .. , T_n`` podle zadaných vstupních hodnot ``k_i``, ``1 <= i <= n`` tak, že ve stromě ``T_i`` jsou hodnoty ``x``, ``k_i <= x < k_i+1``, pokud jsou nerovnosti aplikovatelné. Obrázek ![priklad vstupu](https://i.imgur.com/sNvonyI.png) Snažte se o efektivitu, celé podstromy patřící do jednoho pruhu zpracujte najednou. 1. Definujte datový typ pro reprezentaci binárních vyhledávacích stromů. Snažte se o co nejobecnější definici. 2. Definujte typovou signaturu funkce ``pruhy``, včetně typových tříd. 3. Funkci ``pruhy`` definujte. Budete-li používat pomocné funkce, u každé popište její význam. 4. Pokuste se stručně zdůvodnit korektnost vaší defnice. Řešení: ```haskell data BTree a = Nil | BTree (BTree a) a (BTree a) deriving (Eq, Show) cutUpTo :: Ord a => a -> BTree a -> (BTree a, BTree a) cutUpTo _ Nil = (Nil, Nil) cutUpTo max (BTree left val right) | val >= max = let (ans, restAns) = cutUpTo max left in (ans, BTree restAns val right) | otherwise = let (ans, restAns) = cutUpTo max right in (BTree left val ans, restAns) pruhy :: Ord a => [a] -> BTree a -> [BTree a] pruhy [] tree = [tree] pruhy (x : xs) tree = let (part, rest) = cutUpTo x tree in part : pruhy xs rest testTree = BTree (BTree (BTree Nil 1 Nil) 2 (BTree Nil 4 Nil)) 5 (BTree Nil 6 Nil) ``` ### Run-length encoding/decoding Zdroj: [MFF Forum: Zkouška 12. 6. 2020 (Dvořák, Hric)](http://forum.matfyz.info/viewtopic.php?f=169&t=12066) Definujte funkce ``rle`` a ``rld``, které realizují run-length encoding a decoding. Funkce ```haskell rle :: Eq a => [a] -> [Either a (a,Int)] ``` zakóduje co nejdelší úseky stejných prvků ve vstupním seznamu do dvojice ``(prvek, počet)`` typu ``Either`` s datovým konstruktorem ``Right``. Pokud je prvek v úseku sám, kóduje se pouze prvek vnořený do typu ``Either`` s datovým konstruktorem ``Left``. Příklad: ```haskell > rle ”abbcccda” [Left 'a', Right ('b',2), Right ('c',3), Left 'd', Left 'a'] ``` 1. Definujte funkci ``rle`` s využitím rekurze, ale bez použití stručných seznamů či funkcí vyšších řádů (funkce s funkcionálními parametry). 2. Definujte funkci ``rle`` bez explicitního využití rekurze, ale za použití stručných seznamů či funkcí vyšších řádů. 3. Definujte typovou signaturu funkce ``rld``, která realizuje dekompresi, tj. převod ze seznamu úseků na původní seznam prvků. 4. Definujte funkci ``rld.`` Použijte přitom funkci ``map`` či ``concat``. 5. Bude některá z funkcí fungovat i na nekonečných seznamech? Proč ano nebo proč ne? Řešení: ```haskell rle :: Eq a => [a] -> [Either a (a, Int)] rle [] = [] rle (x : xs) = encoded : rle rest where ((_, count), rest) = munch x xs encoded = if count == 1 then Left x else Right (x, count) munch x [] = ((x, 1), []) munch x (y : ys) | x /= y = ((x, 1), y : ys) | otherwise = let ((_, count), rest) = munch y ys in ((x, count + 1), rest) groups :: Eq a => [a] -> [[a]] groups [] = [] groups xs@(x : xs') = takeWhile (== x) xs : groups (dropWhile (== x) xs') rle2 :: Eq a => [a] -> [Either a (a, Int)] rle2 xs = [ if l == 1 then Left (head group) else Right (head group, l) | group <- groups xs , let l = length group ] ``` ### Převody mezi číselnými soustavami Zdroj: [MFF Forum: 10. 9. 2019 - Dvořák](http://forum.matfyz.info/viewtopic.php?f=169&t=11977) Definujte funkce: ```none prevod1 cislo puvodni ``` pro převod čísla z číselné soustavy o základu ``puvodni`` do ``dekadické číselné soustavy``, a ```none prevod2 cislo nova ``` pro převod čísla z dekadické do číselné soustavy o základu ``nova``. Příklad: ```haskell > prevod1 [1,1,1,0] 2 -- převede binární 1110 do desítkové soustavy 14 > prevod2 33 16 -- převede dekadické číslo 33 do hexadecimální soustavy [2,1] ``` 1. Doplňte typové signatury definovaných funkcí ```haskell prevod1 :: prevod2 :: ``` 2. Definujte funkci ``prevod1`` s využitím rekurze. 3. Sestavte alternativní definici funkce prevod1 s využitím alespoň jedné z funkcí ``map``, ``filter``, ``foldr`` či ``foldl``, ale bez (explicitního) použití rekurze. 4. Definujte funkci ``prevod2`` s využitím funkce ``unfold`` definované následovně: ```haskell unfold :: (t -> Bool) -> (t -> (a, t)) -> t -> [a] unfold done step x = if done x then [] else let (y,ys) = step x in y: unfold done step ys ``` Řešení: ```haskell prevod1 :: [Int] -> Int -> Int prevod1 ds base = go ds 0 where go [] acc = acc go (d : ds) acc = go ds (base * acc + d) prevod1' :: [Int] -> Int -> Int prevod1' ds base = foldl (\acc d -> base * acc + d) 0 ds unfold :: (t -> Bool) -> (t -> (a, t)) -> t -> [a] unfold done step x = if done x then [] else let (y, ys) = step x in y : unfold done step ys moddiv a b = (a `mod` b, a `div` b) prevod2 :: Int -> Int -> [Int] prevod2 n base = reverse $ unfold (== 0) (`moddiv` base) n ``` ### Řády prvků grupy Zdroj: [MFF Forum: 10. 9. 2019 - Dvořák](http://forum.matfyz.info/viewtopic.php?f=169&t=11977) Definujte unární funkci rady, která obdrží multiplikativní tabulku grupy jako matici prvků. První řádek matice obsahuje násobení grupovou jednotkou ``e`` a pořadí prvků odpovídající řádkům a sloupcům je stejné. Vydá seznam všech prvků spolu s jejich řády. Řád prvku ``p`` je nejmenší přirozené číslo ``n`` takové, že ``n``-tá mocnina ``p`` je rovna ``e``. 1. Definujte typovou signaturu funkce ``rady``. 2. Funkci ``rady`` definujte. Příklad: ```haskell > rady [["e","a","b"], ["a","b","e"], ["b","e","a"]] [("e",1), ("a",3), ("b",3)] ``` Řešení: ```haskell rady :: Eq a => [[a]] -> [(a, Int)] rady [] = [] rady table@(firstRow@(e : _) : _) = zip firstRow (map rad firstRow) where transitions = [ ((a, b), c) | (a, (b, c)) <- concat $ zipWith (zip . repeat) firstRow (map (zip firstRow) table) ] lookup :: Eq a => [(a, b)] -> a -> b lookup [] _ = error "Lookup error" lookup ((key, value) : ps) k | key == k = value | otherwise = lookup ps k mult a b = lookup transitions (a, b) rad x = length (takeWhile (/= e) $ iterate (mult x) x) + 1 ``` ### Kumulativní součty Zdroj: [MFF Forum: 24.6.2019 (Dvořák, Hric)](http://forum.matfyz.info/viewtopic.php?f=169&t=11969) Je dána číselná matice ``A``. Definujte funkci ```haskell kumulace :: Num a => [[a]] -> [[a]] ``` která z matice ``A`` vyrobí matici ``B`` stejných rozměrů (viz příklad níže). Každý prvek na souřadnicích ``(i,j)`` bude roven součtu všech hodnot v submatici s levým horním rohem ``(0,0)``a pravým dolním rohem ``(i,j)``. *Poznámka: Snažte se vyhnout opakování stejných výpočtů.* Příklad: ```haskell > kumulace[[1,1,1],[1,2,1],[0,1,0],[1,1,-4]] [[1,2,3],[2,5,7],[2,6,8],[3,8,6]] ``` Řešení: ```haskell kumulace :: Num a => [[a]] -> [[a]] kumulace [] = [] kumulace [[]] = [[]] kumulace table = memo where indices = [ [ (i, j) | (j, _) <- zip [0 ..] row ] | (i, row) <- zip [0 ..] table ] memo = map (map go) indices go (0, 0) = table !! 0 !! 0 go (i, 0) = memo !! (i - 1) !! 0 + table !! i !! 0 go (0, j) = memo !! 0 !! (j - 1) + table !! 0 !! j go (i, j) = (memo !! (i - 1) !! j) + (memo !! i !! (j - 1)) - (memo !! (i - 1) !! (j - 1)) + (table !! i !! j) ``` ### Doplnění hypergrafu Zdroj: [MFF Forum: 24.6.2019 (Dvořák, Hric)](http://forum.matfyz.info/viewtopic.php?f=169&t=11969) Hypergraf je zadán množinou vrcholů a množinou hyperhran, což jsou alespoň dvouprvkové podmnožiny množiny vrcholů. Naší cílem je definovat funkci doplnění, která doplní do hypergrafu ``H`` všechny dvouprvkové (hyper)hrany pro ty dvojice vrcholů, které nejsou společně obsaženy v žádné hyperhraně vstupního hypergrafu ``H``. Funkce tedy např. z hypergrafu s vrcholy ``{1,2,3,4,5}`` a hyperhranani ``{1,3,5}`` a ``{2,3,4}`` vytvoří hypergraf se stejnými vrcholy a hyperhranami ``{1,3,5},{2,3,4},{1,2},{1,4},{5,2}`` a ``{5,4}`` 1. Definujte datový typ pro reprezentaci hypergrafu. Pokuste se o co nejobecnější definici (vrcholy mohou být reprezentovány nejen čísly, ale i znaky, řetězci apod.) 2. Specifikujte typovou signaturu funkce ```haskell doplneni :: ``` 3. Funkci definujte. Řešení: ```haskell data HGraph a = HGraph [a] [[a]] deriving (Eq, Show) fromMaybe :: a -> Maybe a -> a fromMaybe def Nothing = def fromMaybe _ (Just a) = a lookup' :: Eq a => [(a, b)] -> a -> Maybe b lookup' [] _ = Nothing lookup' ((key, value) : ps) k | key == k = Just value | otherwise = lookup' ps k doplneni :: Eq a => HGraph a -> HGraph a doplneni (HGraph vs es) = HGraph vs (es ++ newEdges) where trans_table = zip [0 ..] vs newEdges = [ [ fromMaybe undefined (lookup' trans_table i) , fromMaybe undefined (lookup' trans_table j) ] | i <- [0 .. length vs - 1] , j <- [0 .. i - 1] , not $ isEdge i j ] isEdge i j = or [ elem (fromMaybe undefined (lookup' trans_table i)) edge && elem (fromMaybe undefined (lookup' trans_table j)) edge | edge <- es ] ``` ### Analýza textu Zdroj: [Zkouška 10.6.2019 (Dvořák + Hric)](http://forum.matfyz.info/viewtopic.php?f=169&t=11954) Na vstupu je zadán text jako hodnota typu ``String``. Naším cílem je definovat binární funkci ``stat text n``, která - obdrží takový text a přirozené číslo ``n`` - vrátí všechna slova z tohoto textu o délce alespoň ``n``, setříděná lexikograficky - každé slovo s čísly řádků, kde se slovo vyskytuje Řádky jsou ukončeny znakem ``'\n'``. Slovo je každý maximální podřetězec textu neobsahující mezeru ``' '``, tabulátor ``'\t'`` či konec řádku ``'\n'``. 1. Definujte datovou strukturu pro reprezentaci oboru hodnot funkce stat (pomocí data nebo type). 2. Definujte typovou signaturu funkce stat s použití datové struktury z **1.**. 3. Funkci stat definujte. Řešení: ```haskell newtype Stat = Stat [(Int, String)] deriving (Eq, Show) lines' :: String -> [String] lines' "" = [] lines' ss = line : lines' rest where line = takeWhile (/= '\n') ss rest = dropWhile (/= '\n') ss words' :: String -> [String] words' "" = [] words' ss = case takeWhile (not . isSpace) ss of [] -> [] word -> word : words' rest where isSpace = flip elem [' ', '\t', '\n'] tmp = dropWhile (not . flip elem [' ', '\t', '\n']) ss rest = dropWhile isSpace tmp sortBy :: Ord b => (a -> b) -> [a] -> [a] sortBy _ [] = [] sortBy f (pivot : xs) = left ++ [pivot] ++ right where left = filter (\y -> f y <= f pivot) xs right = filter (\y -> f y > f pivot) xs stat :: String -> Int -> Stat stat text n = Stat sortedWords where numberedLines = zip [1 ..] $ lines' text linesToWords = concatMap (\(line_no, line) -> zip (repeat line_no) (words' line)) numberedLines filteredWords = filter (\(_, word) -> length word >= n) linesToWords sortedWords = sortBy snd filteredWords ``` ### Označkování stromu Zdroj: [MFF Forum: Zkouška 21.6.2018](http://forum.matfyz.info/viewtopic.php?f=169&t=11747) Ohodnotit vrcholy obecného n-arní stromu v pořadí v jakém jsou vrcholy uzavírány, takže post-fixově. Měla se napsat datová struktura pro strom, typová hlavička fce a pak tu funkci implementovat: ```haskell data Tree a = Nil | Tree a [Tree a] label :: Tree a -> Tree (a, Int) ``` Příklad ```haskell > label $ Tree 1 [Tree 1 [Nil],Nil,Tree 2 [Tree 4 [Tree 5 [Nil],Tree 6 [Nil]]],Tree 3 [Nil,Nil,Nil,Nil]] Tree (1,7) [Tree (1,1) [Nil],Nil,Tree (2,5) [Tree (4,4) [Tree (5,2) [Nil],Tree (6,3) [Nil]]],Tree (3,6) [Nil,Nil,Nil,Nil]] ``` Řešení: ```haskell data Tree a = Nil | Tree a [Tree a] deriving (Eq, Show) label :: Tree a -> Tree (a, Int) label = snd . label' 0 label' :: Int -> Tree a -> (Int, Tree (a, Int)) label' n Nil = (n, Nil) label' n (Tree val ts) = (newN, Tree (val, newN) ansTs) where (ansN, ansTs) = sequentialLabel n ts newN = ansN + 1 sequentialLabel :: Int -> [Tree a] -> (Int, [Tree (a, Int)]) sequentialLabel n [] = (n, []) sequentialLabel n (Nil : ts) = let (ansN, ansTs) = sequentialLabel n ts in (ansN, Nil : ansTs) sequentialLabel n (tree : ts) = let (ansN1, ansT ) = label' n tree (ansN2, ansTs) = sequentialLabel ansN1 ts in (ansN2, ansT : ansTs) ``` ### Změna některých prvků Zdroj: [MFF Forum: Zkouška 26.6.2018](http://forum.matfyz.info/viewtopic.php?f=169&t=11756) Definujte funkci ``change``, která: - obdrží seznam, který reprezentuje abecedu povolených prvků, které lze využít k modifikaci - obdrží seznam ``xs`` pro modifikaci - vrátí seznam všech modifikací vstupního seznamu ``xs``, které se od něho liší v právě 3 prvcích. Příklad: ```haskell > change3 "ab" "aabe" ["bbae", "bbba", "bbbb", "baaa", "baab", "abaa", "abab"] ``` 1. Definujte typ funkce ``change3`` co nejobecněji (včetně případných typových tříd) 2. Definujte funkci ``change3`` Řešení: ```haskell change3 :: Eq a => [a] -> [a] -> [[a]] change3 cs xs = map snd $ filter (\(count, _) -> count == 3) $ change' cs xs change' :: Eq a => [a] -> [a] -> [(Int, [a])] change' _ [] = [(0, [])] change' cs (x : xs) = changed ++ notChanged where ans = change' cs xs other = filter (/= x) cs notChanged = map (\(count, ys) -> (count, x : ys)) ans changed = map (\(c, (countChanged, ys)) -> (countChanged + 1, c : ys)) $ [ (c, p) | c <- other, p <- ans ] ``` ### Největší kladná podmatice Zdroj: [MFF Forum: Zkouška 6. 6. 2016 (Dvořák, Hric)](http://forum.matfyz.info/viewtopic.php?f=169&t=10961) Máme zadanou matici (jako seznam seznamů). Naším cílem je vypsat seznam všech dvojic ``(x, y)`` takových, že podmatice ``(1, 1):(x, y)`` bude obsahovat pouze kladné hodnoty. Dvojice ``(x, y)`` musí být vždy nejvyšší možné (t. j. nelze ani v jedne souradnici zvětšit) Řešení: ```haskell scan :: (b -> a -> b) -> b -> [a] -> [b] scan _ acc [] = [acc] scan f acc (x : xs) = acc : scan f (f acc x) xs maxPositive :: (Num a, Ord a) => [[a]] -> [(Int, Int)] maxPositive [] = [] maxPositive [[]] = [] maxPositive matrix = [ (row, count) | (row, count, jump) <- zip3 [1 ..] maxCounts jumpDown , count > 0 , jump ] where posCounts = map (length . takeWhile (> 0)) matrix maxCounts = drop 1 $ scan min (maxBound :: Int) posCounts jumpDown = [True] ++ [ curr > next | (curr, next) <- zip (drop 1 posCounts) (drop 2 posCounts) ] ++ [True] ``` ### Stromový fold Zdroj: [MFF Forum: Zkouška 6. 6. 2016 (Dvořák, Hric)](http://forum.matfyz.info/viewtopic.php?f=169&t=10961) 1. Napiste fold pro binarni stromy ```haskell data Tree a = Leaf a | Tree (Tree a) (Tree a) fold :: (a -> b) -> (b -> b -> b) -> Tree a -> b ``` 2. Napiste one-liner funkci, ktera vypise minimum a maximum z celeho stromu pomoci vami napsaneho foldu. 3. Napiste hlavicku funkce z **2.** Řešení: ```haskell data Tree a = Leaf a | Tree (Tree a) (Tree a) deriving (Eq, Show) fold :: (a -> b) -> (b -> b -> b) -> Tree a -> b fold f _ (Leaf a ) = f a fold f comb (Tree left right) = comb leftAns rightAns where leftAns = fold f comb left rightAns = fold f comb right minmaxT :: Ord a => Tree a -> (a, a) minmaxT = fold (\a -> (a, a)) (\(minLeft, maxLeft) (minRight, maxRight) -> (min minLeft minRight, max maxLeft maxRight) ) ``` ### Tetris Zdroj: [MFF Forum: Zkouška 22.6.](http://forum.matfyz.info/viewtopic.php?f=169&t=11412) Máme obdélníkovou tabulku uloženou po řádcích jako seznam seznamů Intů. Vymažte z ní všechny sloupce, které neobsahují žádnou nulu. Řešení: ```haskell transpose :: [[a]] -> [[a]] transpose [] = [] transpose matrix = case concatMap (take 1) matrix of [] -> [] col -> col : transpose (map (drop 1) matrix) tetris :: (Eq a, Num a) => [[a]] -> [[a]] tetris = transpose . removeFull . transpose where removeFull rows = [ row | row <- rows, 0 `elem` row ] ``` ### Splnění všech podmínek Zdroj: [MFF Forum: Zkouska 20.9.2017](http://forum.matfyz.info/viewtopic.php?f=169&t=11466) Funkce ``podminky`` dostává seznam podmínek jedné proměnné a seznam hodnot. Vydává seznam seznamů hodnot, kde ``i``-tý seznam na výstupu obsahuje hodnoty, pro které byla splněna ``i``-tá podmínka a nebyly splněny předchozí podmínky. Hodnoty, pro které nebyla splněna žádná podmínka, se zahodí. Příklad: ```haskell > podminky [even,(>5),(==3)] [0..9] [[0,2,4,6,8],[7,9],[3]] ``` 1. Napište typovou signaturu funkce podmínky (co nejobecnější, včetně případných typových tříd). 2. Napište funkci ``podminky``. Řešení: ```haskell podminky :: [a -> Bool] -> [a] -> [[a]] podminky [] _ = [] podminky (f : fs) xs = filter f xs : podminky fs rest where rest = filter (not . f) xs ``` ### Stromový take Zdroj: [MFF Forum: Zkouska 20.9.2017](http://forum.matfyz.info/viewtopic.php?f=169&t=11466) Cílem tohoto problému je zobecnit standardní funkci ``take`` na funkci ``takeTree``, která - obdrží obecný kořenový strom a dvě přirozená čísla ``n`` a ``m`` - odstraní ve stromě všechny vrcholy ve hloubce větší než ``m`` (hloubka vrcholu ``v`` je počet hran na cestě z kořene do ``v``) - pro každý vrchol, který má více než ``n`` dětí, odstraní všechny děti (s příslušnými podstromy) kromě ``n`` nejlevějších - výsledný (nejvýše ``n``-ární) strom (hloubky nejvýše ``m``) vrátí. 1. Definujte datový typ pro obecný kořenový strom, v jehož vrcholech jsou uloženy prvky typu ``a``. 2. Využijte váš datový typ k definici nekonečného stromu, tj. takového stromu, že pro každé přirozené číslo ``i`` buďto existuje vrchol s alespoň ``i`` dětmi, nebo existuje vrchol ve hloubce alespoň ``i``. 3. Definujte typovou signaturu funkce ``takeTree``. 4. Funkci ``takeTree`` definujte. Řešení: ```haskell data Tree a = Nil | Tree a [Tree a] deriving (Eq, Show) infiniteTree :: Tree Int infiniteTree = go 0 where go n = Tree n (take (n + 1) $ repeat (go (n + 1))) takeTree :: Int -> Int -> Tree a -> Tree a takeTree n m = go 0 where go _ Nil = Nil go h (Tree val ts) | h == m = Tree val [] | otherwise = let tsAns = take n $ map (go (h + 1)) ts in Tree val tsAns ``` ### Formule Zdroj: [MFF Forum: Zkouška 13. 9. 2017](http://forum.matfyz.info/viewtopic.php?f=169&t=11457) Máme typ: ```haskell data Formule = Konst Bool | Not Formule | And Formule Formule | Or Formule Formule ``` a chceme napsat funci ``gen``, která vygeneruje nekonečný seznam složený z formulí: Příklad: ```haskell gen = [ Konst True, Konst Flase, Not True, Not False, And True True, ... ] ``` Řešení: ```haskell data Formule = Konst Bool | Not Formule | And Formule Formule | Or Formule Formule deriving(Eq, Show) gen :: [Formule] gen = concat memo memo = map genN [0 ..] genN :: Int -> [Formule] genN 0 = [] genN 1 = [Konst True, Konst False] genN n = ands ++ ors where last = memo !! (n - 1) nots = [ Not f | f <- last ] ands = [ And f s | f <- last, s <- last ] ors = [ Or f s | f <- last, s <- last ] ``` ### Převody stromů Zdroj: [MFF Forum: Zkouška 6. 6. 2017](http://forum.matfyz.info/viewtopic.php?f=169&t=11380) Máme dva druhy stromů - obecný n-ární: ```haskell data NTree a = NTree a [NTree a] ``` a n-ární, ve kterém je řečeno, které podstromy jsou vlevo a které vpravo: ```haskell data UspTree a = UspTree [UspTree a] a [UspTree a] ``` Máme napsat funkci, která obecný n-ární strom převede na uspořádaný strom. V každém uzlu obecného n-árního stromu na vstupu je kromě hodnoty uložený taky počet synů, kteří jsou vlevo. Řešení: ```haskell data NTree a = NTree a [NTree a] deriving (Eq, Show) data UspTree a = UspTree [UspTree a] a [UspTree a] deriving (Eq, Show) prevodT :: NTree (Int, a) -> UspTree a prevodT (NTree (n, val) ts) = UspTree (take n ts') val (drop n ts') where ts' = map prevodT ts ``` ### Počet trojúhelníků Zdroj: [MFF Forum: Zkouška 29.5.2017](http://forum.matfyz.info/viewtopic.php?f=169&t=11357) - Navrhněte datový typ ``Graf`` a pro reprezentaci konečného neorientovaného grafu s vrcholy typu ``a``. - Definujte funkci ``troj :: Graf a -> Int``, která k takovému grafu vrátí počet všech jeho trojúhelníků. Priklad: ```haskell > let testGraph = Graph [0..8] [(0, 1), (0, 3), (1, 0), (1, 2), (1, 3), (2, 1), (2, 4), (3, 0), (3, 1), (3, 5), (4, 2), (4, 5), (5, 3), (5, 4), (5, 6), (6, 5), (6, 7), (6, 8), (7, 6), (7, 8), (8, 6), (8, 7)] > troj testGraph [(0,1,3),(6,7,8)] ``` Řešení: ```haskell type Edge a = (a, a) data Graph a = Graph [a] [Edge a] deriving (Eq, Show) troj :: Eq a => Graph a -> [(a, a, a)] troj (Graph xs edges) = troj' edges xs troj' :: Eq a => [Edge a] -> [a] -> [(a, a, a)] troj' _ [] = [] troj' edges (a : vertices) = [ (a, b, c) | n <- [1 .. length vertices - 1] , b <- drop (n - 1) $ take n vertices , (a, b) `elem` edges , c <- drop n vertices , (b, c) `elem` edges , (c, a) `elem` edges ] ++ troj' edges vertices ``` ### Bag fold Zdroj: [MFF Forum: Zkouška 29.5.2017](http://forum.matfyz.info/viewtopic.php?f=169&t=11357) Je dán datový typ ```haskell data Bag a = Item a | Items [Bag a] ``` 1. Definujte funkci ``fold`` pro obecný průchod touto datovou strukturou (to ``(a->b)`` tam zastupuje počáteční hodnotu v normálním foldu) ```haskell fold :: (a -> b) -> ([b] -> b) -> Bag a -> b ``` 2. Pomocí funkce fold definujte funkci ``listy`` která posbírá všechny hodnoty z položek ``Item`` ze všech úrovní zleva. ```haskell listy :: Bag a -> [a] ``` Příklad: ```haskell > listy (Items [Item 1,Items [Item 2, Item 3], Items [Items [Item 4]]]) [1,2,3,4] ``` Řešení: ```haskell data Bag a = Item a | Items [Bag a] deriving (Eq, Show) fold :: (a -> b) -> ([b] -> b) -> Bag a -> b fold f _ (Item a ) = f a fold f comb (Items bs) = comb ans where ans = map (fold f comb) bs listy :: Bag a -> [a] listy = fold (: []) concat ``` ### Hledání skoku Zdroj: [MFF Forum: Zkouška 13. 6. 2016 (Dvořák, Hric)](http://forum.matfyz.info/viewtopic.php?f=169&t=10963) Máte nějakou funkci, která nabývá jen dvou různých funkčních hodnot. Funkce přechází někde (nevíme kde) skokově z jedné funkční hodnoty na druhou. Na vstupu dostanete ``c`` a ``d`` určující ony dvě funkční hodnoty. Dále dostanete seznam ``(x, y)`` bodů, ve kterých jste funkci změřili s nějakou chybou. Napište funkci, která na výstupu tyto body rozdělí na levé a pravé (seznam dvou seznamů) podle toho, které body patří ještě k hodnotě ``c``, a které už k hodnotě ``d``. Pozor, je potřeba minimalizovat celkovou odchylku spočtenou jako součet ``(f(x_i) - y_i)^2`` přes všechny body, kde ``f(x)`` je změřená hodnota (ze seznamu) a ``y`` skutečná hodnota z našeho odhadu. Řešení: ```haskell sortBy :: Ord b => (a -> b) -> [a] -> [a] sortBy _ [] = [] sortBy f (x : xs) = left ++ [x] ++ right where left = sortBy f $ filter (\elem -> f elem < f x) xs right = sortBy f $ filter (\elem -> f elem >= f x) xs minimumBy :: Ord b => (a -> b) -> [a] -> a minimumBy f = (!! 1) . sortBy f skok :: (Ord a, Num a) => a -> a -> [(a, a)] -> ([(a, a)], [(a, a)]) skok _ _ [] = undefined skok c d ps = snd $ minimumBy fst cuts where sorted = sortBy fst ps cuts = [ (cost left right, (left, right)) | n <- [0 .. length sorted] , let left = take n sorted , let right = drop n sorted ] cost xs ys = sum $ [ (fx - c) ^ 2 | (_, fx) <- xs ] ++ [ (fx - d) ^ 2 | (_, fx) <- ys ] ``` ### Násobení/sčítání řídkých polynomů Zdroj: [MFF Forum: Zkouška 28.6.2016 - Dvořák, Hric](http://forum.matfyz.info/viewtopic.php?f=169&t=10993) Mějme řídké polynomy reprezentované pomocí ``[(nenulový koeficient,exponent)]``. Definujte pro ně datový typ (nezapomeňte na nulový polynom) a napište funkci ``mult`` (i její datovou signaturu), která bude řídké polynomy násobit. - *řídký polynom*: u spousty exponentů je nulový koeficient (exponenty prostě nejdou po 1, ale skáčou) ```haskell data Ridky a = Ridky [(Int, a)] ``` Řešení: ```haskell type Order = Int type Coeff = Int newtype Poly = Poly [(Coeff, Order)] deriving (Eq, Show) sortBy :: Ord b => (a -> b) -> [a] -> [a] sortBy _ [] = [] sortBy f (x : xs) = left ++ [x] ++ right where left = sortBy f $ filter (\elem -> f elem < f x) xs right = sortBy f $ filter (\elem -> f elem >= f x) xs groupBy f [] = [] groupBy f xs@(x : xs') = group : groupBy f rest where group = takeWhile ((== f x) . f) xs rest = dropWhile ((== f x) . f) xs normalize :: Poly -> Poly normalize (Poly ps) = Poly p where groups = groupBy snd $ sortBy snd ps p = [ (sum $ map fst group, snd $ head group) | group <- groups ] mult :: Poly -> Poly -> Poly mult (Poly p) (Poly q) = normalize $ Poly ans where ans = [ (c1 * c2, o1 + o2) | (c1, o1) <- p, (c2, o2) <- q ] summ :: Poly -> Poly -> Poly summ p q = summ' (normalize p) (normalize q) summ' :: Poly -> Poly -> Poly summ' (Poly xs) (Poly ys) = Poly $ go xs ys where go xs [] = xs go [] ys = ys go xs@(x@(xCoeff, xOrd) : xs') ys@(y@(yCoeff, yOrd) : ys') | xOrd == yOrd = (xCoeff + yCoeff, xOrd) : go xs' ys' | xOrd > yOrd = x : go xs' ys | otherwise = y : go xs ys' ``` ### Maximo-lexikografické generování všech dvojic Zdroj: [MFF Forum: Zkouška 30. 05. 2016 (Dvořák, Hric)](http://forum.matfyz.info/viewtopic.php?f=169&t=10934) Pro zadané ``K`` máme generovat nekonečný uspořádáný seznam ``K``-tic: uspořádání je definováno tak, že nejprve se třídí podle maximálního prvku v daném seznamu. (jakákoliv ``k``-tice, jejíž maximum je menší nebo rovno ``2`` bude před ``k``-ticí obsahující číslo 3). Když mají dvě k-tice stejné maximum, řadí se lexikograficky. Příklad pro ``K=2``: ```haskell [[0,0],[0,1],[1,0],[1,1],[0,2],[1,2],[2,0],[2,1],[2,2],[0,3] ...] ``` Řešení: ```haskell sort :: Ord a => [a] -> [a] sort [] = [] sort (x : xs) = left ++ [x] ++ right where left = sort $ filter (< x) xs right = sort $ filter (>= x) xs sequencesUpTo :: Int -> Int -> [[Int]] sequencesUpTo 0 _ = [] sequencesUpTo len max = go len [[]] where go 0 acc = acc go k acc = go (k - 1) [ n : seq | n <- [0 .. max], seq <- acc ] maxLex :: Int -> [[Int]] maxLex k = concatMap (sort . withMax) [0 ..] where withMax max = [ seq | seq <- sequencesUpTo k max, max `elem` seq ] ``` ### Ořezání intervalu z BVS Zdroj: [MFF Forum: Zkouška 30. 05. 2016 (Dvořák, Hric)](http://forum.matfyz.info/viewtopic.php?f=169&t=10934) Máme zadaný binární vyhledávací strom (reprezentaci si máme zvolit), a dvě čísla ``D``, ``H``. Máme vrátit BVS, který vznikl ořezáním vstupního stromu tak, aby v něm byly pouze hodnoty ``X`` takové, že ``D<=X<=H``. Řešení: ```haskell data Tree a = Nil | Tree (Tree a) a (Tree a) deriving (Eq, Show) cut :: (Ord a, Num a) => a -> a -> Tree a -> Tree a cut _ _ Nil = Nil cut min max (Tree left val right) | val < min = cut min max right | val > max = cut min max left | otherwise = Tree (cut min max left) val (cut min max right) testTree = Tree (Tree (Tree Nil 1 Nil) 3 (Tree (Tree Nil 4 Nil) 6 (Tree Nil 7 Nil))) 8 (Tree Nil 10 (Tree (Tree Nil 13 Nil) 14 Nil)) ``` ### Otočení v orientované sekvenci Zdroj: [MFF Forum: Zkouška 12.7.2021](http://forum.matfyz.info/viewtopic.php?f=169&t=12193) Na vstupu je daný seznam ``S`` obsahující dvojice ``(položka, orientace)``, kde položky jsou obecné informace nějakého typu (například geny v chromozomu), a orientace je typu ``Bool`` (pro sousměrně a protisměrně). Volání funkce ``otoceni S`` má vydat seznam všech výsledků ``[Vs]`` jako seznam seznamů dvojic stejného typu, kde jeden výsledek vznikne otočením nějaké souvislé části ``S``, přičemž v otočené části změníte informaci o směru. Délka otočené části je od ``1`` do délky ``S``, tj. otáčenou spojitou část vybíráte všemi možnými způsoby. 1. Napište (obecný) typ funkce ``otoceni`` 2. Napište funkci ``otoceni`` 3. Pracovala by Vaše implementace funkce otoceni na nekonečném vstupním seznamu? Šla by napsat správná implementace pro nekonečný seznam? (Stačí myšlenka: proč ano nebo proč ne.) Příklad: ```haskell > otoceni [('a',True),('b',True),('c',False)] [[('a',False),('b',True),('c',False)],[('a',True),('b',False),('c',False)],[('b',False),('a',False),('c',False)],[('a',True),('b',True),('c',True)],[('a',True),('c',True),('b',False)],[('c',True),('b',False),('a',False)]] ``` Řešení: ```haskell split3 :: [a] -> [([a], [a], [a])] split3 as = [ (xs, ys, zs) | (n, _) <- zip [0 ..] (as ++ [undefined]) , let prefix = take n as , let zs = drop n as , (k, _) <- zip [0 ..] (prefix ++ [undefined]) , let xs = take k prefix , let ys = drop k prefix , not $ null ys ] otoceni :: [(a, Bool)] -> [[(a, Bool)]] otoceni ps = [ xs ++ map flipPair ys ++ zs | (xs, ys, zs) <- split3 ps ] where flipPair (x, bool) = (x, not bool) ``` ### Převážení binárního stromu II Je zadán binární strom s vnitřními vrcholy typu ```haskell data Bt a = Void | Node (Bt a) a (Bt a) ``` Definujte funkci ``prevaz`` která projde strom a pro každý vnitřní vrchol prohodí levý a pravý podstrom, pokud je ve vstupním stromě vlevo víc vrcholů než vpravo. Příklad: ```haskell > prevaz (Node (Node (Node Void 'a' Void) 'b' Void) 'c' (Node Void 'ď Void)) Node (Node Void 'ď Void) 'c' (Node Void 'b' (Node Void 'a' Void)) ``` 1. Napište co nejobecnější typ funkce ``prevaz`` a použitých pomocných funkcí 2. Napište funkci ``prevaz.`` 3. Využíváte někde volání lambda-funkce nebo funkce s neúplně zadanými argumenty? Řešení: ```haskell data Bt a = Void | Node (Bt a) a (Bt a) deriving (Eq, Show) prevaz :: Bt a -> Bt a prevaz = snd . prevaz' prevaz' :: Bt a -> (Int, Bt a) prevaz' Void = (0, Void) prevaz' (Node left val right) | leftN > rightN = (count, Node rightAns val leftAns) | otherwise = (count, Node leftAns val rightAns) where (leftN , leftAns ) = prevaz' left (rightN, rightAns) = prevaz' right count = leftN + rightN + 1 ``` ### Diskvalifikováni sousedi Dostanete vstupní graf ``G``, neorientovaný a bez ohodnocení. Vypusťte z něho opakovaně všechny vrcholy, které mají méně sousedů než dané ``k``. Vydejte zbylý graf a seznam vrcholů v poradí, jak jste je vypouštěli. 1. Definujte vhodný typ ``Graf`` a pro graf, který používáte v další definici, přičemž parametr ``a`` je označení vrcholů. 2. Definujte funkci ``centrumG :: Eq a => Graf a -> Int -> (Graf a, [a])`` pro požadovaný výpočet. Řešení: ```haskell data Graph a = Graph [(a, [a])] deriving (Eq, Show) unfold :: (a -> Bool) -> (a -> (a, b)) -> a -> (a, [b]) unfold done step x | done x = (x, []) | otherwise = let (newX, newY ) = step x (ansX, ansYs) = unfold done step newX in (ansX, newY : ansYs) centrumG :: Eq a => Graph a -> Int -> (Graph a, [a]) centrumG (Graph ps) k = (Graph ansPs, bs) where findSmallDegree = filter (\p -> length (snd p) < k) degreeAtLeast ps = null (findSmallDegree ps) removeSmallDegree ps = let toRemove = fst $ head $ findSmallDegree ps in ( [ (v, filter (/= toRemove) ns) | (v, ns) <- ps, v /= toRemove ] , toRemove ) (ansPs, bs) = unfold degreeAtLeast removeSmallDegree ps ```