5 To minimize running time, each node of a trie should contain an array that maps |
5 To minimize running time, each node of a trie should contain an array that maps |
6 letters to subtries. We have chosen a (sometimes) more space efficient |
6 letters to subtries. We have chosen a (sometimes) more space efficient |
7 representation where the subtries are held in an association list, i.e.\ a |
7 representation where the subtries are held in an association list, i.e.\ a |
8 list of (letter,trie) pairs. Abstracting over the alphabet \isa{'a} and the |
8 list of (letter,trie) pairs. Abstracting over the alphabet \isa{'a} and the |
9 values \isa{'v} we define a trie as follows: |
9 values \isa{'v} we define a trie as follows: |
10 *} |
10 *}; |
11 |
11 |
12 datatype ('a,'v)trie = Trie "'v option" "('a * ('a,'v)trie)list"; |
12 datatype ('a,'v)trie = Trie "'v option" "('a * ('a,'v)trie)list"; |
13 |
13 |
14 text{*\noindent |
14 text{*\noindent |
15 The first component is the optional value, the second component the |
15 The first component is the optional value, the second component the |
16 association list of subtries. This is an example of nested recursion involving products, |
16 association list of subtries. This is an example of nested recursion involving products, |
17 which is fine because products are datatypes as well. |
17 which is fine because products are datatypes as well. |
18 We define two selector functions: |
18 We define two selector functions: |
19 *} |
19 *}; |
20 |
20 |
21 consts value :: "('a,'v)trie \\<Rightarrow> 'v option" |
21 consts value :: "('a,'v)trie \\<Rightarrow> 'v option" |
22 alist :: "('a,'v)trie \\<Rightarrow> ('a * ('a,'v)trie)list"; |
22 alist :: "('a,'v)trie \\<Rightarrow> ('a * ('a,'v)trie)list"; |
23 primrec "value(Trie ov al) = ov"; |
23 primrec "value(Trie ov al) = ov"; |
24 primrec "alist(Trie ov al) = al"; |
24 primrec "alist(Trie ov al) = al"; |
25 |
25 |
26 text{*\noindent |
26 text{*\noindent |
27 Association lists come with a generic lookup function: |
27 Association lists come with a generic lookup function: |
28 *} |
28 *}; |
29 |
29 |
30 consts assoc :: "('key * 'val)list \\<Rightarrow> 'key \\<Rightarrow> 'val option"; |
30 consts assoc :: "('key * 'val)list \\<Rightarrow> 'key \\<Rightarrow> 'val option"; |
31 primrec "assoc [] x = None" |
31 primrec "assoc [] x = None" |
32 "assoc (p#ps) x = |
32 "assoc (p#ps) x = |
33 (let (a,b) = p in if a=x then Some b else assoc ps x)"; |
33 (let (a,b) = p in if a=x then Some b else assoc ps x)"; |
35 text{* |
35 text{* |
36 Now we can define the lookup function for tries. It descends into the trie |
36 Now we can define the lookup function for tries. It descends into the trie |
37 examining the letters of the search string one by one. As |
37 examining the letters of the search string one by one. As |
38 recursion on lists is simpler than on tries, let us express this as primitive |
38 recursion on lists is simpler than on tries, let us express this as primitive |
39 recursion on the search string argument: |
39 recursion on the search string argument: |
40 *} |
40 *}; |
41 |
41 |
42 consts lookup :: "('a,'v)trie \\<Rightarrow> 'a list \\<Rightarrow> 'v option"; |
42 consts lookup :: "('a,'v)trie \\<Rightarrow> 'a list \\<Rightarrow> 'v option"; |
43 primrec "lookup t [] = value t" |
43 primrec "lookup t [] = value t" |
44 "lookup t (a#as) = (case assoc (alist t) a of |
44 "lookup t (a#as) = (case assoc (alist t) a of |
45 None \\<Rightarrow> None |
45 None \\<Rightarrow> None |
47 |
47 |
48 text{* |
48 text{* |
49 As a first simple property we prove that looking up a string in the empty |
49 As a first simple property we prove that looking up a string in the empty |
50 trie \isa{Trie~None~[]} always returns \isa{None}. The proof merely |
50 trie \isa{Trie~None~[]} always returns \isa{None}. The proof merely |
51 distinguishes the two cases whether the search string is empty or not: |
51 distinguishes the two cases whether the search string is empty or not: |
52 *} |
52 *}; |
53 |
53 |
54 lemma [simp]: "lookup (Trie None []) as = None"; |
54 lemma [simp]: "lookup (Trie None []) as = None"; |
55 apply(case_tac as, auto).; |
55 by(case_tac as, auto); |
56 |
56 |
57 text{* |
57 text{* |
58 Things begin to get interesting with the definition of an update function |
58 Things begin to get interesting with the definition of an update function |
59 that adds a new (string,value) pair to a trie, overwriting the old value |
59 that adds a new (string,value) pair to a trie, overwriting the old value |
60 associated with that string: |
60 associated with that string: |
61 *} |
61 *}; |
62 |
62 |
63 consts update :: "('a,'v)trie \\<Rightarrow> 'a list \\<Rightarrow> 'v \\<Rightarrow> ('a,'v)trie"; |
63 consts update :: "('a,'v)trie \\<Rightarrow> 'a list \\<Rightarrow> 'v \\<Rightarrow> ('a,'v)trie"; |
64 primrec |
64 primrec |
65 "update t [] v = Trie (Some v) (alist t)" |
65 "update t [] v = Trie (Some v) (alist t)" |
66 "update t (a#as) v = |
66 "update t (a#as) v = |
89 attempt) at the body of \isa{update}: it contains both |
89 attempt) at the body of \isa{update}: it contains both |
90 \isa{let} and a case distinction over type \isa{option}. |
90 \isa{let} and a case distinction over type \isa{option}. |
91 |
91 |
92 Our main goal is to prove the correct interaction of \isa{update} and |
92 Our main goal is to prove the correct interaction of \isa{update} and |
93 \isa{lookup}: |
93 \isa{lookup}: |
94 *} |
94 *}; |
95 |
95 |
96 theorem "\\<forall>t v bs. lookup (update t as v) bs = |
96 theorem "\\<forall>t v bs. lookup (update t as v) bs = |
97 (if as=bs then Some v else lookup t bs)"; |
97 (if as=bs then Some v else lookup t bs)"; |
98 |
98 |
99 txt{*\noindent |
99 txt{*\noindent |
102 \isa{as} or \isa{bs} is required. The choice of \isa{as} is merely |
102 \isa{as} or \isa{bs} is required. The choice of \isa{as} is merely |
103 guided by the intuition that simplification of \isa{lookup} might be easier |
103 guided by the intuition that simplification of \isa{lookup} might be easier |
104 if \isa{update} has already been simplified, which can only happen if |
104 if \isa{update} has already been simplified, which can only happen if |
105 \isa{as} is instantiated. |
105 \isa{as} is instantiated. |
106 The start of the proof is completely conventional: |
106 The start of the proof is completely conventional: |
107 *} |
107 *}; |
108 |
108 |
109 apply(induct_tac as, auto); |
109 apply(induct_tac as, auto); |
110 |
110 |
111 txt{*\noindent |
111 txt{*\noindent |
112 Unfortunately, this time we are left with three intimidating looking subgoals: |
112 Unfortunately, this time we are left with three intimidating looking subgoals: |
116 ~3.~\dots~{\isasymLongrightarrow}~lookup~\dots~bs~=~lookup~t~bs% |
116 ~3.~\dots~{\isasymLongrightarrow}~lookup~\dots~bs~=~lookup~t~bs% |
117 \end{isabellepar}% |
117 \end{isabellepar}% |
118 Clearly, if we want to make headway we have to instantiate \isa{bs} as |
118 Clearly, if we want to make headway we have to instantiate \isa{bs} as |
119 well now. It turns out that instead of induction, case distinction |
119 well now. It turns out that instead of induction, case distinction |
120 suffices: |
120 suffices: |
121 *} |
121 *}; |
122 |
122 |
123 apply(case_tac[!] bs); |
123 apply(case_tac[!] bs); |
124 apply(auto).; |
124 by(auto); |
125 |
125 |
126 text{*\noindent |
126 text{*\noindent |
127 Both \isaindex{case_tac} and \isaindex{induct_tac} |
127 Both \isaindex{case_tac} and \isaindex{induct_tac} |
128 take an optional first argument that specifies the range of subgoals they are |
128 take an optional first argument that specifies the range of subgoals they are |
129 applied to, where \isa{!} means all subgoals, i.e.\ \isa{[1-3]} in our case. Individual |
129 applied to, where \isa{!} means all subgoals, i.e.\ \isa{[1-3]} in our case. Individual |