(* I have not worked with any partners. *)
(** * Lists: Working with Structured Data *)

Require Export LF.Induction.
Module NatList.

(* ################################################################# *)
(** * Pairs of Numbers *)

(** In an [Inductive] type definition, each constructor can take
    any number of arguments -- none (as with [true] and [O]), one (as
    with [S]), or more than one, as here: *)

Inductive natprod : Type :=
| pair : nat -> nat -> natprod.

(** This declaration can be read: "There is just one way to
    construct a pair of numbers: by applying the constructor [pair] to
    two arguments of type [nat]." *)

Check (pair 3 5).

(** Here are two simple functions for extracting the first and
    second components of a pair.  The definitions also illustrate how
    to do pattern matching on two-argument constructors. *)

Definition fst (p : natprod) : nat :=
  match p with
  | pair x y => x
  end.

Definition snd (p : natprod) : nat :=
  match p with
  | pair x y => y
  end.

Compute (fst (pair 3 5)).
(* ===> 3 *)

(** Since pairs are used quite a bit, it is nice to be able to
    write them with the standard mathematical notation [(x,y)] instead
    of [pair x y].  We can tell Coq to allow this with a [Notation]
    declaration. *)

Notation "( x , y )" := (pair x y).

(** The new pair notation can be used both in expressions and in
    pattern matches (indeed, we've actually seen this already in the
    [Basics] chapter, in the definition of the [minus] function --
    this works because the pair notation is also provided as part of
    the standard library): *)

Compute (fst (3,5)).

Definition fst' (p : natprod) : nat :=
  match p with
  | (x,y) => x
  end.

Definition snd' (p : natprod) : nat :=
  match p with
  | (x,y) => y
  end.

Definition swap_pair (p : natprod) : natprod :=
  match p with
  | (x,y) => (y,x)
  end.

(** Let's try to prove a few simple facts about pairs.

    If we state things in a particular (and slightly peculiar) way, we
    can complete proofs with just reflexivity (and its built-in
    simplification): *)

Theorem surjective_pairing' : forall (n m : nat),
  (n,m) = (fst (n,m), snd (n,m)).
Proof.
  reflexivity.  Qed.

(** But [reflexivity] is not enough if we state the lemma in a more
    natural way: *)

Theorem surjective_pairing_stuck : forall (p : natprod),
  p = (fst p, snd p).
Proof.
  simpl. (* Doesn't reduce anything! *)
Abort.

(** We have to expose the structure of [p] so that [simpl] can
    perform the pattern match in [fst] and [snd].  We can do this with
    [destruct]. *)

Theorem surjective_pairing : forall (p : natprod),
  p = (fst p, snd p).
Proof.
  intros p.  destruct p as [n m].  simpl.  reflexivity.  Qed.

(** Notice that, unlike its behavior with [nat]s, [destruct]
    generates just one subgoal here.  That's because [natprod]s can
    only be constructed in one way. *)

(** **** Exercise: 1 star (snd_fst_is_swap)  *)
Theorem snd_fst_is_swap : forall (p : natprod),
  (snd p, fst p) = swap_pair p.
Proof.
  destruct p as [m n]. reflexivity.
Qed.
(** [] *)

(** **** Exercise: 1 star, optional (fst_swap_is_snd)  *)
Theorem fst_swap_is_snd : forall (p : natprod),
  fst (swap_pair p) = snd p.
Proof.
  destruct p. reflexivity.
Qed.
(** [] *)

(* ################################################################# *)
(** * Lists of Numbers *)

(** Generalizing the definition of pairs, we can describe the
    type of _lists_ of numbers like this: "A list is either the empty
    list or else a pair of a number and another list." *)

Inductive natlist : Type :=
  | nil  : natlist
  | cons : nat -> natlist -> natlist.

(** For example, here is a three-element list: *)

Definition mylist := cons 1 (cons 2 (cons 3 nil)).

(** As with pairs, it is more convenient to write lists in
    familiar programming notation.  The following declarations
    allow us to use [::] as an infix [cons] operator and square
    brackets as an "outfix" notation for constructing lists. *)

Notation "x :: l" := (cons x l)
                     (at level 60, right associativity).
Notation "[ ]" := nil.
Notation "[ x ; .. ; y ]" := (cons x .. (cons y nil) ..).

(** It is not necessary to understand the details of these
    declarations, but in case you are interested, here is roughly
    what's going on.  The [right associativity] annotation tells Coq
    how to parenthesize expressions involving several uses of [::] so
    that, for example, the next three declarations mean exactly the
    same thing: *)

Definition mylist1 := 1 :: (2 :: (3 :: nil)).
Definition mylist2 := 1 :: 2 :: 3 :: nil.
Definition mylist3 := [1;2;3].

(** The [at level 60] part tells Coq how to parenthesize
    expressions that involve both [::] and some other infix operator.
    For example, since we defined [+] as infix notation for the [plus]
    function at level 50,

  Notation "x + y" := (plus x y)
                      (at level 50, left associativity).

   the [+] operator will bind tighter than [::], so [1 + 2 :: [3]]
   will be parsed, as we'd expect, as [(1 + 2) :: [3]] rather than [1
   + (2 :: [3])].

   (Expressions like "[1 + 2 :: [3]]" can be a little confusing when
   you read them in a .v file.  The inner brackets, around 3, indicate
   a list, but the outer brackets, which are invisible in the HTML
   rendering, are there to instruct the "coqdoc" tool that the bracketed
   part should be displayed as Coq code rather than running text.)

   The second and third [Notation] declarations above introduce the
   standard square-bracket notation for lists; the right-hand side of
   the third one illustrates Coq's syntax for declaring n-ary
   notations and translating them to nested sequences of binary
   constructors. *)

(* ----------------------------------------------------------------- *)
(** *** Repeat *)

(** A number of functions are useful for manipulating lists.
    For example, the [repeat] function takes a number [n] and a
    [count] and returns a list of length [count] where every element
    is [n]. *)

Fixpoint repeat (n count : nat) : natlist :=
  match count with
  | O => nil
  | S count' => n :: (repeat n count')
  end.

(* ----------------------------------------------------------------- *)
(** *** Length *)

(** The [length] function calculates the length of a list. *)

Fixpoint length (l:natlist) : nat :=
  match l with
  | nil => O
  | h :: t => S (length t)
  end.

(* ----------------------------------------------------------------- *)
(** *** Append *)

(** The [app] function concatenates (appends) two lists. *)

Fixpoint app (l1 l2 : natlist) : natlist :=
  match l1 with
  | nil    => l2
  | h :: t => h :: (app t l2)
  end.

(** Actually, [app] will be used a lot in some parts of what
    follows, so it is convenient to have an infix operator for it. *)

Notation "x ++ y" := (app x y)
                     (right associativity, at level 60).

Example test_app1:             [1;2;3] ++ [4;5] = [1;2;3;4;5].
Proof. reflexivity.  Qed.
Example test_app2:             nil ++ [4;5] = [4;5].
Proof. reflexivity.  Qed.
Example test_app3:             [1;2;3] ++ nil = [1;2;3].
Proof. reflexivity.  Qed.

(* ----------------------------------------------------------------- *)
(** *** Head (with default) and Tail *)

(** Here are two smaller examples of programming with lists.
    The [hd] function returns the first element (the "head") of the
    list, while [tl] returns everything but the first
    element (the "tail").
    Of course, the empty list has no first element, so we
    must pass a default value to be returned in that case.  *)

Definition hd (default:nat) (l:natlist) : nat :=
  match l with
  | nil => default
  | h :: t => h
  end.

Definition tl (l:natlist) : natlist :=
  match l with
  | nil => nil
  | h :: t => t
  end.

Example test_hd1:             hd 0 [1;2;3] = 1.
Proof. reflexivity.  Qed.
Example test_hd2:             hd 0 [] = 0.
Proof. reflexivity.  Qed.
Example test_tl:              tl [1;2;3] = [2;3].
Proof. reflexivity.  Qed.

(* ----------------------------------------------------------------- *)
(** *** Exercises *)

(** **** Exercise: 2 stars, recommended (list_funs)  *)
(** Complete the definitions of [nonzeros], [oddmembers] and
    [countoddmembers] below. Have a look at the tests to understand
    what these functions should do. *)

Fixpoint nonzeros (l:natlist) : natlist :=
  match l with
    | nil => nil
    | O::t => (nonzeros t)
    | h::t => h::(nonzeros t)
  end.

Example test_nonzeros:
  nonzeros [0;1;0;2;3;0;0] = [1;2;3].
Proof. reflexivity.  Qed.

Fixpoint oddmembers (l:natlist) : natlist :=
  match l with
    | nil => nil
    | h::t => match (oddb h) with
      | true => h::(oddmembers t)
      | false => oddmembers t
    end
  end.

Example test_oddmembers:
  oddmembers [0;1;0;2;3;0;0] = [1;3].
Proof. reflexivity.  Qed.

Definition countoddmembers (l:natlist) : nat :=
  length (oddmembers l).

Example test_countoddmembers1:
  countoddmembers [1;0;3;1;4;5] = 4.
Proof. reflexivity.  Qed.

Example test_countoddmembers2:
  countoddmembers [0;2;4] = 0.
Proof. reflexivity.  Qed.

Example test_countoddmembers3:
  countoddmembers nil = 0.
Proof. reflexivity.  Qed.
(** [] *)

(** **** Exercise: 3 stars, advanced (alternate)  *)
(** Complete the definition of [alternate], which "zips up" two lists
    into one, alternating between elements taken from the first list
    and elements from the second.  See the tests below for more
    specific examples.

    Note: one natural and elegant way of writing [alternate] will fail
    to satisfy Coq's requirement that all [Fixpoint] definitions be
    "obviously terminating."  If you find yourself in this rut, look
    for a slightly more verbose solution that considers elements of
    both lists at the same time.  (One possible solution requires
    defining a new kind of pairs, but this is not the only way.)  *)

Fixpoint alternate (l1 l2 : natlist) : natlist :=
  match l1, l2 with
    | nil, _ => l2
    | _, nil => l1
    | (h1::t1), (h2::t2) => h1::h2::(alternate t1 t2)
  end.

Example test_alternate1:
  alternate [1;2;3] [4;5;6] = [1;4;2;5;3;6].
Proof. reflexivity. Qed.

Example test_alternate2:
  alternate [1] [4;5;6] = [1;4;5;6].
Proof. reflexivity. Qed.

Example test_alternate3:
  alternate [1;2;3] [4] = [1;4;2;3].
Proof. reflexivity. Qed.

Example test_alternate4:
  alternate [] [20;30] = [20;30].
Proof. reflexivity. Qed.
(** [] *)

(* ----------------------------------------------------------------- *)
(** *** Bags via Lists *)

(** A [bag] (or [multiset]) is like a set, except that each element
    can appear multiple times rather than just once.  One possible
    implementation is to represent a bag of numbers as a list. *)

Definition bag := natlist.

(** **** Exercise: 3 stars, recommended (bag_functions)  *)
(** Complete the following definitions for the functions
    [count], [sum], [add], and [member] for bags. *)

Fixpoint count (v:nat) (s:bag) : nat :=
  match s with
    | nil => O
    | h::t => match (beq_nat h v) with
      | true => S (count v t)
      | false => count v t
    end
  end.

(** All these proofs can be done just by [reflexivity]. *)

Example test_count1:              count 1 [1;2;3;1;4;1] = 3.
Proof. reflexivity. Qed.
Example test_count2:              count 6 [1;2;3;1;4;1] = 0.
Proof. reflexivity. Qed.

(** Multiset [sum] is similar to set [union]: [sum a b] contains all
    the elements of [a] and of [b].  (Mathematicians usually define
    [union] on multisets a little bit differently -- using max instead
    of sum -- which is why we don't use that name for this operation.)
    For [sum] we're giving you a header that does not give explicit
    names to the arguments.  Moreover, it uses the keyword
    [Definition] instead of [Fixpoint], so even if you had names for
    the arguments, you wouldn't be able to process them recursively.
    The point of stating the question this way is to encourage you to
    think about whether [sum] can be implemented in another way --
    perhaps by using functions that have already been defined.  *)

Definition sum : bag -> bag -> bag :=
  app.

Example test_sum1:              count 1 (sum [1;2;3] [1;4;1]) = 3.
Proof. reflexivity. Qed.

Definition add (v:nat) (s:bag) : bag :=
  v::s.

Example test_add1:                count 1 (add 1 [1;4;1]) = 3.
Proof. reflexivity. Qed.
Example test_add2:                count 5 (add 1 [1;4;1]) = 0.
Proof. reflexivity. Qed.

Definition member (v:nat) (s:bag) : bool :=
  negb (blt_nat (count v s) (S O)).

Example test_member1:             member 1 [1;4;1] = true.
Proof. reflexivity. Qed.

Example test_member2:             member 2 [1;4;1] = false.
Proof. reflexivity. Qed.

Example test_member3:             member 2 [1;2;1] = true.
Proof. reflexivity. Qed.
(** [] *)

(** **** Exercise: 3 stars, optional (bag_more_functions)  *)
(** Here are some more bag functions for you to practice with. *)

(** When remove_one is applied to a bag without the number to remove,
   it should return the same bag unchanged. *)

Fixpoint remove_one (v:nat) (s:bag) : bag :=
  match s with
    | nil => nil
    | h::t => match (beq_nat v h) with
      | true => t
      | false => h::(remove_one v t)
    end
  end.

Example test_remove_one1:
  count 5 (remove_one 5 [2;1;5;4;1]) = 0.
Proof. reflexivity. Qed.

Example test_remove_one2:
  count 5 (remove_one 5 [2;1;4;1]) = 0.
Proof. reflexivity. Qed.

Example test_remove_one3:
  count 4 (remove_one 5 [2;1;4;5;1;4]) = 2.
Proof. reflexivity. Qed.

Example test_remove_one4:
  count 5 (remove_one 5 [2;1;5;4;5;1;4]) = 1.
Proof. reflexivity. Qed.

Fixpoint remove_all (v:nat) (s:bag) : bag :=
  match s with
    | nil => nil
    | h::t => match (beq_nat v h) with
      | true => remove_all v t
      | false => h::(remove_all v t)
    end
  end.

Example test_remove_all1:  count 5 (remove_all 5 [2;1;5;4;1]) = 0.
Proof. reflexivity. Qed.
Example test_remove_all2:  count 5 (remove_all 5 [2;1;4;1]) = 0.
Proof. reflexivity. Qed.
Example test_remove_all3:  count 4 (remove_all 5 [2;1;4;5;1;4]) = 2.
Proof. reflexivity. Qed.
Example test_remove_all4:  count 5 (remove_all 5 [2;1;5;4;5;1;4;5;1;4]) = 0.
Proof. reflexivity. Qed.

Fixpoint subset (s1:bag) (s2:bag) : bool :=
  match s1 with
    | nil => true
    | h::t => match (member h s2) with
      | true => (subset t (remove_one h s2))
      | false => false
    end
  end.

Example test_subset1:              subset [1;2] [2;1;4;1] = true.
Proof. reflexivity. Qed.
Example test_subset2:              subset [1;2;2] [2;1;4;1] = false.
Proof. reflexivity. Qed.
Example test_subset3:              subset [1;2;2] [2;2;1] = true.
Proof. reflexivity. Qed.
Example test_subset4:              subset [1;2;2] [2;1] = false.
Proof. reflexivity. Qed.
(** [] *)

(** **** Exercise: 3 stars, recommended (bag_theorem)  *)
(** Write down an interesting theorem [bag_theorem] about bags
    involving the functions [count] and [add], and prove it.  Note
    that, since this problem is somewhat open-ended, it's possible
    that you may come up with a theorem which is true, but whose proof
    requires techniques you haven't learned yet.  Feel free to ask for
    help if you get stuck! *)

Theorem add_count_plus : forall (n : nat) (s : bag) ,
  count n (add n s) = S (count n s).
Proof.
  intro n. destruct s.
  - simpl. rewrite<-beq_nat_refl. reflexivity.
  - simpl. rewrite<-beq_nat_refl. reflexivity.
Qed.

(** [] *)

(* ################################################################# *)
(** * Reasoning About Lists *)

(** As with numbers, simple facts about list-processing
    functions can sometimes be proved entirely by simplification.  For
    example, the simplification performed by [reflexivity] is enough
    for this theorem... *)

Theorem nil_app : forall l:natlist,
  [] ++ l = l.
Proof. reflexivity. Qed.

(** ... because the [[]] is substituted into the
    "scrutinee" (the expression whose value is being "scrutinized" by
    the match) in the definition of [app], allowing the match itself
    to be simplified. *)

(** Also, as with numbers, it is sometimes helpful to perform case
    analysis on the possible shapes (empty or non-empty) of an unknown
    list. *)

Theorem tl_length_pred : forall l:natlist,
  pred (length l) = length (tl l).
Proof.
  intros l. destruct l as [| n l'].
  - (* l = nil *)
    reflexivity.
  - (* l = cons n l' *)
    reflexivity.  Qed.

(** Here, the [nil] case works because we've chosen to define
    [tl nil = nil]. Notice that the [as] annotation on the [destruct]
    tactic here introduces two names, [n] and [l'], corresponding to
    the fact that the [cons] constructor for lists takes two
    arguments (the head and tail of the list it is constructing). *)

(** Usually, though, interesting theorems about lists require
    induction for their proofs. *)

(* ----------------------------------------------------------------- *)
(** *** Micro-Sermon *)

(** Simply reading example proof scripts will not get you very far!
    It is important to work through the details of each one, using Coq
    and thinking about what each step achieves.  Otherwise it is more
    or less guaranteed that the exercises will make no sense when you
    get to them.  'Nuff said. *)

(* ================================================================= *)
(** ** Induction on Lists *)

(** Proofs by induction over datatypes like [natlist] are a
    little less familiar than standard natural number induction, but
    the idea is equally simple.  Each [Inductive] declaration defines
    a set of data values that can be built up using the declared
    constructors: a boolean can be either [true] or [false]; a number
    can be either [O] or [S] applied to another number; a list can be
    either [nil] or [cons] applied to a number and a list.

    Moreover, applications of the declared constructors to one another
    are the _only_ possible shapes that elements of an inductively
    defined set can have, and this fact directly gives rise to a way
    of reasoning about inductively defined sets: a number is either
    [O] or else it is [S] applied to some _smaller_ number; a list is
    either [nil] or else it is [cons] applied to some number and some
    _smaller_ list; etc. So, if we have in mind some proposition [P]
    that mentions a list [l] and we want to argue that [P] holds for
    _all_ lists, we can reason as follows:

      - First, show that [P] is true of [l] when [l] is [nil].

      - Then show that [P] is true of [l] when [l] is [cons n l'] for
        some number [n] and some smaller list [l'], assuming that [P]
        is true for [l'].

    Since larger lists can only be built up from smaller ones,
    eventually reaching [nil], these two arguments together establish
    the truth of [P] for all lists [l].  Here's a concrete example: *)

Theorem app_assoc : forall l1 l2 l3 : natlist,
  (l1 ++ l2) ++ l3 = l1 ++ (l2 ++ l3).
Proof.
  intros l1 l2 l3. induction l1 as [| n l1' IHl1'].
  - (* l1 = nil *)
    reflexivity.
  - (* l1 = cons n l1' *)
    simpl. rewrite -> IHl1'. reflexivity.  Qed.

(** Notice that, as when doing induction on natural numbers, the
    [as...] clause provided to the [induction] tactic gives a name to
    the induction hypothesis corresponding to the smaller list [l1']
    in the [cons] case. Once again, this Coq proof is not especially
    illuminating as a static written document -- it is easy to see
    what's going on if you are reading the proof in an interactive Coq
    session and you can see the current goal and context at each
    point, but this state is not visible in the written-down parts of
    the Coq proof.  So a natural-language proof -- one written for
    human readers -- will need to include more explicit signposts; in
    particular, it will help the reader stay oriented if we remind
    them exactly what the induction hypothesis is in the second
    case. *)

(** For comparison, here is an informal proof of the same theorem. *)

(** _Theorem_: For all lists [l1], [l2], and [l3],
   [(l1 ++ l2) ++ l3 = l1 ++ (l2 ++ l3)].

   _Proof_: By induction on [l1].

   - First, suppose [l1 = []].  We must show

       ([] ++ l2) ++ l3 = [] ++ (l2 ++ l3),

     which follows directly from the definition of [++].

   - Next, suppose [l1 = n::l1'], with

       (l1' ++ l2) ++ l3 = l1' ++ (l2 ++ l3)

     (the induction hypothesis). We must show

       ((n :: l1') ++ l2) ++ l3 = (n :: l1') ++ (l2 ++ l3).

     By the definition of [++], this follows from

       n :: ((l1' ++ l2) ++ l3) = n :: (l1' ++ (l2 ++ l3)),

     which is immediate from the induction hypothesis.  [] *)

(* ----------------------------------------------------------------- *)
(** *** Reversing a List *)

(** For a slightly more involved example of inductive proof over
    lists, suppose we use [app] to define a list-reversing function
    [rev]: *)

Fixpoint rev (l:natlist) : natlist :=
  match l with
  | nil    => nil
  | h :: t => rev t ++ [h]
  end.

Example test_rev1:            rev [1;2;3] = [3;2;1].
Proof. reflexivity.  Qed.
Example test_rev2:            rev nil = nil.
Proof. reflexivity.  Qed.

(* ----------------------------------------------------------------- *)
(** *** Properties of [rev] *)

(** Now let's prove some theorems about our newly defined [rev].
    For something a bit more challenging than what we've seen, let's
    prove that reversing a list does not change its length.  Our first
    attempt gets stuck in the successor case... *)

Theorem rev_length_firsttry : forall l : natlist,
  length (rev l) = length l.
Proof.
  intros l. induction l as [| n l' IHl'].
  - (* l = [] *)
    reflexivity.
  - (* l = n :: l' *)
    (* This is the tricky case.  Let's begin as usual
       by simplifying. *)
    simpl.
    (* Now we seem to be stuck: the goal is an equality
       involving [++], but we don't have any useful equations
       in either the immediate context or in the global
       environment!  We can make a little progress by using
       the IH to rewrite the goal... *)
    rewrite <- IHl'.
    (* ... but now we can't go any further. *)
Abort.

(** So let's take the equation relating [++] and [length] that
    would have enabled us to make progress and prove it as a separate
    lemma. *)

Theorem app_length : forall l1 l2 : natlist,
  length (l1 ++ l2) = (length l1) + (length l2).
Proof.
  (* WORKED IN CLASS *)
  intros l1 l2. induction l1 as [| n l1' IHl1'].
  - (* l1 = nil *)
    reflexivity.
  - (* l1 = cons *)
    simpl. rewrite -> IHl1'. reflexivity.  Qed.

(** Note that, to make the lemma as general as possible, we
    quantify over _all_ [natlist]s, not just those that result from an
    application of [rev].  This should seem natural, because the truth
    of the goal clearly doesn't depend on the list having been
    reversed.  Moreover, it is easier to prove the more general
    property. *)

(** Now we can complete the original proof. *)

Theorem rev_length : forall l : natlist,
  length (rev l) = length l.
Proof.
  intros l. induction l as [| n l' IHl'].
  - (* l = nil *)
    reflexivity.
  - (* l = cons *)
    simpl. rewrite -> app_length, plus_comm.
    simpl. rewrite -> IHl'. reflexivity.  Qed.

(** For comparison, here are informal proofs of these two theorems:

    _Theorem_: For all lists [l1] and [l2],
       [length (l1 ++ l2) = length l1 + length l2].

    _Proof_: By induction on [l1].

    - First, suppose [l1 = []].  We must show

        length ([] ++ l2) = length [] + length l2,

      which follows directly from the definitions of
      [length] and [++].

    - Next, suppose [l1 = n::l1'], with

        length (l1' ++ l2) = length l1' + length l2.

      We must show

        length ((n::l1') ++ l2) = length (n::l1') + length l2).

      This follows directly from the definitions of [length] and [++]
      together with the induction hypothesis. [] *)

(** _Theorem_: For all lists [l], [length (rev l) = length l].

    _Proof_: By induction on [l].

      - First, suppose [l = []].  We must show

          length (rev []) = length [],

        which follows directly from the definitions of [length]
        and [rev].

      - Next, suppose [l = n::l'], with

          length (rev l') = length l'.

        We must show

          length (rev (n :: l')) = length (n :: l').

        By the definition of [rev], this follows from

          length ((rev l') ++ [n]) = S (length l')

        which, by the previous lemma, is the same as

          length (rev l') + length [n] = S (length l').

        This follows directly from the induction hypothesis and the
        definition of [length]. [] *)

(** The style of these proofs is rather longwinded and pedantic.
    After the first few, we might find it easier to follow proofs that
    give fewer details (which can easily work out in our own minds or
    on scratch paper if necessary) and just highlight the non-obvious
    steps.  In this more compressed style, the above proof might look
    like this: *)

(** _Theorem_:
     For all lists [l], [length (rev l) = length l].

    _Proof_: First, observe that [length (l ++ [n]) = S (length l)]
     for any [l] (this follows by a straightforward induction on [l]).
     The main property again follows by induction on [l], using the
     observation together with the induction hypothesis in the case
     where [l = n'::l']. [] *)

(** Which style is preferable in a given situation depends on
    the sophistication of the expected audience and how similar the
    proof at hand is to ones that the audience will already be
    familiar with.  The more pedantic style is a good default for our
    present purposes. *)

(* ================================================================= *)
(** ** [Search] *)

(** We've seen that proofs can make use of other theorems we've
    already proved, e.g., using [rewrite].  But in order to refer to a
    theorem, we need to know its name!  Indeed, it is often hard even
    to remember what theorems have been proven, much less what they
    are called.

    Coq's [Search] command is quite helpful with this.  Typing
    [Search foo] will cause Coq to display a list of all theorems
    involving [foo].  For example, try uncommenting the following line
    to see a list of theorems that we have proved about [rev]: *)

Search rev.

(** Keep [Search] in mind as you do the following exercises and
    throughout the rest of the book; it can save you a lot of time!

    If you are using ProofGeneral, you can run [Search] with [C-c
    C-a C-a]. Pasting its response into your buffer can be
    accomplished with [C-c C-;]. *)

(* ================================================================= *)
(** ** List Exercises, Part 1 *)

(** **** Exercise: 3 stars (list_exercises)  *)
(** More practice with lists: *)

Theorem app_nil_r : forall l : natlist,
  l ++ [] = l.
Proof.
  induction l.
  - reflexivity.
  - simpl. rewrite->IHl. reflexivity.
Qed.

Theorem rev_app_distr: forall l1 l2 : natlist,
  rev (l1 ++ l2) = rev l2 ++ rev l1.
Proof.
  intros l1 l2. induction l1 as [| n l1' IHl1'].
  - simpl. rewrite->app_nil_r. reflexivity.
  - simpl. rewrite<-app_assoc. rewrite<-IHl1'. reflexivity.
Qed.

Theorem rev_involutive : forall l : natlist,
  rev (rev l) = l.
Proof.
  induction l as [| n l' IHl'].
  - reflexivity.
  - simpl. rewrite->rev_app_distr. simpl. rewrite->IHl'. reflexivity.
Qed.

(** There is a short solution to the next one.  If you find yourself
    getting tangled up, step back and try to look for a simpler
    way. *)

Theorem app_assoc4 : forall l1 l2 l3 l4 : natlist,
  l1 ++ (l2 ++ (l3 ++ l4)) = ((l1 ++ l2) ++ l3) ++ l4.
Proof.
  intros.
  rewrite<-app_assoc. rewrite<-app_assoc. reflexivity.
Qed.

(** An exercise about your implementation of [nonzeros]: *)

Lemma nonzeros_app : forall l1 l2 : natlist,
  nonzeros (l1 ++ l2) = (nonzeros l1) ++ (nonzeros l2).
Proof.
  intros. induction l1 as [| n l1' IHl1'].
  - reflexivity.
  - simpl. destruct n as [|n'].
    + rewrite->IHl1'. reflexivity.
    + simpl. rewrite<-IHl1'. reflexivity.
Qed.
(** [] *)

(** **** Exercise: 2 stars (beq_natlist)  *)
(** Fill in the definition of [beq_natlist], which compares
    lists of numbers for equality.  Prove that [beq_natlist l l]
    yields [true] for every list [l]. *)

Fixpoint beq_natlist (l1 l2 : natlist) : bool :=
  match l1 with
    | nil => match l2 with
      | nil => true
      | _ => false
      end
    | h1::t1 => match l2 with
      | nil => false
      | h2::t2 => match (beq_nat h1 h2) with
        | true => beq_natlist t1 t2
        | false => false
        end
      end
  end.

Example test_beq_natlist1 :
  (beq_natlist nil nil = true).
Proof. reflexivity. Qed.

Example test_beq_natlist2 :
  beq_natlist [1;2;3] [1;2;3] = true.
Proof. reflexivity. Qed.

Example test_beq_natlist3 :
  beq_natlist [1;2;3] [1;2;4] = false.
Proof. reflexivity. Qed.

Theorem beq_natlist_refl : forall l:natlist,
  true = beq_natlist l l.
Proof.
  induction l as [| n l' IHl'].
  - reflexivity.
  - simpl. rewrite<-beq_nat_refl. rewrite<-IHl'. reflexivity.
Qed.
(** [] *)

(* ================================================================= *)
(** ** List Exercises, Part 2 *)

(** **** Exercise: 3 stars, advanced (bag_proofs)  *)
(** Here are a couple of little theorems to prove about your
    definitions about bags above. *)

Theorem count_member_nonzero : forall (s : bag),
  leb 1 (count 1 (1 :: s)) = true.
Proof.
  destruct s.
  - reflexivity.
  - reflexivity.
Qed.

(** The following lemma about [leb] might help you in the next proof. *)

Theorem ble_n_Sn : forall n,
  leb n (S n) = true.
Proof.
  intros n. induction n as [| n' IHn'].
  - (* 0 *)
    simpl.  reflexivity.
  - (* S n' *)
    simpl.  rewrite IHn'.  reflexivity.  Qed.

Check leb.

Theorem remove_decreases_count: forall (s : bag),
  leb (count 0 (remove_one 0 s)) (count 0 s) = true.
Proof.
  induction s.
  - reflexivity.
  - destruct n as [|n'].
    + simpl. rewrite->ble_n_Sn. reflexivity.
    + simpl. rewrite->IHs. reflexivity.
Qed.
(** [] *)

(** **** Exercise: 3 stars, optional (bag_count_sum)  *)
(** Write down an interesting theorem [bag_count_sum] about bags
    involving the functions [count] and [sum], and prove it.  (You may
    find that the difficulty of the proof depends on how you defined
    [count]!) *)

Theorem bag_count_sum: forall (s1 s2 : bag) (n : nat),
  count n (sum s1 s2) = count n s1 + count n s2.
Proof.
  intros. induction s1 as [|h s1' IHs1'].
  - reflexivity.
  - simpl. destruct (beq_nat h n).
    + simpl. rewrite IHs1'. reflexivity.
    + rewrite IHs1'. reflexivity.
Qed.

(** [] *)

(** **** Exercise: 4 stars, advanced (rev_injective)  *)
(** Prove that the [rev] function is injective -- that is,

    forall (l1 l2 : natlist), rev l1 = rev l2 -> l1 = l2.

(There is a hard way and an easy way to do this.) *)

Lemma rev_func:
  forall (l1 l2 : natlist), l1 = l2 -> rev l1 = rev l2.
Proof.
  intros. rewrite->H. reflexivity.
Qed.

Theorem rev_injective:
  forall (l1 l2 : natlist), rev l1 = rev l2 -> l1 = l2.
Proof.
  intros. replace l1 with (rev (rev l1)). replace l2 with (rev (rev l2)).
  apply rev_func. rewrite H. reflexivity.
  rewrite->rev_involutive. reflexivity.
  rewrite->rev_involutive. reflexivity.
Qed.
(** [] *)

(* ################################################################# *)
(** * Options *)

(** Suppose we want to write a function that returns the [n]th
    element of some list.  If we give it type [nat -> natlist -> nat],
    then we'll have to choose some number to return when the list is
    too short... *)

Fixpoint nth_bad (l:natlist) (n:nat) : nat :=
  match l with
  | nil => 42  (* arbitrary! *)
  | a :: l' => match beq_nat n O with
               | true => a
               | false => nth_bad l' (pred n)
               end
  end.

(** This solution is not so good: If [nth_bad] returns [42], we
    can't tell whether that value actually appears on the input
    without further processing. A better alternative is to change the
    return type of [nth_bad] to include an error value as a possible
    outcome. We call this type [natoption]. *)

Inductive natoption : Type :=
  | Some : nat -> natoption
  | None : natoption.

(** We can then change the above definition of [nth_bad] to
    return [None] when the list is too short and [Some a] when the
    list has enough members and [a] appears at position [n]. We call
    this new function [nth_error] to indicate that it may result in an
    error. *)

Fixpoint nth_error (l:natlist) (n:nat) : natoption :=
  match l with
  | nil => None
  | a :: l' => match beq_nat n O with
               | true => Some a
               | false => nth_error l' (pred n)
               end
  end.

Example test_nth_error1 : nth_error [4;5;6;7] 0 = Some 4.
Proof. reflexivity. Qed.
Example test_nth_error2 : nth_error [4;5;6;7] 3 = Some 7.
Proof. reflexivity. Qed.
Example test_nth_error3 : nth_error [4;5;6;7] 9 = None.
Proof. reflexivity. Qed.

(** (In the HTML version, the boilerplate proofs of these
    examples are elided.  Click on a box if you want to see one.)

    This example is also an opportunity to introduce one more small
    feature of Coq's programming language: conditional
    expressions... *)

Fixpoint nth_error' (l:natlist) (n:nat) : natoption :=
  match l with
  | nil => None
  | a :: l' => if beq_nat n O then Some a
               else nth_error' l' (pred n)
  end.

(** Coq's conditionals are exactly like those found in any other
    language, with one small generalization.  Since the boolean type
    is not built in, Coq actually supports conditional expressions over
    _any_ inductively defined type with exactly two constructors.  The
    guard is considered true if it evaluates to the first constructor
    in the [Inductive] definition and false if it evaluates to the
    second. *)

(** The function below pulls the [nat] out of a [natoption], returning
    a supplied default in the [None] case. *)

Definition option_elim (d : nat) (o : natoption) : nat :=
  match o with
  | Some n' => n'
  | None => d
  end.

(** **** Exercise: 2 stars (hd_error)  *)
(** Using the same idea, fix the [hd] function from earlier so we don't
    have to pass a default element for the [nil] case.  *)

Definition hd_error (l : natlist) : natoption :=
  match l with
    | nil => None
    | h::t => Some h
  end.

Example test_hd_error1 : hd_error [] = None.
Proof. reflexivity. Qed.

Example test_hd_error2 : hd_error [1] = Some 1.
Proof. reflexivity. Qed.

Example test_hd_error3 : hd_error [5;6] = Some 5.
Proof. reflexivity. Qed.
(** [] *)

(** **** Exercise: 1 star, optional (option_elim_hd)  *)
(** This exercise relates your new [hd_error] to the old [hd]. *)

Theorem option_elim_hd : forall (l:natlist) (default:nat),
  hd default l = option_elim default (hd_error l).
Proof.
  intros. destruct l.
  - reflexivity.
  - simpl. reflexivity.
Qed.
(** [] *)

End NatList.

(* ################################################################# *)
(** * Partial Maps *)

(** As a final illustration of how data structures can be defined in
    Coq, here is a simple _partial map_ data type, analogous to the
    map or dictionary data structures found in most programming
    languages. *)

(** First, we define a new inductive datatype [id] to serve as the
    "keys" of our partial maps. *)

Inductive id : Type :=
  | Id : nat -> id.

(** Internally, an [id] is just a number.  Introducing a separate type
    by wrapping each nat with the tag [Id] makes definitions more
    readable and gives us the flexibility to change representations
    later if we wish.

    We'll also need an equality test for [id]s: *)

Definition beq_id (x1 x2 : id) :=
  match x1, x2 with
  | Id n1, Id n2 => beq_nat n1 n2
  end.

(** **** Exercise: 1 star (beq_id_refl)  *)
Theorem beq_id_refl : forall x, true = beq_id x x.
Proof.
  destruct x.
  simpl. rewrite<-beq_nat_refl. reflexivity.
Qed.
(** [] *)

(** Now we define the type of partial maps: *)

Module PartialMap.
Export NatList.

Inductive partial_map : Type :=
  | empty  : partial_map
  | record : id -> nat -> partial_map -> partial_map.

(** This declaration can be read: "There are two ways to construct a
    [partial_map]: either using the constructor [empty] to represent an
    empty partial map, or by applying the constructor [record] to
    a key, a value, and an existing [partial_map] to construct a
    [partial_map] with an additional key-to-value mapping." *)

(** The [update] function overrides the entry for a given key in a
    partial map (or adds a new entry if the given key is not already
    present). *)

Definition update (d : partial_map)
                  (x : id) (value : nat)
                  : partial_map :=
  record x value d.

(** Last, the [find] function searches a [partial_map] for a given
    key.  It returns [None] if the key was not found and [Some val] if
    the key was associated with [val]. If the same key is mapped to
    multiple values, [find] will return the first one it
    encounters. *)

Fixpoint find (x : id) (d : partial_map) : natoption :=
  match d with
  | empty         => None
  | record y v d' => if beq_id x y
                     then Some v
                     else find x d'
  end.

(** **** Exercise: 1 star (update_eq)  *)
Theorem update_eq :
  forall (d : partial_map) (x : id) (v: nat),
    find x (update d x v) = Some v.
Proof.
  intros. simpl. rewrite<-beq_id_refl. reflexivity.
Qed.
(** [] *)

(** **** Exercise: 1 star (update_neq)  *)
Theorem update_neq :
  forall (d : partial_map) (x y : id) (o: nat),
    beq_id x y = false -> find x (update d y o) = find x d.
Proof.
  intros. simpl. rewrite->H. reflexivity. Qed.

(** [] *)
End PartialMap.

(** **** Exercise: 2 stars (baz_num_elts)  *)
(** Consider the following inductive definition: *)

Inductive baz : Type :=
  | Baz1 : baz -> baz
  | Baz2 : baz -> bool -> baz.

(** How _many_ elements does the type [baz] have?  (Answer in English
    or the natural language of your choice.)

Zero. Because both [Baz1] and [Baz2] require a [baz] in order to construct a new [baz].
However we do not have a basic [baz] type that can be constructed immediately.
*)
(** [] *)

(** $Date: 2017-08-24 10:54:05 -0400 (Thu, 24 Aug 2017) $ *)


Require Export A007logic.

Definition even (n:nat) : Prop :=
  evenb n = true.

Inductive ev : nat -> Prop :=
  | ev0  : ev 0
  | evSS : forall n : nat, ev n -> ev (S (S n)).

Theorem double_even : forall n,
  ev (double n).
Proof.
  intro.
  induction n.
  simpl. apply ev0.
  simpl. apply evSS. exact IHn.
Qed.

Theorem ev__even : forall n,
                    ev n -> even n.
Proof.
  intros.
  induction H.
  simpl.
  reflexivity.
  unfold even in IHev. unfold even. simpl. assumption.
Qed.

Theorem ev_sum : forall n m,
   ev n -> ev m -> ev (n+m).
Proof.
  intros.
  induction H.
  simpl. assumption.
  simpl. apply evSS. assumption.
Qed.

Inductive beautiful : nat -> Prop :=
| b_0 : beautiful 0
| b_3 : beautiful 3
| b_5 : beautiful 5
| b_sum : forall m n : nat,
            beautiful m -> beautiful n ->
            beautiful (m + n).

Theorem beautiful_8 : beautiful 8.
Proof.
  replace (8) with (3 + 5).
  apply b_sum.
  apply b_3.
  apply b_5.
  reflexivity.
Qed.

Theorem beautiful_8' : beautiful 8.
Proof.
  replace (8) with ((5 + 3) + 0).
  apply b_sum.
  apply b_sum.
  apply b_5.
  apply b_3.
  apply b_0.
  reflexivity.
Qed.

Theorem beautiful_plus_eight:
  forall n, beautiful n -> beautiful (8+n).
Proof.
  intros.
  apply b_sum.
  apply beautiful_8.
  assumption.
Qed.

Theorem b_times2:
  forall n, beautiful n -> beautiful (2 * n).
Proof.
  simpl.
  intros.
  rewrite plus_assoc.
  apply b_sum.
  apply b_sum.
  assumption. assumption.
  apply b_0.
Qed.

Theorem b_timesm:
  forall n m, beautiful n -> beautiful (m * n).
Proof.
  intros.
  induction m.
  simpl. apply b_0.
  simpl. apply b_sum.
  assumption.
  assumption.
Qed.

Inductive gorgeous : nat -> Prop :=
  g_0 : gorgeous 0
| g_plus3 : forall n, gorgeous n -> gorgeous (3+n)
| g_plus5 : forall n, gorgeous n -> gorgeous (5+n).

Theorem gorgeous_plus13: forall n,
                           gorgeous n -> gorgeous (13+n).
Proof.
  intros.
  replace 13 with (3 + 5 + 5).
  replace (3 + 5 + 5 + n) with (3 + (5 + (5 + n))).
  apply g_plus3. apply g_plus5. apply g_plus5.
  assumption.
  reflexivity.
  reflexivity.
Qed.

Theorem gorgeous__beautiful : forall n,
                                gorgeous n -> beautiful n.
Proof.
  intros.
  induction H.
  Case "0".
  apply b_0.
  Case "3 + n".
  apply b_sum. apply b_3. assumption.
  Case "5 + n".
  apply b_sum. apply b_5. assumption.
Qed.

Theorem gorgeous_sum : forall n m,
                         gorgeous n -> gorgeous m -> gorgeous (n + m).
Proof.
  intros.
  induction H.
  Case "0". simpl. assumption.
  Case "3+n". rewrite <- plus_assoc. apply g_plus3. assumption.
  Case "5+n". rewrite <- plus_assoc. apply g_plus5. assumption.
Qed.

Theorem beautiful__gorgeous :
  forall n, beautiful n -> gorgeous n.
Proof.
  intros.
  induction H.
  SCase "0". apply g_0.
  SCase "3". apply g_plus3 with (n:=0). apply g_0.
  SCase "5". apply g_plus5. apply g_0.
  SCase "m + n". apply gorgeous_sum; assumption.
Qed.

Lemma helper_g_times2 :
  forall x y z, x + (z + y)= z + x + y.
Proof.
  intros.
  rewrite plus_assoc.
  replace (x + z) with (z + x). reflexivity.
  rewrite plus_comm. reflexivity.
Qed.

Theorem g_times2:
  forall n, gorgeous n -> gorgeous (2 * n).
Proof.
   intros n H. simpl.
   induction H.

   Case "0".
   simpl. apply g_0.

   Case "3 + n".
   rewrite <- helper_g_times2.
   apply gorgeous_sum. assumption.
   apply g_plus3. rewrite <- plus_assoc. apply g_plus3.
   rewrite plus_comm. simpl. assumption.

   Case "5 + n".
   rewrite <- helper_g_times2.
   apply gorgeous_sum. assumption.
   apply g_plus5. rewrite <- plus_assoc. apply g_plus5.
   rewrite plus_comm. simpl. assumption.
Qed.

Theorem ev_minus2: forall n,
  ev n -> ev (pred (pred n)).
Proof.
  intros.
(*(* the induction way *)
  induction H.
  simpl. apply ev0.
  simpl. apply H.
*)
  (* the inversion way *)
  inversion H.
  simpl. apply ev0.
  simpl. assumption.

  (* ex: Why not `destruct`?
     because destruct does not introduce the inductive rule for
      ev n -> ev (S (S n))
     therefore the second goal will be `ev (S (S n))` while without an
     assumption of `ev n`, which is not provable.
  *)
 Qed.

Theorem SSev__even : forall n,
  ev (S (S n)) -> ev n.
Proof.
  intros.
  inversion H.
  assumption.
Qed.

Theorem SSSSev__even : forall n,
  ev (S (S (S (S n)))) -> ev n.
Proof.
  intros.
  inversion H.
  inversion H1.
  assumption.
Qed.

Theorem even5_nonsense :
  ev 5 -> 2 + 2 = 9.
Proof.
  intros.
  inversion H. inversion H1. inversion H3.
Qed.

Theorem ev_ev__ev : forall n m,
  ev (n+m) -> ev n -> ev m.
Proof.
  intros.
  induction H0.

  simpl in H. assumption.

  simpl in H. inversion H.
  apply IHev in H2. assumption.
Qed.

Theorem ev_plus_plus : forall n m p,
  ev (n+m) -> ev (n+p) -> ev (m+p).
Proof.
  intros.
  apply ev_sum with (n := n + p) in H.
  replace (n + p + (n + m)) with (double n + m + p) in H.
  rewrite <- plus_assoc in H.
  apply ev_ev__ev with (m := m + p) in H. assumption.

  (* disgusting.... *)
  assert (forall n' m', double n' + m' + m' = double (n' + m')).
  clear. intros. rewrite double_plus.
  replace (n' + n' + m' + m') with ((n' + m') + (n' + m')).
  rewrite <- double_plus. reflexivity.
  rewrite <- plus_assoc. replace (m' + (n' + m')) with (n' + m' + m').
  rewrite plus_assoc. rewrite plus_assoc. reflexivity.
  rewrite plus_comm. reflexivity.
  rewrite H1. apply double_even.

  rewrite double_plus.
  rewrite plus_assoc. rewrite <- plus_assoc with (m := p).
  rewrite <- plus_comm with (m := p) (n := n).
  rewrite plus_assoc. rewrite <- plus_assoc with (m := p).
  rewrite plus_comm with (m := m) (n := p).
  rewrite plus_assoc. reflexivity.

  assumption.
Qed.

Inductive pal {X : Type} : list X -> Prop :=
  | pal_null : pal []
  | pal_one : forall x, pal [x]
  | pal_induction : forall (xs : list X) (x : X),
                      pal xs -> pal ([x] ++ xs ++ [x]).

Lemma snoc_app :
  forall {X : Type} (xs : list X) (x : X), snoc xs x = xs ++ [x].
Proof.
  intros.
  induction xs.
  simpl. reflexivity.
  simpl.
  rewrite IHxs. reflexivity.
Qed.

Lemma cons_app :
  forall {X : Type} (xs : list X) (x : X), cons x xs = [x] ++ xs.
Proof.
  intros.
  induction xs.
  simpl. reflexivity.
  simpl. reflexivity.
Qed.

Lemma app_assoc' : forall {X : Type} (l m n: list X),
                     l ++ m ++ n = (l ++ m) ++ n.
Proof.
  induction l.
  simpl.
  reflexivity.
  simpl.
  intros.
  rewrite IHl.
  reflexivity.
Qed.

Goal forall {X : Type} (l : list X), pal (l ++ rev l).
Proof.
  intros.
  induction l.
  Case "l = []".
    simpl. apply pal_null.
  Case "l = x :: l".
    simpl. rewrite cons_app.
    rewrite snoc_app.
    replace ([x] ++ l ++ rev l ++ [x]) with ([x] ++ (l ++ rev l) ++ [x]).
    apply pal_induction. assumption.

    replace ((l ++ rev l) ++ [x]) with (l ++ rev l ++ [x]).
    reflexivity. apply app_assoc'.
Qed.

Lemma rev_appl : forall {X:Type} (l : list X) (x : X),
                   rev (l ++ [x]) = [x] ++ rev l.
Proof.
  intros.
  induction l.
  reflexivity.
  simpl.
  rewrite snoc_app.
  rewrite snoc_app.
  rewrite IHl.
  rewrite cons_app with (xs := (rev l ++ [x0])).
  symmetry.
  apply app_assoc'.
Qed.

Goal forall {X:Type} (l : list X), pal l -> l = rev l.
Proof.
  intros.
  induction H.
  reflexivity.
  reflexivity.

  simpl.
  rewrite snoc_app.
  rewrite rev_appl.
  rewrite cons_app.
  rewrite <- IHpal.
  apply app_assoc'.
Qed.

Lemma rev_appr : forall {X:Type} (l : list X) (x : X),
                   rev ([x] ++ l) = rev l ++ [x].
Proof.
  intros.
  induction l.
  reflexivity.
  simpl.
  rewrite snoc_app.
  rewrite snoc_app.
  reflexivity.
Qed.

Goal forall {X:Type} (l:list X), l = rev l -> pal l.
Proof.
  induction l.
  intros.
  apply pal_null.

  simpl.
  rewrite cons_app.
  rewrite snoc_app.
  intros.

  induction (rev l) eqn:revl.
  inversion H. constructor.

  inversion H. clear IHl0.

  (* TODO: too difficult. 5 star question *)
Abort.

Inductive subseq : list nat -> list nat -> Prop :=
  | ss_nul : forall ns, subseq [] ns
  | ss_mat : forall ns ms, subseq ns ms -> (forall n, subseq (n::ns) (n::ms))
  | ss_notmat : forall ns ms, subseq ns ms -> (forall m, subseq ns (m :: ms)).

Theorem subseq_refl : forall l, subseq l l.
Proof.
  intros.
  induction l.

  constructor.
  constructor. assumption.
Qed.

Lemma app_comm_cons' :
  forall {A:Type} (x y:list A) (a:A), a :: (x ++ y) = (a :: x) ++ y.
Proof.
  intros.
  induction x.
  reflexivity.
  reflexivity.
Qed.

Theorem subseq_app : forall l1 l2 l3, subseq l1 l2 -> subseq l1 (l2 ++ l3).
Proof.
  intros.
  induction H.

  constructor.

  rewrite <- app_comm_cons'.
  constructor. assumption.

  rewrite <- app_comm_cons'.
  constructor. assumption.
Qed.

Lemma subseq_notnull : forall x, forall l1,
                         ~(subseq (x :: l1) []).
Proof.
  intros. intro.
  inversion H.
Qed.

Theorem subseq_trans : forall l1 l2 l3,
                         subseq l1 l2 -> subseq l2 l3 -> subseq l1 l3.
Proof.
  intros l1 l2 l3 H1 H2.
  generalize dependent l2.
  generalize dependent l3.

  induction l1.
  constructor.

  intros.
  induction H1.
  constructor.

(* To save your life, keep away from such tough questions *)
Abort.

Inductive R : nat -> list nat -> Prop :=
      | c1 : R 0 []
      | c2 : forall n l, R n l -> R (S n) (n :: l)
      | c3 : forall n l, R (S n) l -> R n l.

Goal R 2 [1;0].
Proof.
  apply c2. apply c2. apply c1.
Qed.

Goal R 1 [1;2;1;0].
Proof.
  apply c3. apply c2. apply c3. apply c3. apply c2. apply c2. apply c2.
  apply c1.
Qed.

Goal R 6 [3;2;1;0].
Proof.
  (* impossible to prove this *)
Abort.

Inductive le : nat -> nat -> Prop :=
  | le_n : forall n, le n n
  | le_S : forall n m, (le n m) -> (le n (S m)).

Definition lt n m := le (S n) m.

Notation "m <= n" := (le m n).

Inductive square_of : nat -> nat -> Prop :=
  sq : forall n:nat, square_of n (n * n).

Inductive next_nat (n:nat) : nat -> Prop :=
  | nn : next_nat n (S n).

Inductive next_even (n:nat) : nat -> Prop :=
  | ne_1 : ev (S n) -> next_even n (S n)
  | ne_2 : ev (S (S n)) -> next_even n (S (S n)).

Inductive total_relation : nat -> nat -> Prop :=
  | tr_0 : forall m, total_relation 0 m
  | tr_n : forall n m,
             total_relation n m -> total_relation (S n) m.

Theorem total_relation_all : forall n m, total_relation n m.
Proof.
  intros.
  induction n.
  constructor.
  constructor. assumption.
Qed.

Inductive empty_relation : nat -> nat -> Prop :=.

Example er_1: ~(empty_relation 6 5). Proof. intro. induction H. Qed.

Lemma le_trans : forall m n o, m <= n -> n <= o -> m <= o.
Proof.
  intros.
  induction H0.
  assumption.
  constructor.
  apply IHle.
  assumption.
Qed.

Theorem O_le_n : forall n,
  0 <= n.
Proof.
  induction n.
  constructor.
  constructor. assumption.
Qed.

Theorem n_le_m__Sn_le_Sm : forall n m,
  n <= m -> S n <= S m.
Proof.
  intros.
  induction H.
  constructor.
  constructor.
  assumption.
Qed.

Theorem Sn_le_Sm__n_le_m : forall n m,
  S n <= S m -> n <= m.
Proof.
  induction m.
  intros.
  inversion H.
  constructor.

  inversion H2.

  intros.
  inversion H.
  constructor.

  apply IHm in H2.
  constructor.
  assumption.
Qed.

Theorem le_plus_l : forall a b,
  a <= a + b.
Proof.
  induction b.
  rewrite plus_0_r.
  constructor.

  rewrite plus_comm.
  simpl.
  rewrite plus_comm.
  constructor.
  assumption.
Qed.

Lemma plus_le_l : forall n1 n2 m,
                    le (n1 + n2) m -> le n1 m.
Proof.
  intros.
  generalize dependent n2.
  induction n2.

  intros.
  replace (n1 + 0) with n1 in H. assumption.
  rewrite plus_0_r. reflexivity.

  intros. apply IHn2.
  replace (n1 + S n2) with (S (n1 + n2)) in H.
  apply le_S in H.
  apply Sn_le_Sm__n_le_m in H. assumption.
  rewrite plus_comm with (m := S n2) (n := n1).
  simpl.
  rewrite plus_comm with (m := n2) (n := n1). reflexivity.
Qed.

Lemma plus_le_self : forall n m, le n (n + m).
Proof.
  intros.
  induction n.
  simpl. apply O_le_n.
  simpl.
  apply n_le_m__Sn_le_Sm. assumption.
Qed.

Theorem plus_lt : forall n1 n2 m,
  lt (n1 + n2) m ->
  lt n1 m /\ lt n2 m.
Proof.
  unfold lt.
  intros. inversion H.
  split.

  apply n_le_m__Sn_le_Sm.
  apply plus_le_self.

  apply n_le_m__Sn_le_Sm. rewrite plus_comm.
  apply plus_le_self.

  split. subst.

  apply n_le_m__Sn_le_Sm.
  apply Sn_le_Sm__n_le_m in H.
  assert (n1 <= n1 + n2). apply plus_le_self.
  apply le_trans with (n1+n2); assumption.

  apply n_le_m__Sn_le_Sm. subst.
  apply Sn_le_Sm__n_le_m in H.
  assert (n2 <= n1 + n2). rewrite plus_comm. apply plus_le_self.
  apply le_trans with (n1+n2); assumption.
Qed.

Theorem lt_S : forall n m,
  lt n m ->
  lt n (S m).
Proof.
  unfold lt.
  intros.
  apply le_S. assumption.
Qed.

Theorem ble_nat_true : forall n m,
  ble_nat n m = true -> n <= m.
Proof.
  intro.
  induction n.
  intros. apply O_le_n.

  intros.
  induction m.
  simpl in H. inversion H.
  simpl in H. apply n_le_m__Sn_le_Sm. apply IHn.
  assumption.
Qed.

Lemma ble_refl : forall m, ble_nat m m = true.
Proof.
  intros. induction m. reflexivity. simpl. assumption.
Qed.

Theorem le_ble_nat : forall n m,
  n <= m ->
  ble_nat n m = true.
Proof.
  intros.
  generalize dependent n.
  induction m.
  intro. intro.
  inversion H. reflexivity.

  destruct n.
  simpl. reflexivity.

  intro. simpl. apply IHm. apply Sn_le_Sm__n_le_m in H.
  assumption.
Qed.

Theorem ble_nat_true_trans : forall n m o,
  ble_nat n m = true -> ble_nat m o = true -> ble_nat n o = true.
Proof.
  intros.
  apply le_ble_nat.
  apply ble_nat_true in H.
  apply ble_nat_true in H0.

  apply le_trans with m; assumption.
Qed.

Theorem ble_nat_false : forall n m,
  ble_nat n m = false -> ~(n <= m).
Proof.
  intros.
  generalize dependent n.

  induction m. intros. intro. inversion H0. subst. inversion H.

  intros. intro. destruct n. inversion H.

  simpl in H. apply IHm in H. apply Sn_le_Sm__n_le_m in H0.
  apply H. assumption.
Qed.

Module R.

Inductive R : nat -> nat -> nat -> Prop :=
   | c1 : R 0 0 0
   | c2 : forall m n o, R m n o -> R (S m) n (S o)
   | c3 : forall m n o, R m n o -> R m (S n) (S o)
   | c4 : forall m n o, R (S m) (S n) (S (S o)) -> R m n o
   | c5 : forall m n o, R m n o -> R n m o.

Example r_test_1 : R 1 1 2.
Proof. apply c3. apply c2. apply c1. Qed.

Example r_test_2 : R 2 2 6.
Proof.
  apply c3. apply c2. apply c3. apply c2.
Abort.

(* If we dropped constructor c5 from the definition of R, would the set of provable propositions change? Briefly (1 sentence) explain your answer.  *)

(* Answer: No, because c2 and c3 is symmetric over m and n. *)

(* If we dropped constructor c4 from the definition of R, would the set of provable propositions change? Briefly (1 sentence) explain your answer. *)

(* Answer: No, because c4 is equivalent to c2 + c3. *)

Goal forall m n o, m + n = o -> R m n o.
Proof.
  intros.
  generalize dependent o.
  induction m.
    induction n.
      destruct o.
        constructor.
        intro. inversion H.
      destruct o.
        intro. inversion H.
        intro. constructor. apply IHn. inversion H. reflexivity.
    induction n.
      intros. destruct o.
        inversion H.
        constructor. apply IHm. inversion H. reflexivity.
      intros.
        destruct o.
          inversion H.
          destruct o.
            inversion H. destruct m; inversion H1.
            constructor. apply IHm. inversion H. reflexivity.
Qed.

Goal forall m n o, R m n o -> m + n = o.
Proof.
  intros.
  induction H.
    reflexivity.
    rewrite plus_Sn_m. apply f_equal. assumption.
    rewrite <- plus_n_Sm. apply f_equal. assumption.
    inversion IHR. rewrite <- plus_n_Sm in H1. inversion H1. reflexivity.
    rewrite plus_comm. assumption.
Qed.

End R.

Definition between (n m o: nat) : Prop :=
  andb (ble_nat n o) (ble_nat o m) = true.

Definition teen : nat->Prop := between 13 19.

Definition true_for_zero (P:nat->Prop) : Prop :=
  P 0.

Definition true_for_all_numbers (P:nat->Prop) : Prop :=
  forall n, P n.

Definition preserved_by_S (P:nat->Prop) : Prop :=
  forall n', P n' -> P (S n').

Definition natural_number_induction_valid : Prop :=
  forall (P:nat->Prop),
    true_for_zero P ->
    preserved_by_S P ->
    true_for_all_numbers P.

Definition combine_odd_even (Podd Peven : nat -> Prop) : nat -> Prop :=
  fun n => if oddb n then Podd n else Peven n.

Theorem combine_odd_even_intro :
  forall (Podd Peven : nat -> Prop) (n : nat),
    (oddb n = true -> Podd n) ->
    (oddb n = false -> Peven n) ->
    combine_odd_even Podd Peven n.
Proof.
  intros.
  unfold combine_odd_even.
  destruct (oddb n).
  apply H. reflexivity.
  apply H0. reflexivity.
Qed.

Theorem combine_odd_even_elim_odd :
  forall (Podd Peven : nat -> Prop) (n : nat),
    combine_odd_even Podd Peven n ->
    oddb n = true ->
    Podd n.
Proof.
  intros.
  unfold combine_odd_even in H.
  rewrite H0 in H. assumption.
Qed.

Theorem combine_odd_even_elim_even :
  forall (Podd Peven : nat -> Prop) (n : nat),
    combine_odd_even Podd Peven n ->
    oddb n = false ->
    Peven n.
Proof.
  intros.
  unfold combine_odd_even in H.
  rewrite H0 in H. assumption.
Qed.

Fixpoint true_upto_n__true_everywhere n (P : nat -> Prop) : Prop :=
  match n with
    | 0 => (forall m, P m)
    | S n => P (S n) -> true_upto_n__true_everywhere n P
  end.

Example true_upto_n_example :
    (true_upto_n__true_everywhere 3 (fun n => even n))
  = (even 3 -> even 2 -> even 1 -> forall m : nat, even m).
Proof. reflexivity. Qed.


From LF Require Export Basics.

Example add_0_1 : forall n : nat,
  0 + n = n.
Proof. reflexivity. Qed.

Example add_0_r_firsttry : forall n : nat,
  n + 0 = n.
Proof.
  intros n.
  simpl.
Abort.

Example add_0_r_secondtry : forall n : nat,
  n + 0 = n.
Proof.
  intros n. destruct n as [|n'] eqn:E.
  - reflexivity.
  - simpl.
Abort.

Theorem add_0_r : forall n : nat,
  n + 0 = n.
Proof.
  intros n. induction n as [|n' IHn'].
  - reflexivity.
  - simpl. rewrite -> IHn'. reflexivity.
Qed.

Theorem minus_n_n: forall n,
  n - n = 0.
Proof.
  intros n. induction n as [| k].
   - reflexivity.
   - simpl. rewrite -> IHk. reflexivity.
Qed.

Theorem plus_n_Sm: forall n m : nat,
  S (n + m) = n + S m.
Proof.
  intros n m. induction n as [| n'].
  - simpl. reflexivity.
  - simpl. rewrite IHn'. reflexivity.
Qed.

Theorem add_comm : forall a b : nat,
  a + b = b + a.
Proof.
  intros a b. induction a as [| a'].
  - simpl. rewrite add_0_r. reflexivity.
  - simpl. rewrite IHa'. rewrite plus_n_Sm. reflexivity.
Qed.

Theorem mult_0_plus' : forall n m : nat,
  (n + 0 + 0) * m = n * m.
Proof.
  intros n m.
  assert (H: n + 0 + 0 = n).
  {
    (*Set Printing Parentheses.*)
    rewrite add_comm.
    simpl.
    rewrite add_comm.
    simpl.
    reflexivity.
  }
  rewrite -> H.
  reflexivity.
Qed.

Theorem plus_rearrange_firsttry : forall n m p q : nat,
  (n + m) + (p + q) = (m + n) + (p + q).
Proof.
  intros n m p q.
  (* Set Printing Parentheses. *)
  rewrite add_comm.
Abort.

Theorem plus_rearrange : forall n m p q : nat,
  (n + m) + (p + q) = (m + n) + (p + q).
Proof.
  intros n m p q.
  assert (H: n + m = m + n).
  { rewrite add_comm. reflexivity. }
  rewrite H.
  reflexivity.
Qed.

Theorem mul_0_r : forall n : nat,
  n * 0 = 0.
Proof.
  intros n. induction n as [|n' IHn'].
  - reflexivity.
  - simpl. rewrite IHn'. reflexivity.
Qed.

Theorem add_assoc : forall n m p : nat,
  n + (m + p) = (n + m) + p.
Proof.
  intros n m p. induction n as [|n'].
  - reflexivity.
  - simpl. rewrite IHn'. reflexivity.
Qed.

Fixpoint double (n:nat) :=
  match n with
  | O => O
  | S n' => S (S (double n'))
  end.

Lemma double_plus : forall n, double n = n + n .
Proof.
  intros n. induction n as [|n'].
  - reflexivity.
  - simpl. rewrite IHn'. rewrite plus_n_Sm. reflexivity.
Qed.

Theorem eqb_refl : forall n : nat,
  (n =? n) = true.
Proof.
  intros n. induction n as [|n'].
  - simpl. reflexivity.
  - simpl. rewrite IHn'. reflexivity.
Qed.

Theorem even_S : forall n : nat,
  even (S n) = negb (even n).
Proof.
  intros n. induction n as [|n'].
  - simpl. reflexivity.
  - rewrite -> IHn'. rewrite negb_involutive. simpl. reflexivity.
Qed.

Theorem add_assoc' : forall n m p : nat,
  n + (m + p) = (n + m) + p.
Proof.
  intros n m p. induction n as [|n' IHn']. reflexivity.
  simpl. rewrite IHn'. reflexivity.
Qed.

Theorem add_assoc'' : forall n m p : nat,
  n + (m + p) = (n + m) + p.
Proof.
  intros n m p. induction n as [|n' IHn'].
  - (* n = 0 *)
    reflexivity.
  - (* n = S n' *)
    simpl. rewrite IHn'. reflexivity.
(** - _Theorem_: For any [n], [m] and [p],
      n + (m + p) = (n + m) + p.
    _Proof_: By induction on [n].
    - First, suppose [n = 0]. We must show that
        0 + (m + p) = (0 + m) + p.
      This follows directly from the definition of [+].
    - Next, suppose [n = S n'], where
      n' + (m + p) = (n' + m) + p.
    We must now show that
      (S n') + (m + p) = ((S n') + m) + p.
    By the definition of [+], this follows from
      S (n' + (m + p)) = S((n' + m) + p),
    which is immediate from the induction hypothesis. _Qed_.
*)
Qed.

Theorem add_comm' : forall a b : nat,
  a + b = b + a.
Proof.
  intros a b. induction a as [| a'].
  - simpl. rewrite add_0_r. reflexivity.
  - simpl. rewrite IHa'. rewrite plus_n_Sm. reflexivity.
(** - _Theorem_: For any [a] and [b],
      a + b = b + a.
    _Proof_: By induction on [a].
    - First, suppose [a = 0]. We must show that
        0 + b = b + 0.
      This follows directly from the definition of [+].
    - Next, suppose [a = S a'], where
      a' + b = b + 'a.
    We must now show that
      (S a') + b = b + (S a').
    By the definition of [+], this follows from
      S (a' + b) = S (b + a').
    which is immediate from the induction hypothesis. _Qed_.
*)
Qed.

Theorem add_shuffle3 : forall n m p : nat,
  n + (m + p) = m + (n + p).
Proof.
  intros n m p.
  rewrite add_assoc.
  assert (H1: m + (n + p) = m + n + p).
  { rewrite add_assoc. reflexivity. }
  assert (H2: n + m = m + n).
  { rewrite add_comm. reflexivity. }
  rewrite H1.
  rewrite <- H2.
  reflexivity.
Qed.
Theorem  mul_n_0: forall n : nat,
  n * 0 = 0.
Proof.
  induction n.
  - reflexivity.
  Admitted.

Lemma n_k_1 : forall n k : nat,
  n * (1 + k) = n + n * k.
Proof.
  intros n k.
  induction n as [|n' IHn'].
  - simpl. reflexivity.
  - simpl. rewrite add_assoc.
    assert (n' + k = k + n').
    { rewrite add_comm. reflexivity. }
    rewrite H.
    rewrite <- add_assoc.
    rewrite <- IHn'.
    Set Printing Parentheses.
    assert (1 + k = S k).
    { rewrite plus_1_n. reflexivity. }
    rewrite H0.
    reflexivity.
  Qed.

Theorem mul_comm : forall m n : nat,
  m * n = n * m.
Proof.
  intros n m.
  induction n as [|k IHk'].
  - simpl.
    assert (m * 0 = 0).
    induction m.
    -- rewrite mul_0_r. reflexivity.
    -- simpl. rewrite IHm. reflexivity.
    -- rewrite H. reflexivity.
  - assert (m * (S k) = m * (1 + k)).
    { rewrite <- plus_1_n. reflexivity. }
    rewrite H.
    rewrite n_k_1.
    rewrite <- IHk'.
    rewrite <- plus_1_n.
    simpl.
    reflexivity.
Qed.

Check leb.

Lemma leb_a : forall a,
  leb a a = true.
Proof.
  intros [].
  - reflexivity.
  - induction n.
    -- simpl. reflexivity.
    -- simpl. rewrite <- IHn. simpl. reflexivity.
Qed.

Lemma leb_a_S_a : forall a,
  leb a (S a) = true.
Proof.
  intros a.
  induction a.
  - simpl. reflexivity.
  - simpl. rewrite <- IHa. reflexivity.
Qed.

Lemma leb_a_b : forall a b,
  leb a (a + b) = true.
Proof.
  intros a b.
  induction b.
  - rewrite add_comm. simpl. rewrite leb_a. reflexivity.
  - rewrite <- plus_n_Sm.
    destruct b.
    -- rewrite add_comm. simpl. rewrite leb_a_S_a. reflexivity.
    -- rewrite <- IHb.
Abort.

Theorem plus_leb_compat_l : forall n m p : nat,
  n <=? m = true -> (p + n) <=? (p + m) = true.
Proof.
  intros n m p.
  intros H.
  induction p as [|p' IHp'].
  - simpl. rewrite H. reflexivity.
  - simpl. rewrite IHp'. reflexivity.
Qed.

(* guess: destruction *)
(* reality: induction *)
Theorem leb_refl : forall n:nat,
  (n <=? n) = true.
Proof.
  intros [].
  - reflexivity.
  - induction n.
    -- reflexivity.
    -- rewrite <- IHn.
       simpl. reflexivity.
Qed.

(* guess: destruction *)
(* reality: induction *)
Theorem zero_neqb_S : forall n:nat,
  0 =? (S n) = false.
Proof.
  induction n as [|n' IHn'].
  - reflexivity.
  - rewrite <- IHn'. simpl. reflexivity.
Qed.
(* guess: destruction *)
(* reality: destruction *)
Theorem andb_false_r : forall b : bool,
  andb b false = false.
Proof.
  intros [].
  - reflexivity.
  - reflexivity.
Qed.

(* guess: destruction *)
(* reality: destruction *)
Theorem S_neqb_0 : forall n:nat,
(S n) =? 0 = false.
Proof.
  intros [].
  - reflexivity.
  - reflexivity.
  Qed.

(* guess: induction *)
Theorem mult_1_l : forall n:nat, 1 * n = n.
Proof.
  induction n as [|n' IHn'].
  - reflexivity.
  - simpl. rewrite <- add_comm. simpl. reflexivity.
Qed.

Theorem all3_spec : forall b c : bool,
  orb
    (andb b c)
    (orb (negb b)
         (negb c))
  = true.
Proof.
  intros [] [].
  - reflexivity.
  - reflexivity.
  - reflexivity.
  - reflexivity.
Qed.

Theorem mult_plus_distr_r : forall n m p : nat,
  (n + m) * p = (n * p) + (m * p).
Proof.
  intros n m p.
  induction p as [|p' IHp'].
  - rewrite mul_0_r. rewrite mul_0_r. rewrite mul_0_r. reflexivity.
  - assert (Hs1: n * S p' = n * p' + n).
    { rewrite mul_comm. simpl. rewrite mul_comm. rewrite add_comm. reflexivity. }
    assert (Hs2: m * S p' = m * p' + m).
    { rewrite mul_comm. simpl. rewrite mul_comm. rewrite add_comm. reflexivity. }
    assert (Hs3: (n + m) * S p' = (n + m) * p' + (n + m)).
    { rewrite mul_comm. simpl. rewrite mul_comm. rewrite add_comm. reflexivity. }
    rewrite Hs1. rewrite Hs2. rewrite Hs3.
    rewrite IHp'. rewrite add_assoc. rewrite add_assoc.
    assert (Ha: (m * p') + (n * p') = (n * p') + (m * p')).
    { rewrite add_comm. reflexivity. }
    assert (Hf: ((n * p') + n) + (m * p') = (n * p') + (m * p') + n).
    { rewrite add_comm. rewrite add_assoc. rewrite Ha. reflexivity. }
    rewrite Hf.
    reflexivity.
Qed.

Theorem mult_assoc : forall n m p : nat,
  n * (m * p) = (n * m) * p.
Proof.
  intros n m p.
  induction n as [|n' IHn'].
  - simpl. reflexivity.
  - simpl. rewrite IHn'.
    rewrite mult_plus_distr_r.
    reflexivity.
Qed.

Theorem add_shuffle3' : forall n m p : nat,
  n + (m + p) = m + (n + p).
Proof.
  intros n m p.
  rewrite add_assoc.
  replace (n + m) with (m + n).
  - rewrite add_assoc. reflexivity.
  - rewrite add_comm. reflexivity.
Qed.

Theorem bin_to_nat_pres_incr : forall b : bin,
  bin_to_nat (incr b) = 1 + bin_to_nat b.
Proof.
  intros b.
  induction b as [|b' |b'].
  - simpl. reflexivity.
  - simpl. reflexivity.
  - simpl. rewrite IHb'. rewrite plus_1_n. rewrite plus_n_Sm. reflexivity.
Qed.

Fixpoint nat_to_bin (n:nat) : bin :=
  match n with
    | O => Z
    | S n' => incr (nat_to_bin n')
  end.

Theorem nat_bin_nat : forall n,
  bin_to_nat (nat_to_bin n) = n.
Proof.
  intros n.
  induction n as [|n' IHn'].
  - simpl. reflexivity.
  - simpl. rewrite bin_to_nat_pres_incr. rewrite IHn'. reflexivity.
Qed.

Theorem bin_nat_bin_fails : forall b, nat_to_bin (bin_to_nat b) = b.
Abort.

Lemma double_incr : forall n : nat,
  double (S n) = S (S (double n)).
Proof.
  intros n.
  induction n as [|n' IHn'].
  - reflexivity.
  - simpl. rewrite <- IHn'. reflexivity.
Qed.

Definition double_bin (b:bin) : bin :=
  match b with
  | Z => Z
  | n => B_0 n
  end.

Example double_bin_zero : double_bin Z = Z.
Proof.
  simpl. reflexivity.
Qed.

Lemma double_incr_bin : forall b,
  double_bin (incr b) = incr (incr (double_bin b)).
Proof.
  intros b.
  induction b as [|b' |b' IHb'].
  - simpl. reflexivity.
  - simpl. reflexivity.
  - simpl. reflexivity.
Qed.

Theorem bin_nat_bin_fails : forall b, nat_to_bin (bin_to_nat b) = b.
Abort.

Fixpoint normalize (b:bin) : bin :=
  match b with
  | Z => Z
  | B_0 b' => double_bin (normalize b')
  | B_1 b' => incr (double_bin (normalize b'))
end.

Example test_normalize0_1 : normalize Z = Z.
Proof. reflexivity. Qed.

Example test_normalize0_2 : normalize (B_0 Z) = Z.
Proof. reflexivity. Qed.

Example test_normalize0_3 : normalize (B_0 (B_0 Z)) = Z.
Proof. reflexivity. Qed.

Example test_normalize0_4 : normalize (B_0 (B_0 (B_0 Z))) = Z.
Proof. reflexivity. Qed.

Example test_normalize1 : bin_to_nat (normalize (B_1 Z)) = 1.
Proof. reflexivity. Qed.

Example test_normalize2 : bin_to_nat (normalize (B_0 (B_1 Z))) = 2.
Proof. reflexivity. Qed.

Example test_normalize3 : bin_to_nat (normalize (B_1 (B_1 Z))) = 3.
Proof. reflexivity. Qed.

Example test_normalize4 : bin_to_nat (normalize (B_0 (B_0 (B_1 Z)))) = 4.
Proof. reflexivity. Qed.

Theorem bin_nat_bin : forall b, nat_to_bin (bin_to_nat b) = normalize b.
Proof.
Abort.

Lemma nat_to_bin_double : forall n : nat,
  nat_to_bin(double n) = double_bin(nat_to_bin n).
Proof.
  intros n.
  induction n.
  - simpl. reflexivity.
  - simpl. rewrite -> double_incr_bin. rewrite <- IHn. reflexivity.
Qed.

Compute bin_to_nat (normalize (B_0 (B_1 Z))).
Compute bin_to_nat (normalize (B_1 (B_1 Z))).

Theorem bin_nat_bin : forall b,
  nat_to_bin (bin_to_nat b) = normalize b.
Proof.
  intros b.
  induction b as [|b' |b'].
  - simpl. reflexivity.
  - simpl. rewrite <- IHb'.
    destruct (bin_to_nat b').
    -- reflexivity.
    -- rewrite <- double_plus. rewrite -> nat_to_bin_double. reflexivity.
  - simpl. rewrite <- IHb'. rewrite <- double_plus. rewrite <- nat_to_bin_double. reflexivity.
Qed.


(** %\chapter{%#<H0>#Regular expressions and languages%}%#</H0># *)
Section Reg_expression.
Require Import List.
Load Automata.

(** * Regular expressions *)

(** The _regular expressions_ are defined as simple expressions that denote the languages
    accepted by finite automata.

    We would like to define the three operations on languages that the operators of
    regular expressions represent.

    - The _union_ of two languages L and M , denoted %L\cup M%, is the set of strings that
      are either in L or in M, or in both. As a simple example, if L= { 1,01,013} and
      M = {10,2} , then %L\cup M% = {1,01,013,10,2}. In Coq notation, it is defined as:

      <<
     Definition lang_union (a:Alphabet)(l1:Language a)(l2:Language a):Language a :=
         fun w:Word a =>  l1 w \/ l2 w.
      >>

    - The _concatenation_ of languages L and M is the set of words that can be formed by taking
      any string in L and concatenating it with any string in M. For example, if
      [L= {101, 23, 9)] and [M={3,4}] , then [L * M = {1013, 1014, 233, 234, 93, 94}]. In Coq, we represent it as :

      <<
      Definition lang_conc(a:Alphabet)(l1:Language a)(l2:Language a):Language a :=
    fun w:Word a => exists w1:Word a, exists w2:Word a,  w1 ++ w2=w /\ l1 w1 /\ l2 w2.
      >>

    - The _Kleene operator_  (closure) represents the set of words that  can be produced by taking any number
      of words from L, and concatenating all of them.

     L* = L( L^2) (L^3) (.... L^n ...

     In the _Languages_ section, we have proved some useful properties concerning concatenation as
     well as union and Kleene operator

*)

(** Defining the regular expression as an inductive term .

    Below, we make use of 7 constructors according to specific rules . The explanation are
    as follows:

    - the _[empty]_ expression represents a regular expression and denotes the empty set

    - [epsilon] is a regular expression and denotes the set [{epsilon}]

    - For each [a : Alphabet ] , _a_ is a regular expression and denotes the set [{a}]

    - If [r] and [s] are regular expressions, then [r+s] is a regular expression denoting
     the _union_ of L(r) and L(s), i.e L(r+s) = %L(r)\cup L(s)% .

    - If [r] and [s] are regular expressions, then [rs] is a regular expression , denoting the
     _concatenation_ of [L(r)] and [L(s)] i.e L(rs) = $L(r) \cap L(s)$

    - If [r] is a regular expression, then [r*] is a regular expression, denoting the closure of
      [L(r)] . That is, [L(r* )= L(r)]

    - If [r] is a regular expression, then [(r)] is also a regular expression, denoting the exactly
      the same language like [r]. I.e, [L((r)) = L(r)]

*)

Inductive RegExp (a:Alphabet) :Set :=
| Empty : RegExp a

| Epsilon : RegExp a

| Var : Fin a -> RegExp a

| Plus : RegExp a -> RegExp a -> RegExp a

| Conc : RegExp a -> RegExp a -> RegExp a

| Starex : RegExp a-> RegExp a

| Paren : RegExp a-> RegExp a.

Print nfa_void.

Print nfa_eps.
Print nfa_var.
Check Type.
Check ex.
Print ex.
(*
Definition exas (a:Alphabet)(n:nat) :nfa n a.
induction n.
simpl in *.
intuition.
destruct H.
simpl in *. *)

Fixpoint re_nr (a:Alphabet) (r:RegExp a) : nat :=

match r with

| Empty => 1

| Epsilon => 1
| Var xs => 2
|  Plus xs ys => re_nr xs + re_nr ys

| Conc xs ys => re_nr xs + re_nr ys
| Starex xs => re_nr xs + 1

| Paren xs  => re_nr xs
end.

Fixpoint re_to_nfa(a:Alphabet)(r:RegExp a ) : nfa (re_nr r) a :=

 match r with
| Empty => nfa_void a
| Epsilon => nfa_eps a

| Var xs => nfa_var xs

|  Plus xs ys => nfa_disj (re_to_nfa xs) (re_to_nfa ys)

| Conc xs ys => nfa_conc (re_to_nfa xs) (re_to_nfa ys)

| Starex xs => nfa_star (re_to_nfa xs)

| Paren xs => re_to_nfa xs

end.
Inductive Star (a:Alphabet) (l:Language a) : Language a:=
  | nil0: (Star l) nil
  | cons0 : forall (x: Word a)(y:Word a),Star l x /\ l y -> Star l (app x y).

(*Definition single_lang1 (a:Alphabet)(x:Fin a):Language a:=

 fun w => w = nil.*)

Print single_lang.
Fixpoint reg2lang (a:Alphabet)(r : RegExp a) : Language a :=
 match r with
 | Empty => fun w:Word a =>False
 | Epsilon => fun w => w = nil (* fun w:Word a => match w with  |nil => True
                                            | _  => False
                              end *)
 | Var e => single_lang1 e
 | Plus e1 e2 => lang_union (reg2lang e1) (reg2lang e2)
 | Conc e1 e2 => lang_conc (reg2lang e1) (reg2lang e2)
 | Starex e => Star (reg2lang e)
 | Paren e => reg2lang e
  end.

Print nfa_eps.
Lemma klth : forall (a:Alphabet)(w:Word a) (r:RegExp a) , nfa_lang (re_to_nfa r) w -> reg2lang r w.
Proof.
intros.
simpl in *.
induction r.
simpl in *.
unfold nfa_void in H.
simpl in H.
trivial.
simpl in *.
apply nfa_eps_lang in H.
unfold eps_lang in H.
exact H.

simpl in *.
apply nfa_var_lang.

exact H.
(*intuition.
(*We want to prove that in an nfa with the epsilon language does not accept a word
different from <nil>  useful in the theorem of correctness nfa_eps_lang *)
rewrite H0 in H.
assert(False). *)

simpl in *.
apply nfa_disj_lang in H.
admit.
destruct H1.
simpl in *.
unfold single_lang1.
unfold nfa_var in *.
simpl in *.
assert(reg2lang(Paren r) w).
simpl in *.
assert(nfa_lang(nfa_eps a) (a0::w) -> False).
intro.
unfold nfa_eps in H1.

admit.
simpl in *.
unfold single_lang1.
unfold nfa_var in H.
simpl in *.
Lemma klth1 :forall (a:Alphabet)(w:Word a)(r:RegExp a), reg2lang r w -> nfa_lang (re_to_nfa r) w.
intros.
induction r.
simpl in *.
destruct H.
simpl in *.
unfold nfa_eps.
simpl.

(** The meaning of regular expressions *)

(** Definition Kleene star, encountered in the Introduction to Languages lecture *)

Definition reg2nfa_simpl (a:Alphabet) (r:simplRegExp a) :=

    match r with
 | Empty => nfa_void a

 | Epsilon =>nfa_eps a

  end.

Definition reg2nfa_bi (a:Alphabet)(r:biRegExp a) :=

  match r with
  | Var xs => nfa_var xs

 end.

Fixpoint reg2nfa_multi (a:Alphabet) (r:multiRegExp a) :=

  match r with
| Plus s1 s2 => match s1,s2 with

                | Var x, Var y => nfa_disj a (reg2nfa_bi s1)(reg2nfa_bi s2)
                | Var x, Plus l1 l2 => nfa_disj a (reg2nfa_bi s1) (nfa_disj (reg2nfa_multi l1)(reg2nfa_multi l2))

     end

end.

Print reg2nfa.

(** Defining a function that takes a regular expression as an input and produces its
    corresponding language as an output
*)
(*
Fixpoint reg2lang (a:Alphabet)(r : RegExp a) : Language a :=
 match r with
 | Empty => fun w:Word a =>False
 | Epsilon => fun w:Word a => match w with  |nil => True
                                            | _  => False
                              end
 | Var e => single_lang1 e
 | Plus e1 e2 => lang_union (reg2lang e1) (reg2lang e2)
 | Conc e1 e2 => lang_conc (reg2lang e1) (reg2lang e2)
 | Starex e => Star (reg2lang e)
 | Paren e => reg2lang e
  end. *)

(** Translating regular expressions to NFA *)

(*Fixpoint re_to_dfa (a:Alphabet) (r:RegExp a) :=

   match r with

   | Empty => dfa_void

    | Epsilon  => dfa_epsilon

   | Var x => nfa2dfa (nfa_var a x)

 end. *)

  Print nfa.

(** * Closure properties of regular languages : *)

(** We shall prove some well known results that deal with languages that are regular.

   - _Closure under union_ :if 2 languages are regular, then their union  is also regular.
*)

Lemma union1 : forall (a:Alphabet)( l m :Language a) , l w

End Reg_expression.


Require Import Arith.
Require Import Recdef.

Inductive formula : Set :=
    | True  : formula
    | False : formula
    | Var   : nat -> formula
    | Not   : formula -> formula
    | And   : formula -> formula -> formula
    | Or    : formula -> formula -> formula
    | Imply : formula -> formula -> formula.

Inductive equiv : formula -> formula -> Prop :=
    | equiv_and_comm     : forall f1 f2 : formula, equiv (And f1 f2) (And f2 f1)
    | equiv_or_comm      : forall f1 f2 : formula, equiv (Or f1 f2) (Or f2 f1)
    | equiv_and_assoc    : forall f1 f2 f3 : formula,
                           equiv (And (And f1 f2) f3) (And f1 (And f2 f3))
    | equiv_or_assoc     : forall f1 f2 f3 : formula,
                           equiv (Or (Or f1 f2) f3) (Or f1 (Or f2 f3))
    | equiv_and_absorp   : forall f1 f2 : formula, equiv (And f1 (Or f1 f2)) f1
    | equiv_or_absorp    : forall f1 f2 : formula, equiv (Or f1 (And f1 f2)) f1
    | equiv_and_iden     : forall f : formula, equiv (And f True) f
    | equiv_or_iden      : forall f : formula, equiv (Or f False) f
    | equiv_and_distr    : forall f1 f2 f3 : formula,
                           equiv (And f1 (Or f2 f3)) (Or (And f1 f2) (And f1 f3))
    | equiv_or_distr     : forall f1 f2 f3 : formula,
                           equiv (Or f1 (And f2 f3)) (And (Or f1 f2) (Or f1 f3))
    | equiv_and_compl    : forall f : formula, equiv (And f (Not f)) False
    | equiv_or_compl     : forall f : formula, equiv (Or f (Not f)) True
    | equiv_not_cong     : forall f1 f2 : formula,
                           equiv f1 f2 -> equiv (Not f1) (Not f2)
    | equiv_and_cong_l   : forall f1 f2 f3 : formula,
                           equiv f1 f2 -> equiv (And f1 f3) (And f2 f3)
    | equiv_and_cong_r   : forall f1 f2 f3 : formula,
                           equiv f2 f3 -> equiv (And f1 f2) (And f1 f3)
    | equiv_or_cong_l    : forall f1 f2 f3 : formula,
                           equiv f1 f2 -> equiv (Or f1 f3) (Or f2 f3)
    | equiv_or_cong_r    : forall f1 f2 f3 : formula,
                           equiv f2 f3 -> equiv (Or f1 f2) (Or f1 f3)
    | equiv_imply_cong_l : forall f1 f2 f3 : formula,
                           equiv f1 f2 -> equiv (Imply f1 f3) (Imply f2 f3)
    | equiv_imply_cong_r : forall f1 f2 f3 : formula,
                           equiv f2 f3 -> equiv (Imply f1 f2) (Imply f1 f3)
    | equiv_refl         : forall f : formula, equiv f f
    | equiv_sym          : forall f1 f2 : formula, equiv f1 f2 -> equiv f2 f1
    | equiv_trans        : forall f1 f2 f3 : formula,
                           equiv f1 f2 -> equiv f2 f3 -> equiv f1 f3
    | equiv_imply_or     : forall f1 f2 : formula,
                           equiv (Imply f1 f2) (Or (Not f1) f2).

Lemma equiv_not_true :
    equiv (Not True) False.
Proof.
    apply equiv_trans with (And (Not True) True).
    apply equiv_sym.
    apply equiv_and_iden.
    apply equiv_trans with (And True (Not True)).
    apply equiv_and_comm.
    apply equiv_and_compl.
Qed.

Lemma equiv_not_false :
    equiv (Not False) True.
Proof.
    apply equiv_trans with (Or (Not False) False).
    apply equiv_sym.
    apply equiv_or_iden.
    apply equiv_trans with (Or False (Not False)).
    apply equiv_or_comm.
    apply equiv_or_compl.
Qed.

Fixpoint elim_imply (f : formula) : formula :=
    match f with
    | True        => True
    | False       => False
    | Var n       => Var n
    | Not   f1    => Not (elim_imply f1)
    | And   f1 f2 => And (elim_imply f1) (elim_imply f2)
    | Or    f1 f2 => Or (elim_imply f1) (elim_imply f2)
    | Imply f1 f2 => Or (Not (elim_imply f1)) (elim_imply f2)
    end.

Lemma elim_imply_equiv :
    forall f : formula, equiv (elim_imply f) f.
Proof.
    induction f as [| | n | f1 H | f1 H1 f2 H2 | f1 H1 f2 H2 | f1 H1 f2 H2];
    simpl.

        (* Case : f = True *)
        apply equiv_refl.

        (* Case : f = False *)
        apply equiv_refl.

        (* Case : f = Var n *)
        apply equiv_refl.

        (* Case : f = Not f1 *)
        apply (equiv_not_cong _ _ H).

        (* Case : f = And f1 f2 *)
        apply equiv_trans with (And f1 (elim_imply f2)).
        apply (equiv_and_cong_l _ _ _ H1).
        apply (equiv_and_cong_r _ _ _ H2).

        (* Case : f = Or f1 f2 *)
        apply equiv_trans with (Or f1 (elim_imply f2)).
        apply (equiv_or_cong_l _ _ _ H1).
        apply (equiv_or_cong_r _ _ _ H2).

        (* Case : f = Imply f1 f2 *)
        apply equiv_sym.
        apply equiv_trans with (Imply (elim_imply f1) (elim_imply f2)).
        apply equiv_trans with (Imply f1 (elim_imply f2)).
        apply (equiv_imply_cong_r _ _ _ (equiv_sym _ _ H2)).
        apply (equiv_imply_cong_l _ _ _ (equiv_sym _ _ H1)).
        apply equiv_imply_or.
Qed.

Fixpoint height (f : formula) : nat :=
    match f with
    | True        => 0
    | False       => 0
    | Var n       => 0
    | Not   f1    => S (height f1)
    | And   f1 f2 => S (Peano.max (height f1) (height f2))
    | Or    f1 f2 => S (Peano.max (height f1) (height f2))
    | Imply f1 f2 => S (Peano.max (height f1) (height f2))
    end.

Fixpoint not_height (f : formula) : nat :=
    match f with
    | True        => 0
    | False       => 0
    | Var n       => 0
    | Not   f1    => S (height f1)
    | And   f1 f2 => Peano.max (not_height f1) (not_height f2)
    | Or    f1 f2 => Peano.max (not_height f1) (not_height f2)
    | Imply f1 f2 => Peano.max (not_height f1) (not_height f2)
    end.

Lemma not_height_le_height :
    forall f : formula, not_height f <= height f.
Proof.
    induction f as [| | n | f1 H1 | f1 H1 f2 H2 | f1 H1 f2 H2 | f1 H1 f2 H2];
    simpl;
    try trivial.

        (* Case : f = And f1 f2 *)
        apply le_S.
        apply (NPeano.Nat.max_le_compat _ _ _ _ H1 H2).

        (* Case : f = And f1 f2 *)
        apply le_S.
        apply (NPeano.Nat.max_le_compat _ _ _ _ H1 H2).

        (* Case : f = And f1 f2 *)
        apply le_S.
        apply (NPeano.Nat.max_le_compat _ _ _ _ H1 H2).
Qed.

Function move_not_aux (f : formula) {measure not_height} : formula :=
    match f with
    | True   => True
    | False  => False
    | Var n  => Var n
    | Not f1 => match f1 with
        | True          => Not True
        | False         => Not False
        | Var n         => Not (Var n)
        | Not   f1'     => move_not_aux f1'
        | And   f1' f2' => Or  (move_not_aux (Not f1')) (move_not_aux (Not f2'))
        | Or    f1' f2' => And (move_not_aux (Not f1')) (move_not_aux (Not f2'))
        | Imply f1' f2' => Not (Imply f1' f2')
        end
    | And   f1 f2 => And   f1 f2
    | Or    f1 f2 => Or    f1 f2
    | Imply f1 f2 => Imply f1 f2
    end.
Proof.
    (* Case : f = Not (Not f1' *)
    intros _ _ f1' _ _.
    simpl.
    apply lt_S.
    apply (le_lt_trans _ (height f1') _ (not_height_le_height _) (lt_n_Sn _)).

    (* Case : f = Not (And f1' f2') : move_not_aux (Not f2') *)
    intros _ _ f1' f2' _ _.
    simpl.
    apply lt_n_S.
    apply (le_lt_trans _ (Peano.max (height f1') (height f2')) _
           (Max.le_max_r _ _) (lt_n_Sn _)).

    (* Case : f = Not (And f1' f2') : move_not_aux (Not f1')  *)
    intros _ _ f1' f2' _ _.
    simpl.
    apply lt_n_S.
    apply (le_lt_trans _ (Peano.max (height f1') (height f2')) _
           (Max.le_max_l _ _) (lt_n_Sn _)).

    (* Case : f = Not (Or f1' f2') : move_not_aux (Not f2')  *)
    intros _ _ f1' f2' _ _.
    simpl.
    apply lt_n_S.
    apply (le_lt_trans _ (Peano.max (height f1') (height f2')) _
           (Max.le_max_r _ _) (lt_n_Sn _)).

    (* Case : f = Not (Or f1' f2') : move_not_aux (Not f1')  *)
    intros _ _ f1' f2' _ _.
    simpl.
    apply lt_n_S.
    apply (le_lt_trans _ (Peano.max (height f1') (height f2')) _
           (Max.le_max_l _ _) (lt_n_Sn _)).
Defined.

Fixpoint move_not (f : formula) : formula :=
    match f with
    | True   => True
    | False  => False
    | Var n  => Var n
    | Not f1 => match f1 with
        | True          => Not True
        | False         => Not False
        | Var n         => Not (Var n)
        | Not   f1'     => move_not_aux (Not (Not f1'))
        | And   f1' f2' => move_not_aux (Not (And f1' f2'))
        | Or    f1' f2' => move_not_aux (Not (Or  f1' f2'))
        | Imply f1' f2' => Not (Imply (move_not f1') (move_not f2'))
        end
    | And   f1 f2 => And   (move_not f1) (move_not f2)
    | Or    f1 f2 => Or    (move_not f1) (move_not f2)
    | Imply f1 f2 => Imply (move_not f1) (move_not f2)
    end.

Lemma move_not_equiv :
    forall f : formula, equiv (move_not f) f.
Proof.
    induction f as [| | n | f1 H1 | f1 H1 f2 H2 | f1 H1 f2 H2 | f1 H1 f2 H2];
    simpl.

        (* Case : f = True *)
        apply equiv_refl.

        (* Case : f = False *)
        apply equiv_refl.

        (* Case : f = Var n *)
        apply equiv_refl.

        (* Case : f = Not f1 *)
        destruct f1 as [| | m | f1' | f1' f2' | f1' f2' | f1' f2'];
        simpl.

            (* Case : f1 = True *)
            apply (equiv_not_cong _ _ (equiv_refl _)).

            (* Case : f1 = False *)
            apply (equiv_not_cong _ _ (equiv_refl _)).

            (* Case : f1 = Var n *)
            apply (equiv_not_cong _ _ (equiv_refl _)).

            (* Case : f1 = Not f1' *)
            admit.

            (* Case : f1 = And f1' f2' *)
            admit.

            (* Case : f1 = Or f1' f2' *)
            admit.

            (* Case : f1 = Imply f1' f2' *)
            simpl in H1.
            apply (equiv_not_cong _ _ H1).

        (* Case : f = And f1 f2 *)
        apply equiv_trans with (And f1 (move_not f2)).
        apply (equiv_and_cong_l _ _ _ H1).
        apply (equiv_and_cong_r _ _ _ H2).

        (* Case : f = Or f1 f2 *)
        apply equiv_trans with (Or f1 (move_not f2)).
        apply (equiv_or_cong_l _ _ _ H1).
        apply (equiv_or_cong_r _ _ _ H2).

        (* Case : f = Imply f1 f2 *)
        apply equiv_trans with (Imply f1 (move_not f2)).
        apply (equiv_imply_cong_l _ _ _ H1).
        apply (equiv_imply_cong_r _ _ _ H2).
Qed.

Fixpoint eval_const (f : formula) : formula :=
    match f with
    | True  => True
    | False => False
    | Var n => Var n
    | Not True  => False
    | Not False => True
    | Not f1 => match eval_const f1 with
        | True  => False
        | False => True
        | f1'   => Not (eval_const f1)
        end
    | And f1 f2 => match eval_const f1, eval_const f2 with
        | True,  f2'   => f2'
        | False, f2'   => False
        | f1',   True  => f1'
        | f1',   False => False
        | f1',   f2'   => And f1' f2'
        end
    | Or f1 f2 => match eval_const f1, eval_const f2 with
        | True,  f2'   => True
        | False, f2'   => f2'
        | f1',   True  => True
        | f1',   False => f1'
        | f1',   f2'   => Or f1' f2'
        end
    | Imply f1 f2 => match eval_const f1, eval_const f2 with
        | True,  False => False
        | False, f2'   => True
        | f1',   True  => True
        | f1',   f2'   => Imply f1' f2'
        end
    end.

Functional Scheme eval_const_ind := Induction for eval_const Sort Prop.

Lemma eval_const_equiv :
    forall f : formula, equiv (eval_const f) f.
Proof.
Admitted.

Fixpoint or_size (f : formula) : nat :=
    match f with
    | True        => 0
    | False       => 0
    | Var n       => 0
    | Not   f1    => or_size f1
    | And   f1 f2 => or_size f1 + or_size f2
    | Or    f1 f2 => S (height f1 + height f2)
    | Imply f1 f2 => or_size f1 + or_size f2
    end.

Function distr_or_aux (f : formula) {measure or_size} : formula :=
    match f with
    | True   => True
    | False  => False
    | Var n  => Var n
    | Not f1 => Not f1
    | And f1 f2 => And f1 f2
    | Or  f1 f2 => match f1, f2 with
        | f1, And f1' f2' =>
            And (distr_or_aux (Or f1 f1')) (distr_or_aux (Or f1 f2'))
        | And f1' f2', f2 =>
            And (distr_or_aux (Or f1' f2)) (distr_or_aux (Or f2' f2))
        | f1, f2 => Or f1 f2
        end
    | Imply f1 f2 => Imply f1 f2
    end.
Proof.
    (* Case : f = Or True (And f1' f2') : distr_or_aux (Or True f2') *)
    intros _ _ _ _ f1' f2' _ _ .
    simpl.
    apply lt_n_S.
    apply (le_lt_trans _ (Peano.max (height f1') (height f2')) _
           (Max.le_max_r _ _) (lt_n_Sn _)).

    (* Case : f = Or True (And f1' f2') : distr_or_aux (Or True f1') *)
    intros _ _ _ _ f1' f2' _ _ .
    simpl.
    apply lt_n_S.
    apply (le_lt_trans _ (Peano.max (height f1') (height f2')) _
           (Max.le_max_l _ _) (lt_n_Sn _)).

    (* Case : f = Or False (And f1' f2') : distr_or_aux (Or False f2') *)
    intros _ _ _ _ f1' f2' _ _ .
    simpl.
    apply lt_n_S.
    apply (le_lt_trans _ (Peano.max (height f1') (height f2')) _
           (Max.le_max_r _ _) (lt_n_Sn _)).

    (* Case : f = Or True (And f1' f2') : distr_or_aux (Or False f1') *)
    intros _ _ _ _ f1' f2' _ _ .
    simpl.
    apply lt_n_S.
    apply (le_lt_trans _ (Peano.max (height f1') (height f2')) _
           (Max.le_max_l _ _) (lt_n_Sn _)).

    (* Case : f = Or (Var n) (And f1' f2') : distr_or_aux (Or (Var n) f2') *)
    intros _ _ _ n _ f1' f2' _ _ .
    simpl.
    apply lt_n_S.
    apply (le_lt_trans _ (Peano.max (height f1') (height f2')) _
           (Max.le_max_r _ _) (lt_n_Sn _)).

    (* Case : f = Or (Var n) (And f1' f2') : distr_or_aux (Or (Var n) f1') *)
    intros _ _ _ n _ f1' f2' _ _ .
    simpl.
    apply lt_n_S.
    apply (le_lt_trans _ (Peano.max (height f1') (height f2')) _
           (Max.le_max_l _ _) (lt_n_Sn _)).

    (* Case : f = Or (Not f0) (And f1' f2') : distr_or_aux (Or (Not f0) f2' *)
    intros _ _ _ f0 _ f1' f2' _ _.
    simpl.
    repeat apply lt_n_S.
    apply plus_lt_compat_l.
    apply (le_lt_trans _ (Peano.max (height f1') (height f2')) _
           (Max.le_max_r _ _) (lt_n_Sn _)).

    (* Case : f = Or (Not f0) (And f1' f2') : distr_or_aux (Or (Not f0) f1' *)
    intros _ _ _ f0 _ f1' f2' _ _.
    simpl.
    repeat apply lt_n_S.
    apply plus_lt_compat_l.
    apply (le_lt_trans _ (Peano.max (height f1') (height f2')) _
           (Max.le_max_l _ _) (lt_n_Sn _)).

    (* Case : f = Or (And f1 f2') True : distr_or_aux (Or f2' True) *)
    intros _ _ _ f1' f2' _ _ _.
    simpl.
    repeat apply lt_n_S.
    repeat rewrite plus_0_r.
    apply (le_lt_trans _ (Peano.max (height f1') (height f2')) _
           (Max.le_max_r _ _) (lt_n_Sn _)).

    (* Case : f = Or (And f1 f2') True : distr_or_aux (Or f1' True) *)
    intros _ _ _ f1' f2' _ _ _.
    simpl.
    repeat apply lt_n_S.
    repeat rewrite plus_0_r.
    apply (le_lt_trans _ (Peano.max (height f1') (height f2')) _
           (Max.le_max_l _ _) (lt_n_Sn _)).

    (* Case : f = Or (And f1 f2') False : distr_or_aux (Or f2' False) *)
    intros _ _ _ f1' f2' _ _ _.
    simpl.
    repeat apply lt_n_S.
    repeat rewrite plus_0_r.
    apply (le_lt_trans _ (Peano.max (height f1') (height f2')) _
           (Max.le_max_r _ _) (lt_n_Sn _)).

    (* Case : f = Or (And f1 f2') False : distr_or_aux (Or f1' False) *)
    intros _ _ _ f1' f2' _ _ _.
    simpl.
    repeat apply lt_n_S.
    repeat rewrite plus_0_r.
    apply (le_lt_trans _ (Peano.max (height f1') (height f2')) _
           (Max.le_max_l _ _) (lt_n_Sn _)).

    (* Case : f = Or (And f1 f2') (Var n) : distr_or_aux (Or f2' (Var n)) *)
    intros _ _ _ f1' f2' _ n _ _.
    simpl.
    repeat apply lt_n_S.
    repeat rewrite plus_0_r.
    apply (le_lt_trans _ (Peano.max (height f1') (height f2')) _
           (Max.le_max_r _ _) (lt_n_Sn _)).

    (* Case : f = Or (And f1 f2') (Var n) : distr_or_aux (Or f1' (Var n)) *)
    intros _ _ _ f1' f2' _ n _ _.
    simpl.
    repeat apply lt_n_S.
    repeat rewrite plus_0_r.
    apply (le_lt_trans _ (Peano.max (height f1') (height f2')) _
           (Max.le_max_l _ _) (lt_n_Sn _)).

    (* Case : f = Or (And f1' f2') (Not f0) : distr_or_aux (Or f2' (Not f0)) *)
    intros _ _ _ f1' f2' _ f0 _ _.
    simpl.
    apply lt_n_S.
    apply le_lt_n_Sm.
    apply (plus_le_compat_r _ _ _ (Max.le_max_r _ _)).

    (* Case : f = Or (And f1' f2') (Not f0) : distr_or_aux (Or f1' (Not f0)) *)
    intros _ _ _ f1' f2' _ f0 _ _.
    simpl.
    apply lt_n_S.
    apply le_lt_n_Sm.
    apply (plus_le_compat_r _ _ _ (Max.le_max_l _ _)).

    (* Case : f = Or (And f1' f2') (And f1'0 f2'0) : distr_or_aux (Or (And f1' f2') f2'0) *)
    intros _ _ _ f1' f2' _  f1'0 f2'0 _ _.
    simpl.
    repeat apply lt_n_S.
    apply plus_lt_compat_l.
    apply (le_lt_n_Sm _ _ (Max.le_max_r _ _)).

    (* Case : f = Or (And f1' f2') (And f1'0 f2'0) : distr_or_aux (Or (And f1' f2') f1'0) *)
    intros _ _ _ f1' f2' _  f1'0 f2'0 _ _.
    simpl.
    repeat apply lt_n_S.
    apply plus_lt_compat_l.
    apply (le_lt_n_Sm _ _ (Max.le_max_l _ _)).

    (* Case : f = Or (And f1' f2') (Or f0 f3) : distr_or_aux (Or f2' (Or f0 f3)) *)
    intros _ _ _ f1' f2' _ f0 f3 _ _.
    simpl.
    apply lt_n_S.
    apply le_lt_n_Sm.
    apply plus_le_compat_r.
    apply Max.le_max_r.

    (* Case : f = Or (And f1' f2') (Or f0 f3) : distr_or_aux (Or f1' (Or f0 f3)) *)
    intros _ _ _ f1' f2' _ f0 f3 _ _.
    simpl.
    apply lt_n_S.
    apply le_lt_n_Sm.
    apply plus_le_compat_r.
    apply Max.le_max_l.

    (* Case : f = Or (And f1' f2') (Imply f0 f3) : distr_or_aux (Or f2' (Imply f0 f3)) *)
    intros _ _ _ f1' f2' _ f0 f3 _ _.
    simpl.
    apply lt_n_S.
    apply le_lt_n_Sm.
    apply plus_le_compat_r.
    apply Max.le_max_r.

    (* Case : f = Or (And f1' f2') (Imply f0 f3) : distr_or_aux (Or f1' (Imply f0 f3)) *)
    intros _ _ _ f1' f2' _ f0 f3 _ _.
    simpl.
    apply lt_n_S.
    apply le_lt_n_Sm.
    apply plus_le_compat_r.
    apply Max.le_max_l.

    (* Case : f = Or (Or f0 f3) (And f1' f2') : distr_or_aux (Or (Or f0 f3) f2') *)
    intros _ _ _ f0 f3 _ f1' f2' _ _.
    simpl.
    repeat apply lt_n_S.
    apply plus_lt_compat_l.
    apply (le_lt_n_Sm _ _ (Max.le_max_r _ _)).

    (* Case : f = Or (Or f0 f3) (And f1' f2') : distr_or_aux (Or (Or f0 f3) f1') *)
    intros _ _ _ f0 f3 _ f1' f2' _ _.
    simpl.
    repeat apply lt_n_S.
    apply plus_lt_compat_l.
    apply (le_lt_n_Sm _ _ (Max.le_max_l _ _)).

    (* Case : f = Or (Imply f0 f3) (And f1' f2') : distr_or_aux (Or (Imply f0 f3) f2' *)
    intros _ _ _ f0 f3 _ f1' f2' _ _.
    simpl.
    repeat apply lt_n_S.
    apply plus_lt_compat_l.
    apply (le_lt_n_Sm _ _ (Max.le_max_r _ _)).

    (* Case : f = Or (Imply f0 f3) (And f1' f2') : distr_or_aux (Or (Imply f0 f3) f1') *)
    intros _ _ _ f0 f3 _ f1' f2' _ _.
    simpl.
    repeat apply lt_n_S.
    apply plus_lt_compat_l.
    apply (le_lt_n_Sm _ _ (Max.le_max_l _ _)).
Defined.

Fixpoint distr_or (f : formula) : formula :=
    match f with
    | True   => True
    | False  => False
    | Var n  => Var n
    | Not f1 => Not (distr_or f1)
    | And f1 f2 => And (distr_or f1) (distr_or f2)
    | Or  f1 f2 => match f1, f2 with
        | f1, And f1' f2' =>
            And (distr_or_aux (Or f1 f1')) (distr_or_aux (Or f1 f2'))
        | And f1' f2', f2 =>
            And (distr_or_aux (Or f1' f2)) (distr_or_aux (Or f2' f2))
        | f1, f2 => Or (distr_or f1) (distr_or f2)
        end
    | Imply f1 f2 => Imply (distr_or f1) (distr_or f2)
    end.