I have a homework where we are supposed to implement a functional datatype for any generic datatype. The specifications for the class are given in algebraic form. I'm having problems interpreting it and would like some help.
Here are the specifications that we got in algebraic form:
T is any generic datatype
Values V = {nil} ∪ T
Expressions E = {(cons e1 e2) ∣ e1, e2 ∈ E} ∪ V
Let L^n ⊂ E contain only expressions of the form (cons e1 (cons e2 . . . (cons en nil) . . .)
with all ei ∈ T for some n ∈ N. Let further L^∗ = ⋃n∈N L^n.
Functions
empty? ∶ E → {true,false}, e ↦ {true if e = nil, false otherwise
first ∶ L^∗ → E, (cons e1 e2) ↦ e1
rest ∶ L^∗ → E, (cons e1 e2) ↦ e2
prepend ∶ E × L^∗ → L^∗, e, (cons e1 e2) ↦ (cons e (cons e1 e2))
ith ∶ (L^∗)×N → E, (cons e1 . . . (cons ei. . . (cons en nil) . . .), i ↦ ei for 0 < i ≤ n
length ∶ L^∗ → N,l ∈ L^n ↦ n
Here are my interpretations:
Values: The values are of the class T and are nil(null)
Expressions:
Not sure if E is an array that consists of constants e1,e2... and that L^n is also an array that consists of e1,e2... en? Don't understand the (cons en nil) part... are the constants supposed to be nil?
Functions
Empty? function empty is supposed to check an element in the E array has a value or not.
first: returns first element
rest: returns a new array without the first element
prepend: puts the constant e first in the array.
ith: Not sure, maybe return n elements depending on input.
length: give length of the list L
The expressions are a bit messy but could anyone give me some feedback on this? Thanks in advance.
Let T be int. Then V is any int or a nul. Now, expressions can be cons(x y) where x,y are either ints or nuls or other expressions, for example:
1
cons(2 3)
cons(cons(4 5) cons(6 7))
L^n is a special kind of expression where the first argument of cons is limited to be T and the innermost atom must be nul. So this
cons(1 cons(2 cons(3 nul)))
is a valid L^n (specifically L^3), while this is not
cons(cons(4 5) cons(6 7))
(as a side note, L^n is what's called list in lisp, while E is a generic pair).
From your set of functions, only empty? (aka nil), first (=car) and rest (=cdr) are primitive, others can be defined in terms of them (provided you've also got the conditional), for example:
length(E) = 0 if empty(E) else 1 + length(rest(E))
Related
I have written level traversal of binary tree in other language definitions, but I don't know how to represent level traversal in isabelle/hol.Has anyone defined it or how to define it?
In principle, you can do it exactly the same way as in Haskell. The problematic bit is that you have to prove termination of the recursive auxiliary function (what is called tbf in the Haskell code you linked). The easiest way to show this is by finding some sort of measure on the input (a list of trees) that decreases with every recursive call.
I propose the following measure: sum the sizes of all the trees in the list, where the size is the number of all the nodes in the tree (including leaf nodes).
We can use the binary trees from HOL-Library (HOL-Library.Tree). First, we define some auxiliary functions on trees, including our size functions, and prove some facts about them:
primrec tree_values :: "'a tree ⇒ 'a list" where
"tree_values Leaf = []"
| "tree_values (Node l x r) = [x]"
primrec tree_children :: "'a tree ⇒ 'a tree list" where
"tree_children Leaf = []"
| "tree_children (Node l x r) = [l, r]"
primrec tree_size :: "'a tree ⇒ nat" where
"tree_size Leaf = 1"
| "tree_size (Node l x r) = tree_size l + tree_size r + 1"
definition tree_list_size :: "'a tree list ⇒ nat"
where "tree_list_size = sum_list ∘ map tree_size"
lemma tree_size_pos: "tree_size t > 0"
by (induction t) auto
lemma tree_size_nonzero [simp]: "tree_size t ≠ 0"
by (simp add: tree_size_pos)
lemma tree_list_size_children [simp]:
"tree_list_size (tree_children t) = tree_size t - 1"
by (cases t) (auto simp: tree_list_size_def)
Next, we will need another simple lemma on sum_list and concat:
lemma sum_list_concat: "sum_list (concat xs) = sum_list (map sum_list xs)"
by (induction xs) auto
Finally, we can define BFS and prove its termination:
function bfs_aux :: "'a tree list ⇒ 'a list" where
"bfs_aux ts =
(if ts = [] then [] else concat (map tree_values ts) # bfs_aux (concat (map tree_children ts)))"
by auto
termination
proof (relation "measure tree_list_size")
fix ts :: "'a tree list"
assume ts: "ts ≠ []"
have "tree_list_size (concat (map tree_children ts)) =
sum_list (map (tree_list_size ∘ tree_children) ts)"
by (simp add: map_concat sum_list_concat tree_list_size_def o_assoc)
also from ‹ts ≠ []› have "… < sum_list (map tree_size ts)"
by (intro sum_list_strict_mono) (auto simp: tree_size_pos)
also have "… = tree_list_size ts"
by (simp add: tree_list_size_def)
finally show "(concat (map tree_children ts), ts) ∈ measure tree_list_size"
by simp
qed auto
definition bfs :: "'a tree ⇒ 'a list"
where "bfs t = bfs_aux [t]‹›
And we can test it:
value "bfs (⟨⟨⟨Leaf, ''d'', Leaf⟩, ''b'', ⟨Leaf, ''e'', Leaf⟩⟩, ''a'',
⟨⟨Leaf, ''f'', Leaf⟩, ''c'', ⟨Leaf, ''g'', Leaf⟩⟩⟩)"
> "[''a'', ''b'', ''c'', ''d'', ''e'', ''f'', ''g'']"
:: "char list list"
For more on defining functions with non-trivial recursion patterns like this and proving their termination, see the documentation of the function package (Section 4 in particular).
I've been exploring the Foldable class and also the the Monoid class.
Firstly, lets say I want to fold over a list of the Monoid First. Like so:
x :: [First a]
fold? mappend mempty x
Then I assume in this case the most appropriate fold would be foldr, as mappend for First is lazy in it's second argument.
Conversely, for Last we'd want to foldl' (or just foldl I'm not sure).
Now moving away from lists, I've defined a simple binary tree like so:
{-# LANGUAGE GADTs #-}
data BinaryTree a where
BinaryTree :: BinaryTree a -> BinaryTree a -> BinaryTree a
Leaf :: a -> BinaryTree a
And I've made it Foldable with the most straightforward definition:
instance Foldable BinaryTree where
foldMap f (BinaryTree left right) =
(foldMap f left) `mappend` (foldMap f right)
foldMap f (Leaf x) = f x
As Foldable defines fold as simply foldMap id we can now do:
x1 :: BinaryTree (First a)
fold x1
x2 :: BinaryTree (Last a)
fold x2
Assuming our BinaryTree is balanced, and there's not many Nothing values, these operations should take O(log(n)) time I believe.
But Foldable also defines a whole lot of default methods like foldl, foldl', foldr and foldr' based on foldMap.
These default definitions seem to be implemented by composing a bunch of functions, wrapped in a Monoid called Endo, one for each element in the collection, and then composing them all.
For the purpose of this discussion I am not modifying these default definitions.
So lets now consider:
x1 :: BinaryTree (First a)
foldr mappend mempty x1
x2 :: BinaryTree (Last a)
foldl mappend mempty x2
Does running these retain O(log(n)) performance of the ordinary fold? (I'm not worried about constant factors for the moment). Does laziness result in the tree not needing to be fully traversed? Or will the default definitions of foldl and foldr require an entire traversal of the tree?
I tried to go though the algorithm step by step (much like they did on the Foldr Foldl Foldl' article) but I ended up completely confusing myself as this is a bit more complex as it involves an interaction between Foldable, Monoid and Endo.
So what I'm looking for is an explanation of why (or why not) the default definition of say foldr, would only take O(log(n)) time on a balanced binary tree like above. A step by step example like what's from the Foldr Foldl Foldl' article would be really helpful, but I understand if that's too difficult, as I totally confused myself attempting it.
Yes, it has O(log(n)) best case performance.
Endo is a wrapper around (a -> a) kind of functions that:
instance Monoid (Endo a) where
mempty = Endo id
Endo f `mappend` Endo g = Endo (f . g)
And the default implementation of foldr in Data.Foldable:
foldr :: (a -> b -> b) -> b -> t a -> b
foldr f z t = appEndo (foldMap (Endo #. f) t) z
The definition of . (function composition) in case:
(.) f g = \x -> f (g x)
Endo is defined by newtype constructor, so it only exists at compile stage, not run-time.
#. operator changes the type of it's second operand and discard the first.
The newtype constructor and #. operator guarantee that you can ignore the wrapper when considering performance issues.
So the default implementation of foldr can be reduced to:
-- mappend = (.), mempty = id from instance Monoid (Endo a)
foldr :: (a -> b -> b) -> b -> t a -> b
foldr f z t = foldMap f t z
For your Foldable BinaryTree:
foldr f z t
= foldMap f t z
= case t of
Leaf a -> f a z
-- what we care
BinaryTree l r -> ((foldMap f l) . (foldMap f r)) z
The default lazy evaluation in Haskell is ultimately simple, there are just two rules:
function application first
evaluate the arguments from left to right if the values matter
That makes it easy to trace the evaluation of the last line of the code above:
((foldMap f l) . (foldMap f r)) z
= (\z -> foldMap f l (foldMap f r z)) z
= foldMap f l (foldMap f r z)
-- let z' = foldMap f r z
= foldMap f l z' -- height 1
-- if the branch l is still not a Leaf node
= ((foldMap f ll) . (foldMap f lr)) z'
= (\z -> foldMap f ll (foldMap f lr)) z'
= foldMap f ll (foldMap f lr z')
-- let z'' = foldMap f lr z'
= foldMap f ll z'' -- height 2
The right branch of the tree is never expanded before the left has been fully expanded, and it goes one level higher after an O(1) operation of function expansion and application, therefore when it reached the left-most Leaf node:
= foldMap f leaf#(Leaf a) z'heightOfLeftMostLeaf
= f a z'heightOfLeftMostLeaf
Then f looks at the value a and decides to ignore its second argument (like what mappend will do to First values), the evaluation short-circuits, results O(height of the left-most leaf), or O(log(n)) performance when the tree is balanced.
foldl is all the same, it's just foldr with mappend flipped i.e. O(log(n)) best case performance with Last.
foldl' and foldr' are different.
foldl' :: (b -> a -> b) -> b -> t a -> b
foldl' f z0 xs = foldr f' id xs z0
where f' x k z = k $! f z x
At every step of reduction, the argument is evaluated first and then the function application, the tree will be traversed i.e. O(n) best case performance.
I am implementing the Bron-Kerbosch algorithm in Clojure for a class project and having some issues. The issue lies in the final lines of the algorithm
BronKerbosch1(R, P, X):
if P and X are both empty:
report R as a maximal clique
for each vertex v in P:
BronKerbosch1(R ⋃ {v}, P ⋂ N(v), X ⋂ N(v))
P := P \ {v} ;This line
X := X ⋃ {v} ;This line
I know in Clojure there is no sense of "set x = something". But do know there is the assoc function which I think is similar. I would like to know if assoc would be appropriate to complete my implementation.
In my implementation graphs are represented as so
[#{1 3 2} #{0 3 2} #{0 1 3} #{0 1 2}]
Where the 0th node is represented as the first set in the vector, and the values in the set represent edges to other nodes. So that above represents a graph with 4 nodes that is complete (all nodes are connected to all other nodes).
So far my algorithm implementation is
(defn neighV [graph, v]
(let [ret-list (for [i (range (count graph)) :when (contains? (graph i) v)] i)]
ret-list))
(defn Bron-Kerbosch [r, p, x, graph, cliques]
(cond (and (empty? p) (empty? x)) (conj cliques r)
:else
(for [i (range (count p))]
(conj cliques (Bron-Kerbosch (conj r i) (disj p (neighV graph i) (disj x (neighV graph i)) graph cliques)))
)))
So right now I am stuck altering p and x as per the algorithm. I think that I can use assoc to do this but I think it only applies to maps. Would it be possible to use, could someone recommend another function?
assoc does not alter its argument. Like all of the other basic collection operations in Clojure it returns a new immutable collection.
In order to do updates "in place", you will need to stop using the basic Clojure datatypes, and use the native Java types like java.util.HashSet.
The other (and preferred) option is to refactor your algorithm so that all updates are passed to the next iteration or recursion of the code.
Here is an initial attempt to adjust your code to this style, with the caveat that an inner modification may need to be pulled up from the recursive call:
(defn Bron-Kerbosch
[r p x graph cliques]
(if (every? empty? [p x])
(conj cliques r)
(reduce (fn [[cliques p x] v]
(let [neigh (neighV graph v)]
[(conj cliques
;; do we need to propagate updates to p and x
;; from this call back up to this scope?
(Bron-Kerbosch (conj r v)
(disj p neigh)
(disj x neigh)
graph
cliques))
;; here we pass on the new values for p and x
(disj p v)
(conj x v)]))
[cliques p x]
(range (count p)))))
I think given your comment, you'd be better served using loop and recur. It's really not much different than what you have now, but it would eliminate the recursive function call.
I am attempting to generate a nice syntax for mapping a function over the values of an associative list, i.e. I want to write [x ↦ f y | (x ↦ y) ∈ l] for mapAList f l. I came up with
syntax
"_alist_map" :: "['b, pttrn, ('a × 'b) list] ⇒ ('a × 'b) list"
("[x ↦ _ | '(x ↦ _') ∈ _]")
which works, but causes term "(x,y)#[]" to tell me Inner syntax error at "(x , y ) # []" and the (x is shaded slightly different.
The reason seems that once x appears in a mixfix annotation, it now always a literal token to the grammer (a delimiter according to §7.4.1 of isar-ref) and no longer an identifier – just like the syntax for if ... then ... else ... prevents if from being a variable name
Can I somehow work around this problem?
Identifier names used in mixfix annotations cannot be used as identifiers any longer, and I don't know any way around that. Therefore, instead of using x as a variable name, you can pick a non-identifier symbol like \<xX> or \<mapAListvariable> and setup the LaTeX output to print this as x by adding \newcommand{\isasymmapAListvariable}{x} to your root.tex.
You can also add \<xX> or \<mapAListvariable> to the symbols file of Isabelle/JEdit (preferably in $ISABELLE_HOME_USER/etc/symbols) and assign it some Unicode point that will be used for display in Isabelle/JEdit.
I just made a small experiment with a function map_alist that hopefully corresponds to your mapAList and which is defined as follows:
fun map_alist :: "('b ⇒ 'c) ⇒ ('a × 'b) list ⇒ ('a × 'c) list"
where
"map_alist f [] = []" |
"map_alist f ((x, y) # xs) = (x, f y) # map_alist f xs"
Then existing syntax can be used which looks a little bit as you intended. Maybe this is an option?
lemma "map_alist f xs = [(x, f y). (x, y) ← xs]"
by (induct xs) auto
It's feel like I'm stuck, my friends. Can somebody explain me pick equations from "Pearls of Functional Algorithm Design", chapter 11 ("Not the maximum segment sum").
Here is the problem (a little bit simplified)
Let us have some states with given transitions:
data State = E | S | M | N
deriving (Show, Eq)
step E False = E
step E True = S
step S False = M
step S True = S
step M False = M
step M True = N
step N False = N
step N True = N
Now, let's define pick:
pick q = map snd . filter ((== q) . fst) . map (\a -> (foldl step E a, a))
The author claims that the following seven equations hold:
pick E xs = [[]]
pick S [ ] = [ ]
pick S (xs ++ [x]) = map (++[x ]) (pick S xs ++ pick E xs)
pick M [ ] = [ ]
pick M (xs ++ [x ]) = pick M xs ++ pick S xs
pick N [ ] = [ ]
pick N (xs ++ [x]) = pick N xs ++ map (++[x]) (pick N xs ++ pick M xs)
Can somebody explain me in simple words, why these equations are true, how can we prove an obvious proof? I feel like I almost understand S-equations, but altogether this remains elusive.
Ok, I needed to visualize your state graph:
And give a type signature for pick :: State -> [[Bool]] -> [(State, [Bool]).
Now, this doesn't jive with your first equation pick E xs = [[]] - it'd have to be pick E xs = [(E,[])].
Perhaps you meant to define pick this way:
pick :: State -> [[Bool]] -> [[Bool]]
pick q = map snd . filter ((== q) . fst) . map (\a -> (foldl step E a, a))
Assuming that definition, the first equation now makes sense. It claims that if you start at E, the only sequence of booleans in xs that will end at E is the empty list.
Note that this assumes that [] ∈ xs.
Also, if ys = replicate n False, pick E [ys] = [ys], so this implies that ∀ n, ys ∉ xs.
The second, fourth, and sixth equations are all of the form pick _ [ ] = [ ], which is trivially true by the definition of map and filter.
The third equation, pick S (xs ++ [x]) = map (++[x ]) (pick S xs ++ pick E xs) doesn't really make sense either. What I'm guessing it's trying to say is:
pick S (map (++[True] xs) = map (++[True]) (pick S xs ++ pick E xs)
Which is to say - any path starting at E and ending at S can be constructed by taking an existing path to E or S and appending True. Equivalently, that every path that ends at S must end with True.
The fifth equation is similarly nonsensical, and should be stated as:
pick S (map (++[False] xs) = map (++[False]) (pick S xs ++ pick M xs)
And the seventh equation should be restated as:
pick N (map (++ [True]) xs) = pick N xs ++ map (++[True]) (pick N xs ++ pick M xs)