chapter \Exercise Sheet -- Week 11\ theory Exercise11 imports Main "HOL-Library.Code_Target_Numeral" begin text \In the lecture trees have been used to store sets. In this exercise sheet we will use trees to implement maps, i.e., partial functions from keys to values.\ section \Exercise 1 -- 4 points\ text \In this first exercise, the aim is to adapt or extend the known tree implementation so that it can be used to implement maps.\ datatype ('a,'b) tree = Leaf | Node "('a,'b) tree" 'a 'b "('a,'b) tree" fun set_t :: "('a,'b) tree \ 'a set" where "set_t Leaf = {}" | "set_t (Node l x y r) = set_t l \ {x} \ set_t r" inductive ordered :: "('a :: linorder,'b) tree \ bool" where oLeaf: "ordered Leaf" | oNode: "ordered l \ ordered r \ Ball (set_t l) (\ y. y < x) \ Ball (set_t r) (\ y. y > x) \ ordered (Node l x y r)" fun lookup_t :: "('a :: linorder,'b) tree \ 'a \ 'b option" where "lookup_t t k = undefined" fun update_t :: "'a :: linorder \ 'b \ ('a,'b) tree \ ('a,'b) tree" where "update_t k v t = undefined" definition empty_t :: "('a,'b)tree" where "empty_t = undefined" typedef (overloaded) ('a,'b)otree = "{ t :: ('a :: linorder,'b)tree. ordered t}" by (auto intro: oLeaf) setup_lifting type_definition_otree lift_definition lookup_o :: "('a :: linorder,'b)otree \ 'a \ 'b option" is lookup_t . lift_definition empty_o :: "('a :: linorder,'b)otree" is empty_t sorry lift_definition update_o :: "('a :: linorder,'b)otree \ 'a \ 'b \ ('a,'b)otree" is "\ t k v. update_t k v t" sorry lemma empty_o[simp]: "lookup_o empty_o = (\ _. None)" sorry lemma update_o[simp]: "lookup_o (update_o t k v) = (lookup_o t)(k := Some v)" sorry section \Exercise 2 -- 2 points\ text \One of the limitations in Isabelle's mechanism for data refinement via the code generator is that only one type-constructor can be refined. So, in particular it is not possible to directly refine the type @{typ "'a :: linorder \ 'b option"} to implement it via ordered trees. To this end, we first introduce a separate type for maps, a copy of @{typ "'a :: linorder \ 'b option"}, which then can be refined. Here, we provide three operations on map: lookup, empty-map, and update.\ typedef ('a,'b)map = "{ f :: 'a \ 'b option. True}" by auto setup_lifting type_definition_map lift_definition lookup :: "('a,'b)map \ 'a \ 'b option" is "\ f. f" . lift_definition empty_map :: "('a,'b)map" is "\ _. None" by auto lift_definition update :: "('a,'b)map \ 'a \ 'b \ ('a,'b)map" is "\ f x y. f (x := Some y)" by auto text \We now want to implement these operations by the tree based operations. To this end, we first need to disable the lifting-setup for @{typ "('a,'b)otree"}, because ordered trees should not be decomposed into their representative type by @{method transfer}, but only @{typ "('a,'b)map"}.\ lifting_forget otree.lifting text \Define a constant @{term "map_o :: ('a :: linorder,'b)otree \ ('a,'b)map"}, to convert an ordered tree into a map, use this as a pseudo-constructor for the @{typ "('a,'b)map"}-type, and provide code-equations that implement @{const lookup}, @{const empty_map} and @{const update}.\ lemma empty_om[simp]: "map_o empty_o = empty_map" sorry lemma update_om[simp]: "map_o (update_o t k v) = update (map_o t) k v" sorry lemma lookup_om[simp]: "lookup_o t = lookup (map_o t)" sorry code_datatype map_o lemma [code]: "update = undefined" sorry lemma [code]: "empty_map = undefined" sorry lemma [code]: "lookup = undefined" sorry definition "test xs = foldr ((\ (k,v) m. update m k v)) xs empty_map" (* value should display some tree, if you also solved exercise 1 *) value (code) "test [(3 :: int,True), (8,False), (10,True), (7,True), (3,False)]" section \Exercise 3 -- 4 points\ text \Application: Dijkstra's Shortest-Path-Algorithm\ text \Below you find a (hopefully) correct implementation of Dijkstra's algorithm to compute the distances from a starting node to all other nodes. The implementation works as follows. As input to the main loop there are a weighted graph \G\, a set of nodes \done\ which already have their final distance computed, a map \dist\ that stores the currently computed distances, and a list \todo\ of nodes which still need to be processed; moreover \todo\ also contains the distances of these nodes and \todo\ is sorted w.r.t. the distance. The function \process\ takes a list of successor nodes of some selected note (including the edge-weights), and it updates both the distance map and the todo-list.\ fun remove_first :: "('a \ bool) \ 'a list \ 'a list" where "remove_first f [] = []" | "remove_first f (x # xs) = (if f x then xs else x # remove_first f xs)" fun process :: "nat \ 'a set \ ('a \ nat option) \ ('a \ nat) list \ ('a \ nat) list \ ('a \ nat option) \ ('a \ nat) list" where "process dn done dist todos [] = (dist, todos)" | "process dn done dist todos ((s,d_ns) # succs) = \ \already processed node?\ (if s \ done then process dn done dist todos succs \ \is there already some distance computed\ else let ds_new = dn + d_ns in case dist s of \ \if not, store the distance and update todo-list\ None \ process dn done (dist (s := Some ds_new)) (insort_key snd (s,ds_new) todos) succs \ \if yes, compare the new alternative and update on demand\ | Some ds \ if ds \ ds_new then process dn done dist todos succs else let todos' = insort_key snd (s,ds_new) (remove_first ((=) s o fst) todos) in process dn done (dist (s := Some ds_new)) todos' succs)" partial_function (tailrec) dijkstra_main :: "('a \ ('a \ nat) list) \ 'a set \ ('a \ nat option) \ ('a \ nat) list \ ('a \ nat option)" where [code]: "dijkstra_main G done dist todos = (case todos of [] \ dist | ((n,dn) # todos') \ let done' = insert n done; (dist',todo') = process dn done' dist todos' (G n) in dijkstra_main G done' dist' todo')" definition dijkstra :: "('a \ ('a \ nat) list) \ 'a \ ('a \ nat option)" where "dijkstra G start_node = dijkstra_main G {} ((\ _. None)(start_node := Some 0)) [(start_node,0)]" text \Although @{const dijkstra} is already executable our aim is to create a more efficient version of the algorithm where the distance function of type @{typ "'a \ nat option"} is implemented via @{typ "('a,nat)map"} where then internally the ordered trees are used. To this end, we reformulate a variant of @{const process} which operates on maps.\ fun process' :: "nat \ 'a set \ ('a,nat)map \ ('a \ nat) list \ ('a \ nat) list \ ('a,nat)map \ ('a \ nat) list" where "process' dn done dist todos [] = (dist, todos)" | "process' dn done dist todos ((s,d_ns) # succs) = (if s \ done then process' dn done dist todos succs else let ds_new = dn + d_ns in case lookup dist s of None \ process' dn done (update dist s ds_new) (insort_key snd (s,ds_new) todos) succs | Some ds \ if ds \ ds_new then process' dn done dist todos succs else let todos' = insort_key snd (s,ds_new) (remove_first ((=) s o fst) todos) in process' dn done (update dist s ds_new) todos' succs)" text \Task 1 (2 points): Establish the following relation-ship between @{const process} and @{const process'}.\ lemma process_to_process': "process dn done (lookup dist) todos succs = map_prod lookup id (process' dn done dist todos succs)" sorry text \For the main-function, we just define the primed-version as an instance of the unprimed main-function.\ definition dijkstra_main' :: "('a \ ('a \ nat) list) \ 'a set \ ('a,nat)map \ ('a \ nat) list \ ('a \ nat option)" where "dijkstra_main' G done dist todos = dijkstra_main G done (lookup dist) todos" text \Task 2 (2 points): Complete the following code-equation which refers to @{const process'} and @{const dijkstra_main'} in the right-hand side, (and not to @{const process} and @{const dijkstra_main}), and prove it ...\ lemma dijkstra_main_simp[code]: "dijkstra_main' G done dist todos = undefined" sorry text \... and similarly provide a code-equation for @{const dijkstra} which invokes @{const dijkstra_main'} in its right-hand side.\ lemma [code]: "dijkstra G s = dijkstra_main' G undefined undefined undefined" sorry text \If you solved all exercises, the code should now contain a tree-based implementation of the algorithm.\ export_code dijkstra in Haskell module_name Dijkstra definition test_graph :: "int \ (int \ nat) list" where "test_graph = (\ n. if n = 1 then [(2,7), (3,9), (6,14)] else if n = 2 then [(1,7), (3,10), (4,15)] else if n = 3 then [(1,9), (6,2), (4,11), (2,10)] else if n = 4 then [(3,11), (5,6), (2,15)] else if n = 5 then [(6,9), (4,6)] else if n = 6 then [(1,14), (3,2), (5,9)] else [])" value "let s = 1 in (''distances from node '', s, '' are '', map (dijkstra test_graph s) [1..6])" end