Day05_lists
Lists
Notation "x :: y" := (cons x y)
(at level 60, right associativity).
Notation "[ ]" := nil.
Notation "[ x ; .. ; y ]" := (cons x .. (cons y []) ..).
Notation "x ++ y" := (app x y)
(at level 60, right associativity).
Now lists can be written concisely:
Definition list123 := @cons nat 1 (@cons nat 2 (@cons nat 3 (@nil nat))).
Definition list123' := @cons _ 1 (@cons _ 2 (@cons _ 3 (@nil _))).
Definition list123'' := cons 1 (cons 2 (cons 3 nil)).
All that's fine. But check this out.
Such a notation is very common for lists and arrays to use this
"concrete, element-wise" notation---a wide variety of programming
languages use it.
The general idea is that:
[
[ v1; v2; ...; vn-1; vn ]
]
is the same as
[
cons v1 (cons v2 (cons ... (cons vn-1 (cons vn nil))))
]
It's worth noting: when using concrete notation, you only write
the values, never the tail of the list.
But it's easy to get confused! The following quizzes are meant to
hone your understanding.
Combine and split
Fixpoint combine {X Y : Type} (lx : list X) (ly : list Y)
: list (X×Y) :=
match lx, ly with
| [], _ ⇒ []
| _, [] ⇒ []
| x :: tx, y :: ty ⇒ (x, y) :: (combine tx ty)
end.
Try answering the following questions on paper and
checking your answers in Coq:
Fill in the definition of split below. Make sure it passes the
given unit test.
- What is the type of combine (i.e., what does Check @combine print?)
- What does
Compute (combine [1;2] [false;false;true;true]).
Exercise: 2 stars, standard (split)
The function split is the right inverse of combine: it takes a list of pairs and returns a pair of lists. In many functional languages, it is called unzip.Fixpoint split {X Y : Type} (l : list (X×Y))
: (list X) × (list Y)
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
Compute split [(1,false);(2,false)]. (* ==> (1;2,false;false) *)
☐
Filter
Fixpoint filter {X:Type} (test: X→bool) (l:list X)
: (list X) :=
match l with
| [] ⇒ []
| h :: t ⇒ if test h then h :: (filter test t)
else filter test t
end.
For example, if we apply filter to the predicate evenb
and a list of numbers l, it returns a list containing just the
even members of l.
Compute (filter evenb [1;2;3;4]). (* ==> 2;4 *)
Definition length_is_1 {X : Type} (l : list X) : bool :=
eqb (length l) 1.
Compute (filter length_is_1 [ [1; 2]; [3]; [4]; [5;6;7]; []; [8] ]). (* ==> [3]; [4]; [8] *)
Definition countoddmembers' (l:list nat) : nat :=
length (filter oddb l).
Compute (countoddmembers' [1;0;3;1;4;5]). (* ==> 4 *)
Compute (countoddmembers' [0;2;4]). (* ==> 0 *)
Compute (countoddmembers' nil). (* ==> 0 *)
Higher-order Functions and Anonymous Functions: Together Forever
Compute (filter (fun l ⇒ eqb (length l) 1) [ [1; 2]; [3]; [4]; [5;6;7]; []; [8] ]).
(* ==> [3]; [4]; [8] *)
Exercise: 2 stars, standard (filter_even_gt7)
Use filter (instead of Fixpoint) to write a Coq function filter_even_gt7 that takes a list of natural numbers as input and returns a list of just those that are even and greater than 7.Definition filter_even_gt7 (l : list nat) : list nat
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
☐
Compute (filter_even_gt7 [1;2;6;9;10;3;12;8]). (* ==> 10;12;8 *)
Compute (filter_even_gt7 [5;2;6;19;129]). (* ==> *)
Exercise: 3 stars, standard (partition)
Use filter to write a Coq function partition:partition : ∀ X : Type,
(X → bool) → list X → list X × list X
Definition partition {X : Type}
(test : X → bool)
(l : list X)
: list X × list X
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
Compute (partition oddb [1;2;3;4;5]). (* ==> (1;3;5, 2;4) *)
Compute (partition (fun x ⇒ false) [5;9;0]). (* ==> (, 5;9;0) *)
☐
Map
Fixpoint map {X Y: Type} (f:X→Y) (l:list X) : (list Y) :=
match l with
| [] ⇒ []
| h :: t ⇒ (f h) :: (map f t)
end.
It takes a function f and a list l = [n1, n2, n3, ...]
and returns the list [f n1, f n2, f n3,...] , where f has
been applied to each element of l in turn. For example:
The element types of the input and output lists need not be
the same, since map takes two type arguments, X and Y; it
can thus be applied to a list of numbers and a function from
numbers to booleans to yield a list of booleans:
It can even be applied to a list of numbers and
a function from numbers to lists of booleans to
yield a list of lists of booleans:
Compute (map (fun n ⇒ [evenb n;oddb n]) [2;1;2;5]).
(* ==> [true;false];[false;true];[true;false];[false;true] *)
Lists are not the only inductive type for which map makes sense.
Here is a map for the option type:
Definition option_map {X Y : Type} (f : X → Y) (xo : option X)
: option Y :=
match xo with
| None ⇒ None
| Some x ⇒ Some (f x)
end.
Exercise: 2 stars, standard (map_option)
We can also use lists and options together. Define a function map_option that takes a function f : X → option Y and a list of Xs and returns a list of those Ys for which f didn't return None. Take X and Y as implicit arguments.Fixpoint map_option {X Y : Type} (f : X → option Y) (l : list X) : list Y
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
☐
Exercise: 2 stars, standard (flat_map)
The function map maps a list X to a list Y using a function of type X → Y. We can define a similar function, flat_map, which maps a list X to a list Y using a function f of type X → list Y. Your definition should work by 'flattening' the results of f, like so:flat_map (fun n ⇒ [n;n+1;n+2]) [1;5;10]
= [1; 2; 3; 5; 6; 7; 10; 11; 12].
Fixpoint flat_map {X Y: Type} (f: X → list Y) (l: list X)
: (list Y)
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
Compute (flat_map (fun n ⇒ [n;n;n]) [1;5;4]).
(* ==> 1; 1; 1; 5; 5; 5; 4; 4; 4 *)
☐
: (list Y)
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
Compute (flat_map (fun n ⇒ [n;n;n]) [1;5;4]).
(* ==> 1; 1; 1; 5; 5; 5; 4; 4; 4 *)
☐
Exercise: 2 stars, standard, optional (map_option')
The flat_map function defined above is a little bit more general than map_option. Define a function map_option' that has map_option's type, but uses flat_map to do the work. You'll need to somehow take f : X → option Y and produce some other function of type X → list Y.Definition map_option' {X Y : Type} (f: X → option Y) (l : list X) : list Y
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
☐
Higher-order predicates: forallb and existsb
Exercise: 4 stars, standard (forall_exists)
Define two recursive Fixpoints, forallb and existsb. The first checks whether every element in a list satisfies a given predicate:forallb oddb [1;3;5;7;9] = true
forallb negb [false;false] = true
forallb evenb [0;2;4;5] = false
forallb (eqb 5) [] = true
existsb (eqb 5) [0;2;3;6] = false
existsb (andb true) [true;true;false] = true
existsb oddb [1;0;0;0;0;3] = true
existsb evenb [] = false
(* FILL IN HERE *)
(* Do not modify the following line: *)
Definition manual_grade_for_forall_exists : option (nat×string) := None.
☐
Case study: DNA strands and amino acids
We'll redefine eq_base here, to make sure we're all on the same
page. Note how we match on the pair to only specify the true
cases, using _ as a "catch-all" or "default" for the remaining
false cases.
Definition eq_base (b1 b2 : base) : bool :=
match (b1, b2) with
| (C, C) ⇒ true
| (G, G) ⇒ true
| (A, A) ⇒ true
| (T, T) ⇒ true
| (_, _) ⇒ false
end.
match (b1, b2) with
| (C, C) ⇒ true
| (G, G) ⇒ true
| (A, A) ⇒ true
| (T, T) ⇒ true
| (_, _) ⇒ false
end.
Now we can define predicates for strand equality (eq_strand) and
complementarity (complementary).
Fixpoint eq_strand (dna1 dna2 : strand) : bool :=
match (dna1, dna2) with
| ([], []) ⇒ true
| ([], _) ⇒ false
| (_, []) ⇒ false
| (b1 :: dna1', b2 :: dna2') ⇒
eq_base b1 b2 && eq_strand dna1' dna2'
end.
Fixpoint complementary (dna1 dna2 : strand) : bool :=
match (dna1, dna2) with
| ([], []) ⇒ true
| ([], _) ⇒ false
| (_, []) ⇒ false
| (b1 :: dna1', b2 :: dna2') ⇒
eq_base b1 (complement b2) && complementary dna1' dna2'
end.
match (dna1, dna2) with
| ([], []) ⇒ true
| ([], _) ⇒ false
| (_, []) ⇒ false
| (b1 :: dna1', b2 :: dna2') ⇒
eq_base b1 b2 && eq_strand dna1' dna2'
end.
Fixpoint complementary (dna1 dna2 : strand) : bool :=
match (dna1, dna2) with
| ([], []) ⇒ true
| ([], _) ⇒ false
| (_, []) ⇒ false
| (b1 :: dna1', b2 :: dna2') ⇒
eq_base b1 (complement b2) && complementary dna1' dna2'
end.
Exercise: 2 stars, standard (complementary')
Define a different version of complementary that doesn't use recursion; you may of course call a recursive function. (Hint: try using map!)
Definition complementary' (dna1 dna2 : strand) : bool
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
☐
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
☐
Inductive codon : Type :=
| Ala
| Cys
| Asp
| Glu
| Phe
| Gly
| His
| Ile
| Lys
| Leu
| Met
| Asn
| Pro
| Gln
| Arg
| Ser
| Thr
| Val
| Trp
| Tyr
| STOP.
The truth is that the ribosome doesn't work on DNA, it works on
RNA, which uses slightly different bases, replacing thymine (T)
with uracil (U). To simplify things, we'll just work with
DNA.
Programming is often tedious and repetitive. It pays to be careful
and methodical when writing this long function. But... there are
definitely places you can save pattern matches!
Exercise: 3 stars, standard (encode_one)
Using the DNA codon table from Wikipedia(https://en.wikipedia.org/wiki/DNA_codon_table), implement a function that takes three nucleotides and returns the corresponding codon.
Definition encode_one (b1 b2 b3 : base) : codon (* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
Compute (encode_one T A G). (* ===> STOP *)
Compute (encode_one G A T). (* ===> Asp *)
Compute (encode_one A T G). (* ===> Met *)
☐
Compute (encode_one T A G). (* ===> STOP *)
Compute (encode_one G A T). (* ===> Asp *)
Compute (encode_one A T G). (* ===> Met *)
☐