open list

/-
#1. You are to implement a function
that takes a "predicate" function and a
list and that returns a new list: namely
a list that contains all and only those
elements of the first list for which the
given predicate function returns true.

We start by giving you two predicate
functions. Each takes an argument, n,
of type ℕ. The first returns true if
and only if n < 5. The second returns
true if and only if n is even.
-/

/-
Here's a "predicate" function that
takes a natural number as an argument
and returns true if the number is less
than 5 otherwise it returns false.
-/
def lt_5 (n : ℕ) : bool :=
    n < 5

-- example cases
#eval lt_5 2
#eval lt_5 6

/-
Here's another predicate function, one
that returns true if a given nat is even,
and false otherwise. Note that we have
defined this function recursively. Zero
is even, one is not, and (2 + n') is if
and only if n' is.

You can think of a predicate function
as a function that answers the question,
does some value (here a nat) have some
property? The properties in these two
cases are (1) the property of being less
than 5, and (2) the property of being
even.
-/
def evenb : ℕ → bool
| 0 := tt
| 1 := ff
| (n' + 2) := evenb n'

#eval evenb 0
#eval evenb 3
#eval evenb 4
#eval evenb 5

/-
We call the function you are going to
implement a "filter" function, because
it takes a list and returns a "filtered"
version of the list. Call you function
"myfilter".

If you filter an empty list you always
get an empty list as a result. If you
filter a non-empty list, l = (cons h t),
the returned list has h at its head if
and only if the predicate function applied
to h returns true, otherwise the returned
value is just the filtered version of t.

A. [15 points]

Your  task is to complete an incomplete
version of the definition of the myfilter
function. We use a Lean construct new to
you: the if ... then ... else. It works
as you would expect from your work with
other programming languages. Replace the
underscores to complete the definition.
-/

def myfilter : (ℕ → bool) → list ℕ → list ℕ
| p list.nil := list.nil
| p (cons h t) :=
    if p h
    then (list.cons h (myfilter p t))
    else myfilter p t

/-
Here's the definition of a simple list.
-/

def a_list := [0,1,2,3,4,5,6,7,8,9]

/-
B. [10 points]

Replace the underscores in the first two
eval commands below as follows. Replace
the first one with an expression in which
myfilter is used to compute a new list that
contains the numbers in a_list that are
less than 5. Replace the second one with
an expression in which mfilter is used to
compute a list containing even elements of
a_list. You may use the predicate functions
we defined above.

Replace the third underscore with a similar
expression but where you use a λ expression
to specify a predicate function that takes
a nat, n, and returns tt if n is equal to
three and false otherwise. Hint: n=3 is
an expression that will return the desired
bool.
-/

#eval myfilter lt_5 a_list
#eval myfilter evenb a_list
#eval myfilter (λ n:ℕ , n=3) a_list

/-
#2.

Here's a function that takes a function, f,
from ℕ to ℕ, and a value, n, of type ℕ, and
that returns the value that is obtained by
simply applying f to n.
-/

def f_apply : (ℕ → ℕ) → ℕ →  ℕ
| f n := (f n)
-- examples of its use
#eval f_apply nat.succ 3
#eval f_apply (λ n : ℕ, n * n) 3

/-
A. [10 points]

Write a function, f_apply_2, that takes a
function, f, from ℕ to ℕ, and a value, n,
of type ℕ, and that returns (read this
carefully) the value obtained by applying
f twice to n: the result of applying f to
the result of applying f to n. For example,
if f is the function that squares its nat
argument, then (f_apply_2 f 3) returns 81,
as f applied to 3 is 9 and f applied to 9
is 81.
-/

-- Your answer here

def f_apply_2 : (ℕ → ℕ) → ℕ → ℕ
| f n := f (f n)

/-
B. [10 points]

Write a function f_apply_k that takes (1)
a function, f, of type ℕ to ℕ, (2) a value,
n, of type ℕ, and (3) a value, k of type ℕ,
and that returns the result of applying f
to n k times.

Note that f_apply applies f to n once and
ff_apply applies f to n twice. Your job is
to write a function that is general in the
sense that you specify by a parameter, k,
how many times to apply f.

Hint 1: Use recursion. Note: The result of
applying any function, f, to n, zero times
is just n.
-/

-- Answer here
def f_apply_k : (ℕ → ℕ ) → ℕ → ℕ → ℕ
| f n 0 := n
| f n (k+1) :=  f (f_apply_k f n k)

/-
Use #eval to evaluate an expression in
which the squaring function, expressed
as a λ expression, is applied to 3 two
times. You should be able to confirm that
you get the same answer given by using
the f_apply_2 function in the example above.
-/

-- Answer here
#eval f_apply_k (λ n:ℕ, n*n) 3 2
#eval f_apply_2 (λ n:ℕ, n*n) 3

/-
C. [Extra Credit]

Write a function f_apply_k_fun that takes (1)
a function, f, of type ℕ to ℕ, (2) a value,
k of type ℕ, and that returns a function that,
when applied to a natural number, n, returns
the result of applying f to n k times.
-/
def f_apply_k_fun : (ℕ → ℕ ) → ℕ  → (ℕ → ℕ)
| f k :=  (λ n:ℕ, (f_apply_k f n k))

/-
#3: [15 points]

Write a function, mcompose, that
takes two functions, g and f (in that
order), each of type ℕ → ℕ, and that
returns *a function* that, when applied
to an argument, n, of type ℕ, returns
the result of applying g to the result
of applying f to n.
-/

-- Answer here
def mcompose : (ℕ → ℕ ) → (ℕ → ℕ) → (ℕ → ℕ)
| f g := (λ n:ℕ, f_apply f (f_apply g n))

/-
#4. Higher-Order Functions

4A. [10 points] Provide an implementatation of
a function, map_pred that takes as its arguments
(1) a predicate function of type ℕ → bool, (2) a
list of natural numbers (of type "list nat"),
and that returns a list in which each ℕ value,
n,in the argument list is replaced by true (tt)
if the predicate returns true for a, otherwise
false (ff).

For example, if the predicate function returns
true if and only if its argument is zero, then
applying map to this function and to the list
[0,1,2,0,1,0] must return [tt,ff,ff,tt,ff,tt].

Test your code by using #eval or #reduce to evaluate
an expression in which map_pred is applied to
such an "is_zero" predicate function and to the
list 0,1,2,0,1,0]. Express the predicate function
as a lambda abstraction within the #eval command.

NOTE: You will have to use list.nil and list.cons
to refer to the nil and cons constructors of the
library-provided list data type, as you already
have definitions for list and cons in the current
namespace.
-/

-- Answer here
def map_pred : (ℕ → bool) → list ℕ → list bool
| f list.nil := list.nil
|f (list.cons h t) := list.cons (f h) (map_pred f t)

#eval map_pred (λ n:ℕ, n=0) [0,1,3,0,1,0]

/-
4B. [10 points] Implement a function, reduce_or,
that takes as its argument a list of Boolean values
and that reduces the list to a single Boolean value:
tt if there is at least one true value in the list,
otherwise ff. Note: the Lean libraries provide the
function "bor" to compute "b1 or b2", where b1 and
b2 are Booleans. We recommend that you include
tests of your solution.
-/

-- example
#reduce bor tt tt

-- Answer here
def reduce_or : list bool → bool
| list.nil := ff
| (list.cons h t) := bor h (reduce_or t)

#reduce reduce_or []
#reduce reduce_or [tt,tt, tt]
#reduce reduce_or [ff, tt, tt]
#reduce reduce_or [tt, ff, tt]
#reduce reduce_or [tt, ff, ff]
#reduce reduce_or [ff, ff, ff]
/-
4C. [10points] Implement a function, reduce_and,
that takes as its argument a list of Boolean values
and that reduces the list to a single Boolean value:
tt if every value in the list is true, otherwise ff.
-/

-- Note: band implements the Boolean "and" function
#reduce band tt tt

-- Answer here
def reduce_and : list bool → bool
| list.nil := ff
| [tt] := tt
| (list.cons h t) := band h (reduce_and t)

#reduce reduce_and []
#reduce reduce_and [tt, tt, tt]
#reduce reduce_and [ff, tt, tt]
#reduce reduce_and [tt, ff, tt]
#reduce reduce_and [tt, ff, ff]
#reduce reduce_and [ff, ff, ff]

/-
4D. [10 points] Define a function, all_zero, that
takes a list of nat values and returns true if and
only if they are all zero. Express your answer using
map and reduce functions that you have previously
defined above. Again we recommend that you test your
solution.
-/

-- Answer here (replace the _'s as needed)

def all_zero : list nat → bool
| list.nil := ff
| (list.cons h t) := reduce_and (map_pred (λ n:ℕ, n=0) (list.cons h t))

/-
Some tests
-/
#reduce all_zero []
#reduce all_zero [0,0,0,0]
#reduce all_zero [1,0,0,0]
#reduce all_zero [0,1,0,0]
#reduce all_zero [1,0,0,1]


-- Define a predicate `sorted` that takes a list of natural numbers and returns a proposition
-- that is true if the list is sorted in non-decreasing order
def sorted : list ℕ → Prop
| [] := true -- An empty list is always sorted
| [x] := true -- A list with a single element is always sorted
| (x :: y :: rest) := x ≤ y ∧ sorted (y :: rest) -- A list with two or more elements is sorted if the first two elements are in non-decreasing order and the remaining elements are also sorted

-- The following code defines and verifies the correctness of an insertion sort algorithm
-- that uses the `sorted` predicate to ensure that the output is always sorted

-- Define a function `insert` that takes a natural number `x` and a sorted list of natural numbers
-- `ys` and returns a new sorted list with `x` inserted in the correct position
def insert (x : ℕ) : list ℕ → list ℕ
| [] := [x] -- If `ys` is empty, `x` is the only element and is inserted at the front of the list
| (y :: ys) := if x ≤ y then x :: y :: ys else y :: insert ys -- If `ys` is non-empty, compare `x` with the first element `y` and recursively insert `x` into the remaining list `ys`

-- Define a function `insertion_sort` that takes a list of natural numbers and returns a sorted list
-- using the `insert` function to repeatedly insert each element into the correct position
def insertion_sort : list ℕ → list ℕ
| [] := [] -- An empty list is already sorted
| (x :: xs) := insert x (insertion_sort xs) -- For a non-empty list, insert the first element `x` into the sorted list `insertion_sort xs`

-- Define a theorem `insertion_sort_correct` that states that `insertion_sort` always returns a sorted list
theorem insertion_sort_correct (xs : list ℕ) : sorted (insertion_sort xs) :=
list.rec_on xs -- Perform structural induction on `xs`
  (show sorted (insertion_sort []), by simp [insertion_sort]) -- The base case is an empty list, which is already sorted
  (assume x xs ih,
    have h₁ : sorted (insertion_sort xs), from ih, -- Assume that `insertion_sort xs` is sorted
    have h₂ : sorted (insert x (insertion_sort xs)), from
      match insertion_sort xs with
      | [] := by simp [insertion_sort, sorted] -- If `xs` is empty, `insertion_sort xs` is empty and `x` is the only element, which is trivially sorted
      | (y :: ys) := by simp [insertion_sort, sorted] at h₁ ⊢; -- If `xs` is non-empty, use the induction hypothesis to show that `insertion_sort xs` is sorted
        exact ⟨le_total x y, h₁⟩ -- Then use the `sorted` predicate to show that `x` is inserted into the correct position and the resulting list is sorted
      end,
    show sorted (insertion_sort (x :: xs)), by simp [insertion_sort, sorted] at h₂ ⊢; exact h₂) -- Finally, use the `sorted` predicate to show that `insertion_sort (x :: xs)` is sorted by applying the `insert` function to the first element `x` and the sorted list `


import .love01_definitions_and_statements_demo

/-! # LoVe Exercise 1: Definitions and Statements

Replace the placeholders (e.g., `:= sorry`) with your solutions. -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/-! ## Question 1: Truncated Subtraction

1.1. Define the function `sub` that implements truncated subtraction on natural
numbers by recursion. "Truncated" means that results that mathematically would
be negative are represented by 0. For example:

    `sub 7 2 = 5`
    `sub 2 7 = 0` -/

def sub : ℕ → ℕ → ℕ :=
    | 0, m => 0
    | n, 0 => n
    | n+1, m+1 =>  sub n m

/-! 1.2. Check that your function works as expected. -/

#eval sub 0 0   -- expected: 0
#eval sub 0 1   -- expected: 0
#eval sub 0 7   -- expected: 0
#eval sub 1 0   -- expected: 1
#eval sub 1 1   -- expected: 0
#eval sub 3 0   -- expected: 3
#eval sub 2 7   -- expected: 0
#eval sub 3 1   -- expected: 2
#eval sub 3 3   -- expected: 0
#eval sub 3 7   -- expected: 0
#eval sub 7 2   -- expected: 5

/-! ## Question 2: Arithmetic Expressions

Consider the type `aexp` from the lecture and the function `eval` that
computes the value of an expression. You will find the definitions in the file
`love01_definitions_and_statements_demo.lean`. One way to find them quickly is
to

1. hold the Control (on Linux and Windows) or Command (on macOS) key pressed;
2. move the cursor to the identifier `aexp` or `eval`;
3. click the identifier. -/

#check aexp
#check eval

/-! 2.1. Test that `eval` behaves as expected. Make sure to exercise each
constructor at least once. You can use the following environment in your tests.
What happens if you divide by zero?

Make sure to use `#eval`. For technical reasons, `#reduce` does not work well
here. Note that `#eval` (Lean's evaluation command) and `eval` (our evaluation
function on `aexp`) are unrelated. -/

def some_env : string → ℤ
| "x" := 3
| "y" := 17
| _   := 201

#eval eval some_env (aexp.var "x")   -- expected: 3
-- invoke `#eval` here

/-! 2.2. The following function simplifies arithmetic expressions involving
addition. It simplifies `0 + e` and `e + 0` to `e`. Complete the definition so
that it also simplifies expressions involving the other three binary
operators. -/

def simplify : aexp → aexp
| (aexp.add (aexp.num 0) e₂) := simplify e₂
| (aexp.add e₁ (aexp.num 0)) := simplify e₁
-- insert the missing cases here
-- catch-all cases below
| (aexp.num i)               := aexp.num i
| (aexp.var x)               := aexp.var x
| (aexp.add e₁ e₂)           := aexp.add (simplify e₁) (simplify e₂)
| (aexp.sub e₁ e₂)           := aexp.sub (simplify e₁) (simplify e₂)
| (aexp.mul e₁ e₂)           := aexp.mul (simplify e₁) (simplify e₂)
| (aexp.div e₁ e₂)           := aexp.div (simplify e₁) (simplify e₂)

/-! 2.3. Is the `simplify` function correct? In fact, what would it mean for it
to be correct or not? Intuitively, for `simplify` to be correct, it must
return an arithmetic expression that yields the same numeric value when
evaluated as the original expression.

Given an environment `env` and an expression `e`, state (without proving it)
the property that the value of `e` after simplification is the same as the
value of `e` before. -/

lemma simplify_correct (env : string → ℤ) (e : aexp) :
  true :=   -- replace `true` by your lemma statement
sorry

/-! ## Question 3: λ-Terms

3.1. Complete the following definitions, by replacing the `sorry` markers by
terms of the expected type.

Hint: A procedure for doing so systematically is described in Section 1.1.4 of
the Hitchhiker's Guide. As explained there, you can use `_` as a placeholder
while constructing a term. By hovering over `_`, you will see the current
logical context. -/

def I : α → α :=
λa, a

def K : α → β → α :=
λa b, a

def C : (α → β → γ) → β → α → γ :=
sorry

def proj_1st : α → α → α :=
sorry

/-! Please give a different answer than for `proj_1st`. -/

def proj_2nd : α → α → α :=
sorry

def some_nonsense : (α → β → γ) → α → (α → γ) → β → γ :=
sorry

/-! 3.2. Show the typing derivation for your definition of `C` above, on paper
or using ASCII or Unicode art. You might find the characters `–` (to draw
horizontal bars) and `⊢` useful. -/

-- write your solution in a comment here or on paper

end LoVe


open list

/-
#1. You are to implement a function
that takes a "predicate" function and a
list and that returns a new list: namely
a list that contains all and only those
elements of the first list for which the
given predicate function returns true.

We start by giving you two predicate
functions. Each takes an argument, n,
of type ℕ. The first returns true if
and only if n < 5. The second returns
true if and only if n is even.
-/

/-
Here's a "predicate" function that
takes a natural number as an argument
and returns true if the number is less
than 5 otherwise it returns false.
-/
def lt_5 (n : ℕ) : bool :=
    n < 5

-- example cases
#eval lt_5 2
#eval lt_5 6

/-
Here's another predicate function, one
that returns true if a given nat is even,
and false otherwise. Note that we have
defined this function recursively. Zero
is even, one is not, and (2 + n') is if
and only if n' is.

You can think of a predicate function
as a function that answers the question,
does some value (here a nat) have some
property? The properties in these two
cases are (1) the property of being less
than 5, and (2) the property of being
even.
-/
def evenb : ℕ → bool
| 0 := tt
| 1 := ff
| (n' + 2) := evenb n'

#eval evenb 0
#eval evenb 3
#eval evenb 4
#eval evenb 5

/-
We call the function you are going to
implement a "filter" function, because
it takes a list and returns a "filtered"
version of the list. Call you function
"myfilter".

If you filter an empty list you always
get an empty list as a result. If you
filter a non-empty list, l = (cons h t),
the returned list has h at its head if
and only if the predicate function applied
to h returns true, otherwise the returned
value is just the filtered version of t.

A. [15 points]

Your  task is to complete an incomplete
version of the definition of the myfilter
function. We use a Lean construct new to
you: the if ... then ... else. It works
as you would expect from your work with
other programming languages. Replace the
underscores to complete the definition.
-/

def myfylter : (ℕ → bool) →  (list ℕ) →  ( list ℕ)
| p [] := []
| p (cons h t) :=
    if p h = tt
    then (cons (h) (myfylter p t))
    else myfylter p t

/-
Here's the definition of a simple list.
-/

def a_list := [0,1,2,3,4,5,6,7,8,9]

/-
B. [10 points]

Replace the underscores in the first two
eval commands below as follows.
(1) Replace the first one with an expression in which
myfilter is used to compute a new list that
contains the numbers in a_list that are
less than 5.

(2) Replace the second one with an expression in which myfilter is used to
compute a list containing even elements of
a_list. You may use the predicate functions
we defined above.

(3) Replace the third underscore with a similar
expression but where you use a λ expression
to specify a predicate function that takes
a nat, n, and returns tt if n is equal to
three and false otherwise. Hint: n=3 is
an expression that will return the desired
bool.
-/

#eval (myfylter (lt_5)(a_list))
#eval (myfylter (evenb)(a_list))
#eval (myfylter (λ n:nat, if n = 3 then tt else ff) (a_list))

/-
#2.

Here's a function that takes a function, f,
from ℕ to ℕ, and a value, n, of type ℕ, and
that returns the value that is obtained by
simply applying f to n.
-/

def f_apply : (ℕ → ℕ) → ℕ → ℕ
| f n := (f n)

-- examples of its use
#eval f_apply nat.succ 3
#eval f_apply (λ n : ℕ, n * n) 3

/-
A. [10 points]

Write a function, f_apply_2, that takes a
function, f, from ℕ to ℕ, and a value, n,
of type ℕ, and that returns (read this
carefully) the value obtained by applying
f twice to n: the result of applying f to
the result of applying f to n. For example,
if f is the function that squares its nat
argument, then (f_apply_2 f 3) returns 81,
as f applied to 3 is 9 and f applied to 9
is 81.
-/

-- Your answer here

def f_apply_2 : (ℕ → ℕ) → ℕ → ℕ
| f n := f(f n)

/-
B. [10 points]

Write a function f_apply_k that takes (1)
a function, f, of type ℕ to ℕ, (2) a value,
n, of type ℕ, and (3) a value, k of type ℕ,
and that returns the result of applying f
to n k times.

Note that f_apply applies f to n once and
f_apply_2 applies f to n twice. Your job is
to write a function that is general in the
sense that you specify by a parameter, k,
how many times to apply f.

Hint 1: Use recursion. Note: The result of
applying any function, f, to n, zero times
is just n.
-/

-- Answer here

/-
k = 0
k = r + 1
keep on doing until r is 0

f n = f ( f_apply_k(f,l,r))
-/

def f_apply_k : (ℕ → ℕ ) → ℕ → ℕ → ℕ
|f n 0 := n
|f n (nat.succ k) := f (f_apply_k f n k) -- stop when k = 0

#eval f_apply_k (λ n : ℕ, n * n) 3 2

#eval nat.pred 5
/-
Use #eval to evaluate an expression in
which the squaring function, expressed
as a λ expression, is applied to 3 two
times. You should be able to confirm that
you get the same answer given by using
the ff_apply function in the example above.
-/

-- Answer here

/-
C. [Extra Credit]

Write a function f_apply_k_fun that takes (1)
a function, f, of type ℕ to ℕ, (2) a value,
k of type ℕ, and that returns a function that,
when applied to a natural number, n, returns
the result of applying f to n k times.
-/

/-
#3: [15 points]

Write a function, mcompose, that
takes two functions, g and f (in that
order), each of type ℕ → ℕ, and that
returns *a function* that, when applied
to an argument, n, of type ℕ,

returns the result of applying g to the result
of applying f to n.
-/

-- Answer here

def mcompose : (ℕ → ℕ ) →  ( ℕ → ℕ ) →  (ℕ → ℕ ) :=
λ g f,
λ n:nat ,g (f n)

/-
#4. Higher-Order Functions

4A. [10 points] Provide an implementatation of
a function, map_pred that takes as its arguments
(1) a predicate function of type ℕ → bool, (2) a
list of natural numbers (of type "list nat"),
and that returns a list in which each ℕ value,
n,in the argument list is replaced by true (tt)
if the predicate returns true for a, otherwise
false (ff).

For example, if the predicate function returns
true if and only if its argument is zero, then
applying map to this function and to the list
[0,1,2,0,1,0] must return [tt,ff,ff,tt,ff,tt].

Test your code by using #eval or #reduce to evaluate
an expression in which map_pred is applied to
such an "is_zero" predicate function and to the
list 0,1,2,0,1,0]. Express the predicate function
as a lambda abstraction within the #eval command.

NOTE: You will have to use list.nil and list.cons
to refer to the nil and cons constructors of the
library-provided list data type, as you already
have definitions for list and cons in the current
namespace.
-/

-- Answer here

def map_pred : (ℕ → bool) → list nat → list bool
|f list.nil := list.nil
|f(list.cons h t) := list.cons (f h) ( map_pred f t)

def is_zero : ℕ → bool
| 0 := tt
| nat := ff

#eval map_pred is_zero [0,1,2,1,0]

/-
4B. [10 points] Implement a function, reduce_or,
that takes as its argument a list of Boolean values
and that reduces the list to a single Boolean value:
tt if there is at least one true value in the list,
otherwise ff. Note: the Lean libraries provide the
function "bor" to compute "b1 or b2", where b1 and
b2 are Booleans. We recommend that you include
tests of your solution.
-/

-- example
#reduce bor tt tt

-- Answer here
def reduce_or: list bool → bool
| [] := ff
| (cons h t) := bor h (reduce_or t)

/-
4C. [10points] Implement a function, reduce_and,
that takes as its argument a list of Boolean values
and that reduces the list to a single Boolean value:
tt if every value in the list is true, otherwise ff.
-/

-- Note: band implements the Boolean "and" function
#reduce band tt tt

-- Answer here
def reduce_and: list bool → bool
| []  := ff
|(cons h t) := band (h) (reduce_and(t))

/-
4D. [10 points] Define a function, all_zero, that
takes a list of nat values and returns true if and
only if they are all zero. Express your answer using
map and reduce functions that you have previously
defined above. Again we recommend that you test your
solution.
-/

-- Answer here (replace the _'s as needed)

def all_zero : list nat → bool
| [] := tt
| (cons h t):=
    if is_zero h
         then band tt (all_zero t)
    else ff
/-
Some tests
-/
#reduce all_zero []
#reduce all_zero [0,0,0,0]
#reduce all_zero [1,0,0,0]
#reduce all_zero [0,1,0,0]
#reduce all_zero [1,0,0,1]


-- we can say let f be the function which maps any natural number x to x + 5.
-- The expression λ x : nat, x + 5 is just a symbolic representation of the right-hand side of this assignment.
#check fun x : nat, x + 5
#check λ x : nat, x + 5

-- Here are some more abstract examples
constants α β  : Type
constants a1 a2 : α
constants b1 b2 : β

constant f : α → α
constant g : α → β
constant h : α → β → α
constant p : α → α → bool

#check fun x : α, f x
#check λ x : α , f x
#check λ x : α , f (f x)
#check λ x : α , h x b1
#check λ y : β , h a1 y
#check λ x : α , p (f (f x)) (h (f a1) b2)
#check λ x : α , λ y : β , h (f x) y
#check λ (x : α) (y : β), h (f x ) y
#check λ x y, h (f x) y

-- Some mathematically common examples of operations of functions can be described in terms of lambda abstraction:
constants α' β' γ : Type
constant f' : α' → β'
constant g' : β' → γ
constant b  : β'

#check λ x : α', x -- denotes the identity function on α'
#check λ x : α', b -- denotes the constant fn that always returns b
#check λ x : α', g' (f' x) -- denotes the composition of f and g
#check λ x , g' (f' x) -- We can omit the type declaration as Lean can infer

-- We can abstract over any of the constants in the previous definitions:
#check λ b : β', λ x : α', x
#check λ (b: β') (x: α'), x
#check λ (g' : β' → γ) (f' : α' → β') (x : α'), g' (f' x)

-- Lean let's us combine lambdas, so the second example is equivalent to the first.
-- We can even abstract over the type
#check λ (α β : Type*) (b : β) (x : α), x
#check λ (α β γ : Type*) (g: β → γ) (f : α → β) (x : α), g (f x)
-- The last expression, e.g denotes the fn that takes three types α β γ and two
-- fns g : β → γ and f : α → β and returns the composition of g and f

-- notice the types of the following expressions(P.S: bound variables)
constants α'' β'' γ'' : Type
constant f'' : α'' → β''
constant g'' : β'' → γ''
constant h'' : α'' → α''
constants (a' : α'') (b' : β'')

#check (λ x : α'', x) a'
#check (λ x : α'' , b') a'
#check (λ x : α'', b') (h'' a')
#check (λ x : α'', g'' (f'' x)) (h'' (h'' a'))

#check (λ (v: β'' → γ'') (u: α'' → β'') x, v (u x)) g'' f'' a'

#check (λ (Q R S: Type*) (v: R → S) (u : Q → R) (x: Q),
       v (u x)) α'' β'' γ'' g'' f'' a'

-- As expected, the expression (λ x : α, x) a has type α
-- In fact, more should be true: applying the expression (λ x : α, x) to a should “return” the value a

constants α''' β''' γ''' : Type
constant f''' : α''' → β'''
constant g''' : β''' → γ'''
constant h''' : α''' → α'''
constants (a'' : α''') (b'' : β''')

#reduce (λ x : α''', x) a''
#reduce (λ x : α''', b'') a''
#reduce (λ x : α''', b'') (h''' a'')
#reduce (λ x : α''', g''' (f''' x)) a''

#reduce (λ (v: β''' → γ''') (u: α''' → β''') x, v (u x)) g''' f''' a''

#reduce (λ (Q R S: Type*) (v: R → S) (u : Q → R) (x: Q),
       v (u x)) α''' β''' γ''' g''' f''' a''

-- The command #reduce tells Lean to evaluate an expression by reducing it to normal
-- form, which is to say, carrying out all computational reductions that are
-- sanctioned by the underlying logic
-- The process of simplifying an expression (λ x, t)s to t[s/x]
-- that is, t with s substituted for the variable x – is known as beta reduction
-- and two terms that beta reduce to a common term are called beta equivalent

-- other forms of reduction
constants m n : nat
constant b''' : bool

#print "reduce pairs"
#reduce (m,n).1
#reduce (m,n).2

#print "reducing boolean expressions"
#reduce tt && ff
#reduce ff && b'''
#reduce b''' && ff

#print "reducing arithmetic expressions"
#reduce n + 0
#reduce n + 2
#reduce 2 + 3

-- Important!!!: every term has a computational behavior, and supports a
-- notion of reduction, or normalization
-- In principle, two terms that reduce to the same value are called definitionally equal


/-
Read, and if you have already read, then re-read, the
chapters in the notes on proofs of disjunctions and negations.
We have added some new material, especially under negation
elimination.

In proofs of bi-implications, use comments to mark the start of
the proofs of the implications in each direction. Label one as
"forward" the other other as "backward."

The collaboration policy for this homework is "no collaboration
allowed." You may study and discuss the underlying concepts with
anyone.

You may provide proofs in the style of your choice: term-style,
tactic style, or mixed. Yes, you can using tactic scripts within
terms and terms within tactic scripts. You may use any tactics
you know of. As a courtesy, we provide begin/end pairs, in case
you should want to use them. Otherwise you may delete them.
-/

/-
1. 15 points
-/
example : ∀ (P Q : Prop), P ∧ Q → P ∨ Q :=
begin
end

/-
2. 15 points
-/
example :
    ∀ (P Q R : Prop), (P ∨ Q) → (Q ∨ R) → ¬ Q → (P ∧ R) :=
begin
end

/-
3. 15 points
-/
example :
    ∀ (P Q R : Prop), P ∧ (Q ∨ R) ↔ (P ∧ Q) ∨ (P ∧ R) :=
begin
end

/-
4. 10 points
-/

example : ∀ (P Q R : Prop), P → Q → R → ¬ Q → (Q ∨ ¬ Q) :=
begin
end

open classical      -- hint: you can now use em easily

/-
4a. 5 points. Write *your own* proof of this conjecture.
-/

example : ∀ (P Q : Prop), ¬ (P ∨ Q) ↔ ¬ P ∧ ¬ Q :=
begin
end

/-
4b. 5 points. Is this theorem classically true in neither, one, or
both directions. Explain your answer in relation to your proof.

Answer:
-/

/-
5. 5 points. Write *your own proof* of this conjecture.
-/
example : ∀ (P Q : Prop), ¬ (P ∧ Q) ↔ (¬ P ∨ ¬ Q) :=
begin
end

/-
6. 10 points
-/

example : ∀ (P : Prop), (¬ ¬ P → P) ↔ (P ∨ ¬ P) :=
begin
end

/-
7. 5 points

Tranlate the preceding proposition into English,
referring explicitly to the principles of negation
elimination and excluded middle. Write your sentence
here:

-/

/-
8. [10 points]
-/

example :
    (∀ ( P Q : Prop ), (P → Q) ↔ (¬ Q → ¬ P)) →
        ∀ (Raining Wet : Prop), (¬ Wet → ¬ Raining) →
            (Raining → Wet) :=
begin
end

/-
9. [5 points]

What is the name of the principle expressed by the
premise, (P → Q) ↔ (¬ Q → ¬ P)), in the preceding
problem? Answer here:

-/


-- ---------------------------------------------------------------------
-- Ejercicio 1. Realizar las siguientes acciones:
--    1. Importar la teoría de anillos.
--    2. Crear el espacio de nombres my_ring
--    3. Declarar R como una variable sobre anillos.
--    4. Declarar a y b como variables sobre R.
-- ----------------------------------------------------------------------

import algebra.ring            -- 1
namespace my_ring              -- 2
variables {R : Type*} [ring R] -- 3
variables {a b : R}            -- 4

-- ---------------------------------------------------------------------
-- Ejercicio 2. Demostrar que si
--    a + b = 0
-- entonces
--    -a = b
-- ----------------------------------------------------------------------

theorem neg_eq_of_add_eq_zero
  (h : a + b = 0)
  : -a = b :=
calc
  -a  = -a + 0       : by rw add_zero
  ... = -a + (a + b) : by rw h
  ... = b            : by rw neg_add_cancel_left

-- ---------------------------------------------------------------------
-- Ejercicio 3. Demostrar que
--     (a + b) + -b = a
-- ----------------------------------------------------------------------

-- 1ª demostración
example : (a + b) + -b = a :=
calc (a + b) + -b = a + (b + -b) : by rw add_assoc
              ... = a + 0        : by rw add_right_neg
              ... = a            : by rw add_zero

-- 2ª demostración
lemma neg_add_cancel_right : (a + b) + -b = a :=
by rw [add_assoc, add_right_neg, add_zero]

-- ---------------------------------------------------------------------
-- Ejercicio 4. Demostrar que si
--    a + b = 0
-- entonces
--    a = -b
-- ----------------------------------------------------------------------

theorem eq_neg_of_add_eq_zero
  (h : a + b = 0)
  : a = -b :=
calc
  a   = (a + b) + -b : by rw neg_add_cancel_right
  ... = 0 + -b       : by rw h
  ... = -b           : by rw zero_add

-- ---------------------------------------------------------------------
-- Ejercicio 5. Demostrar que
--    -0 = 0
-- ----------------------------------------------------------------------

theorem neg_zero : (-0 : R) = 0 :=
begin
  apply neg_eq_of_add_eq_zero,
  rw add_zero,
end

-- El desarrollo de la prueba es
--
--    R : Type u_1,
--    _inst_1 : ring R
--    ⊢ -0 = 0
-- apply neg_eq_of_add_eq_zero,
--    ⊢ 0 + 0 = 0
-- rw add_zero,
--    no goals

-- ---------------------------------------------------------------------
-- Ejercicio 6. Demostrar que
--     -(-a) = a
-- ----------------------------------------------------------------------

theorem neg_neg : -(-a) = a :=
begin
  apply neg_eq_of_add_eq_zero,
  rw add_left_neg,
end

-- El desarrollo de la prueba es
--
--    R : Type u_1,
--    _inst_1 : ring R,
--    a : R
--    ⊢ - -a = a
-- apply neg_eq_of_add_eq_zero,
--    ⊢ -a + a = 0
-- rw add_left_neg,
--    no goals

end my_ring


/- LoVe Exercise 5: Inductive Predicates -/

import .love05_inductive_predicates_demo

namespace LoVe

/- Question 1: Even and Odd -/

/- The `even` predicate is true for even numbers and false for odd numbers. -/

#check even

/- 1.1. Prove that 0, 2, 4, and 6 are even. -/

lemma even_0 : even 0 := even.zero
lemma even_2 : even 2 := even.add_two _ even_0
lemma even_4 : even 4 := even.add_two _ even_2
lemma even_6 : even 6 := even.add_two _ even_4

/- We define `odd` as the negation of `even`: -/

def odd (n : ℕ) : Prop :=
  ¬ even n

/- 1.2. Prove that 1 is odd and register this fact as a `simp` rule.

Hint: `cases` is useful to reason about hypotheses of the form `even …`. -/

@[simp] lemma odd_1 :
  odd 1 :=
by intro h; cases h

/- 1.3. Prove that 3, 5, and 7 are odd. -/

example : odd 3 := by intro h; cases h; cases h_a
example : odd 5 := by intro h; cases h; cases h_a; cases h_a_a
example : odd 7 := by intro h; cases h; cases h_a; cases h_a_a; cases h_a_a_a

/- 1.4. Complete the following proof by structural induction.

Hint: You can rely implicitly on computation for the induction step. -/

lemma even_two_times :
  ∀m : ℕ, even (2 * m)
| 0       := even.zero
| (m + 1) :=
  begin
    apply even.add_two,
    apply even_two_times
  end

/- 1.5. Complete the following proof by rule induction.

Hint: You can use the `cases` tactic (or `match … with`) to destruct an
existential quantifier and extract the witness. -/

lemma even_imp_exists_two_times :
  ∀n : ℕ, even n → ∃m, n = 2 * m
| _ even.zero            := exists.intro 0 (by simp)
| _ (even.add_two n hen) :=
  begin
    cases even_imp_exists_two_times n hen,
    use w + 1,
    rw h,
    linarith
  end

/- 1.6. Using `even_two_times` and `even_imp_exists_two_times`, prove the
following equivalence. -/

lemma even_iff_exists_two_times (n : ℕ) :
  even n ↔ ∃m, n = 2 * m :=
begin
  apply iff.intro,
  { apply even_imp_exists_two_times },
  { intro h,
    cases h,
    simp *,
    apply even_two_times }
end

/- Question 2: Binary Trees -/

/- 2.1. Prove the converse of `is_full_mirror`. You may exploit already proved
lemmas (e.g., `is_full_mirror`, `mirror_mirror`). -/

lemma mirror_is_full {α : Type} :
  ∀t : btree α, is_full (mirror t) → is_full t :=
begin
  intros t fmt,
  have fmmt : is_full (mirror (mirror t)) := is_full_mirror _ fmt,
  rw mirror_mirror at fmmt,
  assumption
end

/- 2.2. Define a function that counts the number of constructors (`empty` or
`node`) in a tree. -/

def count {α : Type} : btree α → ℕ
| empty        := 1
| (node _ l r) := count l + count r + 1

end LoVe


import algebra.big_operators.basic
import tactic.interactive tactic.ring tactic.fin_cases

/- Exercise 1
 Show that for every natural number n, there exists m with m > n

 Useful ingredients:
  nat.lt_succ_self; the tactics intro, use, apply
-/

lemma exists_greater_a : ∀ (n : ℕ), ∃ (m : ℕ), m > n :=
begin
 intro n,
 use n + 1,
 apply nat.lt_succ_self,
end

lemma exists_greater_b (n : ℕ) : ∃ m, m > n :=
 exists.intro (n + 1) n.lt_succ_self

/- Exercise 2
 Show that if we append a list to itself, then the length doubles.

 Useful ingredients:
  list.length_append, two_mul
-/

lemma double_length_a {α : Type} (l : list α) :
 (l ++ l).length = 2 * l.length :=
begin
 rw[list.length_append l l],ring
end

lemma double_length_b {α : Type} (l : list α) :
 (l ++ l).length = 2 * l.length :=
 (list.length_append l l).trans (two_mul l.length).symm

/- Exercise 3
 Show that 2 * (0 + 1 + ... + n) = n * (n + 1)

 Useful ingredients:
  finset.sum_range, finset.sum_range_succ; the tactics
  induction, rw and ring
-/

lemma range_sum_a (n : ℕ) :
 2 * ((finset.range (n + 1)).sum id) = n * (n + 1) :=
begin
 induction n with n ih,
 {refl},
 {have e : nat.succ n = n + 1 := rfl,
  rw[finset.sum_range_succ id (n + 1),mul_add,ih,id,e],
  ring,
 }
end

lemma range_sum_b : ∀ n : ℕ,
 2 * ((finset.range (n + 1)).sum id) = n * (n + 1)
| 0 := rfl
| (n + 1) := begin
 rw[finset.sum_range_succ id (n + 1),mul_add,range_sum_a n,id],
 ring,
end

/- Exercise 4
 Show that every natural number can be written in the form
 2 * m or 2 * m + 1.

 This one seems harder than it should be.

 Useful ingredients: the operations n / 2 and n % 2, the theorems
  nat.mod_add_div and nat.mod_lt, the fin_cases tactic.

 For a different approach: the functions nat.bodd and nat.div2
  and the theorem nat.bodd_add_div2
-/

lemma even_odd_a : ∀ (n : ℕ), (∃ m, n = 2 * m) ∨ (∃ m, n = 2 * m + 1)
| 0 := or.inl ⟨0,rfl⟩
| 1 := or.inr ⟨0,rfl⟩
| (n + 2) := begin
 rcases even_odd_a n with ⟨m,e⟩ | ⟨m,e⟩; rw[e],
 {left,use m + 1,ring},
 {right,use m + 1,ring},
end

lemma even_odd_b (n : ℕ) : (∃ m, n = 2 * m) ∨ (∃ m, n = 2 * m + 1) :=
begin
 let r := n % 2,
 let m := n / 2,
 have h : n = 2 * m + r := (nat.mod_add_div n 2).symm.trans (add_comm _ _),
 have r_lt_2 : r < 2 := nat.mod_lt n dec_trivial,
 let r0 : fin 2 := ⟨r,r_lt_2⟩,
 fin_cases r0,
 {left,
  use m,
  have hr : r = 0 := fin.veq_of_eq this,
  rw[hr, add_zero] at h,
  exact h,
 },{
  right,
  use m,
  have hr : r = 1 := fin.veq_of_eq this,
  rw[hr] at h,
  exact h,
 },
end

lemma even_odd_c_aux (n m : ℕ) (b : bool) (e : (cond b 1 0) + 2 * m = n) :
 (∃ m, n = 2 * m) ∨ (∃ m, n = 2 * m + 1) :=
begin
 cases b,
 {left ,exact ⟨m,(e.symm.trans (zero_add _))⟩,},
 {right,exact ⟨m,e.symm.trans (add_comm _ _)⟩,},
end

lemma even_odd_c (n : ℕ) : (∃ m, n = 2 * m) ∨ (∃ m, n = 2 * m + 1) :=
 even_odd_c_aux n n.div2 (nat.bodd n) (nat.bodd_add_div2 n)

/- Exercise 5
 Given propositions p and q, show that p → q is equivalent to
 p → (p → q)

 Useful ingredients:
  The tactics split, intros and exact.

 Alternatively, you can use the tauto tactic, which will do
 all the work for you.
-/

lemma ppq_a (p q : Prop) : (p → q) ↔ (p → p → q) :=
begin
 split,
 {intros hpq hp _,exact hpq hp,},
 {intros hppq hp,exact hppq hp hp,}
end

lemma ppq_b (p q : Prop) : (p → q) ↔ (p → p → q) :=
 ⟨λ hpq hp _,hpq hp,λ hppq hp,hppq hp hp⟩

lemma ppq_c (p q : Prop) : (p → q) ↔ (p → p → q) :=
 by tauto

/- Exercise 6
 If G is a group where a^2 = 1 for all a, then G is commutative.

 Proof: for any a and b we have a^2 = 1 and b^2 = 1 and (ab)^2 = 1.
  Write the last of these as abab = 1, multiply on the left by
  a and on the right by b to get aababb = ab.  Cancel aa = 1 and
  bb = 1 to get ba = ab.

 Useful ingredients:
  pow_two, calc, rw, mul_assoc, one_mul, mul_one
-/

lemma elem_ab_a {G : Type*} [group G] (h : ∀ a : G, a ^ 2 = 1) :
 ∀ a b : G, a * b = b * a :=
begin
 intros a b,
 let ea : a * a = 1 := (pow_two a).symm.trans (h a),
 let eb : b * b = 1 := (pow_two b).symm.trans (h b),
 let eab : (a * b) * (a * b) = 1 :=
   (pow_two (a * b)).symm.trans (h (a * b)),
 exact calc
  a * b = (a * 1) * b : by rw[mul_one]
  ... = (a * ((a * b) * (a * b))) * b : by rw[← eab]
  ... = ((a * (a * b)) * (a * b)) * b : by rw[← (mul_assoc a (a * b) (a * b))]
  ... = (b * (a * b)) * b : by rw[← (mul_assoc a a b),ea,one_mul b]
  ... = b * (a * (b * b)) : by rw[mul_assoc b (a * b) b,mul_assoc a b b]
  ... = b * a : by rw[eb,mul_one a]
end

/- Exercise 7
 Encapsulate Exercise 6 as a typeclass instance
-/

class elementary_two_group (G : Type*) extends (group G) :=
 (square_one : ∀ a : G, a ^ 2 = 1)

instance elementary_two_group_commutes
 (G : Type*) [e : elementary_two_group G] : comm_group G :=
 { mul_comm := elem_ab_a e.square_one,
   ..e
 }

/- Exercise 8
 Define an inductive type of nonempty lists
 (which we will call pos_list).

 The basic structure will be similar to the definition of
 arbitrary lists, which can be done like this:

 inductive list (T : Type)
| nil : list
| cons : T → T → list

 However, instead of the constructor `nil`, which constructs the
 empty list, we should have a constructor called `const`, which
 constructs a list of length one.

 We can then give inductive definitions of functions that give
 the first and last element of a nonempty list, the length
 (normalised so that lists with one entry count as having length
 zero), the reverse and so on.
-/

inductive pos_list (α : Type) : Type
| const : α → pos_list
| cons : α → pos_list → pos_list

namespace pos_list

variable {α : Type}

def length : pos_list α → ℕ
| (const a) := 0
| (cons a p) := p.length.succ

def to_list : pos_list α → list α
| (const a) := [a]
| (cons a p) := list.cons a (to_list p)

def head : pos_list α → α
| (const a) := a
| (cons a p) := a

def foot : pos_list α → α
| (const a) := a
| (cons a p) := p.foot

def append : pos_list α → pos_list α → pos_list α
| (const a) q := cons a q
| (cons a p) q := cons a (append p q)

lemma head_append : ∀ (p q : pos_list α),
 head (append p q) = head p
| (const a) q := rfl
| (cons a p) q := rfl

lemma foot_append : ∀ (p q : pos_list α),
 foot (append p q) = foot q
| (const a) q := rfl
| (cons a p) q := foot_append p q

lemma append_assoc : ∀ (p q r : pos_list α),
 append (append p q) r = append p (append q r)
| (const a) q r := rfl
| (cons a p) q r := by {dsimp[append],rw[append_assoc p q r],}

def reverse : pos_list α → pos_list α
| (const a) := const a
| (cons a p) := p.reverse.append (const a)

lemma head_reverse : ∀ (p : pos_list α), p.reverse.head = p.foot
| (const a) := rfl
| (cons a p) := by {dsimp[reverse,foot],rw[head_append,head_reverse p],}

lemma foot_reverse : ∀ (p : pos_list α), p.reverse.foot = p.head
| (const a) := rfl
| (cons a p) := by {dsimp[reverse,head],rw[foot_append],refl,}

lemma reverse_append : ∀ (p q : pos_list α),
 reverse (append p q) = append (reverse q) (reverse p)
| (const a) q := rfl
| (cons a p) q := by {dsimp[append,reverse],rw[reverse_append p q,append_assoc],}

lemma reverse_reverse : ∀ (p : pos_list α), p.reverse.reverse = p
| (const a) := rfl
| (cons a p) := begin
 dsimp[reverse],rw[reverse_append,reverse_reverse p],refl,
end

end pos_list

/- Exercise 9
 Set up some basic theory of graphs.  In more detail, suppose
 we have a type V (to be considered as the vertex set).  To
 make this a graph, we should have a predicate E so that E a b
 is true if there is an edge from a to b.  We are considering
 simple undirected graphs with no loops, to this relation should
 be symmetric (ie E a b implies E b a) and irreflexive (so
 E a a is always false).

 Then define a type of paths in a graph.  We can start by
 defining a predicate `is_path` on nonempty lists of vertices,
 which checks whether adjacent vertices in the list are linked
 by an edge.  Using this, we can define paths as a subtype of
 nonempty lists.

 Now define a relation `are_connected` on V, which is satisfied
 by a pair of vertices if there is a path between them.  Prove
 that this is an equivalence relation.
-/

class graph (V : Type) : Type :=
(edge : V → V → Prop)
(symm : ∀ {u v : V}, edge u v → edge v u)
(asymm : ∀ v : V, ¬ edge v v)

namespace graph

variables {V : Type} [graph V]

def is_path : pos_list V → Prop
| (pos_list.const _) := true
| (pos_list.cons v p) := (edge v p.head) ∧ is_path p

lemma is_path_append (p q : pos_list V) :
 is_path (pos_list.append p q) ↔
  is_path p ∧ edge p.foot q.head ∧ is_path q :=
begin
 induction p with a a p ih,
 {dsimp[is_path,pos_list.foot,pos_list.append,is_path],
  rw[true_and]
 },
 {dsimp[pos_list.append,pos_list.foot,is_path],
  rw[pos_list.head_append,ih,and_assoc],
 }
end

lemma is_path_reverse : ∀ {p : pos_list V},
 is_path p → is_path p.reverse
| (pos_list.const a) _ := by {dsimp[pos_list.reverse,is_path],trivial}
| (pos_list.cons a p) h := begin
 dsimp[pos_list.reverse,is_path],
 dsimp[is_path] at h,
 rw[is_path_append,pos_list.head,pos_list.foot_reverse,is_path,and_true],
 exact ⟨is_path_reverse h.right,symm h.left⟩,
end
variable (V)

def path : Type := { p : pos_list V // is_path p }

variable {V}

def path_between (a b : V) : Type :=
 { p : pos_list V // is_path p ∧ p.head = a ∧ p.foot = b }

namespace path

def head (p : path V) : V := p.val.head

def foot (p : path V) : V := p.val.foot

def length (p : path V) : ℕ := p.val.length

def cons (v : V) (p : path V) (h : edge v p.head) : path V :=
 ⟨pos_list.cons v p.val,⟨h,p.property⟩⟩

def const (v : V) : path_between v v :=
 ⟨pos_list.const v,⟨trivial,rfl,rfl⟩⟩

def reverse {u v : V} : ∀ (p : path_between u v), path_between v u
| ⟨p,⟨p_path,p_head,p_foot⟩⟩  :=
 ⟨p.reverse,⟨is_path_reverse p_path,
            (pos_list.head_reverse p).trans p_foot,
            (pos_list.foot_reverse p).trans p_head⟩⟩

def splice {u v w : V} :
 ∀ (p : path_between u v) (q : path_between v w),
     path_between u w
| ⟨p,⟨p_path,p_head,p_foot⟩⟩ ⟨pos_list.const x,⟨q_path,q_head,q_foot⟩⟩ :=
    ⟨p,begin
        change x = v at q_head,
        change x = w at q_foot,
        replace p_foot := p_foot.trans (q_head.symm.trans q_foot),
        exact ⟨p_path,p_head,p_foot⟩
       end⟩
| ⟨p,⟨p_path,p_head,p_foot⟩⟩ ⟨pos_list.cons x q,⟨q_path,q_head,q_foot⟩⟩ :=
   ⟨pos_list.append p q,
    begin
     change x = v at q_head,
     change q.foot = w at q_foot,
     rw[pos_list.head_append,p_head,pos_list.foot_append,q_foot],
     split,
     {dsimp[is_path] at q_path,rw[is_path_append,p_foot,← q_head],
      exact ⟨p_path,q_path⟩},
     {split;refl,}
    end
   ⟩

instance are_connected : setoid V := {
  r := λ a b, nonempty (path_between a b),
  iseqv := ⟨
   λ a, nonempty.intro (const a),
   λ a b ⟨p⟩, nonempty.intro (reverse p),
   λ a b c ⟨p⟩ ⟨q⟩, nonempty.intro (splice p q)
  ⟩
}

end path

end graph

/-
 Exercise 10

 Construct an equivalence `ℕ ≃ E ⊕ O`, where `E` and `O` are the
 subtypes of even and odd natural numbers.  Here `E ⊕ O` is
 alternative notation for `sum E O`, which is defined in the core
 lean library to be the disjoint union of E and O.

 Equivalences are defined in data.equiv: an equivalence from X to
 Y is a structure with four members: functions
 `to_fun : X → Y` and `inv_fun : Y → X`, and proofs that `inv_fun`
 is both left inverse and right inverse to `to_fun`.
-/

def E := {n : ℕ // n.bodd = ff}

def O := {n : ℕ // n.bodd = tt}

def bool_elim {C : Sort*} {b : bool} : b ≠ ff → b ≠ tt → C :=
 by {intros,cases b;contradiction}

def f (n : ℕ) : E ⊕ O :=
begin
 by_cases n_even : n.bodd = ff,
 {exact sum.inl ⟨n,n_even⟩},
 by_cases n_odd : n.bodd = tt,
 {exact sum.inr ⟨n,n_odd⟩},
 exact bool_elim n_even n_odd
end

def g : E ⊕ O → ℕ
| (sum.inl n) := n.val
| (sum.inr n) := n.val

lemma fg : ∀ x : E ⊕ O, f (g x) = x
| (sum.inl ⟨n,n_even⟩) := by {dsimp[g,f],rw[dif_pos n_even]}
| (sum.inr ⟨n,n_odd⟩) :=
   begin
    have n_not_even : n.bodd ≠ ff :=
     by {rw[n_odd],intro e,injection e,},
    dsimp[g,f],
    rw[dif_neg n_not_even,dif_pos n_odd],
   end

lemma gf (n : ℕ) : g (f n) = n :=
begin
 dsimp[f],
 by_cases n_even : n.bodd = ff,
 {rw[dif_pos n_even],refl},
 by_cases n_odd : n.bodd = tt,
  {have n_not_even  : n.bodd ≠ ff :=
   by {rw[n_odd],intro e,injection e,},
   rw[dif_neg n_not_even,dif_pos n_odd],
   refl,
  },
 exact bool_elim n_even n_odd
end

def F : ℕ ≃ (E ⊕ O) := ⟨f,g,gf,fg⟩


import tactic.linarith

namespace mth1001

/-
The following is a Lean proof that `10 < 20`.
The line `example : 10 < 20` can be read as, 'Here is a proof of `10 < 20`.

The line `by norm_num` says, 'the result is proved using `norm_num`, a 'tactic'
that can prove many numerical equations and inequalities.

You know the proof is valid because there are no error messages in the
Lean Infoview window and no red wavy text.
-/

example : 10 < 20 :=
by norm_num

/-
In standard mathematical language (henceforth 'standard maths'), the following can be read as

  `6 ≥ 2` follows using known numerical equations or inequalities.

Note: `≥` is written `\ge`. Can you guess how to write `≤`?
-/
example : 6 ≥ 2 := by norm_num

/-
Write the following in standard maths. Note: `≠` is written `\ne`.
-/
example : 15 ≠ 10 :=
by norm_num

-- Play around with this. Come up with your own examples similar to those above.
-- See what happens if you try to use `norm_num` to prove something false, like `1 = 0`.

/-
Let's make things interesting by introducing variables. Here, `ℤ` is the 'type' of integers and is
written `\Z`. The following command introduces variables `x` and `y` of type `ℤ`.
-/

variables (x y : ℤ)

/-
The `linarith` tactic can prove many equations and inequalities involving
variables. The standard maths tranlation of the following proof is:

  For every integer `x`, `x ≥ x` follows using known linear equations and inequalities.

-/
example : x ≥ x :=
by linarith

/-
We can suppress error messages by writing `sorry` where Lean expects a proof.
Lean warns us that the proof is not complete with a yellow wavy line and /or a warning message
in the Infoview window.
-/

-- Exercise 001:
-- Replace `sorry` with `by linarith` in the following example. Write the example in standard maths.
example : y + 5 ≥ y + 3 :=
sorry

/-
`linarith` can only handle _linear_ equations and inequalities --- those involving no quadratic or
higher-order terms. The `ring` tactic can simplify many other algebraic expressions.

The following states (in standard maths):

  For every integer `y`, `(y + 2)^2 = y^2 + 4*y + 4` follows by algebra.

Note: `^` represent exponentiation. Do not confuse it with `∧`, which you'll see later.
-/
example : (y + 2)^2 = y^2 + 4*y + 4 :=
by ring

/-
Some of the claims in the next few examples are true while others are false!
Determine, by hand, which are which. Test your guesses by using either the
`linarith` or the `ring` tactic to replace the sorry.

For each true statement, write out the example in standard maths.
-/

-- Exercise 002:
-- Is the following true or false?
example : (x + y)^2 = x^2 + y^2 :=
sorry

-- Exercise 003:
-- Is the following true or false?
example : (x - y) * (x + y) = x^2 - y^2 :=
sorry

-- Exercise 004:
-- Is the following true or false?
example : 3 * x + 8 ≥ 5 * x :=
sorry

/-
The previous example may cause some confusion. This isn't a question of *finding* the values of `x`
for which the equality `3 * x + 8 ≥ 5 * x` is true. Rather the problem is, 'Can you
prove, *for every* value of `x`, that `3 * x + 8 ≥ 5 * x`? If so, give a proof!'.
-/

/-
Of course, there *are* values of `x` for which the inequality holds. You can
easily that the inequality holds if `x ≤ 4`. In Lean, we write `h : x ≤ 4`
to mean, 'let `h` denote the premise (or assumption or hypothesis) that `x ≤ 4`'.

There's nothing special about `h`, we could instead write `k : x ≤ 4`,
`jane : x ≤ 4`, or `hal9000 : x ≤ 4`.
-/

/-
In English, we write the result below as, 'Given the premise `k : x ≤ 4`, the
inequality `3 * x + 8 ≥ 5 * x` follows'.

In normal mathematical writing, we do not label premises. Thus, we could write,
'Given `x ≤ 4`, the inequality `3 * x + 8 ≥ 5 * x` follows'.
-/

example (k : x ≤ 4) : 3 * x + 8 ≥ 5 * x :=
by linarith

/-
If `x ≤ 0`, then also `x ≤ 4`, so we should expect to get the conclusion of the above example from
this new premise.
-/

example (k : x ≤ 0) : 3 * x  + 8 ≥ 5 * x :=
by linarith

/-
More generally, the result is true with any stronger premise. That is, suppose we assert
`k₁ : x ≤ y` together with  `k₂ : y ≤ 4`. It then follows that `x ≤ 4` and we recover the result.

Here, we type `k\1` for `k₁`.
-/

example (k₁ : x ≤ y) (k₂ : y ≤ 4) : 3 * x + 8 ≥ 5 * x :=
by linarith

/-
What's special about `4` is that it's the *largest* integer with this property. We'll consider
how to formalise this notion in a later section.
-/

-- Exercise 005:
/- Complete the following statement and proof. Ensure you fill in the first `sorry` with
the *smallest* integer value that works.
-/
example (h : x ≥ sorry) : 9 * x + 6 ≥ 2 * x + 20 :=
sorry

-- Exercise 006:
/- Complete the following statement and proof. Fill in the first `sorry` with the *largest*
integer value that works. Can you prove (by hand) *why* this is the largest value?
-/
example (h₁ : -3 * x + 2 * y ≤ 2 ) (h₂ : 5 * x + y ≥ sorry): x ≥ 6 :=
sorry

/-
SUMMARY:

In this file, you have learned about:

* Statements of results in Lean, in the form `example premise₁ … premiseₙ : conclusion`.
* Translating between Lean and standard maths.
* The idea of a premise, also known as a hypothesis or assumption. Lean notation for premises.
* One-line tactic-style proofs, using `by`.
* Variables in Lean, using `variables`.
* The `norm_num` tactic for proving numerical equations and inequalities.
* The `linarith` tactic for proving linear equations and inequalities (involving variables).
* The `ring` tactic for simplifying more general equations.
* Writing subscripts in Lean.
-/

end mth1001


-- In propositions as types paradigm, theorems involving only → can be proved using
-- lambda abstraction and application.
-- In Lean the theorem command introduces a new theorem

constants p q : Prop

theorem t1 : p → q → p := λ hp : p, λ hq : q, hp

-- This looks like the definition of a constant the only difference is that instead of Type
-- we have Prop. Intuitively our proof of p → q → p assumes p and q are true, and uses
-- the first hypothesis (trivially) to establish that the consclusion p is true

-- Lean tags proofs as irreducible, which serves as a hint to the parser (more precisely, the elaborator)
-- that there is generally no need to unfold it when processing a file

-- Lean is generally able to process and check proofs in parallel,
--since assessing the correctness of one proof does not require knowing the details of another.

-- As with definitions, the #print command will show you the proof of a theorem.
#print t1

-- Notice that lambda abstractions hp : p and hq : q can be viewed as temporary assumptions
-- in the proof of t1. Lean provides an alternative syntax assume for such lambda abstraction

theorem t1' : p → q → p :=
assume hp : p,
assume hq : q,
hp

-- Lean also allows us to specify the type of the final term hp, explicitly, with show statement
theorem t1'' : p → q → p :=
assume hp : p,
assume hq : q,
show p, from hp

-- The show command does nothing more than annotate the type, and,
-- internally, all the presentations of t1 that we have seen produce the same term

-- Lean also allows you to use the alternative syntax lemma instead of theorem
lemma t1''' : p → q → p :=
assume hp : p,
assume hq : q,
show p, from hp

-- As with ordinary definitions, we can move the lambda-abstracted variable to the left of the colon
theorem t1'''' (hp : p) (hq : q) : p := hp

#check t1''''

-- Now we can apply the theorem t1 just as a function application
axiom hp : p
theorem t2 : q → p := t1 hp

-- Here, the axiom command is alternative syntax for constant.
-- Declaring a “constant” hp : p is tantamount to declaring that p is true, as witnessed by hp.

-- Applying the theorem t1 : p → q → p to the fact hp : p that p is true yields the theorem t2 : q → p

--  t1 is true for any propositions p and q,
theorem t1''''' (p q : Prop) (hp : p) (hq : q) : p := hp
#check t1'''''

-- he symbol ∀ is alternate syntax for Π, and
-- later we will see how Pi types let us model universal quantifiers more generally.

-- we can move all parameters to the right of the colon:
theorem t1'''''' : ∀ (p q : Prop), p → q → p :=
λ (p q: Prop) (hp: p) (hq : q), hp

-- If p and q are declared variables, Lean will generalize them automatically

variables p q : Prop
theorem t1''''''': p → q → p := λ (hp : p) (hq : q), hp

-- By the proposition-as-types correspondence we can declare the assumption hp that p
-- holds as another variable
variable hp : p

theorem t1'''''''' : q → p := λ (hq : q), hp

#check t1''''''''

-- We can also write the above as ∀ (p q : Prop) (hp : p) (hq :q), p,
-- since the arrow denotes nothing more than a Pi type in which the target does not depend on
-- the bound variable

-- When we generalize t1 in such a way, we can apply it to different pairs of propositions,
-- to obtain different instances of the general theorem

theorem t1''''''''' (p q : Prop) (hp : p) (hq : q) :p := hp

variables r s : Prop

#check t1''''''''' p q
#check t1''''''''' r s
#check t1''''''''' (r → s) (r → s)

variable h : r → s
#check t1''''''''' (r → s) (s → r) h

-- Let's now consider the composition function now with propositions instead of types

variables p' q' r' s' : Prop

theorem t2' (h₁ : q' → r') (h₂ : p' → q'): p' → r' :=
assume h₃ : p',
show r', from h₁  (h₂ h₃)

#check t2'


/-
This in-class exercise requires solutions to two problems
-/

/-
PROBLEM #1.

In Lean, define pf1 to be a proof of the proposition
that, "for any proposition, Q, ¬ (Q ∧ ¬ Q). We write
this as ∀ Q : Prop, ¬ (Q ∧ ¬ Q). In English it says,
it's not possible for any proposition to be both true
and false.

There are at least three forms a solution can take.

#1. You can prove the proposition by declaring
pf1 to be a proof (value) of the proposition/type
as written, then use a lambda expression to give
the actual proof term. Here we give you the first
line of the lambda, which you can read as either
(A) "a function that takes a proposition Q as an
argument and that returns ...", or as (B) "assume
Q is some proposition and then return ..." You
have to fill in the _. You know of course that
it could be another lambda expression.

theorem pf1 : ∀ Q : Prop, ¬ (Q ∧ ¬ Q)  :=
    λ (Q : Prop),
        _

#2: You can use an equivalent tactic script. It
will start like this:

theorem pf1' : ∀ Q : Prop, ¬ (Q ∧ ¬ Q)  :=
assume Q : Prop,
...

You have to fill in the rest in place of the
elipsis.

#3: You can prove the theorem by writing an
ordinary function. It'd start like this:

theorem pf1'' (Q : Prop) (pfQnQ : Q ∧ ¬ Q) : false :=
_

Again you have to replace the _ with "code"
that constructs the required proof.

For extra credit, give the answer in all three
forms.

Here are some extra hints.

Hint #1: Remember that, assuming that A is any
proposition (such as Q ∧ ¬ Q), ¬ A simply means
(A → false). So what does ¬ (Q ∧ ¬ Q) mean? That
is the proposition you need to prove, and its
form will tell you a lot about what for a proof
has to have.

Hint #2: Hover your mouse cursor over the underscores
in the preceding code. That will tell you exactly
what proof you need to fill in in place of the _.

Hint #3: (Q ∧ ¬ Q) → false is an implication.
Remember what a proof of an implication looks like.
Use a corresponding expression in place of the _.
Yes, we're repeating the same hint as before but
using other words.

Hint #4: Remember the elimination rules for ∧. If
you assume you have a proof of (Q ∧ ¬ Q) what two
smaller proofs can you derive from it?

Hint #5: Remember again: ¬ Q  means (Q → false).
If you have a proof of Q → false, then you have
a function that, if it is given an assumed proof
of Q, reduces it to a proof of false. Look for a
way to get this function and then to apply it to
a proof of Q!
-/

/- SOLUTION #1 -/

theorem pf1 : ∀ Q : Prop, ¬ (Q ∧ ¬ Q)  :=
    λ (Q : Prop) (q : (Q ∧ ¬ Q)),
        q.2 q.1

/- SOLUTION #2 -/

theorem pf1' : ∀ Q : Prop, ¬ (Q ∧ ¬ Q)  :=
    begin
        assume Q : Prop,
        assume q : (Q ∧ ¬ Q),
        show false,
        from q.2 q.1
    end

/- SOLUTION #3 -/

theorem pf1'' (Q : Prop) (pfQnQ : Q ∧ ¬ Q) : false :=
    begin
        have pfQ := and.elim_left pfQnQ,
        have pfnQ := and.elim_right pfQnQ,
        exact pfnQ pfQ
    end
/-
PROBLEM #2.

Produce a proof, pf2, of the proposition, that
for any propositions, P and Q, (P ∧ Q) ∧ (P ∧ ¬ Q)
→ false. (You could of course also write this as
¬ ((P ∧ Q) ∧ (P ∧ ¬ Q)). You can use the partial
solutions we gave for the last problem as models.
Start by assuming (P Q : Prop) rather than just
Q : Prop. Hint: Remember the and elimination rules.
Look for ways to extract from the assumed proof
the other proofs that you'll need to combine in
some way to construct a proof of false. You may
provide your answer in any of the three forms we
discussed. For extra credit, provide it in all
three forms.
-/

/- #1 -/

theorem pf2 :
    ∀ P Q : Prop, (P ∧ Q) ∧ (P ∧ ¬ Q) → false :=
        λ (P Q : Prop),
            λ (p : (P ∧ Q) ∧ (P ∧ ¬ Q)),
                (p.2).2 (p.1).2
/- #2 -/

theorem pf2' :
    ∀ P Q : Prop, (P ∧ Q) ∧ (P ∧ ¬ Q) → false :=
        begin
            assume P Q : Prop,
            assume p : (P ∧ Q) ∧ (P ∧ ¬ Q),
            show false,
            from (p.2).2 (p.1).2
        end

/- #3 -/

theorem pf1''' (P Q : Prop) (pf : (P ∧ Q) ∧ (P ∧ ¬ Q)) : false :=
    begin
    have pfL := and.elim_left pf,
    have pfR := and.elim_right pf,
    have pfQ := and.elim_right pfL,
    have pfnQ := and.elim_right pfR,
    exact pfnQ pfQ
    end


import data.real.basic

/-
In the previous file, we saw how to rewrite using equalities.
The analogue operation with mathematical statements is rewriting using
equivalences. This is also done using the `rw` tactic.
Lean uses ↔ to denote equivalence instead of ⇔.

In the following exercises we will use the lemma:

  sub_nonneg {x y : ℝ} : 0 ≤ y - x ↔ x ≤ y

The curly braces around x and y instead of parentheses mean Lean will always try to figure out what
x and y are from context, unless we really insist on telling it (we'll see how to insist much later).
Let's not worry about that for now.

In order to announce an intermediate statement we use:

  have my_name : my statement,

This triggers the apparition of a new goal: proving the statement. After this is done,
the statement becomes available under the name `my_name`.
We can focus on the current goal by typing tactics between curly braces.
-/

example {a b c : ℝ} (hab : a ≤ b) : c + a ≤ c + b :=
begin
  rw ← sub_nonneg,
  have key : (c + b) - (c + a) = b - a, -- Here we introduce an intermediate statement named key
  { ring, },  -- and prove it between curly braces
  rw key, -- we can now use the key statement
  rw sub_nonneg,
  exact hab,
end

/-
Of course the previous lemma is already in the core library, named `add_le_add_left`, so we can use it below.

Let's prove a variation (without invoking commutativity of addition since this would spoil our fun).
-/

-- 0009
example {a b : ℝ} (hab : a ≤ b) (c : ℝ) : a + c ≤ b + c :=
begin
  rw ←sub_nonneg,
  have key : (b + c) - (a + c) = b - a, ring,
  rw key,
  rwa sub_nonneg,
end

/-
Let's see how we could use this lemma. It is already in the core library, under the name `add_le_add_right`:

  add_le_add_right {a b : ℝ} (hab : a ≤ b) (c : ℝ) : a + c ≤ b + c

This can be read as: "add_le_add_right is a function that will take as input real numbers a and b, an
assumption `hab` claiming a ≤ b and a real number c, and will output a proof of a + c ≤ b + c".

In addition, recall that curly braces around a b mean Lean will figure out those arguments unless we
insist to help. This is because they can be deduced from the next argument `hab`.
So it will be sufficient to feed `hab` and c to this function.
-/

example {a b : ℝ}  (ha : 0 ≤ a) : b ≤ a + b :=
begin
  calc b = 0 + b : by ring
     ... ≤ a + b : by exact add_le_add_right ha b,
end

/-
In the second line of the above proof, we need to prove 0 + b ≤ a + b.
The proof after the colon says: this is exactly lemma `add_le_add_right` applied to ha and b.
Actually the `calc` block expects proof terms, and the `by` keyword is used to tell Lean we will use tactics
to build such a proof term. But since the only tactic used in this block is `exact`, we can skip
tactics entirely, and write:
-/

example (a b : ℝ)  (ha : 0 ≤ a) : b ≤ a + b :=
begin
  calc b = 0 + b : by ring
     ... ≤ a + b : add_le_add_right ha b,
end

/- Let's do a variant. -/

-- 0010
example (a b : ℝ) (hb : 0 ≤ b) : a ≤ a + b :=
begin
  calc
    a   = a + 0 : by ring
    ... ≤ a + b : add_le_add_left hb a,
end

/-
The two preceding examples are in the core library :

  le_add_of_nonneg_left  {a b : ℝ} (ha : 0 ≤ a) : b ≤ a + b
  le_add_of_nonneg_right {a b : ℝ} (hb : 0 ≤ b) : a ≤ a + b

Again, there won't be any need to memorize those names, we will
soon see how to get rid of such goals automatically.
But we can already try to understand how their names are built:
"le_add" describe the conclusion "less or equal than some addition"
It comes first because we are focussed on proving stuff, and
auto-completion works by looking at the beginning of words.
"of" introduces assumptions. "nonneg" is Lean's abbreviation for non-negative.
"left" or "right" disambiguates between the two variations.

Let's use those lemmas by hand for now.
-/

-- 0011
example (a b : ℝ) (ha : 0 ≤ a) (hb : 0 ≤ b) : 0 ≤ a + b :=
begin
  calc 0 ≤ a     : ha
     ... ≤ a + b : le_add_of_nonneg_right hb,
end

/- And let's combine with our earlier lemmas. -/

-- 0012
example (a b c d : ℝ) (hab : a ≤ b) (hcd : c ≤ d) : a + c ≤ b + d :=
begin
  calc a + c ≤ b + c : add_le_add_right hab c
       ...   ≤ b + d : add_le_add_left hcd b,
end

/-
In the above examples, we prepared proofs of assumptions of our lemmas beforehand, so
that we could feed them to the lemmas. This is called forward reasonning.
The `calc` proofs also belong to this category.

We can also announce the use of a lemma, and provide proofs after the fact, using
the `apply` tactic. This is called backward reasonning because we get the conclusion
first, and provide proofs later. Using `rw` on the goal (rather than on an assumption
from the local context) is also backward reasonning.

Let's do that using the lemma

  mul_nonneg' {x y : ℝ} (hx : 0 ≤ x) (hy : 0 ≤ y) : 0 ≤ x*y
-/

example (a b c  : ℝ) (hc : 0 ≤ c) (hab : a ≤ b) : a*c ≤ b*c :=
begin
  rw ← sub_nonneg,
  have key : b*c - a*c = (b - a)*c,
  { ring },
  rw key,
  apply mul_nonneg, -- Here we don't provide proofs for the lemma's assumptions
  -- Now we need to provide the proofs.
  { rw sub_nonneg,
    exact hab },
  { exact hc },
end

/-
Let's prove the same statement using only forward reasonning: announcing stuff,
proving it by working with known facts, moving forward.
-/

example (a b c  : ℝ) (hc : 0 ≤ c) (hab : a ≤ b) : a*c ≤ b*c :=
begin
  have hab' : 0 ≤ b - a,
  { rw ← sub_nonneg at hab,
    exact hab, },
  have h₁ : 0 ≤ (b - a)*c,
  { exact mul_nonneg hab' hc },
  have h₂ : (b - a)*c = b*c - a*c,
  { ring, },
  have h₃ : 0 ≤ b*c - a*c,
  { rw h₂ at h₁,
    exact h₁, },
  rw sub_nonneg at h₃,
  exact h₃,
end

/-
One reason why the backward reasoning proof is shorter is because Lean can
infer of lot of things by comparing the goal and the lemma statement. Indeed
in the `apply mul_nonneg'` line, we didn't need to tell Lean that x = b - a
and y = c in the lemma. It was infered by "unification" between the lemma
statement and the goal.

To be fair to the forward reasoning version, we should introduce a convenient
variation on `rw`. The `rwa` tactic performs rewrite and then looks for an
assumption matching the goal. We can use it to rewrite our latest proof as:
-/

example (a b c  : ℝ) (hc : 0 ≤ c) (hab : a ≤ b) : a*c ≤ b*c :=
begin
  have hab' : 0 ≤ b - a,
  { rwa ← sub_nonneg at hab, },
  have h₁ : 0 ≤ (b - a)*c,
  { exact mul_nonneg hab' hc },
  have h₂ : (b - a)*c = b*c - a*c,
  { ring, },
  have h₃ : 0 ≤ b*c - a*c,
  { rwa h₂ at h₁, },
  rwa sub_nonneg at h₃,
end

/-
Let's now combine forward and backward reasonning, to get our most
efficient proof of this statement. Note in particular how unification is used
to know what to prove inside the parentheses in the `mul_nonneg'` arguments.
-/
example (a b c : ℝ) (hc : 0 ≤ c) (hab : a ≤ b) : a*c ≤ b*c :=
begin
  rw ← sub_nonneg,
  calc 0 ≤ (b - a)*c  : mul_nonneg (by rwa sub_nonneg) hc
     ... =  b*c - a*c : by ring,
end

/-
Let's now practice all three styles using:

  mul_nonneg_of_nonpos_of_nonpos {a b : α} (ha : a ≤ 0) (hb : b ≤ 0) : 0 ≤ a * b

  sub_nonpos {a b : α} : a - b ≤ 0 ↔ a ≤ b
-/

/- First using mostly backward reasonning -/
-- 0013
example (a b c : ℝ) (hc : c ≤ 0) (hab :  a ≤ b) : b*c ≤ a*c :=
begin
  rw ←sub_nonneg,
  have key : a * c - b * c = (a - b) * c, ring,
  rw key,
  apply mul_nonneg_of_nonpos_of_nonpos,
    rwa sub_nonpos,
  exact hc,
end

/- Using forward reasonning -/
-- 0014
example (a b c : ℝ) (hc : c ≤ 0) (hab :  a ≤ b) : b*c ≤ a*c :=
begin
  replace hab, exact sub_nonpos.mpr hab,
  replace hab, exact mul_nonneg_of_nonpos_of_nonpos hab hc,
  have : (a - b) * c = a * c - b * c, ring,
  rw this at hab, clear this,
  exact sub_nonneg.mp hab,
end

/-- Using a combination of both, with a `calc` block -/
-- 0015
example (a b c : ℝ) (hc : c ≤ 0) (hab :  a ≤ b) : b*c ≤ a*c :=
begin
  rw ←sub_nonneg,
  calc 0 ≤ (a - b) * c : mul_nonneg_of_nonpos_of_nonpos (sub_nonpos.mpr hab) hc
     ... = a * c - b * c : by ring,
end

/-
Let's now move to proving implications. Lean denotes implications using
a simple arrow →, the same it uses for functions (say denoting the type of functions
from ℕ to ℕ by ℕ → ℕ). This is because it sees a proof of P ⇒ Q as a function turning
a proof of P into a proof Q.

Many of the examples that we already met are implications under the hood. For instance we proved

  le_add_of_nonneg_left (a b : ℝ) (ha : 0 ≤ a) : b ≤ a + b

But this can be rephrased as

  le_add_of_nonneg_left (a b : ℝ) : 0 ≤ a → b ≤ a + b

In order to prove P → Q, we use the tactic `intros`, followed by an assumption name.
This creates an assumption with that name asserting that P holds, and turns the goal into Q.

Let's check we can go from our old version of `le_add_of_nonneg_left` to the new one.

-/
example (a b : ℝ): 0 ≤ a → b ≤ a + b :=
begin
  intros ha,
  exact le_add_of_nonneg_left ha,
end

/-
Actually Lean doesn't make any difference between those two versions. It is also happy with
-/
example (a b : ℝ): 0 ≤ a → b ≤ a + b :=
le_add_of_nonneg_left

/- No tactic state is shown in the above line because we don't even need to enter
tactic mode using `begin` or `by`.

Let's practise using `intros`. -/

-- 0016
example (a b : ℝ): 0 ≤ b → a ≤ a + b :=
begin
  intro hb,
  exact le_add_of_nonneg_right hb,
end

/-
What about lemmas having more than one assumption? For instance:

  add_nonneg {a b : ℝ} (ha : 0 ≤ a) (hb : 0 ≤ b) : 0 ≤ a + b

A natural idea is to use the conjunction operator (logical AND), which Lean denotes
by ∧. Assumptions built using this operator can be decomposed using the `cases` tactic,
which is a very general assumption-decomposing tactic.
-/
example {a b : ℝ} : (0 ≤ a ∧ 0 ≤ b) → 0 ≤ a + b :=
begin
  intros hyp,
  cases hyp with ha hb,
  exact add_nonneg ha hb,
end

/-
Needing that intermediate line invoking `cases` shows this formulation is not what is used by
Lean. It rather sees `add_nonneg` as two nested implications:
if a is non-negative then if b is non-negative then a+b is non-negative.
It reads funny, but it is much more convenient to use in practice.
-/
example {a b : ℝ} : 0 ≤ a → (0 ≤ b → 0 ≤ a + b) :=
add_nonneg

/-
The above pattern is so common that implications are defined as right-associative operators,
hence parentheses are not needed above.

Let's prove that the naive conjunction version implies the funny Lean version. For this we need
to know how to prove a conjunction. The `split` tactic creates two goals from a conjunction goal.
It can also be used to create two implication goals from an equivalence goal.
-/
example {a b : ℝ} (H : (0 ≤ a ∧ 0 ≤ b) → 0 ≤ a + b) : 0 ≤ a → (0 ≤ b → 0 ≤ a + b) :=
begin
  intros ha,
  intros hb,
  apply H,
  split,
  exact ha,
  exact hb,
end

/-
Let's practice `cases` and `split`. In the next exercise, P, Q and R denote
unspecified mathematical statements.
-/

-- 0017
example (P Q R : Prop) : P ∧ Q → Q ∧ P :=
begin
  intro h,
  cases h with hp hq,
  split,
    exact hq,
  exact hp,
end

/-
Of course using `split` only to be able to use `exact` twice in a row feels silly. One can
also use the anonymous constructor syntax: ⟨ ⟩
Beware those are not parentheses but angle brackets. This is a generic way of providing
compound objects to Lean when Lean already has a very clear idea of what it is waiting for.

So we could have replaced the last three lines by:
  exact ⟨hQ, hP⟩

We can also combine the `intros` steps. We can now compress our earlier proof to:
-/

example {a b : ℝ} (H : (0 ≤ a ∧ 0 ≤ b) → 0 ≤ a + b) : 0 ≤ a → (0 ≤ b → 0 ≤ a + b) :=
begin
  intros ha hb,
  exact H ⟨ha, hb⟩,
end

/-
The anonymous contructor trick actually also works in `intros` provided we use
its recursive version `rintros`. So we can replace
  intro h,
  cases h with h₁ h₂
by
  rintros ⟨h₁, h₂⟩,
Now redo the previous exercise using all those compressing techniques, in exactly two lines. -/

-- 0018
example (P Q R : Prop): P ∧ Q → Q ∧ P :=
begin
  rintro ⟨hp, hq⟩,
  exact ⟨hq, hp⟩,
end

/-
We are ready to come back to the equivalence between the different formulations of
lemmas having two assumptions. Remember the `split` tactic can be used to split
an equivalence into two implications.
-/

-- 0019
example (P Q R : Prop) : (P ∧ Q → R) ↔ (P → (Q → R)) :=
begin
  split,
    intros hpq hp hq,
    exact hpq ⟨hp, hq⟩,
  rintro hpq ⟨hp, hq⟩,
  exact hpq hp hq,
end

/-
If you used more than five lines in the above exercise then try to compress things
(without simply removing line ends).

One last compression technique: given a proof h of a conjunction P ∧ Q, one can get
a proof of P using h.left and a proof of Q using h.right, without using cases.
One can also use the more generic (but less legible) names h.1 and h.2.

Similarly, given a proof h of P ↔ Q, one can get a proof of P → Q using h.mp
and a proof of Q → P using h.mpr (or the generic h.1 and h.2 that are even less legible
in this case).

Before the final exercise in this file, let's make sure we'll be able to leave
without learning 10 lemma names. The `linarith` tactic will prove any equality or
inequality or contradiction that follows by linear combinations of assumptions from the
context (with constant coefficients).
-/

example (a b : ℝ) (hb : 0 ≤ b) : a ≤ a + b :=
begin
  linarith,
end

/-
Now let's enjoy this for a while.
-/

-- 0020
example (a b : ℝ) (ha : 0 ≤ a) (hb : 0 ≤ b) : 0 ≤ a + b :=
begin
  linarith,
end

/- And let's combine with our earlier lemmas. -/

-- 0021
example (a b c d : ℝ) (hab : a ≤ b) (hcd : c ≤ d) : a + c ≤ b + d :=
begin
  linarith,
end

/-
Final exercise

In the last exercise of this file, we will use the divisibility relation on ℕ,
denoted by ∣ (beware this is a unicode divisibility bar, not the ASCII pipe character),
and the gcd function.

The definitions are the usual ones, but our goal is to avoid using these definitions and
only use the following three lemmas:

  dvd_refl (a : ℕ) : a ∣ a

  dvd_antisymm {a b : ℕ} : a ∣ b → b ∣ a → a = b :=

  dvd_gcd_iff {a b c : ℕ} : c ∣ gcd a b ↔ c ∣ a ∧ c ∣ b
-/

-- All functions and lemmas below are about natural numbers.
open nat

-- 0022
example (a b : ℕ) : a ∣ b ↔ gcd a b = a :=
begin
  /- not sure why dvd_gcd_iff would be preferable -/
  cases gcd_dvd a b with ha hb,
  split,
    intro h,
    apply dvd_antisymm,
      exact ha,
    rw dvd_gcd_iff,
    exact ⟨dvd_refl a, h⟩,
  intro h,
  rwa ← h,
end


import .lovelib

/-! # LoVe Homework 5: Inductive Predicates

Homework must be done individually. -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/-! ## Question 1 (3 points): A Type of λ-Terms

Recall the following type of λ-terms from question 3 of exercise 4: -/

inductive term : Type
| var : string → term
| lam : string → term → term
| app : term → term → term

/-! 1.1 (1 point). Define an inductive predicate `is_app` that returns `true` if
its argument is of the form `term.app …` and that returns false otherwise. -/

-- enter your definition here

/-! 1.2 (2 points). Define an inductive predicate `is_abs_free` that is true if
and only if its argument is a λ-term that contains no λ-expressions. -/

-- enter your definition here

/-! ## Question 2 (6 points): Even and Odd

Consider the following inductive definition of even numbers: -/

inductive even : ℕ → Prop
| zero            : even 0
| add_two {n : ℕ} : even n → even (n + 2)

/-! 2.1 (1 point). Define a similar predicate for odd numbers, by completing the
Lean definition below. The definition should distinguish two cases, like `even`,
and should not rely on `even`. -/

inductive odd : ℕ → Prop
-- supply the missing cases here

/-! 2.2 (1 point). Give proof terms for the following propositions, based on
your answer to question 2.1. -/

lemma odd_3 :
  odd 3 :=
sorry

lemma odd_5 :
  odd 5 :=
sorry

/-! 2.3 (2 points). Prove the following lemma by rule induction, as a "paper"
proof. This is a good exercise to develop a deeper understanding of how rule
induction works (and is good practice for the final exam).

    lemma even_odd {n : ℕ} (heven : even n) :
      odd (n + 1) :=

Guidelines for paper proofs:

We expect detailed, rigorous, mathematical proofs. You are welcome to use
standard mathematical notation or Lean structured commands (e.g., `assume`,
`have`, `show`, `calc`). You can also use tactical proofs (e.g., `intro`,
`apply`), but then please indicate some of the intermediate goals, so that we
can follow the chain of reasoning.

Major proof steps, including applications of induction and invocation of the
induction hypothesis, must be stated explicitly. For each case of a proof by
induction, you must list the inductive hypotheses assumed (if any) and the goal
to be proved. Minor proof steps corresponding to `refl`, `simp`, or `linarith`
need not be justified if you think they are obvious (to humans), but you should
say which key lemmas they depend on. You should be explicit whenever you use a
function definition or an introduction rule for an inductive predicate. -/

-- enter your paper proof here

/-! 2.4 (1 point). Prove the same lemma again, but this time in Lean: -/

lemma even_odd {n : ℕ} (heven : even n) :
  odd (n + 1) :=
sorry

/-! 2.5 (1 point). Prove the following lemma in Lean.

Hint: Recall that `¬ a` is defined as `a → false`. -/

lemma even_not_odd {n : ℕ} (heven : even n) :
  ¬ odd n :=
sorry

end LoVe


import tactic
import data.real.basic
import data.set

/--
Definition for convergence of a sequence a n to a limit t.
-/
def tendsto (a : ℕ → ℝ) (t : ℝ) : Prop :=
∀ ε > 0, ∃ B : ℕ, ∀ n, B ≤ n → |a n - t| < ε

/--
Lemma for rewriting tendsto without using the change tactic.
-/
lemma tendsto_def {a : ℕ → ℝ} {t : ℝ} :
  tendsto a t ↔ ∀ ε, 0 < ε → ∃ B : ℕ, ∀ n, B ≤ n → |a n - t| < ε :=
begin
  refl
end

/--
A sequence a n tends to t iff -a n tends to -t.
This is a problem from the problem sheets converted to an iff.
-/
theorem tendsto_neg_iff {a : ℕ → ℝ} {t : ℝ} :tendsto a t ↔ tendsto (λ n, - a n) (-t) :=
begin
  split,
  intro ha,
  rw tendsto_def at ⊢ ha,
  intro eps,
  intro he,
  specialize ha eps,
  have hb : (∃ (B : ℕ), ∀ (n : ℕ), B ≤ n → |a n - t| < eps),
  apply ha, exact he,
  cases hb,
  use hb_w,
  intro n,
  intro i,
  rw ←abs_neg (- a n - -t),
  ring_nf,
  apply hb_h,exact i,

  intro ha,
  rw tendsto_def at ⊢ ha,
  intro eps,
  intro he,
  specialize ha eps,
  have hb: (∃ (B : ℕ), ∀ (n : ℕ), B ≤ n → | -a n - -t| < eps),
  apply ha, exact he,
  cases hb,
  use hb_w,
  intros n i,
  specialize hb_h n,
  rw ←abs_neg (a n  -t),
  ring_nf,
  ring_nf at hb_h,
  apply hb_h, exact i,
end
#lint


-- BOTH:
import data.real.basic

/- TEXT:
.. _the_existential_quantifier:

The Existential Quantifier
--------------------------

The existential quantifier, which can be entered as ``\ex`` in VS Code,
is used to represent the phrase "there exists."
The formal expression ``∃ x : ℝ, 2 < x ∧ x < 3`` in Lean says
that there is a real number between 2 and 3.
(We will discuss the conjunction symbol, ``∧``, in :numref:`conjunction_and_biimplication`.)
The canonical way to prove such a statement is to exhibit a real number
and show that it has the stated property.
The number 2.5, which we can enter as ``5 / 2``
or ``(5 : ℝ) / 2`` when Lean cannot infer from context that we have
the real numbers in mind, has the required property,
and the ``norm_num`` tactic can prove that it meets the description.

.. index:: use, tactics ; use

There are a few ways we can put the information together.
Given a goal that begins with an existential quantifier,
the ``use`` tactic is used to provide the object,
leaving the goal of proving the property.
TEXT. -/
-- QUOTE:
example : ∃ x : ℝ, 2 < x ∧ x < 3 :=
begin
  use 5 / 2,
  norm_num
end
-- QUOTE.

/- TEXT:
.. index:: anonyomous constructor

Alternatively, we can use Lean's *anonyomous constructor* notation
to construct the proof.
TEXT. -/
-- QUOTE:
example : ∃ x : ℝ, 2 < x ∧ x < 3 :=
begin
  have h : 2 < (5 : ℝ) / 2 ∧ (5 : ℝ) / 2 < 3,
    by norm_num,
  exact ⟨5 / 2, h⟩
end
-- QUOTE.

/- TEXT:
The left and right angle brackets,
which can be entered as ``\<`` and ``\>`` respectively,
tell Lean to put together the given data using
whatever construction is appropriate
for the current goal.
We can use the notation without going first into tactic mode:
TEXT. -/
-- QUOTE:
example : ∃ x : ℝ, 2 < x ∧ x < 3 :=
⟨5 / 2, by norm_num⟩
-- QUOTE.

/- TEXT:
So now we know how to *prove* an exists statement.
But how do we *use* one?
If we know that there exists an object with a certain property,
we should be able to give a name to an arbitrary one
and reason about it.
For example, remember the predicates ``fn_ub f a`` and ``fn_lb f a``
from the last section,
which say that ``a`` is an upper bound or lower bound on ``f``,
respectively.
We can use the existential quantifier to say that "``f`` is bounded"
without specifying the bound:
TEXT. -/
-- BOTH:
-- QUOTE:
def fn_ub (f : ℝ → ℝ) (a : ℝ) : Prop := ∀ x, f x ≤ a
def fn_lb (f : ℝ → ℝ) (a : ℝ) : Prop := ∀ x, a ≤ f x

def fn_has_ub (f : ℝ → ℝ) := ∃ a, fn_ub f a
def fn_has_lb (f : ℝ → ℝ) := ∃ a, fn_lb f a
-- QUOTE.

/- TEXT:
We can use the theorem ``fn_ub_add`` from the last section
to prove that if ``f`` and ``g`` have upper bounds,
then so does ``λ x, f x + g x``.
TEXT. -/

-- BOTH:
theorem fn_ub_add {f g : ℝ → ℝ} {a b : ℝ}
    (hfa : fn_ub f a) (hgb : fn_ub g b) :
  fn_ub (λ x, f x + g x) (a + b) :=
λ x, add_le_add (hfa x) (hgb x)

section
-- QUOTE:
variables {f g : ℝ → ℝ}

-- EXAMPLES:
example (ubf : fn_has_ub f) (ubg : fn_has_ub g) :
  fn_has_ub (λ x, f x + g x) :=
begin
  cases ubf with a ubfa,
  cases ubg with b ubfb,
  use a + b,
  apply fn_ub_add ubfa ubfb
end
-- QUOTE.

/- TEXT:
.. index:: cases, tactics ; cases

The ``cases`` tactic unpacks the information
in the existential quantifier.
Given the hypothesis ``ubf`` that there is an upper bound
for ``f``,
``cases`` adds a new variable for an upper
bound to the context,
together with the hypothesis that it has the given property.
The ``with`` clause allows us to specify the names
we want Lean to use.
The goal is left unchanged;
what *has* changed is that we can now use
the new object and the new hypothesis
to prove the goal.
This is a common pattern in mathematics:
we unpack objects whose existence is asserted or implied
by some hypothesis, and then use it to establish the existence
of something else.

Try using this pattern to establish the following.
You might find it useful to turn some of the examples
from the last section into named theorems,
as we did with ``fn_ub_add``,
or you can insert the arguments directly
into the proofs.
TEXT. -/
-- QUOTE:
example (lbf : fn_has_lb f) (lbg : fn_has_lb g) :
  fn_has_lb (λ x, f x + g x) :=
sorry

example {c : ℝ} (ubf : fn_has_ub f) (h : c ≥ 0):
  fn_has_ub (λ x, c * f x) :=
sorry
-- QUOTE.

-- SOLUTIONS:
example (lbf : fn_has_lb f) (lbg : fn_has_lb g) :
  fn_has_lb (λ x, f x + g x) :=
begin
  cases lbf with a lbfa,
  cases lbg with b lbgb,
  use a + b,
  intro x,
  exact add_le_add (lbfa x) (lbgb x)
end

example {c : ℝ} (ubf : fn_has_ub f) (h : c ≥ 0):
  fn_has_ub (λ x, c * f x) :=
begin
  cases ubf with a lbfa,
  use c * a,
  intro x,
  exact mul_le_mul_of_nonneg_left (lbfa x) h
end

/- TEXT:
.. index:: rintros, tactics ; rintros, rcases, tactics ; rcases

The task of unpacking information in a hypothesis is
so important that Lean and mathlib provide a number of
ways to do it.
A cousin of the ``cases`` tactic, ``rcases``, is more
flexible in that it allows us to unpack nested data.
(The "r" stands for "recursive.")
In the ``with`` clause for unpacking an existential quantifier,
we name the object and the hypothesis by presenting
them as a pattern ``⟨a, h⟩`` that ``rcases`` then tries to match.
The ``rintro`` tactic (which can also be written ``rintros``)
is a combination of ``intros`` and ``rcases``.
These examples illustrate their use:
TEXT. -/
-- QUOTE:
example (ubf : fn_has_ub f) (ubg : fn_has_ub g) :
  fn_has_ub (λ x, f x + g x) :=
begin
  rcases ubf with ⟨a, ubfa⟩,
  rcases ubg with ⟨b, ubfb⟩,
  exact ⟨a + b, fn_ub_add ubfa ubfb⟩
end

example : fn_has_ub f → fn_has_ub g →
  fn_has_ub (λ x, f x + g x) :=
begin
  rintros ⟨a, ubfa⟩ ⟨b, ubfb⟩,
  exact ⟨a + b, fn_ub_add ubfa ubfb⟩
end
-- QUOTE.

/- TEXT:
In fact, Lean also supports a pattern-matching lambda
in expressions and proof terms:
TEXT. -/
-- QUOTE:
example : fn_has_ub f → fn_has_ub g →
  fn_has_ub (λ x, f x + g x) :=
λ ⟨a, ubfa⟩ ⟨b, ubfb⟩, ⟨a + b, fn_ub_add ubfa ubfb⟩
-- QUOTE.

-- BOTH:
end

/- TEXT:
These are power-user moves, and there is no harm
in favoring the use of ``cases`` until you are more comfortable
with the existential quantifier.
But we will come to learn that all of these tools,
including ``cases``, ``use``, and the anonymous constructors,
are like Swiss army knives when it comes to theorem proving.
They can be used for a wide range of purposes,
not just for unpacking exists statements.

To illustrate one way that ``rcases`` can be used,
we prove an old mathematical chestnut:
if two integers ``x`` and ``y`` can each be written as
a sum of two squares,
then so can their product, ``x * y``.
In fact, the statement is true for any commutative
ring, not just the integers.
In the next example, ``rcases`` unpacks two existential
quantifiers at once.
We then provide the magic values needed to express ``x * y``
as a sum of squares as a list to the ``use`` statement,
and we use ``ring`` to verify that they work.
TEXT. -/
section
-- QUOTE:
variables {α : Type*} [comm_ring α]

def sum_of_squares (x : α) := ∃ a b, x = a^2 + b^2

theorem sum_of_squares_mul {x y : α}
    (sosx : sum_of_squares x) (sosy : sum_of_squares y) :
  sum_of_squares (x * y) :=
begin
  rcases sosx with ⟨a, b, xeq⟩,
  rcases sosy with ⟨c, d, yeq⟩,
  rw [xeq, yeq],
  use [a*c - b*d, a*d + b*c],
  ring
end
-- QUOTE.
/- TEXT:
This proof doesn't provide much insight,
but here is one way to motivate it.
A *Gaussian integer* is a number of the form :math:`a + bi`
where :math:`a` and :math:`b` are integers and :math:`i = \sqrt{-1}`.
The *norm* of the Gaussian integer :math:`a + bi` is, by definition,
:math:`a^2 + b^2`.
So the norm of a Gaussian integer is a sum of squares,
and any sum of squares can be expressed in this way.
The theorem above reflects the fact that norm of a product of
Gaussian integers is the product of their norms:
if :math:`x` is the norm of :math:`a + bi` and
:math:`y` in the norm of :math:`c + di`,
then :math:`xy` is the norm of :math:`(a + bi) (c + di)`.
Our cryptic proof illustrates the fact that
the proof that is easiest to formalize isn't always
the most perspicuous one.
In the chapters to come,
we will provide you with the means to define the Gaussian
integers and use them to provide an alternative proof.

The pattern of unpacking an equation inside an existential quantifier
and then using it to rewrite an expression in the goal
comes up often,
so much so that the ``rcases`` tactic provides
an abbreviation:
if you use the keyword ``rfl`` in place of a new identifier,
``rcases`` does the rewriting automatically (this trick doesn't work
with pattern-matching lambdas).
TEXT. -/
-- QUOTE:
theorem sum_of_squares_mul' {x y : α}
    (sosx : sum_of_squares x) (sosy : sum_of_squares y) :
  sum_of_squares (x * y) :=
begin
  rcases sosx with ⟨a, b, rfl⟩,
  rcases sosy with ⟨c, d, rfl⟩,
  use [a*c - b*d, a*d + b*c],
  ring
end
-- QUOTE.

end
/- TEXT:
As with the universal quantifier,
you can find existential quantifiers hidden all over
if you know how to spot them.
For example, divisibility is implicitly an "exists" statement.
TEXT. -/
-- BOTH:
section
variables {a b c : ℕ}

-- EXAMPLES:
-- QUOTE:
example (divab : a ∣ b) (divbc : b ∣ c) : a ∣ c :=
begin
  cases divab with d beq,
  cases divbc with e ceq,
  rw [ceq, beq],
  use (d * e), ring
end
-- QUOTE.

/- TEXT:
And once again, this provides a nice setting for using
``rcases`` with ``rfl``.
Try it out in the proof above.
It feels pretty good!

Then try proving the following:
TEXT. -/
-- QUOTE:
example (divab : a ∣ b) (divac : a ∣ c) : a ∣ (b + c) :=
sorry
-- QUOTE.

-- SOLUTIONS:
example (divab : a ∣ b) (divbc : b ∣ c) : a ∣ c :=
begin
  rcases divab with ⟨d, rfl⟩,
  rcases divbc with ⟨e, rfl⟩,
  use (d * e), ring
end

example (divab : a ∣ b) (divac : a ∣ c) : a ∣ (b + c) :=
begin
  rcases divab with ⟨d, rfl⟩,
  rcases divac with ⟨e, rfl⟩,
  use (d + e), ring
end

-- BOTH:
end

/- TEXT:
.. index:: surjective function

For another important example, a function :math:`f : \alpha \to \beta`
is said to be *surjective* if for every :math:`y` in the
codomain, :math:`\beta`,
there is an :math:`x` in the domain, :math:`\alpha`,
such that :math:`f(x) = y`.
Notice that this statement includes both a universal
and an existential quantifier, which explains
why the next example makes use of both ``intro`` and ``use``.
TEXT. -/
-- BOTH:
section
open function

-- EXAMPLES:
-- QUOTE:
example {c : ℝ} : surjective (λ x, x + c) :=
begin
  intro x,
  use x - c,
  dsimp, ring
end
-- QUOTE.

/- TEXT:
Try this example yourself:
TEXT. -/
-- QUOTE:
example {c : ℝ} (h : c ≠ 0) : surjective (λ x, c * x) :=
sorry
-- QUOTE.

-- SOLUTIONS:
example {c : ℝ} (h : c ≠ 0) : surjective (λ x, c * x) :=
begin
  intro x,
  use x / c,
  dsimp, rw [mul_div_cancel' _ h]
end

example {c : ℝ} (h : c ≠ 0) : surjective (λ x, c * x) :=
begin
  intro x,
  use x / c,
  field_simp [h], ring
end

/- TEXT:
index:: field_simp, tactic ; field_simp

At this point, it is worth mentioning that there is a tactic, `field_simp`,
that will often clear denominators in a useful way.
It can be used in conjunction with the ``ring`` tactic.
TEXT. -/
-- QUOTE:
example (x y : ℝ) (h : x - y ≠ 0) : (x^2 - y^2) / (x - y) = x + y :=
by { field_simp [h], ring }
-- QUOTE.

/- TEXT:
You can use the theorem ``div_mul_cancel``.
The next example uses a surjectivity hypothesis
by applying it to a suitable value.
Note that you can use ``cases`` with any expression,
not just a hypothesis.
TEXT. -/
-- QUOTE:
example {f : ℝ → ℝ} (h : surjective f) : ∃ x, (f x)^2 = 4 :=
begin
  cases h 2 with x hx,
  use x,
  rw hx,
  norm_num
end
-- QUOTE.

-- BOTH:
end

/- TEXT:
See if you can use these methods to show that
the composition of surjective functions is surjective.
TEXT. -/
-- BOTH:
section
open function
-- QUOTE:
variables {α : Type*} {β : Type*} {γ : Type*}
variables {g : β → γ} {f : α → β}

-- EXAMPLES:
example (surjg : surjective g) (surjf : surjective f) :
  surjective (λ x, g (f x)) :=
sorry
-- QUOTE.

-- SOLUTIONS:
example (surjg : surjective g) (surjf : surjective f) :
  surjective (λ x, g (f x)) :=
begin
  intro z,
  rcases surjg z with ⟨y, rfl⟩,
  rcases surjf y with ⟨x, rfl⟩,
  use [x, rfl]
end

-- BOTH:
end


/-
Copyright (c) 2019 Neil Strickland. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Neil Strickland

This is about the function falling n k = n (n - 1) ... (n - k + 1)
Note that the above formula involves subtraction but we give a
different recursive definition that only involves addition and
so works more smoothly as a map ℕ → ℕ → ℕ.

-/

import data.nat.choose

open nat

def falling : ℕ → ℕ → ℕ
| 0 0 := 1
| 0 (k + 1) := 0
| (n + 1) 0 := 1
| (n + 1) (k + 1) := (n + 1) * falling n k

@[simp]
lemma falling_zero (n : ℕ) : falling n 0 = 1 := begin
 cases n; refl,
end

@[simp]
lemma falling_succ (n k : ℕ) : falling (n + 1) (k + 1) =
 (n + 1) * falling n k := rfl

@[simp]
lemma falling_zero_succ (k : ℕ) : falling 0 (k + 1) = 0 := rfl

/- falling n n = n! -/
lemma falling_factorial : ∀ n : ℕ, falling n n = factorial n
| 0 := rfl
| (n + 1) := by {rw[falling,factorial,falling_factorial n]}

/- falling p n = p ! / (p - n)! for p ≥ n.
   We formalise this with p = n + m rather than with an inequality.
-/
lemma falling_factorial_quot : ∀ n m : ℕ, (falling (n + m) n) * factorial m = factorial (n + m)
| n 0 := by {rw[add_zero,factorial,mul_one,falling_factorial n]}
| 0 (m + 1) := by {rw[zero_add,falling,one_mul]}
| (n + 1) (m + 1) := calc
   falling ((n + 1) + (m + 1)) (n + 1) * factorial (m + 1) =
    ((n + 1) + (m + 1)) * (falling ((n + 1) + m) n) * factorial (m + 1) : rfl
   ... = ((n + 1) + (m + 1)) * ((falling (n + (m + 1)) n) * factorial (m + 1)) :
    by rw[mul_assoc,add_assoc n 1 m,add_comm 1 m]
   ... = ((n + 1) + (m + 1)) * factorial (n + (m + 1)) :
    by {rw[falling_factorial_quot n (m + 1)]}
   ... = (n + (m + 1) + 1) * factorial (n + (m + 1)) :
    by {rw[add_assoc n 1 (m + 1),add_comm 1 (m + 1),← add_assoc n (m + 1) 1]}
   ... = factorial (n + (m + 1) + 1) : rfl
   ... = factorial ((n + 1) + (m + 1)) :
    by {rw[add_assoc n (m + 1) 1,add_assoc n 1 (m + 1),add_comm (m + 1) 1],}