section propositional

variables P Q R : Prop

------------------------------------------------
-- Proposições de dupla negaço:
------------------------------------------------

theorem doubleneg_intro :
  P → ¬¬P  :=
begin
  intro hp,
  intro hnp,
  contradiction,
end

theorem doubleneg_elim :
  ¬¬P → P  :=
begin
  intro hnnp,
  by_cases P,
  assumption,
  have hboom : false := hnnp h,
  contradiction,

end

theorem doubleneg_law :
  ¬¬P ↔ P  :=
begin
  split,
  apply doubleneg_elim,
  apply doubleneg_intro,
end

------------------------------------------------
-- Comutatividade dos ∨,∧:
------------------------------------------------

theorem disj_comm :
  (P ∨ Q) → (Q ∨ P)  :=
begin
  intro hor,
  cases hor with hp hq,
  right,
  assumption,
  left,
  assumption,
end

theorem conj_comm :
  (P ∧ Q) → (Q ∧ P)  :=
begin
  intro hand,
  cases hand with hp hq,
  split,
  assumption,
  assumption,

end

------------------------------------------------
-- Proposições de interdefinabilidade dos →,∨:
------------------------------------------------

theorem impl_as_disj_converse :
  (¬P ∨ Q) → (P → Q)  :=
begin
  intro hor,
  intro hp,
  cases hor with hnp hq,
  contradiction,
  assumption,
end

theorem disj_as_impl :
  (P ∨ Q) → (¬P → Q)  :=
begin
  intro hor,
  intro hnp,
  cases hor with hp hq,
  contradiction,
  assumption,
end

------------------------------------------------
-- Proposições de contraposição:
------------------------------------------------

theorem impl_as_contrapositive :
  (P → Q) → (¬Q → ¬P)  :=
begin
  intro hpq,
  intro hnq,
  intro hp,
  have hq : Q := hpq hp,
  contradiction,
end

theorem impl_as_contrapositive_converse :
  (¬Q → ¬P) → (P → Q)  :=
begin
  intro hnqnp,
  intro hp,
  by_contra hnq,
  have hnp : ¬P := hnqnp hnq,
  contradiction,
end

theorem contrapositive_law :
  (P → Q) ↔ (¬Q → ¬P)  :=
begin
  split,
  apply impl_as_contrapositive,
  apply impl_as_contrapositive_converse,
end

------------------------------------------------
-- A irrefutabilidade do LEM:
------------------------------------------------

theorem lem_irrefutable :
  ¬¬(P∨¬P)  :=
begin
  intro hnor,
  apply hnor,
  right,
  intro hp,
  apply hnor,
  left,
  assumption,
end

------------------------------------------------
-- A lei de Peirce
------------------------------------------------

theorem peirce_law_weak :
  ((P → Q) → P) → ¬¬P  :=
begin
  intro hpqp,
  intro hnp,
  apply hnp,
  apply hpqp,
  intro hp,
  contradiction,
end

------------------------------------------------
-- Proposições de interdefinabilidade dos ∨,∧:
------------------------------------------------

theorem disj_as_negconj :
  P∨Q → ¬(¬P∧¬Q)  :=
begin
  intro hor,
  intro hand,
  cases hand with hnp hnq,
  cases hor with hp hq,
  contradiction,
  contradiction,
end

theorem conj_as_negdisj :
  P∧Q → ¬(¬P∨¬Q)  :=
begin
  intro hand,
  intro hor,
  cases hand with hp hq,
  cases hor with hnp hnq,
  contradiction,
  contradiction,
end

------------------------------------------------
-- As leis de De Morgan para ∨,∧:
------------------------------------------------

theorem demorgan_disj :
  ¬(P∨Q) → (¬P ∧ ¬Q)  :=
begin
  intro hnor,
  split,
  intro hp,
  apply hnor,
  left,
  assumption,
  intro hq,
  apply hnor,
  right,
  assumption,
end

theorem demorgan_disj_converse :
  (¬P ∧ ¬Q) → ¬(P∨Q)  :=
begin
  intro hand,
  intro hor,
  cases hand with hnp hnq,
  cases hor with hp hq,
  contradiction,
  contradiction,
end

theorem demorgan_conj :
  ¬(P∧Q) → (¬Q ∨ ¬P)  :=
begin
  intro hnand,
  by_contra hnor,
  apply hnand,
  split,
  by_contra hnp,
  apply hnor,
  right,
  assumption,
  by_contra hnq,
  apply hnor,
  left,
  assumption,
end

theorem demorgan_conj_converse :
  (¬Q ∨ ¬P) → ¬(P∧Q)  :=
begin
  intro hor,
  intro hand,
  cases hand with hp hq,
  cases hor with hnq hnp,
  contradiction,
  contradiction,
end

theorem demorgan_conj_law :
  ¬(P∧Q) ↔ (¬Q ∨ ¬P)  :=
begin
  split,
  apply demorgan_conj,
  apply demorgan_conj_converse,
end

theorem demorgan_disj_law :
  ¬(P∨Q) ↔ (¬P ∧ ¬Q)  :=
begin
  split,
  apply demorgan_disj,
  apply demorgan_disj_converse,
end

------------------------------------------------
-- Proposições de distributividade dos ∨,∧:
------------------------------------------------

theorem distr_conj_disj :
  P∧(Q∨R) → (P∧Q)∨(P∧R)  :=
begin
  intro hand,
  cases hand with hp hor,
  cases hor with hq hr,
  left,
  split,
  assumption,
  assumption,
  right,
  split,
  assumption,
  assumption,
end

theorem distr_conj_disj_converse :
  (P∧Q)∨(P∧R) → P∧(Q∨R)  :=
begin
  intro hor,
  cases hor with handpq handpr,
  split,
  cases handpq with hp hq,
  assumption,
  cases handpq with hp hq,
  left,
  assumption,
  split,
  cases handpr with hp hr,
  assumption,
  cases handpr with hp hr,
  right,
  assumption,
end

theorem distr_disj_conj :
  P∨(Q∧R) → (P∨Q)∧(P∨R)  :=
begin
  intro hor,
  cases hor with hp handqr,
  split,
  left,
  assumption,
  left,
  assumption,
  cases handqr with hq hr,
  split,
  right,
  assumption,
  right,
  assumption,
end

theorem distr_disj_conj_converse :
  (P∨Q)∧(P∨R) → P∨(Q∧R)  :=
begin
  intro hand,
  cases hand with horpq horpr,
  cases horpr with hp hr,
  left,
  assumption,
  cases horpq with hp hq,
  left,
  assumption,
  right,
  split,
  assumption,
  assumption,
end

------------------------------------------------
-- Currificação
------------------------------------------------

theorem curry_prop :
  ((P∧Q)→R) → (P→(Q→R))  :=
begin
  intro handr,
  intro hp,
  intro hq,
  apply handr,
  split,
  assumption,
  assumption,
end

theorem uncurry_prop :
  (P→(Q→R)) → ((P∧Q)→R)  :=
begin
  intro hpqr,
  intro hand,
  cases hand with hp hq,
  apply hpqr,
  assumption,
  assumption,
end

------------------------------------------------
-- Reflexividade da →:
------------------------------------------------

theorem impl_refl :
  P → P  :=
begin
  intro hp,
  assumption,
end

------------------------------------------------
-- Weakening and contraction:
------------------------------------------------

theorem weaken_disj_right :
  P → (P∨Q)  :=
begin
  intro hp,
  left,
  assumption,
end

theorem weaken_disj_left :
  Q → (P∨Q)  :=
begin
  intro hq,
  right,
  assumption,
end

theorem weaken_conj_right :
  (P∧Q) → P  :=
begin
  intro hand,
  cases hand with hp hq,
  assumption,
end

theorem weaken_conj_left :
  (P∧Q) → Q  :=
begin
  intro hand,
  cases hand with hp hq,
  assumption,
end

theorem conj_idempot :
  (P∧P) ↔ P :=
begin
  split,
  apply weaken_conj_right,
  intro hp,
  split,
  assumption,
  assumption,
end

theorem disj_idempot :
  (P∨P) ↔ P  :=
begin
  split,
  intro hor,
  cases hor with hp hp,
  assumption,
  assumption,
  apply weaken_disj_right,
end

end propositional

----------------------------------------------------------------

section predicate

variable U : Type
variables P Q : U -> Prop

------------------------------------------------
-- As leis de De Morgan para ∃,∀:
------------------------------------------------

theorem demorgan_exists :
  ¬(∃x, P x) → (∀x, ¬P x)  :=
begin
  intro hnepx,
  intro x,
  intro hpx,
  apply hnepx,
  existsi x,
  assumption,
end

theorem demorgan_exists_converse :
  (∀x, ¬P x) → ¬(∃x, P x)  :=
begin
  intro hforallnpx,
  intro hepx,
  cases hepx with x hpx,
  have hnpx : ¬P x := hforallnpx x,
  contradiction,
end

theorem demorgan_forall :
  ¬(∀x, P x) → (∃x, ¬P x)  :=
begin
  intro hnforallpx,
  by_contra hnenpx,
  apply hnforallpx,
  intro x,
  by_contra hnpx,
  apply hnenpx,
  existsi x,
  assumption,

end

theorem demorgan_forall_converse :
  (∃x, ¬P x) → ¬(∀x, P x)  :=
begin
  intro henpx,
  intro hforallpx,
  cases henpx with x hnpx,
  have hpx : P x := hforallpx x,
  contradiction,
end

theorem demorgan_forall_law :
  ¬(∀x, P x) ↔ (∃x, ¬P x)  :=
begin
  split,
  apply demorgan_forall,
  apply demorgan_forall_converse,
end

theorem demorgan_exists_law :
  ¬(∃x, P x) ↔ (∀x, ¬P x)  :=
begin
  split,
  apply demorgan_exists,
  apply demorgan_exists_converse,
end

------------------------------------------------
-- Proposições de interdefinabilidade dos ∃,∀:
------------------------------------------------

theorem exists_as_neg_forall :
  (∃x, P x) → ¬(∀x, ¬P x)  :=
begin
  intro hepx,
  intro hforallnpx,
  cases hepx with x hpx,
  have hnpx : ¬P x := hforallnpx x,
  contradiction,
end

theorem forall_as_neg_exists :
  (∀x, P x) → ¬(∃x, ¬P x)  :=
begin
  intro hforallpx,
  intro henpx,
  cases henpx with x hnpx,
  have hpx : P x := hforallpx x,
  contradiction,
end

theorem forall_as_neg_exists_converse :
  ¬(∃x, ¬P x) → (∀x, P x)  :=
begin
  intro hnenpx,
  intro x,
  by_contra hnpx,
  apply hnenpx,
  existsi x,
  assumption,
end

theorem exists_as_neg_forall_converse :
  ¬(∀x, ¬P x) → (∃x, P x)  :=
begin
  intro hnforallnpx,
  by_contra hnepx,
  apply hnforallnpx,
  intro x,
  by_contra hpx,
  apply hnepx,
  existsi x,
  assumption,
end

theorem forall_as_neg_exists_law :
  (∀x, P x) ↔ ¬(∃x, ¬P x)  :=
begin
  split,
  apply forall_as_neg_exists,
  apply forall_as_neg_exists_converse,
end

theorem exists_as_neg_forall_law :
  (∃x, P x) ↔ ¬(∀x, ¬P x)  :=
begin
  split,
  apply exists_as_neg_forall,
  apply exists_as_neg_forall_converse,
end

------------------------------------------------
--  Proposições de distributividade de quantificadores:
------------------------------------------------

theorem exists_conj_as_conj_exists :
  (∃x, P x ∧ Q x) → (∃x, P x) ∧ (∃x, Q x)  :=
begin
  intro heand,
  cases heand with x hand,
  cases hand with hpx hqx,
  split,
  existsi x,
  assumption,
  existsi x,
  assumption,
end

theorem exists_disj_as_disj_exists :
  (∃x, P x ∨ Q x) → (∃x, P x) ∨ (∃x, Q x)  :=
begin
  intro heor,
  cases heor with x hor,
  cases hor with hpx hqx,
  left,
  existsi x,
  assumption,
  right,
  existsi x,
  assumption,
end

theorem exists_disj_as_disj_exists_converse :
  (∃x, P x) ∨ (∃x, Q x) → (∃x, P x ∨ Q x)  :=
begin
  intro hor,
  cases hor with hepx heqx,
  cases hepx with x hpx,
  existsi x,
  left,
  assumption,
  cases heqx with x hqx,
  existsi x,
  right,
  assumption,
end

theorem forall_conj_as_conj_forall :
  (∀x, P x ∧ Q x) → (∀x, P x) ∧ (∀x, Q x)  :=
begin
  intro hforalland,
  split,
  intro x,
  have hand : P x ∧ Q x := hforalland x,
  cases hand with hpx hqx,
  assumption,
  intro x,
  have hand : P x ∧ Q x := hforalland x,
  cases hand with hpx hqx,
  assumption,
end

theorem forall_conj_as_conj_forall_converse :
  (∀x, P x) ∧ (∀x, Q x) → (∀x, P x ∧ Q x)  :=
begin
  intro hand,
  cases hand with hforallpx hforallqx,
  intro x,
  split,
  have hpx : P x := hforallpx x,
  assumption,
  have hqx : Q x := hforallqx x,
  assumption,
end

theorem forall_disj_as_disj_forall_converse :
  (∀x, P x) ∨ (∀x, Q x) → (∀x, P x ∨ Q x)  :=
begin
  intro hor,
  intro x,
  cases hor with hforallpx hforallqx,
  left,
  have hpx : P x := hforallpx x,
  assumption,
  right,
  have hqx : Q x := hforallqx x,
  assumption,
end

/- NOT THEOREMS --------------------------------

theorem forall_disj_as_disj_forall :
  (∀x, P x ∨ Q x) → (∀x, P x) ∨ (∀x, Q x)  :=
begin
end

theorem exists_conj_as_conj_exists_converse :
  (∃x, P x) ∧ (∃x, Q x) → (∃x, P x ∧ Q x)  :=
begin
end

---------------------------------------------- -/

end predicate


import tactic.push_neg
import data.int.order.basic
import data.nat.basic

example (h : ∃ p: ℕ, ¬ ∀ n : ℕ, n > p) (h' : ∃ p: ℕ, ¬ ∃ n : ℕ, n < p) : ¬ ∀ n : ℕ, n = 0 :=
begin
  push_neg at *,
  guard_target_strict ∃ (n : ℕ), n ≠ 0,
  guard_hyp_strict h : ∃ (p n : ℕ), n ≤ p,
  guard_hyp_strict h' : ∃ (p : ℕ), ∀ (n : ℕ), p ≤ n,
  use 1,
end

-- In the next example, ℤ should be ℝ in maths, but I don't want to import real numbers
-- for testing only

example (a : ℕ → ℤ) (l : ℤ) (h : ¬ ∀ ε > 0, ∃ N, ∀ n ≥ N, | a n - l | < ε) : true :=
begin
  push_neg at h,
  guard_hyp_strict h : ∃ (ε : ℤ), ε > 0 ∧ ∀ (N : ℕ), ∃ (n : ℕ), n ≥ N ∧ ε ≤ |a n - l|,
  trivial
end

example (f : ℤ → ℤ) (x₀ y₀) (h : ¬ ∀ ε > 0, ∃ δ > 0, ∀ x, |x - x₀| ≤ δ → |f x - y₀| ≤ ε) : true :=
begin
  push_neg at h,
  guard_hyp_strict h : ∃ (ε : ℤ), ε > 0 ∧ ∀ δ > 0, (∃ (x : ℤ), |x - x₀| ≤ δ ∧ ε < |f x - y₀| ),
  trivial
end

example (n) : n*n ≠ 1 → n ≠ 1 :=
begin
  contrapose,
  rw [not_not, not_not],
  intro h,
  rw [h, one_mul]
end

example (n) : n*n ≠ 1 → n ≠ 1 :=
begin
  contrapose!,
  intro h,
  rw [h, one_mul]
end

example (n) (h : n*n ≠ 1) : n ≠ 1 :=
begin
  contrapose h,
  rw not_not at *,
  rw [h, one_mul]
end

example (n) (h : n*n ≠ 1) : n ≠ 1 :=
begin
  contrapose! h,
  rw [h, one_mul]
end

example (n) (h : n*n ≠ 1) : n ≠ 1 :=
begin
  contrapose! h with newh,
  rw [newh, one_mul]
end

example : 0 = 0 :=
begin
  success_if_fail_with_msg { contrapose }
    "The goal is not an implication, and you didn't specify an assumption",
  refl
end

-- Remember that ∀ is the same as Π which is a generalization of → so we need to make sure
-- `contrapose` fails with a helpful error message in the next example.
example : ∀ x : ℕ, x = x :=
begin
  success_if_fail_with_msg { contrapose }
    "contrapose only applies to nondependent arrows between props",
  intro, refl
end

open tactic
example (X : Type) (f : X → ℕ) (h : ¬∀ x, f x = 0) (hf : false) : false :=
begin
  have h1 := h,
  -- h h1: ¬∀ (x : X), f x = 0
  push_neg at h,
  push_neg at h1,
  (do ht ← get_local `h >>= infer_type,
      h1t ← get_local `h1 >>= infer_type,
      guard (ht = h1t) ),
  exact hf
end

/-! Test the option `trace.push_neg.use_distrib` for changing the normal form of `¬(P ∧ Q)`. -/

section

example (a b : ℤ) (h : ¬ (∃ x, (a < x ∧ x < b))) : ∀ x, a < x → b ≤ x :=
begin
  push_neg at h,
  exact h,
end

set_option trace.push_neg.use_distrib true

example (a b : ℤ) (h : ¬ (∃ x, (a < x ∧ x < b))) : ∀ x, x ≤ a ∨ b ≤ x :=
begin
  push_neg at h,
  exact h,
end

end


-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    f '' s ⊆ t ↔ s ⊆ f ⁻¹' t
-- ----------------------------------------------------------------------

import data.set.function

universes u v
variable  α : Type u
variable  β : Type v
variable  f : α → β
variables (s : set α) (t : set β)

open set

example : f '' s ⊆ t ↔ s ⊆ f ⁻¹' t :=
begin
  split,
  { intros h x xs,
    show f x ∈ t,
      { calc f x ∈ f '' s : mem_image_of_mem f xs
             ... ⊆ t      : h }},
  { intros h y hy,
    rcases hy with ⟨x,xs,fxy⟩,
    rw ← fxy,
    apply h,
    exact xs },
end

-- Prueba
-- ======

/-
α : Type u,
β : Type v,
f : α → β,
s : set α,
t : set β
⊢ f '' s ⊆ t ↔ s ⊆ f ⁻¹' t
  >> split,
| ⊢ f '' s ⊆ t → s ⊆ f ⁻¹' t
|   >> { intros h x xs,
| h : f '' s ⊆ t,
| x : α,
| xs : x ∈ s
| ⊢ x ∈ f ⁻¹' t
|   >>   show f x ∈ t,
|   >>     { calc f x ∈ f '' s : mem_image_of_mem f xs
|   >>            ... ⊆ t      : h }},
⊢ s ⊆ f ⁻¹' t → f '' s ⊆ t
  >> { intros h y hy,
h : s ⊆ f ⁻¹' t,
y : β,
hy : y ∈ f '' s
⊢ y ∈ t
  >>   rcases hy with ⟨x,xs,fxy⟩,
x : α,
xs : x ∈ s,
fxy : f x = y
⊢ y ∈ t
  >>   rw ← fxy,
⊢ f x ∈ t
  >>   apply h,
⊢ x ∈ s
  >>   exact xs },
no goals
-/


-- ---------------------------------------------------------------------
-- 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
--    a - b = a + -b
-- ----------------------------------------------------------------------

-- 1ª demostración
theorem sub_eq_add_neg  : a - b = a + -b :=
-- by library_search
sub_eq_add_neg a b

end my_ring


import data.list.basic tactic.linarith
import list.lemmas.long_lemmas  misc_lemmas

open list
/-- The minimum element of a non-empty list-/
def list.min {X : Type*} [decidable_linear_order X] :
Π (L : list X), (L ≠ []) → X
| ([]) hL := false.elim $ hL rfl
| (x :: []) hL := x
| (x :: y :: L) _ := min x (list.min (y :: L) dec_trivial)

/--extended version of list.min on lists of integers that maps the empty list to 0-/
def list.min' (L : list ℤ) : ℤ :=
if h : L = [] then 0 else list.min L h

/--If we have two non-empty lists of the same length and for each index, elements with the same
   index differ by at most d, then their minimums differ by at most d-/
theorem list.min_change (L M : list ℤ) (hL : L ≠ []) (hLM : L.length = M.length) (hM : M ≠ []) (d : ℤ)
(hdist : ∀ (i : ℕ) (hiL : i < L.length) (hiM : i < M.length), abs (L.nth_le i hiL - M.nth_le i hiM) ≤ d) :
  abs (L.min hL - M.min hM) ≤ d :=
begin
  -- if L = nil, that contradicts hL
  cases L with n Ln,
    contradiction,
  -- L = (n :: Ln)
  revert n M, -- so the inductive hypothesis does not depend on n or M
  induction Ln with n1 Ln1 IH,

    -- base case : Ln = nil (so L = [n])
    {
      intros n M hM hL hLM hdist,
      change 1 = length M at hLM, -- as L = [n], and length [n] = length (n :: nil) = 1 by definition
      symmetry' at hLM, -- change 1 = length M to length M = 1
      rw length_eq_one at hLM, -- now hLM implies M is a singleton
      cases hLM with m hLM, -- say M = [m], for some a of type ℕ
      simp only [hLM],
      show abs (n - m) ≤ d, /- because by def of list.min the minimum of a singleton
                               is its only element -/
      convert hdist 0 (zero_lt_one) _, -- that is true because by hdist, abs (L.nth_le 0 hiL - M.nth_le 0 hiM) ≤ d
      -- have already told Lean i is 0 and 0 < 1 (which is the length of [n] by def)
      -- prove 0 < length M
        swap,
        {rw hLM,
        exact zero_lt_one},
      /- now prove m = nth_le M 0 _ (which convert reduced the goal to as it Lean noticed
                                     all other similarities to hdist)-/
      simp only [hLM], --
      refl, -- by def of nth_le
    },

     --inductive step : Ln = (n1 :: Ln1)
    { intros n M hM hL hLM hdist,
      -- if M = nil, that contradicts hM
      cases M with m Mm,
        contradiction,
      -- now M = (m :: Mm)
      -- but if Mm = nil, then by hLM length (n :: n1 :: Ln1) = length (m :: nil). Contradiction!
      cases Mm with m1 Mm1,
        cases hLM,
      -- so M = (m :: m1 :: Mm1)
      unfold list.min, /- changes list.min (n :: n1 :: Ln1) hL to min n (list.min (n1 :: Ln1) _)
                           and list.min (m :: m1 :: Mm1) hM to min m (list.min (m1 :: Mm1) _)
                           (third constructor of list.min)-/
      /- we want to use the inductive hypothesis with M being (m1 :: Mm1)-/
      replace hLM := nat.succ_inj hLM, -- get : length (n1 :: Ln1) = length (m1 :: Mm1)
      -- the following is the version of hdist for M being (m1 :: Mm1)
      have hyp : (∀ (i : ℕ) (hiL : i < length (n1 :: Ln1)) (hiM : i < length (m1 :: Mm1)),
                 abs (nth_le (n1 :: Ln1) i hiL - nth_le (m1 :: Mm1) i hiM) ≤ d),
        intros i hiL hiM,
        exact hdist (i + 1) (nat.succ_lt_succ hiL) (nat.succ_lt_succ hiM), -- follows from hdist
      /- use the inductive hypothesis to get (h : abs (list.min (n1 :: Ln1) _ - list.min (m1 :: Mm1) _) ≤ d)
         (the other hypotheses, that the lists are non-empty, are true by decidability)-/
      have h := IH n1 (m1 :: Mm1) dec_trivial dec_trivial hLM hyp,
      have hmn : abs (n - m) ≤ d,
        exact hdist 0 dec_trivial dec_trivial, -- n, m are the 0th elements, so holds by hdist
      exact abs_min_sub_min hmn h} -- (see misc_lemmas)
end

/--If we have two lists of the same length and for each index, elements with the same
   index differ by at most some non-negative value d, then their extended minimums (min') differ by at most d-/
lemma list.min'_change (L M : list ℤ) (hLM : L.length = M.length) (d : ℤ) (hd : 0 ≤ d)
(hdist : ∀ (i : ℕ) (hiL : i < L.length) (hiM : i < M.length), abs (L.nth_le i hiL - M.nth_le i hiM) ≤ d) :
  abs (L.min' - M.min') ≤ d :=
  begin
  /- if M and L are non-empty, this is basically proven by the lemma above, because of
     how list.min' is defined.
     Therefore we prove the empty-list-case separately-/
  cases M with m Mn,
    -- M = nil (so hLM is : length L = length nil)
    {rw length at hLM, -- length nil = 0 by def of length
    rw length_eq_zero at hLM, -- length L = 0 ↔ L = nil
    rw hLM,
    -- now this is true by the definition of list.min' and hd
    unfold list.min',
    split_ifs,
      {exact hd}, -- case where nil = nil
      {contradiction},}, -- case where ¬ (nil = nil), a contradiction

  -- M = (m :: Mn)
  /- now this is true by lis.min_change, but we first need to show M and N are non-empty-/
  {have hM : ¬ (m :: Mn : list ℤ) = nil := by simp,
  have hL : ¬ L = nil,
    {apply ne_nil_of_length_pos, -- prove 0 < length L instead
    rw hLM,
    exact dec_trivial,}, -- true by decideability

  unfold list.min',
  split_ifs, -- (L = nil)-case killed immediately
  exact list.min_change L (m :: Mn) hL hLM hM d hdist,},

  end


import tactic.group

variables {G : Type} [group G]

example (a b c d : G) : c *(a*b)*(b⁻¹*a⁻¹)*c = c*c :=
by group

example (a b c d : G) : (b*c⁻¹)*c *(a*b)*(b⁻¹*a⁻¹)*c = b*c :=
by group

example (a b c d : G) : c⁻¹*(b*c⁻¹)*c *(a*b)*(b⁻¹*a⁻¹*b⁻¹)*c = 1 :=
by group

-- The following is known as the Hall-Witt identity,
-- see e.g.
-- https://en.wikipedia.org/wiki/Three_subgroups_lemma#Proof_and_the_Hall%E2%80%93Witt_identity
example (g h k : G) :
  g * ⁅⁅g⁻¹, h⁆, k⁆ * g⁻¹ * k * ⁅⁅k⁻¹, g⁆, h⁆ * k⁻¹ * h *⁅⁅h⁻¹, k⁆, g⁆ * h⁻¹ = 1 :=
by group

example (a : G) : a^2*a = a^3 :=
by group

example (n m : ℕ) (a : G) : a^n*a^m = a^(n+m) :=
by group

example (a b c d : G) : c *(a*b^2)*((b*b)⁻¹*a⁻¹)*c = c*c :=
by group

example (n m : ℕ) (a : G) : a^n*(a⁻¹)^n = 1 :=
by group

example (n m : ℕ) (a : G) : a^2*a⁻¹*a⁻¹ = 1 :=
by group

example (n m : ℕ) (a : G) : a^n*a^m = a^(m+n) :=
by group

example (n : ℕ) (a : G) : a^(n-n) = 1 :=
by group

example (n : ℤ) (a : G) : a^(n-n) = 1 :=
by group

example (n : ℤ) (a : G) (h : a^(n*(n+1)-n -n^2) = a) : a = 1 :=
begin
 group at h,
 exact h.symm,
 end

example (a b c d : G) (h : c = (a*b^2)*((b*b)⁻¹*a⁻¹)*d) : a*c*d⁻¹ = a :=
begin
  group at h,
  rw h,
  group,
end

-- The next example can be expand to require an arbitrarily high number of alternation
-- between simp and ring

example (n m : ℤ) (a b : G) : a^n*b^n*a^n*a^n*a^-n*a^-n*b^-n*a^-n = 1 :=
by group

-- Test that group deals with `1⁻¹` properly

example (x y : G) : (x⁻¹ * (x * y) * y⁻¹)⁻¹ = 1 :=
by group

example (x : G) (h : x = 1) : x = 1 :=
begin
  group,
  exact h,
end


/-
Copyright (c) 2016 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jeremy Avigad, Leonardo de Moura
-/

import Mathlib.Init.Algebra.Order

universe u

section

variable {α : Type u} [LinearOrder α]

lemma min_def (a b : α) : min a b = if a ≤ b then a else b := LinearOrder.min_def ..

lemma min_le_left (a b : α) : min a b ≤ a := by
  if h : a ≤ b
  then simp [min_def, if_pos h, le_refl]
  else simp [min_def, if_neg h]; exact le_of_not_le h

lemma min_le_right (a b : α) : min a b ≤ b := by
  if h : a ≤ b
  then simp [min_def, if_pos h]; exact h
  else simp [min_def, if_neg h, le_refl]

lemma le_min {a b c : α} (h₁ : c ≤ a) (h₂ : c ≤ b) : c ≤ min a b := by
  if h : a ≤ b
  then simp [min_def, if_pos h]; exact h₁
  else simp [min_def, if_neg h]; exact h₂

lemma max_def (a b : α) : max a b = if a ≤ b then b else a := LinearOrder.max_def ..

lemma le_max_left (a b : α) : a ≤ max a b := by
  if h : a ≤ b
  then simp [max_def, if_pos h]; exact h
  else simp [max_def, if_neg h, le_refl]

lemma le_max_right (a b : α) : b ≤ max a b := by
  if h : a ≤ b
  then simp [max_def, if_pos h]
  else simp [max_def, if_neg h]; exact le_of_not_le h

lemma max_le {a b c : α} (h₁ : a ≤ c) (h₂ : b ≤ c) : max a b ≤ c := by
  if h : a ≤ b
  then simp [max_def, if_pos h]; exact h₂
  else simp [max_def, if_neg h]; exact h₁

lemma eq_min {a b c : α} (h₁ : c ≤ a) (h₂ : c ≤ b) (h₃ : ∀{d}, d ≤ a → d ≤ b → d ≤ c) :
  c = min a b :=
le_antisymm (le_min h₁ h₂) (h₃ (min_le_left a b) (min_le_right a b))

lemma min_comm (a b : α) : min a b = min b a :=
eq_min (min_le_right a b) (min_le_left a b) (λ {_} h₁ h₂ => le_min h₂ h₁)

lemma min_assoc (a b c : α) : min (min a b) c = min a (min b c) :=
by apply eq_min
   . apply le_trans; apply min_le_left; apply min_le_left
   . apply le_min; apply le_trans; apply min_le_left; apply min_le_right; apply min_le_right
   . intros d h₁ h₂; apply le_min; apply le_min h₁; apply le_trans h₂; apply min_le_left;
     apply le_trans h₂; apply min_le_right

lemma min_left_comm : @LeftCommutative α α min :=
left_comm min (@min_comm α _) (@min_assoc α _)

@[simp]
lemma min_self (a : α) : min a a = a := by simp [min_def]

lemma min_eq_left {a b : α} (h : a ≤ b) : min a b = a :=
by apply Eq.symm; apply eq_min (le_refl _) h; intros; assumption

lemma min_eq_right {a b : α} (h : b ≤ a) : min a b = b :=
by rw [min_comm]
   exact min_eq_left h

lemma eq_max {a b c : α} (h₁ : a ≤ c) (h₂ : b ≤ c) (h₃ : ∀{d}, a ≤ d → b ≤ d → c ≤ d) :
    c = max a b :=
le_antisymm (h₃ (le_max_left a b) (le_max_right a b)) (max_le h₁ h₂)

lemma max_comm (a b : α) : max a b = max b a :=
eq_max (le_max_right a b) (le_max_left a b) (λ {_} h₁ h₂ => max_le h₂ h₁)

lemma max_assoc (a b c : α) : max (max a b) c = max a (max b c) := by
  apply eq_max
  · apply le_trans; apply le_max_left a b; apply le_max_left
  · apply max_le; apply le_trans; apply le_max_right a b; apply le_max_left; apply le_max_right
  · intros d h₁ h₂; apply max_le; apply max_le h₁; apply le_trans (le_max_left _ _) h₂;
    apply le_trans (le_max_right _ _) h₂

lemma max_left_comm : ∀ (a b c : α), max a (max b c) = max b (max a c) :=
left_comm max (@max_comm α _) (@max_assoc α _)

@[simp] lemma max_self (a : α) : max a a = a := by simp [max_def]

lemma max_eq_left {a b : α} (h : b ≤ a) : max a b = a :=
by apply Eq.symm; apply eq_max (le_refl _) h; intros; assumption

lemma max_eq_right {a b : α} (h : a ≤ b) : max a b = b :=
by rw [←max_comm b a]; exact max_eq_left h

/- these rely on lt_of_lt -/

lemma min_eq_left_of_lt {a b : α} (h : a < b) : min a b = a :=
min_eq_left (le_of_lt h)

lemma min_eq_right_of_lt {a b : α} (h : b < a) : min a b = b :=
min_eq_right (le_of_lt h)

lemma max_eq_left_of_lt {a b : α} (h : b < a) : max a b = a :=
max_eq_left (le_of_lt h)

lemma max_eq_right_of_lt {a b : α} (h : a < b) : max a b = b :=
max_eq_right (le_of_lt h)

/- these use the fact that it is a linear ordering -/

lemma lt_min {a b c : α} (h₁ : a < b) (h₂ : a < c) : a < min b c :=
Or.elim (le_or_gt b c)
  (λ h : b ≤ c => by rwa [min_eq_left h])
  (λ h : b > c => by rwa [min_eq_right_of_lt h])

lemma max_lt {a b c : α} (h₁ : a < c) (h₂ : b < c) : max a b < c :=
Or.elim (le_or_gt a b)
  (λ h : a ≤ b => by rwa [max_eq_right h])
  (λ h : a > b => by rwa [max_eq_left_of_lt h])

end


/-
Copyright (c) 2020. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jalex Stark, Yury Kudryashov
-/
import data.real.basic

/-!
# Sum of the Reciprocals of the Triangular Numbers

This file proves Theorem 42 from the [100 Theorems List](https://www.cs.ru.nl/~freek/100/).

We interpret “triangular numbers” as naturals of the form $\frac{k(k+1)}{2}$ for natural `k`.
We prove that the sum of the first `n` triangular numbers is equal to $2 - \frac2n$.

## Tags

discrete_sum
-/

open_locale big_operators
open finset

lemma inverse_triangle_sum :
  ∀ n, ∑ k in range n, (2 : ℚ) / (k * (k + 1)) = if n = 0 then 0 else 2 - (2 : ℚ) / n :=
begin
  refine sum_range_induction _ _ (if_pos rfl) _,
  rintro (_|n), { rw [if_neg, if_pos]; norm_num },
  simp_rw [if_neg (nat.succ_ne_zero _), nat.succ_eq_add_one],
  have A : (n + 1 + 1 : ℚ) ≠ 0, by { norm_cast, norm_num },
  push_cast,
  field_simp [nat.cast_add_one_ne_zero],
  ring
end


class myfld (f : Type) :=
(add : f -> f -> f)
(mul : f -> f -> f)
(negate : f -> f)
(zero : f)
(reciprocal : ∀ (x : f), (x ≠ zero) -> f) /- Reciprocal of zero is undefined.-/
(one : f)
(add_assoc : ∀ (x y z : f), (add x (add y z)) = (add (add x y) z))
(mul_assoc : ∀ (x y z : f), (mul x (mul y z)) = (mul (mul x y) z))
(add_comm : ∀ (x y : f), (add x y) = (add y x))
(mul_comm : ∀ (x y : f), (mul x y) = (mul y x))
(add_zero : ∀ (x : f), x = (add x zero))
(mul_one : ∀ (x : f), x = (mul x one))
(add_negate : ∀ (x : f), (add x (negate x)) = zero)
(mul_reciprocal : ∀ (x : f), ∀ (proof : x ≠ zero), (mul x (reciprocal x proof)) = one)
(distrib : ∀ (x y z : f), (add (mul x z) (mul y z)) = (mul (add x y) z))
(zero_distinct_one : one ≠ zero) /- By definition, 0 and 1 can't be the same element. -/

infix ` .+ `:1000000 := myfld.add
infix ` .* `:1000001 := myfld.mul
prefix ` .- `:1000002 := myfld.negate

def square (f : Type) [myfld f] : f -> f
| x := x .* x

@[simp] lemma add_comm (f : Type) [myfld f] (x y : f) : x .+ y = y .+ x :=
begin
  exact (myfld.add_comm : ∀ (x y : f), (x .+ y) = (y .+ x)) x y,
end

@[simp] lemma one_mul_simp (f : Type) [myfld f] (x : f) : myfld.one .* x = x :=
begin
  rw myfld.mul_comm, have h : x = x .* myfld.one, exact myfld.mul_one x, cc,
end

@[simp] lemma add_assoc (f : Type) [myfld f] (x y z : f) : ((x .+ y) .+ z) = (x .+ (y .+ z)) :=
begin
  symmetry, exact myfld.add_assoc x y z,
end

@[simp] lemma mul_comm (f : Type) [myfld f] (x y : f) : x .* y = y .* x :=
begin
  exact myfld.mul_comm x y,
end

@[simp] lemma mul_assoc (f : Type) [myfld f] (x y z : f) : ((x .* y) .* z) = (x .* (y .* z)) :=
begin
  symmetry, exact myfld.mul_assoc x y z,
end

lemma zero_add (f : Type) [myfld f] (x : f) : x = (myfld.zero : f) .+ x :=
begin
  rw add_comm f (myfld.zero : f) x,
  exact (myfld.add_zero : ∀ (x : f), x = (x .+ myfld.zero)) x,
end

@[simp] lemma zero_simp (f : Type) [myfld f] (x : f) : (x .+ myfld.zero) = x :=
begin
  symmetry, exact myfld.add_zero x,
end

@[simp] lemma simp_zero (f : Type) [myfld f] (x : f) : (myfld.zero .+ x) = x :=
begin
  symmetry, exact zero_add f x,
end

@[simp] lemma distrib_simp (f : Type) [myfld f] (x y z : f) : ((x .+ y) .* z) = ((x .* z) .+ (y .* z)) :=
begin
  symmetry, exact myfld.distrib x y z,
end

@[simp] lemma distrib_simp_alt (f : Type) [myfld f] (x y z : f) : (x .* (y .+ z)) = (x .* y) .+ (x .* z) :=
begin
  rw myfld.mul_comm x (y .+ z), rw myfld.mul_comm x y, rw myfld.mul_comm x z,
  exact distrib_simp f y z x,
end

@[simp] lemma one_mul (f : Type) [myfld f] (x : f) : (myfld.one : f) .* x = x :=
begin
  rw (myfld.mul_comm), symmetry, exact (myfld.mul_one) x,
end

@[simp] lemma simp_mul_one (f : Type) [myfld f] (x : f) : x .* (myfld.one : f) = x :=
begin
  rw myfld.mul_comm, exact one_mul f x,
end

@[simp] lemma add_negate_simp (f : Type) [myfld f] (x : f) : x .+ .-x = myfld.zero :=
begin
  exact myfld.add_negate x,
end

@[simp] lemma add_negate_simp_alt (f : Type) [myfld f] (x : f) : (.- x) .+ x = myfld.zero :=
begin
  rw myfld.add_comm (.- x) x, exact add_negate_simp f x,
end

@[simp] lemma mul_reciprocal (f : Type) [myfld f] (x : f) (proof : x ≠ myfld.zero) :
                x .* (myfld.reciprocal x proof) = myfld.one :=
begin
  exact myfld.mul_reciprocal x proof,
end

lemma equal_ne_zero (f : Type) [myfld f] (x y : f) : (x = y) -> (x ≠ myfld.zero) -> (y ≠ myfld.zero) :=
begin
  intros h1 h2, rw h1 at h2, exact h2,
end


/- Exercise 1.2: Basics — Proofs -/

/- Question 1: Natural numbers -/

namespace my_nat

def add : ℕ → ℕ → ℕ
| 0            n := n
| (nat.succ m) n := nat.succ (add m n)

-- def add : nat → nat → nat
-- | m nat.zero     := m
-- | m (nat.succ n) := nat.succ (add m n)

-- lemma natzero (m : nat) : add m 0 = m:=
-- begin
-- induction m,
-- simp[add],
-- refl
-- end

-- lemma test (m : nat) :  add 0 m = m:=
-- begin
-- induction m,
-- refl,
-- simp[add]

-- end

-- lemma add_commm (m n : nat) : add m n = add n m :=
-- begin
-- induction m,
-- simp[natzero, add],

-- end

/- We proved these properties already in the lecture, so we take them as axioms here. (We will often
use `sorry` like this to avoid clutter in exercise sheets. But keep in mind that `sorry` is a risky
construct.) -/

lemma add_zero : ∀m : ℕ, add m 0 = m
| (nat.succ m) := by simp [add, add_zero m]
| 0            := by refl

lemma add_succ : ∀m n : ℕ, add m (nat.succ n) = nat.succ (add m n)
| m 0 := by refl

lemma add_comm : ∀m n : ℕ, add m n = add n m := sorry
lemma add_assoc : ∀l m n : ℕ, add (add l m) n = add l (add m n) := sorry

instance : is_commutative ℕ add := ⟨add_comm⟩
instance : is_associative ℕ add := ⟨add_assoc⟩

def mul : ℕ → ℕ → ℕ
| 0            _ := 0
| (nat.succ m) n := add n (mul m n)

lemma mul_add (m n : ℕ) : ∀l : ℕ, mul (add l m) n = add (mul l n) (mul m n) := sorry

/- 1.1. Prove the following recursive equations on the second argument of `mul`. -/

lemma mul_zero : ∀m : ℕ, mul m 0 = 0
:= sorry

lemma mul_succ : ∀m n : ℕ, mul m (nat.succ n) = add (mul m n) m
:= sorry

/- 1.2. Prove commutativity and associativity of multiplication by induction. Choose the induction
variable carefully. Use the pattern matching syntax. -/

lemma mul_comm : ∀m n : ℕ, mul m n = mul n m
:= sorry

lemma mul_assoc : ∀l m n : ℕ, mul (mul l m) n = mul l (mul m n)
:= sorry

/- 1.3. Prove the same lemmas as in 1.2, but using the `induction` tactic instead. Try to reuse as
much of the above proof as possible. -/

example (m n : ℕ) : mul m n = mul n m :=
sorry

example : ∀l m n : ℕ, mul (mul l m) n = mul l (mul m n) :=
sorry

/- 1.4. Prove the symmetric variant of `mul_add` using `rw`. To apply commutativity at a specific
position instantiate the rule (e.g. `mul_comm n`). -/

lemma add_mul (m n l : ℕ) : mul n (add l m) = add (mul n l) (mul n m) :=
sorry

end my_nat

/- Question 2: Lists -/

def reverse {α : Type} : list α → list α
| []        := []
| (x :: xs) := reverse xs ++ [x]

/- Another pesky `sorry`. -/
lemma reverse_reverse {α : Type} (xs : list α) : reverse (reverse xs) = xs := sorry

/- We define a new accumulator-based version of `reverse`. The first argument serves as the
accumulator. This definition is *tail-recursive*, meaning that compilers and interpreters can easily
optimize the recursion away, resulting in more efficient code. -/

def areverse {α : Type} : list α → list α → list α
| ys []        := ys
| ys (x :: xs) := areverse (x :: ys) xs

/- 2.1. Our intention is that `areverse [] xs` should be equal to `reverse xs`. But if we start an
induction, we quickly see that the induction hypothesis is not strong enough. Start by proving the
following generalization (using pattern matching or the `induction` tactic): -/

-- lemma areverse_eq_reverse_append {α : Type} : ∀ys xs : list α, areverse ys xs = reverse xs ++ ys
-- | [] xs := begin simp[reverse], simp[areverse_eq_reverse] end

/- 2.2. Derive the desired equation. -/

lemma areverse_eq_reverse {α : Type} (xs : list α) : areverse [] xs = reverse xs:=
begin induction xs, simp[areverse], simp[reverse], simp[areverse], simp[reverse], rw[<-xs_ih],  end

/- 2.3. Prove the following property. Hint: A one-line inductionless proof is possible. -/

lemma areverse_areverse {α : Type} (xs : list α) : areverse [] (areverse [] xs) = xs :=
:= sorry


/-
Copyright (c) 2022 Kevin Buzzard. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author : Kevin Buzzard
-/

import tactic -- imports all the Lean tactics
import data.real.basic -- the reals

/-!

# Sets in Lean, sheet 6 : pushforward and pullback

## Pushforward of a set along a map

If `f : X → Y` then given a subset `S : set X` of `X` we can push it
forward to make a subset `f(S) : set Y` of `Y`. The definition
of `f(S)` is `{y : Y | ∃ x : X, x ∈ S ∧ f x = y}`.

However `f(S)` doesn't make sense in Lean, because `f` eats
terms of type `X` and not `S`, which has type `set X`.
In Lean we use the notation `f '' S` for this. This is notation
for `set.image` and if you need any API for this, it's likely
to use the word `image`.

## Pullback of a set along a map

If `f : X → Y` then given a subset `T : set Y` of `Y` we can
pull it back to make a subset `f⁻¹(T) : set X` of `X`. The definition
of `f⁻¹(T)` is `{x : X | f x ∈ T}`.

However `f⁻¹(T)` doesn't make sense in Lean either, because
`⁻¹` is notation for `has_inv.inv`, whose type in Lean
is `α → α`. In other words, if `x` has a certain type, then
`x⁻¹` *must* have the same type. The notation was basically designed
for group theory. In Lean we use the notation `f ⁻¹' T` for this pullback.

-/

variables (X Y : Type) (f : X → Y) (S : set X) (T : set Y)

example : S ⊆ f ⁻¹' (f '' S) :=
begin
  sorry
end

example : f '' (f ⁻¹' T) ⊆ T :=
begin
  sorry
end

example : f '' S ⊆ T ↔ S ⊆ f ⁻¹' T :=
begin
  sorry
end

-- image and preimage form a Galois connection
-- look up Galois connections in the mathlib docs to learn about what these are
-- right click on `galois_connection` or do `unfold galois_connection` to
-- find out what this question's asking
-- NB `≤` on subsets is defined to be `⊆`
example : galois_connection (λ S, f '' S) (λ T, f ⁻¹' T) :=
begin
  sorry
end


import MyNat.Addition
import MyNat.Multiplication
import MultiplicationWorld.Level1 -- zero_mul
import MultiplicationWorld.Level4 -- mul_add
namespace MyNat
open MyNat

/-!
# Multiplication World

## Level 5: `mul_assoc`

We now have enough to prove that multiplication is associative.

## Random tactic hints

Did you know you can [repeat](../Tactics/repeat.lean.md) a tactic as many times as necessary
to complete the goal?

## Lemma

Multiplication is associative.
In other words, for all natural numbers `a`, `b` and `c`, we have
` (ab)c = a(bc). `
-/
lemma mul_assoc (a b c : MyNat) : (a * b) * c = a * (b * c) := by
  induction c with
  | zero =>
    rw [zero_is_0]
    repeat
      rw [mul_zero]
  | succ c ih =>
    rw [mul_succ]
    rw [mul_succ]
    rw [ih]
    rw [mul_add]

/-!
A mathematician could now remark that you have proved that the natural
numbers form a monoid under multiplication.
-/

-- instance : AddMonoid MyNat where
--   add_zero := add_zero
--   zero_add := zero_add
--   add_assoc := add_assoc
--   nsmul :=  λ x y => (myNatFromNat x) * y
--   nsmul_zero' := zero_mul
--   nsmul_succ' n x := by
--     simp

-- BUGBUG: complete these instances...

/-!
Next up is [Multiplication Level 6](./Level6.lean.md).
-/