/-
Copyright (c) 2015 Leonardo de Moura. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura, Haitao Zhang, Floris van Doorn

List combinators.
-/
import data.list.basic data.equiv
open nat prod decidable function helper_tactics algebra

namespace list
variables {A B C : Type}

section replicate

-- 'replicate i n' returns the list contain i copies of n.
definition replicate : ℕ → A → list A
| 0 a := []
| (succ n) a := a :: replicate n a

theorem length_replicate : ∀ (i : ℕ) (a : A), length (replicate i a) = i
| 0 a := rfl
| (succ i) a := calc
  length (replicate (succ i) a) = length (replicate i a) + 1 : rfl
                           ...  = i + 1                      : length_replicate
end replicate

/- map -/
definition map (f : A → B) : list A → list B
| []       := []
| (a :: l) := f a :: map l

theorem map_nil (f : A → B) : map f [] = []

theorem map_cons (f : A → B) (a : A) (l : list A) : map f (a :: l) = f a :: map f l

lemma map_concat (f : A → B) (a : A) : Πl, map f (concat a l) = concat (f a) (map f l)
| nil    := rfl
| (b::l) := begin rewrite [concat_cons, +map_cons, concat_cons, map_concat] end

lemma map_append (f : A → B) : ∀ l₁ l₂, map f (l₁++l₂) = (map f l₁)++(map f l₂)
| nil    := take l, rfl
| (a::l) := take l', begin rewrite [append_cons, *map_cons, append_cons, map_append] end

lemma map_reverse (f : A → B) : Πl, map f (reverse l) = reverse (map f l)
| nil    := rfl
| (b::l) := begin rewrite [reverse_cons, +map_cons, reverse_cons, map_concat, map_reverse] end

lemma map_singleton (f : A → B) (a : A) : map f [a] = [f a] := rfl

theorem map_id [simp] : ∀ l : list A, map id l = l
| []      := rfl
| (x::xs) := begin rewrite [map_cons, map_id] end

theorem map_id' {f : A → A} (H : ∀x, f x = x) : ∀ l : list A, map f l = l
| []      := rfl
| (x::xs) := begin rewrite [map_cons, H, map_id'] end

theorem map_map [simp] (g : B → C) (f : A → B) : ∀ l, map g (map f l) = map (g ∘ f) l
| []       := rfl
| (a :: l) :=
  show (g ∘ f) a :: map g (map f l) = map (g ∘ f) (a :: l),
  by rewrite (map_map l)

theorem length_map [simp] (f : A → B) : ∀ l : list A, length (map f l) = length l
| []       := by esimp
| (a :: l) :=
  show length (map f l) + 1 = length l + 1,
  by rewrite (length_map l)

theorem mem_map {A B : Type} (f : A → B) : ∀ {a l}, a ∈ l → f a ∈ map f l
| a []      i := absurd i !not_mem_nil
| a (x::xs) i := or.elim (eq_or_mem_of_mem_cons i)
   (suppose a = x, by rewrite [this, map_cons]; apply mem_cons)
   (suppose a ∈ xs, or.inr (mem_map this))

theorem exists_of_mem_map {A B : Type} {f : A → B} {b : B} :
    ∀{l}, b ∈ map f l → ∃a, a ∈ l ∧ f a = b
| []     H := false.elim H
| (c::l) H := or.elim (iff.mp !mem_cons_iff H)
                (suppose b = f c,
                  exists.intro c (and.intro !mem_cons (eq.symm this)))
                (suppose b ∈ map f l,
                  obtain a (Hl : a ∈ l) (Hr : f a = b), from exists_of_mem_map this,
                  exists.intro a (and.intro (mem_cons_of_mem _ Hl) Hr))

theorem eq_of_map_const {A B : Type} {b₁ b₂ : B} : ∀ {l : list A}, b₁ ∈ map (const A b₂) l → b₁ = b₂
| []     h := absurd h !not_mem_nil
| (a::l) h :=
  or.elim (eq_or_mem_of_mem_cons h)
    (suppose b₁ = b₂, this)
    (suppose b₁ ∈ map (const A b₂) l, eq_of_map_const this)

definition map₂ (f : A → B → C) : list A → list B → list C
| []      _       := []
| _       []      := []
| (x::xs) (y::ys) := f x y :: map₂ xs ys

theorem map₂_nil1 (f : A → B → C) : ∀ (l : list B), map₂ f [] l = []
| []     := rfl
| (a::y) := rfl

theorem map₂_nil2 (f : A → B → C) : ∀ (l : list A), map₂ f l [] = []
| []     := rfl
| (a::y) := rfl

theorem length_map₂ : ∀(f : A → B → C) x y, length (map₂ f x y) = min (length x) (length y)
| f []       [] := rfl
| f (xh::xr) [] := rfl
| f [] (yh::yr) := rfl
| f (xh::xr) (yh::yr) := calc
  length (map₂ f (xh::xr) (yh::yr))
          = length (map₂ f xr yr) + 1               : rfl
      ... = min (length xr) (length yr) + 1         : length_map₂
      ... = min (length (xh::xr)) (length (yh::yr)) : min_succ_succ

/- filter -/
definition filter (p : A → Prop) [h : decidable_pred p] : list A → list A
| []     := []
| (a::l) := if p a then a :: filter l else filter l

theorem filter_nil [simp] (p : A → Prop) [h : decidable_pred p] : filter p [] = []

theorem filter_cons_of_pos [simp] {p : A → Prop} [h : decidable_pred p] {a : A} : ∀ l, p a → filter p (a::l) = a :: filter p l :=
λ l pa, if_pos pa

theorem filter_cons_of_neg [simp] {p : A → Prop} [h : decidable_pred p] {a : A} : ∀ l, ¬ p a → filter p (a::l) = filter p l :=
λ l pa, if_neg pa

theorem of_mem_filter {p : A → Prop} [h : decidable_pred p] {a : A} : ∀ {l}, a ∈ filter p l → p a
| []     ain := absurd ain !not_mem_nil
| (b::l) ain := by_cases
  (assume pb  : p b,
    have a ∈ b :: filter p l, by rewrite [filter_cons_of_pos _ pb at ain]; exact ain,
    or.elim (eq_or_mem_of_mem_cons this)
      (suppose a = b, by rewrite [-this at pb]; exact pb)
      (suppose a ∈ filter p l, of_mem_filter this))
  (suppose ¬ p b, by rewrite [filter_cons_of_neg _ this at ain]; exact (of_mem_filter ain))

theorem mem_of_mem_filter {p : A → Prop} [h : decidable_pred p] {a : A} : ∀ {l}, a ∈ filter p l → a ∈ l
| []     ain := absurd ain !not_mem_nil
| (b::l) ain := by_cases
  (λ pb  : p b,
    have a ∈ b :: filter p l, by rewrite [filter_cons_of_pos _ pb at ain]; exact ain,
    or.elim (eq_or_mem_of_mem_cons this)
      (suppose a = b, by rewrite this; exact !mem_cons)
      (suppose a ∈ filter p l, mem_cons_of_mem _ (mem_of_mem_filter this)))
  (suppose ¬ p b, by rewrite [filter_cons_of_neg _ this at ain]; exact (mem_cons_of_mem _ (mem_of_mem_filter ain)))

theorem mem_filter_of_mem {p : A → Prop} [h : decidable_pred p] {a : A} : ∀ {l}, a ∈ l → p a → a ∈ filter p l
| []     ain pa := absurd ain !not_mem_nil
| (b::l) ain pa := by_cases
  (λ pb  : p b, or.elim (eq_or_mem_of_mem_cons ain)
    (λ aeqb : a = b, by rewrite [filter_cons_of_pos _ pb, aeqb]; exact !mem_cons)
    (λ ainl : a ∈ l, by rewrite [filter_cons_of_pos _ pb]; exact (mem_cons_of_mem _ (mem_filter_of_mem ainl pa))))
  (λ npb : ¬ p b, or.elim (eq_or_mem_of_mem_cons ain)
    (λ aeqb : a = b, absurd (eq.rec_on aeqb pa) npb)
    (λ ainl : a ∈ l, by rewrite [filter_cons_of_neg _ npb]; exact (mem_filter_of_mem ainl pa)))

theorem filter_sub [simp] {p : A → Prop} [h : decidable_pred p] (l : list A) : filter p l ⊆ l :=
λ a ain, mem_of_mem_filter ain

theorem filter_append {p : A → Prop} [h : decidable_pred p] : ∀ (l₁ l₂ : list A), filter p (l₁++l₂) = filter p l₁ ++ filter p l₂
| []      l₂ := rfl
| (a::l₁) l₂ := by_cases
  (suppose p a, by rewrite [append_cons, *filter_cons_of_pos _ this, filter_append])
  (suppose ¬ p a, by rewrite [append_cons, *filter_cons_of_neg _ this, filter_append])

/- foldl & foldr -/
definition foldl (f : A → B → A) : A → list B → A
| a []       := a
| a (b :: l) := foldl (f a b) l

theorem foldl_nil [simp] (f : A → B → A) (a : A) : foldl f a [] = a

theorem foldl_cons [simp] (f : A → B → A) (a : A) (b : B) (l : list B) : foldl f a (b::l) = foldl f (f a b) l

definition foldr (f : A → B → B) : B → list A → B
| b []       := b
| b (a :: l) := f a (foldr b l)

theorem foldr_nil [simp] (f : A → B → B) (b : B) : foldr f b [] = b

theorem foldr_cons [simp] (f : A → B → B) (b : B) (a : A) (l : list A) : foldr f b (a::l) = f a (foldr f b l)

section foldl_eq_foldr
  -- foldl and foldr coincide when f is commutative and associative
  parameters {α : Type} {f : α → α → α}
  hypothesis (Hcomm  : ∀ a b, f a b = f b a)
  hypothesis (Hassoc : ∀ a b c, f (f a b) c = f a (f b c))
  include Hcomm Hassoc

  theorem foldl_eq_of_comm_of_assoc : ∀ a b l, foldl f a (b::l) = f b (foldl f a l)
  | a b  nil    := Hcomm a b
  | a b  (c::l) :=
    begin
      change foldl f (f (f a b) c) l = f b (foldl f (f a c) l),
      rewrite -foldl_eq_of_comm_of_assoc,
      change foldl f (f (f a b) c) l = foldl f (f (f a c) b) l,
      have H₁ : f (f a b) c = f (f a c) b, by rewrite [Hassoc, Hassoc, Hcomm b c],
      rewrite H₁
    end

  theorem foldl_eq_foldr : ∀ a l, foldl f a l = foldr f a l
  | a nil      := rfl
  | a (b :: l) :=
    begin
      rewrite foldl_eq_of_comm_of_assoc,
      esimp,
      change f b (foldl f a l) = f b (foldr f a l),
      rewrite foldl_eq_foldr
    end
end foldl_eq_foldr

theorem foldl_append [simp] (f : B → A → B) : ∀ (b : B) (l₁ l₂ : list A), foldl f b (l₁++l₂) = foldl f (foldl f b l₁) l₂
| b []      l₂ := rfl
| b (a::l₁) l₂ := by rewrite [append_cons, *foldl_cons, foldl_append]

theorem foldr_append [simp] (f : A → B → B) : ∀ (b : B) (l₁ l₂ : list A), foldr f b (l₁++l₂) = foldr f (foldr f b l₂) l₁
| b []      l₂ := rfl
| b (a::l₁) l₂ := by rewrite [append_cons, *foldr_cons, foldr_append]

/- all & any -/
definition all (l : list A) (p : A → Prop) : Prop :=
foldr (λ a r, p a ∧ r) true l

definition any (l : list A) (p : A → Prop) : Prop :=
foldr (λ a r, p a ∨ r) false l

theorem all_nil_eq [simp] (p : A → Prop) : all [] p = true

theorem all_nil (p : A → Prop) : all [] p := trivial

theorem all_cons_eq (p : A → Prop) (a : A) (l : list A) : all (a::l) p = (p a ∧ all l p)

theorem all_cons {p : A → Prop} {a : A} {l : list A} (H1 : p a) (H2 : all l p) : all (a::l) p :=
and.intro H1 H2

theorem all_of_all_cons {p : A → Prop} {a : A} {l : list A} : all (a::l) p → all l p :=
assume h, by rewrite [all_cons_eq at h]; exact (and.elim_right h)

theorem of_all_cons {p : A → Prop} {a : A} {l : list A} : all (a::l) p → p a :=
assume h, by rewrite [all_cons_eq at h]; exact (and.elim_left h)

theorem all_cons_of_all {p : A → Prop} {a : A} {l : list A} : p a → all l p → all (a::l) p :=
assume pa alllp, and.intro pa alllp

theorem all_implies {p q : A → Prop} : ∀ {l}, all l p → (∀ x, p x → q x) → all l q
| []     h₁ h₂ := trivial
| (a::l) h₁ h₂ :=
  have all l q, from all_implies (all_of_all_cons h₁) h₂,
  have q a, from h₂ a (of_all_cons h₁),
  all_cons_of_all this `all l q`

theorem of_mem_of_all {p : A → Prop} {a : A} : ∀ {l}, a ∈ l → all l p → p a
| []     h₁ h₂ := absurd h₁ !not_mem_nil
| (b::l) h₁ h₂ :=
  or.elim (eq_or_mem_of_mem_cons h₁)
    (suppose a = b,
      by rewrite [all_cons_eq at h₂, -this at h₂]; exact (and.elim_left h₂))
    (suppose a ∈ l,
      have all l p, by rewrite [all_cons_eq at h₂]; exact (and.elim_right h₂),
      of_mem_of_all `a ∈ l` this)

theorem all_of_forall {p : A → Prop} : ∀ {l}, (∀a, a ∈ l → p a) → all l p
| []     H := !all_nil
| (a::l) H := all_cons (H a !mem_cons)
                       (all_of_forall (λ a' H', H a' (mem_cons_of_mem _ H')))

theorem any_nil [simp] (p : A → Prop) : any [] p = false

theorem any_cons [simp] (p : A → Prop) (a : A) (l : list A) : any (a::l) p = (p a ∨ any l p)

theorem any_of_mem {p : A → Prop} {a : A} : ∀ {l}, a ∈ l → p a → any l p
| []     i h := absurd i !not_mem_nil
| (b::l) i h :=
  or.elim (eq_or_mem_of_mem_cons i)
    (suppose a = b, by rewrite [-this]; exact (or.inl h))
    (suppose a ∈ l,
      have any l p, from any_of_mem this h,
      or.inr this)

theorem exists_of_any {p : A → Prop} : ∀{l : list A}, any l p → ∃a, a ∈ l ∧ p a
| []     H := false.elim H
| (b::l) H := or.elim H
                (assume H1 : p b, exists.intro b (and.intro !mem_cons H1))
                (assume H1 : any l p,
                  obtain a (H2l : a ∈ l) (H2r : p a), from exists_of_any H1,
                  exists.intro a (and.intro (mem_cons_of_mem b H2l) H2r))

definition decidable_all (p : A → Prop) [H : decidable_pred p] : ∀ l, decidable (all l p)
| []       := decidable_true
| (a :: l) :=
  match H a with
  | inl Hp₁ :=
    match decidable_all l with
    | inl Hp₂ := inl (and.intro Hp₁ Hp₂)
    | inr Hn₂ := inr (not_and_of_not_right (p a) Hn₂)
    end
  | inr Hn := inr (not_and_of_not_left (all l p) Hn)
  end

definition decidable_any (p : A → Prop) [H : decidable_pred p] : ∀ l, decidable (any l p)
| []       := decidable_false
| (a :: l) :=
  match H a with
  | inl Hp := inl (or.inl Hp)
  | inr Hn₁ :=
    match decidable_any l with
    | inl Hp₂ := inl (or.inr Hp₂)
    | inr Hn₂ := inr (not_or Hn₁ Hn₂)
    end
  end

/- zip & unzip -/
definition zip (l₁ : list A) (l₂ : list B) : list (A × B) :=
map₂ (λ a b, (a, b)) l₁ l₂

definition unzip : list (A × B) → list A × list B
| []            := ([], [])
| ((a, b) :: l) :=
  match unzip l with
  | (la, lb) := (a :: la, b :: lb)
  end

theorem unzip_nil [simp] : unzip (@nil (A × B)) = ([], [])

theorem unzip_cons [simp] (a : A) (b : B) (l : list (A × B)) :
   unzip ((a, b) :: l) = match unzip l with (la, lb) := (a :: la, b :: lb) end :=
rfl

theorem zip_unzip : ∀ (l : list (A × B)), zip (pr₁ (unzip l)) (pr₂ (unzip l)) = l
| []            := rfl
| ((a, b) :: l) :=
  begin
    rewrite unzip_cons,
    have r : zip (pr₁ (unzip l)) (pr₂ (unzip l)) = l, from zip_unzip l,
    revert r,
    eapply prod.cases_on (unzip l),
    intro la lb r,
    rewrite -r
  end

section mapAccumR
variable {S : Type}

-- This runs a function over a list returning the intermediate results and a
-- a final result.
definition mapAccumR : (A → S → S × B) → list A → S → (S × list B)
| f [] c := (c, [])
| f (y::yr) c :=
  let r := mapAccumR f yr c in
  let z := f y (pr₁ r) in
  (pr₁ z, pr₂ z :: pr₂ r)

theorem length_mapAccumR
: ∀ (f : A → S → S × B) (x : list A) (s : S),
  length (pr₂ (mapAccumR f x s)) = length x
| f (a::x) s := calc
  length (pr₂ (mapAccumR f (a::x) s))
                = length x + 1              : { length_mapAccumR f x s }
            ... = length (a::x)             : rfl
| f [] s := calc
  length (pr₂ (mapAccumR f [] s))
                = 0 : rfl
end mapAccumR

section mapAccumR₂
variable {S : Type}
-- This runs a function over two lists returning the intermediate results and a
-- a final result.
definition mapAccumR₂
: (A → B → S → S × C) → list A → list B → S → S × list C
| f [] _ c := (c,[])
| f _ [] c := (c,[])
| f (x::xr) (y::yr) c :=
  let r := mapAccumR₂ f xr yr c in
  let q := f x y (pr₁ r) in
  (pr₁ q, pr₂ q :: (pr₂ r))

theorem length_mapAccumR₂
: ∀ (f : A → B → S → S × C) (x : list A) (y : list B) (c : S),
  length (pr₂ (mapAccumR₂ f x y c)) = min (length x) (length y)
| f (a::x) (b::y) c := calc
    length (pr₂ (mapAccumR₂ f (a::x) (b::y) c))
              = length (pr₂ (mapAccumR₂ f x y c)) + 1  : rfl
          ... = min (length x) (length y) + 1             : length_mapAccumR₂ f x y c
          ... = min (length (a::x)) (length (b::y))       : min_succ_succ
| f (a::x) [] c := rfl
| f [] (b::y) c := rfl
| f [] []     c := rfl

end mapAccumR₂

/- flat -/
definition flat (l : list (list A)) : list A :=
foldl append nil l

/- product -/
section product

definition product : list A → list B → list (A × B)
| []      l₂ := []
| (a::l₁) l₂ := map (λ b, (a, b)) l₂ ++ product l₁ l₂

theorem nil_product (l : list B) : product (@nil A) l = []

theorem product_cons (a : A) (l₁ : list A) (l₂ : list B)
        : product (a::l₁) l₂ = map (λ b, (a, b)) l₂ ++ product l₁ l₂

theorem product_nil : ∀ (l : list A), product l (@nil B) = []
| []     := rfl
| (a::l) := by rewrite [product_cons, map_nil, product_nil]

theorem eq_of_mem_map_pair₁  {a₁ a : A} {b₁ : B} {l : list B} : (a₁, b₁) ∈ map (λ b, (a, b)) l → a₁ = a :=
assume ain,
assert pr1 (a₁, b₁) ∈ map pr1 (map (λ b, (a, b)) l), from mem_map pr1 ain,
assert a₁ ∈ map (λb, a) l, by revert this; rewrite [map_map, ↑pr1]; intro this; assumption,
eq_of_map_const this

theorem mem_of_mem_map_pair₁ {a₁ a : A} {b₁ : B} {l : list B} : (a₁, b₁) ∈ map (λ b, (a, b)) l → b₁ ∈ l :=
assume ain,
assert pr2 (a₁, b₁) ∈ map pr2 (map (λ b, (a, b)) l), from mem_map pr2 ain,
assert b₁ ∈ map (λx, x) l, by rewrite [map_map at this, ↑pr2 at this]; exact this,
by rewrite [map_id at this]; exact this

theorem mem_product {a : A} {b : B} : ∀ {l₁ l₂}, a ∈ l₁ → b ∈ l₂ → (a, b) ∈ product l₁ l₂
| []      l₂ h₁ h₂ := absurd h₁ !not_mem_nil
| (x::l₁) l₂ h₁ h₂ :=
  or.elim (eq_or_mem_of_mem_cons h₁)
    (assume aeqx  : a = x,
      assert (a, b) ∈ map (λ b, (a, b)) l₂, from mem_map _ h₂,
      begin rewrite [-aeqx, product_cons], exact mem_append_left _ this end)
    (assume ainl₁ : a ∈ l₁,
      assert (a, b) ∈ product l₁ l₂, from mem_product ainl₁ h₂,
      begin rewrite [product_cons], exact mem_append_right _ this end)

theorem mem_of_mem_product_left {a : A} {b : B} : ∀ {l₁ l₂}, (a, b) ∈ product l₁ l₂ → a ∈ l₁
| []      l₂ h := absurd h !not_mem_nil
| (x::l₁) l₂ h :=
  or.elim (mem_or_mem_of_mem_append h)
    (suppose (a, b) ∈ map (λ b, (x, b)) l₂,
       assert a = x, from eq_of_mem_map_pair₁ this,
       by rewrite this; exact !mem_cons)
    (suppose (a, b) ∈ product l₁ l₂,
      have a ∈ l₁, from mem_of_mem_product_left this,
      mem_cons_of_mem _ this)

theorem mem_of_mem_product_right {a : A} {b : B} : ∀ {l₁ l₂}, (a, b) ∈ product l₁ l₂ → b ∈ l₂
| []      l₂ h := absurd h !not_mem_nil
| (x::l₁) l₂ h :=
  or.elim (mem_or_mem_of_mem_append h)
    (suppose (a, b) ∈ map (λ b, (x, b)) l₂,
      mem_of_mem_map_pair₁ this)
    (suppose (a, b) ∈ product l₁ l₂,
      mem_of_mem_product_right this)

theorem length_product : ∀ (l₁ : list A) (l₂ : list B), length (product l₁ l₂) = length l₁ * length l₂
| []      l₂ := by rewrite [length_nil, zero_mul]
| (x::l₁) l₂ :=
  assert length (product l₁ l₂) = length l₁ * length l₂, from length_product l₁ l₂,
  by rewrite [product_cons, length_append, length_cons,
              length_map, this, right_distrib, one_mul, add.comm]
end product

-- new for list/comb dependent map theory
definition dinj₁ (p : A → Prop) (f : Π a, p a → B) := ∀ ⦃a1 a2⦄ (h1 : p a1) (h2 : p a2), a1 ≠ a2 → (f a1 h1) ≠ (f a2 h2)
definition dinj (p : A → Prop) (f : Π a, p a → B) := ∀ ⦃a1 a2⦄ (h1 : p a1) (h2 : p a2), (f a1 h1) = (f a2 h2) → a1 = a2

definition dmap (p : A → Prop) [h : decidable_pred p] (f : Π a, p a → B) : list A → list B
| []       := []
| (a::l)   := if P : (p a) then cons (f a P) (dmap l) else (dmap l)

-- properties of dmap
section dmap

variable {p : A → Prop}
variable [h : decidable_pred p]
include h
variable {f : Π a, p a → B}

lemma dmap_nil : dmap p f [] = [] := rfl
lemma dmap_cons_of_pos {a : A} (P : p a) : ∀ l, dmap p f (a::l) = (f a P) :: dmap p f l :=
      λ l, dif_pos P
lemma dmap_cons_of_neg {a : A} (P : ¬ p a) : ∀ l, dmap p f (a::l) = dmap p f l :=
      λ l, dif_neg P

lemma mem_dmap : ∀ {l : list A} {a} (Pa : p a), a ∈ l → (f a Pa) ∈ dmap p f l
| []     := take a Pa Pinnil, by contradiction
| (a::l) := take b Pb Pbin, or.elim (eq_or_mem_of_mem_cons Pbin)
              (assume Pbeqa, begin
                rewrite [eq.symm Pbeqa, dmap_cons_of_pos Pb],
                exact !mem_cons
              end)
              (assume Pbinl,
                decidable.rec_on (h a)
                (assume Pa, begin
                  rewrite [dmap_cons_of_pos Pa],
                  apply mem_cons_of_mem,
                  exact mem_dmap Pb Pbinl
                end)
                (assume nPa, begin
                  rewrite [dmap_cons_of_neg nPa],
                  exact mem_dmap Pb Pbinl
                end))

lemma exists_of_mem_dmap : ∀ {l : list A} {b : B}, b ∈ dmap p f l → ∃ a P, a ∈ l ∧ b = f a P
| []     := take b, by rewrite dmap_nil; contradiction
| (a::l) := take b, decidable.rec_on (h a)
  (assume Pa, begin
  rewrite [dmap_cons_of_pos Pa, mem_cons_iff],
  intro Pb, cases Pb with Peq Pin,
    exact exists.intro a (exists.intro Pa (and.intro !mem_cons Peq)),
    assert Pex : ∃ (a : A) (P : p a), a ∈ l ∧ b = f a P, exact exists_of_mem_dmap Pin,
    cases Pex with a' Pex', cases Pex' with Pa' P',
    exact exists.intro a' (exists.intro Pa' (and.intro (mem_cons_of_mem a (and.left P')) (and.right P')))
  end)
  (assume nPa,  begin
  rewrite [dmap_cons_of_neg nPa],
  intro Pin,
  assert Pex : ∃ (a : A) (P : p a), a ∈ l ∧ b = f a P, exact exists_of_mem_dmap Pin,
  cases Pex with a' Pex', cases Pex' with Pa' P',
  exact exists.intro a' (exists.intro Pa' (and.intro (mem_cons_of_mem a (and.left P')) (and.right P')))
  end)

lemma map_dmap_of_inv_of_pos {g : B → A} (Pinv : ∀ a (Pa : p a), g (f a Pa) = a) :
                          ∀ {l : list A}, (∀ ⦃a⦄, a ∈ l → p a) → map g (dmap p f l) = l
| []     := assume Pl, by rewrite [dmap_nil, map_nil]
| (a::l) := assume Pal,
            assert Pa : p a, from Pal a !mem_cons,
            assert Pl : ∀ a, a ∈ l → p a,
              from take x Pxin, Pal x (mem_cons_of_mem a Pxin),
            by rewrite [dmap_cons_of_pos Pa, map_cons, Pinv, map_dmap_of_inv_of_pos Pl]

lemma mem_of_dinj_of_mem_dmap (Pdi : dinj p f) :
      ∀ {l : list A} {a} (Pa : p a), (f a Pa) ∈ dmap p f l → a ∈ l
| []     := take a Pa Pinnil, by contradiction
| (b::l) := take a Pa Pmap,
              decidable.rec_on (h b)
              (λ Pb, begin
                rewrite (dmap_cons_of_pos Pb) at Pmap,
                rewrite mem_cons_iff at Pmap,
                rewrite mem_cons_iff,
                apply (or_of_or_of_imp_of_imp Pmap),
                  apply Pdi,
                  apply mem_of_dinj_of_mem_dmap Pa
              end)
              (λ nPb, begin
                 rewrite (dmap_cons_of_neg nPb) at Pmap,
                 apply mem_cons_of_mem,
                 exact mem_of_dinj_of_mem_dmap Pa Pmap
              end)

lemma not_mem_dmap_of_dinj_of_not_mem (Pdi : dinj p f) {l : list A} {a} (Pa : p a) :
  a ∉ l → (f a Pa) ∉ dmap p f l :=
not.mto (mem_of_dinj_of_mem_dmap Pdi Pa)

end dmap

section
open equiv
definition list_equiv_of_equiv {A B : Type} : A ≃ B → list A ≃ list B
| (mk f g l r) :=
  mk (map f) (map g)
   begin intros, rewrite [map_map, id_of_left_inverse l, map_id], try reflexivity end
   begin intros, rewrite [map_map, id_of_righ_inverse r, map_id], try reflexivity end

private definition to_nat : list nat → nat
| []      := 0
| (x::xs) := succ (mkpair (to_nat xs) x)

open prod.ops

private definition of_nat.F : Π (n : nat), (Π m, m < n → list nat) → list nat
| 0        f := []
| (succ n) f := (unpair n).2 :: f (unpair n).1 (unpair_lt n)

private definition of_nat : nat → list nat :=
well_founded.fix of_nat.F

private lemma of_nat_zero : of_nat 0 = [] :=
well_founded.fix_eq of_nat.F 0

private lemma of_nat_succ (n : nat)
      : of_nat (succ n) = (unpair n).2 :: of_nat (unpair n).1 :=
well_founded.fix_eq of_nat.F (succ n)

private lemma to_nat_of_nat (n : nat) : to_nat (of_nat n) = n :=
nat.case_strong_induction_on n
 _
 (λ n ih,
  begin
    rewrite of_nat_succ, unfold to_nat,
    have to_nat (of_nat (unpair n).1) = (unpair n).1, from ih _ (le_of_lt_succ (unpair_lt n)),
    rewrite this, rewrite mkpair_unpair
  end)

private lemma of_nat_to_nat : ∀ (l : list nat), of_nat (to_nat l) = l
| []      := rfl
| (x::xs) := begin unfold to_nat, rewrite of_nat_succ, rewrite *unpair_mkpair, esimp, congruence, apply of_nat_to_nat end

definition list_nat_equiv_nat : list nat ≃ nat :=
mk to_nat of_nat of_nat_to_nat to_nat_of_nat

definition list_equiv_self_of_equiv_nat {A : Type} : A ≃ nat → list A ≃ A :=
suppose A ≃ nat, calc
  list A ≃ list nat : list_equiv_of_equiv this
     ... ≃ nat      : list_nat_equiv_nat
     ... ≃ A        : this
end
end list

attribute list.decidable_any [instance]
attribute list.decidable_all [instance]
