{
"cells": [
{
"cell_type": "markdown",
"id": "residential-answer",
"metadata": {},
"source": [
"# Množice\n",
"\n",
"Ogledali si bomo dve implementaciji podatkovnega tipa [množica](https://en.wikipedia.org/wiki/Set_(abstract_data_type)). V jeziku OCaml množico opišemo z naslednjo signaturo:\n",
""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "moving-highway",
"metadata": {},
"outputs": [],
"source": [
"type order = Less | Equal | Greater\n",
"\n",
"module type SET =\n",
"sig\n",
" type element\n",
" val cmp : element -> element -> order\n",
" type set\n",
" val empty : set\n",
" val member : element -> set -> bool\n",
" val add : element -> set -> set\n",
" val remove : element -> set -> set\n",
" val to_list : set -> element list\n",
" val fold : ('a -> element -> 'a) -> 'a -> set -> 'a\n",
"end"
]
},
{
"cell_type": "markdown",
"id": "looking-burst",
"metadata": {},
"source": [
"V množici se vsak element lahko pojavi samo enkrat. Tip elementov, ki jih vsebuje množica, mora biti primerljiv – elemente primerjamo s funkcijo `cmp`, ki vrne `Less`, `Equal` ali `Greater`. Množica podpira naslednje operacije:\n",
"\n",
"* `member e s` vrne `true`, če množica `s` vsebuje element `e`,\n",
"* `add e s` doda element `e` v množico `s`,\n",
"* `remove e s` izbriše element `e` iz množice `s`\n",
"* `to_list s` vrne seznam elementov v množici `s` (v poljubnem vrstnem redu).\n",
"\n",
"\n",
"## Implementacija s seznamom\n",
"\n",
"Definirajte strukturo `IntListSet`, ki elemente množice hrani v seznamu. Tip elementov naj bo celo število (`int`), za primerjavo pa lahko uporabite kar funkcijo `ocaml_cmp`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "three-device",
"metadata": {},
"outputs": [],
"source": [
"let ocaml_cmp x y =\n",
" let c = Stdlib.compare x y in\n",
" if c < 0 then Less\n",
" else if c > 0 then Greater\n",
" else Equal"
]
},
{
"cell_type": "markdown",
"id": "ordered-tours",
"metadata": {},
"source": [
"Primer uporabe:\n",
"\n",
" # let s = IntListSet.add 2 (IntListSet.add 4 IntListSet.empty) ;;\n",
" val s : IntListSet.set = \n",
" # IntListSet.member 4 s ;;\n",
" - : bool = true\n",
"\n",
"Zgledujte se po implementaciji prioritetne vrste `MyFirstQueue` s predavanj.\n",
""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cellular-daisy",
"metadata": {},
"outputs": [],
"source": [
"module IntListSet : SET with type element = int =\n",
"struct\n",
" (* dopolni *)\n",
"end\n",
"\n",
"let s = IntListSet.add 2 (IntListSet.add 4 IntListSet.empty) ;;\n",
"IntListSet.member 4 s ;;"
]
},
{
"cell_type": "markdown",
"id": "impaired-listing",
"metadata": {},
"source": [
"## Generični `ListSet`\n",
"\n",
"Definicijo `IntListSet` spremenite v funktor `ListSet`, ki sprejme strukturo, ki zadošča signaturi"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "sound-disposal",
"metadata": {},
"outputs": [],
"source": [
"module type ORDERED =\n",
" sig\n",
" type t\n",
" val cmp : t -> t -> order\n",
" end"
]
},
{
"cell_type": "markdown",
"id": "sporting-render",
"metadata": {},
"source": [
"ter vrne implementacijo množic s seznami. Nastavek je naslednji:\n",
"\n",
" module ListSet(M : ORDERED) : SET with type element = M.t =\n",
" struct\n",
" ⋮\n",
" end\n",
"\n",
"Primer uporabe:\n",
"\n",
" # module S = ListSet(struct type t = string let cmp = ocaml_cmp end) ;;\n",
" module S :\n",
" sig\n",
" type element = string\n",
" val cmp : element -> element -> order\n",
" type set\n",
" val empty : set\n",
" val member : element -> set -> bool\n",
" val add : element -> set -> set\n",
" val remove : element -> set -> set\n",
" val to_list : set -> element list\n",
" val fold : ('a -> element -> 'a) -> 'a -> set -> 'a\n",
" end\n",
" # let s = S.add \"foo\" (S.add \"bar\" S.empty) ;;\n",
" val s : S.set = \n",
" # S.member \"foo\" s ;;\n",
" - : bool = true\n",
"\n",
"Zgledujte se po implementaciji prioritetne vrste `ListQueue` s predavanj.\n",
""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "neural-interstate",
"metadata": {},
"outputs": [],
"source": [
"module ListSet(M : ORDERED) : SET with type element = M.t =\n",
"struct\n",
" (* dopolni *)\n",
"end\n",
" \n",
"module S = ListSet(struct type t = string let cmp = ocaml_cmp end) ;;\n",
"let s = S.add \"foo\" (S.add \"bar\" S.empty) ;;\n",
"S.member \"foo\" s ;;"
]
},
{
"cell_type": "markdown",
"id": "imposed-netscape",
"metadata": {},
"source": [
"## Primerjava učinkovitosti `ListSet` in `AVLSet`\n",
"\n",
"Modul `AVLSet` implementira funktor, ki tako kot `ListSet` sprejme strukturo s signaturo `ORDERED` in vrne\n",
"implementacijo množic z AVL drevesi, glej datoteko `avlset.ml`.\n",
""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "complicated-incentive",
"metadata": {},
"outputs": [],
"source": [
"(* datoteka avlset.ml *)\n",
"module AVLSet(M : ORDERED) : SET with type element = M.t =\n",
" struct\n",
" type element = M.t\n",
" let cmp = M.cmp\n",
"\n",
" type set = Empty | Node of element * int * set * set\n",
"\n",
" let empty = Empty\n",
"\n",
" let height = function\n",
" | Empty -> 0\n",
" | Node (_, h, _, _) -> h\n",
"\n",
" let leaf v = Node (v, 1, Empty, Empty)\n",
"\n",
" let node (v, l, r) = Node (v, 1 + max (height l) (height r), l, r)\n",
"\n",
" let rec member x = function\n",
" | Empty -> false\n",
" | Node (y, _, l, r) ->\n",
" begin\n",
" match cmp x y with\n",
" | Equal -> true\n",
" | Less -> member x l\n",
" | Greater -> member x r\n",
" end\n",
"\n",
" let rec to_list = function\n",
" | Empty -> []\n",
" | Node (x, _, l, r) -> to_list l @ [x] @ to_list r\n",
"\n",
" let rotateLeft = function\n",
" | Node (x, _, a, Node (y, _, b, c)) -> node (y, node (x, a, b), c)\n",
" | t -> t\n",
"\n",
" (* alternativni zapis s case *)\n",
" let rotateRight = function\n",
" | Node (y, _, Node (x, _, a, b), c) -> node (x, a, node (y, b, c))\n",
" | t -> t\n",
"\n",
" let imbalance = function\n",
" | Empty -> 0\n",
" | Node (_, _, l, r) -> height l - height r\n",
"\n",
" let balance = function\n",
" | Empty -> Empty\n",
" | Node (x, _, l, r) as t ->\n",
" begin\n",
" match imbalance t with\n",
" | 2 ->\n",
" (match imbalance l with\n",
" | -1 -> rotateRight (node (x, rotateLeft l, r))\n",
" | _ -> rotateRight t)\n",
" | -2 ->\n",
" (match imbalance r with\n",
" | 1 -> rotateLeft (node (x, l, rotateRight r))\n",
" | _ -> rotateLeft t)\n",
" | _ -> t\n",
" end\n",
"\n",
" let rec add x = function\n",
" | Empty -> leaf x\n",
" | Node (y, _, l, r) as t ->\n",
" begin\n",
" match cmp x y with\n",
" | Equal -> t\n",
" | Less -> balance (node (y, add x l, r))\n",
" | Greater -> balance (node (y, l, add x r))\n",
" end\n",
"\n",
" let rec remove x = function\n",
" | Empty -> Empty\n",
" | Node (y, _, l, r) ->\n",
" let rec removeSuccessor = function\n",
" | Empty -> assert false\n",
" | Node (x, _, Empty, r) -> (r, x)\n",
" | Node (x, _, l, r) ->\n",
" let (l', y) = removeSuccessor l in\n",
" (balance (node (x, l', r)), y)\n",
" in\n",
" match cmp x y with\n",
" | Less -> balance (node (y, remove x l, r))\n",
" | Greater -> balance (node (y, l, remove x r))\n",
" | Equal ->\n",
" begin match (l, r) with\n",
" | (_, Empty) -> l\n",
" | (Empty, _) -> r\n",
" | _ ->\n",
" let (r', y') = removeSuccessor r in\n",
" balance (node (y', l, r'))\n",
" end\n",
"\n",
" let rec fold f x = function\n",
" | Empty -> x\n",
" | Node (y, _, l, r) -> fold f (f (fold f x l) y) r\n",
"\n",
" end"
]
},
{
"cell_type": "markdown",
"id": "appointed-voltage",
"metadata": {},
"source": [
"Preverimo, da je `AVLSet` res učinkovitejši od `ListSet`. Najprej definiramo dve\n",
"implementaciji množic celih števil, eno s seznami in eno z AVL drevesi:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "compact-southwest",
"metadata": {},
"outputs": [],
"source": [
"module SL = ListSet (struct type t = int let cmp = ocaml_cmp end)\n",
"module SA = AVLSet (struct type t = int let cmp = ocaml_cmp end)"
]
},
{
"cell_type": "markdown",
"id": "auburn-glossary",
"metadata": {},
"source": [
"### Množica `{1, 2, ..., n}`\n",
"\n",
"Definirajte funkcijo `add_list n`, ki vrne množico (implementirano s seznami) števil od `1` do `n`. Primer uporabe:\n",
"\n",
" # SL.to_list (add_list 10) ;;\n",
" - : SL.element list = [10; 9; 8; 7; 6; 5; 4; 3; 2; 1]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "combined-wednesday",
"metadata": {},
"outputs": [],
"source": [
"(* let rec add_list = … *)\n",
"SL.to_list (add_list 10)"
]
},
{
"cell_type": "markdown",
"id": "dangerous-program",
"metadata": {},
"source": [
"Na enak način definirajte še funkcijo `add_avl`, ki množico implementira z AVL drevesi:\n",
"\n",
" # SA.to_list (add_avl 10) ;;\n",
" - : SA.element list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "incorporated-mounting",
"metadata": {},
"outputs": [],
"source": [
"(* let rec add_avl = … *)\n",
"SA.to_list (add_avl 10)"
]
},
{
"cell_type": "markdown",
"id": "geographic-neighborhood",
"metadata": {},
"source": [
"### Čas izvajanja\n",
"\n",
"Podana je funkcija `time`, ki izmeri čas izvajanja dane funkcije `f`:\n",
"\n",
" "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "wanted-tonight",
"metadata": {},
"outputs": [],
"source": [
"let time f =\n",
" let start = Sys.time () in\n",
" f () ;\n",
" let stop = Sys.time () in\n",
" stop -. start"
]
},
{
"cell_type": "markdown",
"id": "executed-waterproof",
"metadata": {},
"source": [
"Funkcija `f` je »thunk«, torej sprejme le argument tipa `unit`. Čas, ki ga OCaml potrebuje, da sešteje vsa števila od 1 do 10000000:\n",
"\n",
" # time (fun () -> let s = ref 0 in for i = 1 to 10000000 do s := !s + i done; !s) ;;\n",
" - : float = 0.17635\n",
"\n",
"Izmerite, koliko časa traja vstavljanje velikega števila elementov v množico.\n",
"Primerjajte implementaciji `SL` in `SA`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "distant-clock",
"metadata": {},
"outputs": [],
"source": [
"(* time (fun () -> … ) ;;\n",
"time … *)"
]
},
{
"cell_type": "markdown",
"id": "weighted-history",
"metadata": {},
"source": [
"## Fold\n",
"\n",
"Funkcija `fold` sprejme\n",
"\n",
"* funkcijo `f` dveh argumentov,\n",
"* začetno vrednost `z` in\n",
"* množico `m = {x₁, x₂, …, xᵢ}`\n",
"\n",
"ter izračuna rezultat tako, da s pomočjo `f` elemente enega za drugim kombinira s trenutno vrednostjo:\n",
"\n",
" f (… (f (f z x₁) x₂) …) xᵢₙ\n",
"\n",
"Primer, kjer je funkcija `f` kar sešetvane, začetna vrednost `z` je `0` in množica `m` je `{1,2,3,4}`:\n",
"\n",
" # SL.fold ( + ) 0 (add_list 4) ;;\n",
" - : int = 10\n",
"\n",
"Signaturi [`SET`](#module_set_cell) dodajte funkcijo `fold`:\n",
"\n",
" val fold : ('a -> element -> 'a) -> 'a -> set -> 'a\n",
"\n",
"in jo implementirajte v funktorjih [`ListSet`](#ListSet) in [`AVLSet`](#AVLSet)."
]
},
{
"cell_type": "markdown",
"id": "composed-wealth",
"metadata": {},
"source": [
"## Unija, filter in presek\n",
"\n",
"Definirajte funktor `SetOps`, ki sprejme strukturo s signaturo `SET` in\n",
"implementira funkcije `union`, `filter` ter `intersection`. Nastavek za rešitev:\n",
"\n",
" module SetOps(S : SET) =\n",
" struct\n",
" let union a b = …\n",
" let filter p a = …\n",
" let intersection a b = …\n",
" end\n",
"\n",
"Unijo in presek poznamo, funkcija `filter` pa sprejme predikat (funkcijo, ki\n",
"vrača vrednosti tipa `bool`) in množico, ter novo množico tistih elementov, ki\n",
"zadoščajo predikatu. Pri definiciji funkcij si pomagajte s funkcijo `fold`.\n",
"\n",
"Primer:\n",
"\n",
" # module SA_ops = SetOps(SA) ;;\n",
" module SA_ops :\n",
" sig\n",
" val union : SA.set -> SA.set -> SA.set\n",
" val filter : (SA.element -> bool) -> SA.set -> SA.set\n",
" val intersection : SA.set -> SA.set -> SA.set\n",
" end\n",
" # SA.to_list (SA_ops.filter (fun x -> x > 10) (add_avl 20)) ;;\n",
" - : SA.element list = [11; 12; 13; 14; 15; 16; 17; 18; 19; 20]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "headed-synthetic",
"metadata": {},
"outputs": [],
"source": [
"module SetOps(S : SET) =\n",
" struct\n",
" (* dopolni *)\n",
" end\n",
"\n",
"module SA_ops = SetOps(SA)\n",
";;\n",
"SA.to_list (SA_ops.filter (fun x -> x > 10) (add_avl 20))"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "OCaml default",
"language": "OCaml",
"name": "ocaml-jupyter"
},
"language_info": {
"codemirror_mode": "text/x-ocaml",
"file_extension": ".ml",
"mimetype": "text/x-ocaml",
"name": "OCaml",
"nbconverter_exporter": null,
"pygments_lexer": "OCaml",
"version": "4.08.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}