/-
Copyright (c) 2014 Parikshit Khanna. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Parikshit Khanna, Jeremy Avigad, Leonardo de Moura, Floris van Doorn, Mario Carneiro

! This file was ported from Lean 3 source module data.list.defs
! leanprover-community/mathlib commit d2d8742b0c21426362a9dacebc6005db895ca963
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Logic.Basic
import Mathbin.Tactic.Cache
import Mathbin.Data.Rbmap.Basic
import Mathbin.Data.Rbtree.DefaultLt

/-!
## Definitions on lists

> THIS FILE IS SYNCHRONIZED WITH MATHLIB4.
> Any changes to this file require a corresponding PR to mathlib4.

This file contains various definitions on lists. It does not contain
proofs about these definitions, those are contained in other files in `data/list`
-/

namespace List

open Function Nat

universe u v w x

variable {α β γ δ ε ζ : Type _}

instance [DecidableEq α] : SDiff (List α) :=
  ⟨List.diff⟩

#print List.replicate /-
/-- Create a list of `n` copies of `a`. Same as `function.swap list.repeat`. -/
@[simp]
def replicate : ℕ → α → List α
  | 0, _ => []
  | succ n, a => a :: replicate n a
#align list.replicate List.replicate
-/

#print List.splitAt /-
/-- Split a list at an index.

     split_at 2 [a, b, c] = ([a, b], [c]) -/
def splitAt : ℕ → List α → List α × List α
  | 0, a => ([], a)
  | succ n, [] => ([], [])
  | succ n, x :: xs =>
    let (l, r) := split_at n xs
    (x :: l, r)
#align list.split_at List.splitAt
-/

/-- An auxiliary function for `split_on_p`. -/
def splitOnPAux {α : Type u} (P : α → Prop) [DecidablePred P] :
    List α → (List α → List α) → List (List α)
  | [], f => [f []]
  | h :: t, f => if P h then f [] :: split_on_p_aux t id else split_on_p_aux t fun l => f (h :: l)
#align list.split_on_p_aux List.splitOnPAux

/- warning: list.split_on_p -> List.splitOnP is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} (P : α -> Prop) [_inst_1 : DecidablePred.{succ u1} α P], (List.{u1} α) -> (List.{u1} (List.{u1} α))
but is expected to have type
  forall {α : Type.{u1}}, (α -> Bool) -> (List.{u1} α) -> (List.{u1} (List.{u1} α))
Case conversion may be inaccurate. Consider using '#align list.split_on_p List.splitOnPₓ'. -/
/-- Split a list at every element satisfying a predicate. -/
def splitOnP {α : Type u} (P : α → Prop) [DecidablePred P] (l : List α) : List (List α) :=
  splitOnPAux P l id
#align list.split_on_p List.splitOnP

/- warning: list.split_on -> List.splitOn is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α], α -> (List.{u1} α) -> (List.{u1} (List.{u1} α))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : BEq.{u1} α], α -> (List.{u1} α) -> (List.{u1} (List.{u1} α))
Case conversion may be inaccurate. Consider using '#align list.split_on List.splitOnₓ'. -/
/-- Split a list at every occurrence of an element.

    [1,1,2,3,2,4,4].split_on 2 = [[1,1],[3],[4,4]] -/
def splitOn {α : Type u} [DecidableEq α] (a : α) (as : List α) : List (List α) :=
  as.splitOnP (· = a)
#align list.split_on List.splitOn

#print List.concat /-
/-- Concatenate an element at the end of a list.

     concat [a, b] c = [a, b, c] -/
@[simp]
def concat : List α → α → List α
  | [], a => [a]
  | b :: l, a => b :: concat l a
#align list.concat List.concat
-/

#print List.head? /-
/-- `head' xs` returns the first element of `xs` if `xs` is non-empty;
it returns `none` otherwise -/
@[simp]
def head? : List α → Option α
  | [] => none
  | a :: l => some a
#align list.head' List.head?
-/

/- warning: list.to_array -> List.toArray is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} (l : List.{u1} α), Array'.{u1} (List.length.{u1} α l) α
but is expected to have type
  forall {α : Type.{u1}}, (List.{u1} α) -> (Array.{u1} α)
Case conversion may be inaccurate. Consider using '#align list.to_array List.toArrayₓ'. -/
/-- Convert a list into an array (whose length is the length of `l`). -/
def toArray (l : List α) : Array' l.length α where data v := l.nthLe v.1 v.2
#align list.to_array List.toArray

#print List.getD /-
/-- "default" `nth` function: returns `d` instead of `none` in the case
  that the index is out of bounds. -/
def getD : ∀ (l : List α) (n : ℕ) (d : α), α
  | [], _, d => d
  | x :: xs, 0, d => x
  | x :: xs, n + 1, d => nthd xs n d
#align list.nthd List.getD
-/

#print List.getI /-
/-- "inhabited" `nth` function: returns `default` instead of `none` in the case
  that the index is out of bounds. -/
def getI [h : Inhabited α] (l : List α) (n : Nat) : α :=
  getD l n default
#align list.inth List.getI
-/

#print List.modifyNthTail /-
/-- Apply a function to the nth tail of `l`. Returns the input without
  using `f` if the index is larger than the length of the list.

     modify_nth_tail f 2 [a, b, c] = [a, b] ++ f [c] -/
@[simp]
def modifyNthTail (f : List α → List α) : ℕ → List α → List α
  | 0, l => f l
  | n + 1, [] => []
  | n + 1, a :: l => a :: modify_nth_tail n l
#align list.modify_nth_tail List.modifyNthTail
-/

#print List.modifyHead /-
/-- Apply `f` to the head of the list, if it exists. -/
@[simp]
def modifyHead (f : α → α) : List α → List α
  | [] => []
  | a :: l => f a :: l
#align list.modify_head List.modifyHead
-/

#print List.modifyNth /-
/-- Apply `f` to the nth element of the list, if it exists. -/
def modifyNth (f : α → α) : ℕ → List α → List α :=
  modifyNthTail (modifyHead f)
#align list.modify_nth List.modifyNth
-/

#print List.modifyLast /-
/-- Apply `f` to the last element of `l`, if it exists. -/
@[simp]
def modifyLast (f : α → α) : List α → List α
  | [] => []
  | [x] => [f x]
  | x :: xs => x :: modify_last xs
#align list.modify_last List.modifyLast
-/

#print List.insertNth /-
/-- `insert_nth n a l` inserts `a` into the list `l` after the first `n` elements of `l`
 `insert_nth 2 1 [1, 2, 3, 4] = [1, 2, 1, 3, 4]`-/
def insertNth (n : ℕ) (a : α) : List α → List α :=
  modifyNthTail (List.cons a) n
#align list.insert_nth List.insertNth
-/

section Take'

variable [Inhabited α]

#print List.takeI /-
/-- Take `n` elements from a list `l`. If `l` has less than `n` elements, append `n - length l`
elements `default`. -/
def takeI : ∀ n, List α → List α
  | 0, l => []
  | n + 1, l => l.headI :: take' n l.tail
#align list.take' List.takeI
-/

end Take'

/- warning: list.take_while -> List.takeWhile is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} (p : α -> Prop) [_inst_1 : DecidablePred.{succ u1} α p], (List.{u1} α) -> (List.{u1} α)
but is expected to have type
  forall {α : Type.{u1}}, (α -> Bool) -> (List.{u1} α) -> (List.{u1} α)
Case conversion may be inaccurate. Consider using '#align list.take_while List.takeWhileₓ'. -/
/-- Get the longest initial segment of the list whose members all satisfy `p`.

     take_while (λ x, x < 3) [0, 2, 5, 1] = [0, 2] -/
def takeWhile (p : α → Prop) [DecidablePred p] : List α → List α
  | [] => []
  | a :: l => if p a then a :: take_while l else []
#align list.take_while List.takeWhile

#print List.scanl /-
/-- Fold a function `f` over the list from the left, returning the list
  of partial results.

     scanl (+) 0 [1, 2, 3] = [0, 1, 3, 6] -/
def scanl (f : α → β → α) : α → List β → List α
  | a, [] => [a]
  | a, b :: l => a :: scanl (f a b) l
#align list.scanl List.scanl
-/

/-- Auxiliary definition used to define `scanr`. If `scanr_aux f b l = (b', l')`
then `scanr f b l = b' :: l'` -/
def scanrAux (f : α → β → β) (b : β) : List α → β × List β
  | [] => (b, [])
  | a :: l =>
    let (b', l') := scanr_aux l
    (f a b', b' :: l')
#align list.scanr_aux List.scanrAux

#print List.scanr /-
/-- Fold a function `f` over the list from the right, returning the list
  of partial results.

     scanr (+) 0 [1, 2, 3] = [6, 5, 3, 0] -/
def scanr (f : α → β → β) (b : β) (l : List α) : List β :=
  let (b', l') := scanrAux f b l
  b' :: l'
#align list.scanr List.scanr
-/

#print List.prod /-
/-- Product of a list.

     prod [a, b, c] = ((1 * a) * b) * c -/
def prod [Mul α] [One α] : List α → α :=
  foldl (· * ·) 1
#align list.prod List.prod
-/

#print List.sum /-
-- Later this will be tagged with `to_additive`, but this can't be done yet because of import
-- dependencies.
/-- Sum of a list.

     sum [a, b, c] = ((0 + a) + b) + c -/
def sum [Add α] [Zero α] : List α → α :=
  foldl (· + ·) 0
#align list.sum List.sum
-/

#print List.alternatingSum /-
/-- The alternating sum of a list. -/
def alternatingSum {G : Type _} [Zero G] [Add G] [Neg G] : List G → G
  | [] => 0
  | g :: [] => g
  | g :: h :: t => g + -h + alternating_sum t
#align list.alternating_sum List.alternatingSum
-/

#print List.alternatingProd /-
/-- The alternating product of a list. -/
def alternatingProd {G : Type _} [One G] [Mul G] [Inv G] : List G → G
  | [] => 1
  | g :: [] => g
  | g :: h :: t => g * h⁻¹ * alternating_prod t
#align list.alternating_prod List.alternatingProd
-/

#print List.partitionMap /-
/-- Given a function `f : α → β ⊕ γ`, `partition_map f l` maps the list by `f`
  whilst partitioning the result it into a pair of lists, `list β × list γ`,
  partitioning the `sum.inl _` into the left list, and the `sum.inr _` into the right list.
  `partition_map (id : ℕ ⊕ ℕ → ℕ ⊕ ℕ) [inl 0, inr 1, inl 2] = ([0,2], [1])`    -/
def partitionMap (f : α → Sum β γ) : List α → List β × List γ
  | [] => ([], [])
  | x :: xs =>
    match f x with
    | Sum.inr r => Prod.map id (cons r) <| partition_map xs
    | Sum.inl l => Prod.map (cons l) id <| partition_map xs
#align list.partition_map List.partitionMap
-/

/- warning: list.find -> List.find? is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} (p : α -> Prop) [_inst_1 : DecidablePred.{succ u1} α p], (List.{u1} α) -> (Option.{u1} α)
but is expected to have type
  forall {α : Type.{u1}}, (α -> Bool) -> (List.{u1} α) -> (Option.{u1} α)
Case conversion may be inaccurate. Consider using '#align list.find List.find?ₓ'. -/
/-- `find p l` is the first element of `l` satisfying `p`, or `none` if no such
  element exists. -/
def find? (p : α → Prop) [DecidablePred p] : List α → Option α
  | [] => none
  | a :: l => if p a then some a else find l
#align list.find List.find?

/- warning: list.mfind -> List.findM is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {m : Type.{u1} -> Type.{u2}} [_inst_1 : Monad.{u1, u2} m] [_inst_2 : Alternative.{u1, u2} m], (α -> (m PUnit.{succ u1})) -> (List.{u1} α) -> (m α)
but is expected to have type
  forall {α : Type.{u1}} {m : Type.{u1} -> Type.{u2}} [_inst_1 : Alternative.{u1, u2} m], (α -> (m PUnit.{succ u1})) -> (List.{u1} α) -> (m α)
Case conversion may be inaccurate. Consider using '#align list.mfind List.findMₓ'. -/
/-- `mfind tac l` returns the first element of `l` on which `tac` succeeds, and
fails otherwise. -/
def findM {α} {m : Type u → Type v} [Monad m] [Alternative m] (tac : α → m PUnit) : List α → m α :=
  List.firstM fun a => tac a $> a
#align list.mfind List.findM

#print List.findM?' /-
/-- `mbfind' p l` returns the first element `a` of `l` for which `p a` returns
true. `mbfind'` short-circuits, so `p` is not necessarily run on every `a` in
`l`. This is a monadic version of `list.find`. -/
def findM?' {m : Type u → Type v} [Monad m] {α : Type u} (p : α → m (ULift Bool)) :
    List α → m (Option α)
  | [] => pure none
  | x :: xs => do
    let ⟨px⟩ ← p x
    if px then pure (some x) else mbfind' xs
#align list.mbfind' List.findM?'
-/

section

variable {m : Type → Type v} [Monad m]

#print List.findM? /-
/-- A variant of `mbfind'` with more restrictive universe levels. -/
def findM? {α} (p : α → m Bool) (xs : List α) : m (Option α) :=
  xs.findM?' (Functor.map ULift.up ∘ p)
#align list.mbfind List.findM?
-/

/- warning: list.many -> List.anyM is a dubious translation:
lean 3 declaration is
  forall {m : Type -> Type.{u2}} [_inst_1 : Monad.{0, u2} m] {α : Type.{u1}}, (α -> (m Bool)) -> (List.{u1} α) -> (m Bool)
but is expected to have type
  forall {m : Type -> Type.{u1}} [_inst_1 : Monad.{0, u1} m] {α : Type.{u2}}, (α -> (m Bool)) -> (List.{u2} α) -> (m Bool)
Case conversion may be inaccurate. Consider using '#align list.many List.anyMₓ'. -/
-- Implementing this via `mbfind` would give us less universe polymorphism.
/-- `many p as` returns true iff `p` returns true for any element of `l`.
`many` short-circuits, so if `p` returns true for any element of `l`, later
elements are not checked. This is a monadic version of `list.any`. -/
def anyM {α : Type u} (p : α → m Bool) : List α → m Bool
  | [] => pure False
  | x :: xs => do
    let px ← p x
    if px then pure tt else many xs
#align list.many List.anyM

/- warning: list.mall -> List.allM is a dubious translation:
lean 3 declaration is
  forall {m : Type -> Type.{u2}} [_inst_1 : Monad.{0, u2} m] {α : Type.{u1}}, (α -> (m Bool)) -> (List.{u1} α) -> (m Bool)
but is expected to have type
  forall {m : Type -> Type.{u1}} [_inst_1 : Monad.{0, u1} m] {α : Type.{u2}}, (α -> (m Bool)) -> (List.{u2} α) -> (m Bool)
Case conversion may be inaccurate. Consider using '#align list.mall List.allMₓ'. -/
/-- `mall p as` returns true iff `p` returns true for all elements of `l`.
`mall` short-circuits, so if `p` returns false for any element of `l`, later
elements are not checked. This is a monadic version of `list.all`. -/
def allM {α : Type u} (p : α → m Bool) (as : List α) : m Bool :=
  not <$> anyM (fun a => not <$> p a) as
#align list.mall List.allM

#print List.orM /-
/-- `mbor xs` runs the actions in `xs`, returning true if any of them returns
true. `mbor` short-circuits, so if an action returns true, later actions are
not run. This is a monadic version of `list.bor`. -/
def orM : List (m Bool) → m Bool :=
  anyM id
#align list.mbor List.orM
-/

#print List.andM /-
/-- `mband xs` runs the actions in `xs`, returning true if all of them return
true. `mband` short-circuits, so if an action returns false, later actions are
not run. This is a monadic version of `list.band`. -/
def andM : List (m Bool) → m Bool :=
  allM id
#align list.mband List.andM
-/

end

/- warning: list.foldl_with_index_aux -> List.foldlWithIndexAux is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {β : Type.{u2}}, (Nat -> α -> β -> α) -> Nat -> α -> (List.{u2} β) -> α
but is expected to have type
  forall {α : Sort.{u2}} {β : Type.{u1}}, (Nat -> α -> β -> α) -> Nat -> α -> (List.{u1} β) -> α
Case conversion may be inaccurate. Consider using '#align list.foldl_with_index_aux List.foldlWithIndexAuxₓ'. -/
/-- Auxiliary definition for `foldl_with_index`. -/
def foldlWithIndexAux {α : Sort _} {β : Type _} (f : ℕ → α → β → α) : ℕ → α → List β → α
  | _, a, [] => a
  | i, a, b :: l => foldl_with_index_aux (i + 1) (f i a b) l
#align list.foldl_with_index_aux List.foldlWithIndexAux

/- warning: list.foldl_with_index -> List.foldlIdx is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {β : Type.{u2}}, (Nat -> α -> β -> α) -> α -> (List.{u2} β) -> α
but is expected to have type
  forall {α : Sort.{u1}} {β : Type.{u2}}, (Nat -> α -> β -> α) -> α -> (List.{u2} β) -> (optParam.{1} Nat (OfNat.ofNat.{0} Nat 0 (instOfNatNat 0))) -> α
Case conversion may be inaccurate. Consider using '#align list.foldl_with_index List.foldlIdxₓ'. -/
/-- Fold a list from left to right as with `foldl`, but the combining function
also receives each element's index. -/
def foldlIdx {α : Sort _} {β : Type _} (f : ℕ → α → β → α) (a : α) (l : List β) : α :=
  foldlWithIndexAux f 0 a l
#align list.foldl_with_index List.foldlIdx

/-- Auxiliary definition for `foldr_with_index`. -/
def foldrWithIndexAux {α : Type _} {β : Sort _} (f : ℕ → α → β → β) : ℕ → β → List α → β
  | _, b, [] => b
  | i, b, a :: l => f i a (foldr_with_index_aux (i + 1) b l)
#align list.foldr_with_index_aux List.foldrWithIndexAux

/- warning: list.foldr_with_index -> List.foldrIdx is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Sort.{u2}}, (Nat -> α -> β -> β) -> β -> (List.{u1} α) -> β
but is expected to have type
  forall {α : Type.{u1}} {β : Sort.{u2}}, (Nat -> α -> β -> β) -> β -> (List.{u1} α) -> (optParam.{1} Nat (OfNat.ofNat.{0} Nat 0 (instOfNatNat 0))) -> β
Case conversion may be inaccurate. Consider using '#align list.foldr_with_index List.foldrIdxₓ'. -/
/-- Fold a list from right to left as with `foldr`, but the combining function
also receives each element's index. -/
def foldrIdx {α : Type _} {β : Sort _} (f : ℕ → α → β → β) (b : β) (l : List α) : β :=
  foldrWithIndexAux f 0 b l
#align list.foldr_with_index List.foldrIdx

/- warning: list.find_indexes -> List.findIdxs is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} (p : α -> Prop) [_inst_1 : DecidablePred.{succ u1} α p], (List.{u1} α) -> (List.{0} Nat)
but is expected to have type
  forall {α : Type.{u1}}, (α -> Bool) -> (List.{u1} α) -> (List.{0} Nat)
Case conversion may be inaccurate. Consider using '#align list.find_indexes List.findIdxsₓ'. -/
/-- `find_indexes p l` is the list of indexes of elements of `l` that satisfy `p`. -/
def findIdxs (p : α → Prop) [DecidablePred p] (l : List α) : List Nat :=
  foldrIdx (fun i a is => if p a then i :: is else is) [] l
#align list.find_indexes List.findIdxs

/- warning: list.indexes_values -> List.indexesValues is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} (p : α -> Prop) [_inst_1 : DecidablePred.{succ u1} α p], (List.{u1} α) -> (List.{u1} (Prod.{0, u1} Nat α))
but is expected to have type
  forall {α : Type.{u1}}, (α -> Bool) -> (List.{u1} α) -> (List.{u1} (Prod.{0, u1} Nat α))
Case conversion may be inaccurate. Consider using '#align list.indexes_values List.indexesValuesₓ'. -/
/-- Returns the elements of `l` that satisfy `p` together with their indexes in
`l`. The returned list is ordered by index. -/
def indexesValues (p : α → Prop) [DecidablePred p] (l : List α) : List (ℕ × α) :=
  foldrIdx (fun i a l => if p a then (i, a) :: l else l) [] l
#align list.indexes_values List.indexesValues

/- warning: list.indexes_of -> List.indexesOf is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α], α -> (List.{u1} α) -> (List.{0} Nat)
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : BEq.{u1} α], α -> (List.{u1} α) -> (List.{0} Nat)
Case conversion may be inaccurate. Consider using '#align list.indexes_of List.indexesOfₓ'. -/
/-- `indexes_of a l` is the list of all indexes of `a` in `l`. For example:
```
indexes_of a [a, b, a, a] = [0, 2, 3]
```
-/
def indexesOf [DecidableEq α] (a : α) : List α → List Nat :=
  findIdxs (Eq a)
#align list.indexes_of List.indexesOf

section MfoldWithIndex

variable {m : Type v → Type w} [Monad m]

#print List.foldlIdxM /-
/-- Monadic variant of `foldl_with_index`. -/
def foldlIdxM {α β} (f : ℕ → β → α → m β) (b : β) (as : List α) : m β :=
  as.foldlIdx
    (fun i ma b => do
      let a ← ma
      f i a b)
    (pure b)
#align list.mfoldl_with_index List.foldlIdxM
-/

#print List.foldrIdxM /-
/-- Monadic variant of `foldr_with_index`. -/
def foldrIdxM {α β} (f : ℕ → α → β → m β) (b : β) (as : List α) : m β :=
  as.foldrIdx
    (fun i a mb => do
      let b ← mb
      f i a b)
    (pure b)
#align list.mfoldr_with_index List.foldrIdxM
-/

end MfoldWithIndex

section MmapWithIndex

variable {m : Type v → Type w} [Applicative m]

/- warning: list.mmap_with_index_aux -> List.mmapWithIndexAux is a dubious translation:
lean 3 declaration is
  forall {m : Type.{u1} -> Type.{u2}} [_inst_1 : Applicative.{u1, u2} m] {α : Type.{u3}} {β : Type.{u1}}, (Nat -> α -> (m β)) -> Nat -> (List.{u3} α) -> (m (List.{u1} β))
but is expected to have type
  forall {m : Type.{u2} -> Type.{u3}} [_inst_1 : Applicative.{u2, u3} m] {α : Type.{u1}} {β : Type.{u2}}, (Nat -> α -> (m β)) -> Nat -> (List.{u1} α) -> (m (List.{u2} β))
Case conversion may be inaccurate. Consider using '#align list.mmap_with_index_aux List.mmapWithIndexAuxₓ'. -/
/-- Auxiliary definition for `mmap_with_index`. -/
def mmapWithIndexAux {α β} (f : ℕ → α → m β) : ℕ → List α → m (List β)
  | _, [] => pure []
  | i, a :: as => List.cons <$> f i a <*> mmap_with_index_aux (i + 1) as
#align list.mmap_with_index_aux List.mmapWithIndexAux

/- warning: list.mmap_with_index -> List.mapIdxM is a dubious translation:
lean 3 declaration is
  forall {m : Type.{u1} -> Type.{u2}} [_inst_1 : Applicative.{u1, u2} m] {α : Type.{u3}} {β : Type.{u1}}, (Nat -> α -> (m β)) -> (List.{u3} α) -> (m (List.{u1} β))
but is expected to have type
  forall {m : Type.{u3}} {_inst_1 : Type.{u1}} {α : Type.{u1} -> Type.{u2}} [β : Monad.{u1, u2} α], (List.{u3} m) -> (Nat -> m -> (α _inst_1)) -> (α (List.{u1} _inst_1))
Case conversion may be inaccurate. Consider using '#align list.mmap_with_index List.mapIdxMₓ'. -/
/-- Applicative variant of `map_with_index`. -/
def mapIdxM {α β} (f : ℕ → α → m β) (as : List α) : m (List β) :=
  mmapWithIndexAux f 0 as
#align list.mmap_with_index List.mapIdxM

/- warning: list.mmap_with_index'_aux -> List.mapIdxMAux' is a dubious translation:
lean 3 declaration is
  forall {m : Type.{u1} -> Type.{u2}} [_inst_1 : Applicative.{u1, u2} m] {α : Type.{u3}}, (Nat -> α -> (m PUnit.{succ u1})) -> Nat -> (List.{u3} α) -> (m PUnit.{succ u1})
but is expected to have type
  forall {m : Type.{u1} -> Type.{u2}} [_inst_1 : Monad.{u1, u2} m] {α : Type.{u3}}, (Nat -> α -> (m PUnit.{succ u1})) -> Nat -> (List.{u3} α) -> (m PUnit.{succ u1})
Case conversion may be inaccurate. Consider using '#align list.mmap_with_index'_aux List.mapIdxMAux'ₓ'. -/
/-- Auxiliary definition for `mmap_with_index'`. -/
def mapIdxMAux' {α} (f : ℕ → α → m PUnit) : ℕ → List α → m PUnit
  | _, [] => pure ⟨⟩
  | i, a :: as => f i a *> mmap_with_index'_aux (i + 1) as
#align list.mmap_with_index'_aux List.mapIdxMAux'

/- warning: list.mmap_with_index' -> List.mapIdxM' is a dubious translation:
lean 3 declaration is
  forall {m : Type.{u1} -> Type.{u2}} [_inst_1 : Applicative.{u1, u2} m] {α : Type.{u3}}, (Nat -> α -> (m PUnit.{succ u1})) -> (List.{u3} α) -> (m PUnit.{succ u1})
but is expected to have type
  forall {m : Type.{u1} -> Type.{u2}} [_inst_1 : Monad.{u1, u2} m] {α : Type.{u3}}, (Nat -> α -> (m PUnit.{succ u1})) -> (List.{u3} α) -> (m PUnit.{succ u1})
Case conversion may be inaccurate. Consider using '#align list.mmap_with_index' List.mapIdxM'ₓ'. -/
/-- A variant of `mmap_with_index` specialised to applicative actions which
return `unit`. -/
def mapIdxM' {α} (f : ℕ → α → m PUnit) (as : List α) : m PUnit :=
  mapIdxMAux' f 0 as
#align list.mmap_with_index' List.mapIdxM'

end MmapWithIndex

#print List.lookmap /-
/-- `lookmap` is a combination of `lookup` and `filter_map`.
  `lookmap f l` will apply `f : α → option α` to each element of the list,
  replacing `a → b` at the first value `a` in the list such that `f a = some b`. -/
def lookmap (f : α → Option α) : List α → List α
  | [] => []
  | a :: l =>
    match f a with
    | some b => b :: l
    | none => a :: lookmap l
#align list.lookmap List.lookmap
-/

/- warning: list.countp -> List.countp is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} (p : α -> Prop) [_inst_1 : DecidablePred.{succ u1} α p], (List.{u1} α) -> Nat
but is expected to have type
  forall {α : Type.{u1}}, (α -> Bool) -> (List.{u1} α) -> Nat
Case conversion may be inaccurate. Consider using '#align list.countp List.countpₓ'. -/
/-- `countp p l` is the number of elements of `l` that satisfy `p`. -/
def countp (p : α → Prop) [DecidablePred p] : List α → Nat
  | [] => 0
  | x :: xs => if p x then succ (countp xs) else countp xs
#align list.countp List.countp

/- warning: list.count -> List.count is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α], α -> (List.{u1} α) -> Nat
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : BEq.{u1} α], α -> (List.{u1} α) -> Nat
Case conversion may be inaccurate. Consider using '#align list.count List.countₓ'. -/
/-- `count a l` is the number of occurrences of `a` in `l`. -/
def count [DecidableEq α] (a : α) : List α → Nat :=
  countp (Eq a)
#align list.count List.count

#print List.isPrefix /-
/-- `is_prefix l₁ l₂`, or `l₁ <+: l₂`, means that `l₁` is a prefix of `l₂`,
  that is, `l₂` has the form `l₁ ++ t` for some `t`. -/
def isPrefix (l₁ : List α) (l₂ : List α) : Prop :=
  ∃ t, l₁ ++ t = l₂
#align list.is_prefix List.isPrefix
-/

#print List.isSuffix /-
/-- `is_suffix l₁ l₂`, or `l₁ <:+ l₂`, means that `l₁` is a suffix of `l₂`,
  that is, `l₂` has the form `t ++ l₁` for some `t`. -/
def isSuffix (l₁ : List α) (l₂ : List α) : Prop :=
  ∃ t, t ++ l₁ = l₂
#align list.is_suffix List.isSuffix
-/

#print List.isInfix /-
/-- `is_infix l₁ l₂`, or `l₁ <:+: l₂`, means that `l₁` is a contiguous
  substring of `l₂`, that is, `l₂` has the form `s ++ l₁ ++ t` for some `s, t`. -/
def isInfix (l₁ : List α) (l₂ : List α) : Prop :=
  ∃ s t, s ++ l₁ ++ t = l₂
#align list.is_infix List.isInfix
-/

-- mathport name: «expr <+: »
infixl:50 " <+: " => isPrefix

-- mathport name: «expr <:+ »
infixl:50 " <:+ " => isSuffix

-- mathport name: «expr <:+: »
infixl:50 " <:+: " => isInfix

#print List.inits /-
/-- `inits l` is the list of initial segments of `l`.

     inits [1, 2, 3] = [[], [1], [1, 2], [1, 2, 3]] -/
@[simp]
def inits : List α → List (List α)
  | [] => [[]]
  | a :: l => [] :: map (fun t => a :: t) (inits l)
#align list.inits List.inits
-/

#print List.tails /-
/-- `tails l` is the list of terminal segments of `l`.

     tails [1, 2, 3] = [[1, 2, 3], [2, 3], [3], []] -/
@[simp]
def tails : List α → List (List α)
  | [] => [[]]
  | a :: l => (a :: l) :: tails l
#align list.tails List.tails
-/

/- warning: list.sublists'_aux -> List.sublists'Aux is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u_1}} {β : Type.{u_2}}, (List.{u_1} α) -> ((List.{u_1} α) -> (List.{u_2} β)) -> (List.{u_2} (List.{u_2} β)) -> (List.{u_2} (List.{u_2} β))
but is expected to have type
  forall {α : Type.{u}}, α -> (List.{u} (List.{u} α)) -> (List.{u} (List.{u} α)) -> (List.{u} (List.{u} α))
Case conversion may be inaccurate. Consider using '#align list.sublists'_aux List.sublists'Auxₓ'. -/
def sublists'Aux : List α → (List α → List β) → List (List β) → List (List β)
  | [], f, r => f [] :: r
  | a :: l, f, r => sublists'_aux l f (sublists'_aux l (f ∘ cons a) r)
#align list.sublists'_aux List.sublists'Aux

#print List.sublists' /-
/-- `sublists' l` is the list of all (non-contiguous) sublists of `l`.
  It differs from `sublists` only in the order of appearance of the sublists;
  `sublists'` uses the first element of the list as the MSB,
  `sublists` uses the first element of the list as the LSB.

     sublists' [1, 2, 3] = [[], [3], [2], [2, 3], [1], [1, 3], [1, 2], [1, 2, 3]] -/
def sublists' (l : List α) : List (List α) :=
  sublists'Aux l id []
#align list.sublists' List.sublists'
-/

/- warning: list.sublists_aux -> List.sublistsAux is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u_1}} {β : Type.{u_2}}, (List.{u_1} α) -> ((List.{u_1} α) -> (List.{u_2} β) -> (List.{u_2} β)) -> (List.{u_2} β)
but is expected to have type
  forall {α : Type.{u}}, α -> (List.{u} (List.{u} α)) -> (List.{u} (List.{u} α))
Case conversion may be inaccurate. Consider using '#align list.sublists_aux List.sublistsAuxₓ'. -/
def sublistsAux : List α → (List α → List β → List β) → List β
  | [], f => []
  | a :: l, f => f [a] (sublists_aux l fun ys r => f ys (f (a :: ys) r))
#align list.sublists_aux List.sublistsAux

#print List.sublists /-
/-- `sublists l` is the list of all (non-contiguous) sublists of `l`; cf. `sublists'`
  for a different ordering.

     sublists [1, 2, 3] = [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]] -/
def sublists (l : List α) : List (List α) :=
  [] :: sublistsAux l cons
#align list.sublists List.sublists
-/

def sublistsAux₁ : List α → (List α → List β) → List β
  | [], f => []
  | a :: l, f => f [a] ++ sublists_aux₁ l fun ys => f ys ++ f (a :: ys)
#align list.sublists_aux₁ List.sublistsAux₁

section Forall₂

variable {r : α → β → Prop} {p : γ → δ → Prop}

#print List.Forall₂ /-
/-- `forall₂ R l₁ l₂` means that `l₁` and `l₂` have the same length,
  and whenever `a` is the nth element of `l₁`, and `b` is the nth element of `l₂`,
  then `R a b` is satisfied. -/
inductive Forall₂ (R : α → β → Prop) : List α → List β → Prop
  | nil : forall₂ [] []
  | cons {a b l₁ l₂} : R a b → forall₂ l₁ l₂ → forall₂ (a :: l₁) (b :: l₂)
#align list.forall₂ List.Forall₂
-/

attribute [simp] forall₂.nil

end Forall₂

#print List.All₂ /-
/-- `l.all₂ p` is equivalent to `∀ a ∈ l, p a`, but unfolds directly to a conjunction, i.e.
`list.all₂ p [0, 1, 2] = p 0 ∧ p 1 ∧ p 2`. -/
@[simp]
def All₂ (p : α → Prop) : List α → Prop
  | [] => True
  | x :: [] => p x
  | x :: l => p x ∧ all₂ l
#align list.all₂ List.All₂
-/

/-- Auxiliary definition used to define `transpose`.
  `transpose_aux l L` takes each element of `l` and appends it to the start of
  each element of `L`.

  `transpose_aux [a, b, c] [l₁, l₂, l₃] = [a::l₁, b::l₂, c::l₃]` -/
def transposeAux : List α → List (List α) → List (List α)
  | [], ls => ls
  | a :: i, [] => [a] :: transpose_aux i []
  | a :: i, l :: ls => (a :: l) :: transpose_aux i ls
#align list.transpose_aux List.transposeAux

#print List.transpose /-
/-- transpose of a list of lists, treated as a matrix.

     transpose [[1, 2], [3, 4], [5, 6]] = [[1, 3, 5], [2, 4, 6]] -/
def transpose : List (List α) → List (List α)
  | [] => []
  | l :: ls => transposeAux l (transpose ls)
#align list.transpose List.transpose
-/

#print List.sections /-
/-- List of all sections through a list of lists. A section
  of `[L₁, L₂, ..., Lₙ]` is a list whose first element comes from
  `L₁`, whose second element comes from `L₂`, and so on. -/
def sections : List (List α) → List (List α)
  | [] => [[]]
  | l :: L => bind (sections L) fun s => map (fun a => a :: s) l
#align list.sections List.sections
-/

section Permutations

#print List.permutationsAux2 /-
/-- An auxiliary function for defining `permutations`. `permutations_aux2 t ts r ys f` is equal to
`(ys ++ ts, (insert_left ys t ts).map f ++ r)`, where `insert_left ys t ts` (not explicitly
defined) is the list of lists of the form `insert_nth n t (ys ++ ts)` for `0 ≤ n < length ys`.

    permutations_aux2 10 [4, 5, 6] [] [1, 2, 3] id =
      ([1, 2, 3, 4, 5, 6],
       [[10, 1, 2, 3, 4, 5, 6],
        [1, 10, 2, 3, 4, 5, 6],
        [1, 2, 10, 3, 4, 5, 6]]) -/
def permutationsAux2 (t : α) (ts : List α) (r : List β) : List α → (List α → β) → List α × List β
  | [], f => (ts, r)
  | y :: ys, f =>
    let (us, zs) := permutations_aux2 ys fun x : List α => f (y :: x)
    (y :: us, f (t :: y :: us) :: zs)
#align list.permutations_aux2 List.permutationsAux2
-/

private def meas : (Σ'_ : List α, List α) → ℕ × ℕ
  | ⟨l, i⟩ => (length l + length i, length l)
#align list.meas list.meas

-- mathport name: «expr ≺ »
local infixl:50 " ≺ " => InvImage (Prod.Lex (· < ·) (· < ·)) meas

#print List.permutationsAux.rec /-
/-- A recursor for pairs of lists. To have `C l₁ l₂` for all `l₁`, `l₂`, it suffices to have it for
`l₂ = []` and to be able to pour the elements of `l₁` into `l₂`. -/
@[elab_as_elim]
def permutationsAux.rec {C : List α → List α → Sort v} (H0 : ∀ is, C [] is)
    (H1 : ∀ t ts is, C ts (t :: is) → C is [] → C (t :: ts) is) : ∀ l₁ l₂, C l₁ l₂
  | [], is => H0 is
  | t :: ts, is =>
    have h1 : ⟨ts, t :: is⟩ ≺ ⟨t :: ts, is⟩ :=
      show
        Prod.Lex _ _ (succ (length ts + length is), length ts)
          (succ (length ts) + length is, length (t :: ts))
        by rw [Nat.succ_add] <;> exact Prod.Lex.right _ (lt_succ_self _)
    have h2 : ⟨is, []⟩ ≺ ⟨t :: ts, is⟩ := Prod.Lex.left _ _ (Nat.lt_add_of_pos_left (succ_pos _))
    H1 t ts is (permutations_aux.rec ts (t :: is)) (permutations_aux.rec is [])termination_by'
  ⟨(· ≺ ·), @InvImage.wf _ _ _ meas (WellFounded.prod_lex lt_wf lt_wf)⟩
#align list.permutations_aux.rec List.permutationsAux.rec
-/

#print List.permutationsAux /-
/-- An auxiliary function for defining `permutations`. `permutations_aux ts is` is the set of all
permutations of `is ++ ts` that do not fix `ts`. -/
def permutationsAux : List α → List α → List (List α) :=
  @permutationsAux.rec (fun _ _ => List (List α)) (fun is => []) fun t ts is IH1 IH2 =>
    foldr (fun y r => (permutationsAux2 t ts r y id).2) IH1 (is :: IH2)
#align list.permutations_aux List.permutationsAux
-/

#print List.permutations /-
/-- List of all permutations of `l`.

     permutations [1, 2, 3] =
       [[1, 2, 3], [2, 1, 3], [3, 2, 1],
        [2, 3, 1], [3, 1, 2], [1, 3, 2]] -/
def permutations (l : List α) : List (List α) :=
  l :: permutationsAux l []
#align list.permutations List.permutations
-/

#print List.permutations'Aux /-
/-- `permutations'_aux t ts` inserts `t` into every position in `ts`, including the last.
This function is intended for use in specifications, so it is simpler than `permutations_aux2`,
which plays roughly the same role in `permutations`.

Note that `(permutations_aux2 t [] [] ts id).2` is similar to this function, but skips the last
position:

    permutations'_aux 10 [1, 2, 3] =
      [[10, 1, 2, 3], [1, 10, 2, 3], [1, 2, 10, 3], [1, 2, 3, 10]]
    (permutations_aux2 10 [] [] [1, 2, 3] id).2 =
      [[10, 1, 2, 3], [1, 10, 2, 3], [1, 2, 10, 3]] -/
@[simp]
def permutations'Aux (t : α) : List α → List (List α)
  | [] => [[t]]
  | y :: ys => (t :: y :: ys) :: (permutations'_aux ys).map (cons y)
#align list.permutations'_aux List.permutations'Aux
-/

#print List.permutations' /-
/-- List of all permutations of `l`. This version of `permutations` is less efficient but has
simpler definitional equations. The permutations are in a different order,
but are equal up to permutation, as shown by `list.permutations_perm_permutations'`.

     permutations [1, 2, 3] =
       [[1, 2, 3], [2, 1, 3], [2, 3, 1],
        [1, 3, 2], [3, 1, 2], [3, 2, 1]] -/
@[simp]
def permutations' : List α → List (List α)
  | [] => [[]]
  | t :: ts => (permutations' ts).bind <| permutations'Aux t
#align list.permutations' List.permutations'
-/

end Permutations

/-- `erasep p l` removes the first element of `l` satisfying the predicate `p`. -/
def eraseP (p : α → Prop) [DecidablePred p] : List α → List α
  | [] => []
  | a :: l => if p a then l else a :: erasep l
#align list.erasep List.erasePₓ

#print List.extractp /-
/-- `extractp p l` returns a pair of an element `a` of `l` satisfying the predicate
  `p`, and `l`, with `a` removed. If there is no such element `a` it returns `(none, l)`. -/
def extractp (p : α → Prop) [DecidablePred p] : List α → Option α × List α
  | [] => (none, [])
  | a :: l =>
    if p a then (some a, l)
    else
      let (a', l') := extractp l
      (a', a :: l')
#align list.extractp List.extractp
-/

#print List.revzip /-
/-- `revzip l` returns a list of pairs of the elements of `l` paired
  with the elements of `l` in reverse order.

`revzip [1,2,3,4,5] = [(1, 5), (2, 4), (3, 3), (4, 2), (5, 1)]`
 -/
def revzip (l : List α) : List (α × α) :=
  zip l l.reverse
#align list.revzip List.revzip
-/

#print List.product /-
/-- `product l₁ l₂` is the list of pairs `(a, b)` where `a ∈ l₁` and `b ∈ l₂`.

     product [1, 2] [5, 6] = [(1, 5), (1, 6), (2, 5), (2, 6)] -/
def product (l₁ : List α) (l₂ : List β) : List (α × β) :=
  l₁.bind fun a => l₂.map <| Prod.mk a
#align list.product List.product
-/

-- mathport name: list.product
infixr:82
  " ×ˢ " =>-- This notation binds more strongly than (pre)images, unions and intersections.
  List.product

#print List.sigma /-
/-- `sigma l₁ l₂` is the list of dependent pairs `(a, b)` where `a ∈ l₁` and `b ∈ l₂ a`.

     sigma [1, 2] (λ_, [(5 : ℕ), 6]) = [(1, 5), (1, 6), (2, 5), (2, 6)] -/
protected def sigma {σ : α → Type _} (l₁ : List α) (l₂ : ∀ a, List (σ a)) : List (Σa, σ a) :=
  l₁.bind fun a => (l₂ a).map <| Sigma.mk a
#align list.sigma List.sigma
-/

/-- Auxliary definition used to define `of_fn`.

  `of_fn_aux f m h l` returns the first `m` elements of `of_fn f`
  appended to `l` -/
def ofFnAux {n} (f : Fin n → α) : ∀ m, m ≤ n → List α → List α
  | 0, h, l => l
  | succ m, h, l => of_fn_aux m (le_of_lt h) (f ⟨m, h⟩ :: l)
#align list.of_fn_aux List.ofFnAux

#print List.ofFn /-
/-- `of_fn f` with `f : fin n → α` returns the list whose ith element is `f i`
  `of_fun f = [f 0, f 1, ... , f(n - 1)]` -/
def ofFn {n} (f : Fin n → α) : List α :=
  ofFnAux f n (le_refl _) []
#align list.of_fn List.ofFn
-/

#print List.ofFnNthVal /-
/-- `of_fn_nth_val f i` returns `some (f i)` if `i < n` and `none` otherwise. -/
def ofFnNthVal {n} (f : Fin n → α) (i : ℕ) : Option α :=
  if h : i < n then some (f ⟨i, h⟩) else none
#align list.of_fn_nth_val List.ofFnNthVal
-/

#print List.Disjoint /-
/-- `disjoint l₁ l₂` means that `l₁` and `l₂` have no elements in common. -/
def Disjoint (l₁ l₂ : List α) : Prop :=
  ∀ ⦃a⦄, a ∈ l₁ → a ∈ l₂ → False
#align list.disjoint List.Disjoint
-/

section Pairwise

variable (R : α → α → Prop)

#print List.Pairwise /-
/-- `pairwise R l` means that all the elements with earlier indexes are
  `R`-related to all the elements with later indexes.

     pairwise R [1, 2, 3] ↔ R 1 2 ∧ R 1 3 ∧ R 2 3

  For example if `R = (≠)` then it asserts `l` has no duplicates,
  and if `R = (<)` then it asserts that `l` is (strictly) sorted. -/
inductive Pairwise : List α → Prop
  | nil : Pairwise []
  | cons : ∀ {a : α} {l : List α}, (∀ a' ∈ l, R a a') → Pairwise l → Pairwise (a :: l)
#align list.pairwise List.Pairwise
-/

variable {R}

#print List.pairwise_cons /-
@[simp]
theorem pairwise_cons {a : α} {l : List α} :
    Pairwise R (a :: l) ↔ (∀ a' ∈ l, R a a') ∧ Pairwise R l :=
  ⟨fun p => by cases' p with a l n p <;> exact ⟨n, p⟩, fun ⟨n, p⟩ => p.cons n⟩
#align list.pairwise_cons List.pairwise_cons
-/

attribute [simp] pairwise.nil

#print List.instDecidablePairwise /-
instance instDecidablePairwise [DecidableRel R] (l : List α) : Decidable (Pairwise R l) := by
  induction' l with hd tl ih <;> [exact is_true pairwise.nil,
    exact decidable_of_iff' _ pairwise_cons]
#align list.decidable_pairwise List.instDecidablePairwise
-/

end Pairwise

#print List.pwFilter /-
/-- `pw_filter R l` is a maximal sublist of `l` which is `pairwise R`.
  `pw_filter (≠)` is the erase duplicates function (cf. `dedup`), and `pw_filter (<)` finds
  a maximal increasing subsequence in `l`. For example,

     pw_filter (<) [0, 1, 5, 2, 6, 3, 4] = [0, 1, 2, 3, 4] -/
def pwFilter (R : α → α → Prop) [DecidableRel R] : List α → List α
  | [] => []
  | x :: xs =>
    let IH := pw_filter xs
    if ∀ y ∈ IH, R x y then x :: IH else IH
#align list.pw_filter List.pwFilter
-/

section Chain

variable (R : α → α → Prop)

#print List.Chain /-
/-- `chain R a l` means that `R` holds between adjacent elements of `a::l`.

     chain R a [b, c, d] ↔ R a b ∧ R b c ∧ R c d -/
inductive Chain : α → List α → Prop
  | nil {a : α} : chain a []
  | cons : ∀ {a b : α} {l : List α}, R a b → chain b l → chain a (b :: l)
#align list.chain List.Chain
-/

#print List.Chain' /-
/-- `chain' R l` means that `R` holds between adjacent elements of `l`.

     chain' R [a, b, c, d] ↔ R a b ∧ R b c ∧ R c d -/
def Chain' : List α → Prop
  | [] => True
  | a :: l => Chain R a l
#align list.chain' List.Chain'
-/

variable {R}

#print List.chain_cons /-
@[simp]
theorem chain_cons {a b : α} {l : List α} : Chain R a (b :: l) ↔ R a b ∧ Chain R b l :=
  ⟨fun p => by cases' p with _ a b l n p <;> exact ⟨n, p⟩, fun ⟨n, p⟩ => p.cons n⟩
#align list.chain_cons List.chain_cons
-/

attribute [simp] chain.nil

#print List.decidableChain /-
instance decidableChain [DecidableRel R] (a : α) (l : List α) : Decidable (Chain R a l) := by
  induction l generalizing a <;> simp only [chain.nil, chain_cons] <;> skip <;> infer_instance
#align list.decidable_chain List.decidableChain
-/

#print List.decidableChain' /-
instance decidableChain' [DecidableRel R] (l : List α) : Decidable (Chain' R l) := by
  cases l <;> dsimp only [chain'] <;> infer_instance
#align list.decidable_chain' List.decidableChain'
-/

end Chain

#print List.Nodup /-
/-- `nodup l` means that `l` has no duplicates, that is, any element appears at most
  once in the list. It is defined as `pairwise (≠)`. -/
def Nodup : List α → Prop :=
  Pairwise (· ≠ ·)
#align list.nodup List.Nodup
-/

#print List.nodupDecidable /-
instance nodupDecidable [DecidableEq α] : ∀ l : List α, Decidable (Nodup l) :=
  List.instDecidablePairwise
#align list.nodup_decidable List.nodupDecidable
-/

#print List.dedup /-
/-- `dedup l` removes duplicates from `l` (taking only the last occurrence).
  Defined as `pw_filter (≠)`.

     dedup [1, 0, 2, 2, 1] = [0, 2, 1] -/
def dedup [DecidableEq α] : List α → List α :=
  pwFilter (· ≠ ·)
#align list.dedup List.dedup
-/

#print List.destutter' /-
/-- Greedily create a sublist of `a :: l` such that, for every two adjacent elements `a, b`,
`R a b` holds. Mostly used with ≠; for example, `destutter' (≠) 1 [2, 2, 1, 1] = [1, 2, 1]`,
`destutter' (≠) 1, [2, 3, 3] = [1, 2, 3]`, `destutter' (<) 1 [2, 5, 2, 3, 4, 9] = [1, 2, 5, 9]`. -/
def destutter' (R : α → α → Prop) [DecidableRel R] : α → List α → List α
  | a, [] => [a]
  | a, h :: l => if R a h then a :: destutter' h l else destutter' a l
#align list.destutter' List.destutter'
-/

#print List.destutter /-
/-- Greedily create a sublist of `l` such that, for every two adjacent elements `a, b ∈ l`,
`R a b` holds. Mostly used with ≠; for example, `destutter (≠) [1, 2, 2, 1, 1] = [1, 2, 1]`,
`destutter (≠) [1, 2, 3, 3] = [1, 2, 3]`, `destutter (<) [1, 2, 5, 2, 3, 4, 9] = [1, 2, 5, 9]`. -/
def destutter (R : α → α → Prop) [DecidableRel R] : List α → List α
  | h :: l => destutter' R h l
  | [] => []
#align list.destutter List.destutter
-/

#print List.range' /-
/-- `range' s n` is the list of numbers `[s, s+1, ..., s+n-1]`.
  It is intended mainly for proving properties of `range` and `iota`. -/
@[simp]
def range' : ℕ → ℕ → List ℕ
  | s, 0 => []
  | s, n + 1 => s :: range' (s + 1) n
#align list.range' List.range'
-/

#print List.reduceOption /-
/-- Drop `none`s from a list, and replace each remaining `some a` with `a`. -/
def reduceOption {α} : List (Option α) → List α :=
  List.filterMap id
#align list.reduce_option List.reduceOption
-/

#print List.ilast' /-
/-- `ilast' x xs` returns the last element of `xs` if `xs` is non-empty;
it returns `x` otherwise -/
@[simp]
def ilast' {α} : α → List α → α
  | a, [] => a
  | a, b :: l => ilast' b l
#align list.ilast' List.ilast'
-/

#print List.getLast? /-
/-- `last' xs` returns the last element of `xs` if `xs` is non-empty;
it returns `none` otherwise -/
@[simp]
def getLast? {α} : List α → Option α
  | [] => none
  | [a] => some a
  | b :: l => last' l
#align list.last' List.getLast?
-/

#print List.rotate /-
/-- `rotate l n` rotates the elements of `l` to the left by `n`

     rotate [0, 1, 2, 3, 4, 5] 2 = [2, 3, 4, 5, 0, 1] -/
def rotate (l : List α) (n : ℕ) : List α :=
  let (l₁, l₂) := List.splitAt (n % l.length) l
  l₂ ++ l₁
#align list.rotate List.rotate
-/

#print List.rotate' /-
/-- rotate' is the same as `rotate`, but slower. Used for proofs about `rotate`-/
def rotate' : List α → ℕ → List α
  | [], n => []
  | l, 0 => l
  | a :: l, n + 1 => rotate' (l ++ [a]) n
#align list.rotate' List.rotate'
-/

section Choose

variable (p : α → Prop) [DecidablePred p] (l : List α)

#print List.chooseX /-
/-- Given a decidable predicate `p` and a proof of existence of `a ∈ l` such that `p a`,
choose the first element with this property. This version returns both `a` and proofs
of `a ∈ l` and `p a`. -/
def chooseX : ∀ l : List α, ∀ hp : ∃ a, a ∈ l ∧ p a, { a // a ∈ l ∧ p a }
  | [], hp => False.elim (Exists.elim hp fun a h => not_mem_nil a h.left)
  | l :: ls, hp =>
    if pl : p l then ⟨l, ⟨Or.inl rfl, pl⟩⟩
    else
      let ⟨a, ⟨a_mem_ls, pa⟩⟩ :=
        choose_x ls (hp.imp fun b ⟨o, h₂⟩ => ⟨o.resolve_left fun e => pl <| e ▸ h₂, h₂⟩)
      ⟨a, ⟨Or.inr a_mem_ls, pa⟩⟩
#align list.choose_x List.chooseX
-/

#print List.choose /-
/-- Given a decidable predicate `p` and a proof of existence of `a ∈ l` such that `p a`,
choose the first element with this property. This version returns `a : α`, and properties
are given by `choose_mem` and `choose_property`. -/
def choose (hp : ∃ a, a ∈ l ∧ p a) : α :=
  chooseX p l hp
#align list.choose List.choose
-/

end Choose

/- warning: list.mmap_filter -> List.filterMapM is a dubious translation:
lean 3 declaration is
  forall {m : Type -> Type.{u1}} [_inst_1 : Monad.{0, u1} m] {α : Type.{u2}} {β : Type}, (α -> (m (Option.{0} β))) -> (List.{u2} α) -> (m (List.{0} β))
but is expected to have type
  forall {m : Type.{u1} -> Type.{u2}} [_inst_1 : Monad.{u1, u2} m] {α : Type.{u1}} {β : Type.{u1}}, (α -> (m (Option.{u1} β))) -> (List.{u1} α) -> (m (List.{u1} β))
Case conversion may be inaccurate. Consider using '#align list.mmap_filter List.filterMapMₓ'. -/
/-- Filters and maps elements of a list -/
def filterMapM {m : Type → Type v} [Monad m] {α β} (f : α → m (Option β)) : List α → m (List β)
  | [] => return []
  | h :: t => do
    let b ← f h
    let t' ← t.filterMapM
    return <|
        match b with
        | none => t'
        | some x => x :: t'
#align list.mmap_filter List.filterMapM

/- warning: list.mmap_upper_triangle -> List.mapDiagM is a dubious translation:
lean 3 declaration is
  forall {m : Type.{u} -> Type.{u_1}} [_inst_1 : Monad.{u, u_1} m] {α : Type.{u}} {β : Type.{u}}, (α -> α -> (m β)) -> (List.{u} α) -> (m (List.{u} β))
but is expected to have type
  forall {m : Type.{u_1} -> Type.{u_2}} {_inst_1 : Type.{u_3}} {α : Type.{u_1}} [β : Monad.{u_1, u_2} m], (_inst_1 -> _inst_1 -> (m α)) -> (List.{u_3} _inst_1) -> (m (List.{u_1} α))
Case conversion may be inaccurate. Consider using '#align list.mmap_upper_triangle List.mapDiagMₓ'. -/
/-- `mmap_upper_triangle f l` calls `f` on all elements in the upper triangular part of `l × l`.
That is, for each `e ∈ l`, it will run `f e e` and then `f e e'`
for each `e'` that appears after `e` in `l`.

Example: suppose `l = [1, 2, 3]`. `mmap_upper_triangle f l` will produce the list
`[f 1 1, f 1 2, f 1 3, f 2 2, f 2 3, f 3 3]`.
-/
def mapDiagM {m} [Monad m] {α β : Type u} (f : α → α → m β) : List α → m (List β)
  | [] => return []
  | h :: t => do
    let v ← f h h
    let l ← t.mapM (f h)
    let t ← t.mapDiagM
    return <| v :: l ++ t
#align list.mmap_upper_triangle List.mapDiagM

#print List.mapDiagM' /-
/-- `mmap'_diag f l` calls `f` on all elements in the upper triangular part of `l × l`.
That is, for each `e ∈ l`, it will run `f e e` and then `f e e'`
for each `e'` that appears after `e` in `l`.

Example: suppose `l = [1, 2, 3]`. `mmap'_diag f l` will evaluate, in this order,
`f 1 1`, `f 1 2`, `f 1 3`, `f 2 2`, `f 2 3`, `f 3 3`.
-/
def mapDiagM' {m} [Monad m] {α} (f : α → α → m Unit) : List α → m Unit
  | [] => return ()
  | h :: t => (f h h >> t.mapM' (f h)) >> t.mapDiagM'
#align list.mmap'_diag List.mapDiagM'
-/

#print List.traverse /-
protected def traverse {F : Type u → Type v} [Applicative F] {α β : Type _} (f : α → F β) :
    List α → F (List β)
  | [] => pure []
  | x :: xs => List.cons <$> f x <*> traverse xs
#align list.traverse List.traverse
-/

#print List.getRest /-
/-- `get_rest l l₁` returns `some l₂` if `l = l₁ ++ l₂`.
  If `l₁` is not a prefix of `l`, returns `none` -/
def getRest [DecidableEq α] : List α → List α → Option (List α)
  | l, [] => some l
  | [], _ => none
  | x :: l, y :: l₁ => if x = y then get_rest l l₁ else none
#align list.get_rest List.getRest
-/

#print List.dropSlice /-
/-- `list.slice n m xs` removes a slice of length `m` at index `n` in list `xs`.
-/
def dropSlice {α} : ℕ → ℕ → List α → List α
  | 0, n, xs => xs.drop n
  | succ n, m, [] => []
  | succ n, m, x :: xs => x :: slice n m xs
#align list.slice List.dropSlice
-/

#print List.map₂Left' /-
/-- Left-biased version of `list.map₂`. `map₂_left' f as bs` applies `f` to each
pair of elements `aᵢ ∈ as` and `bᵢ ∈ bs`. If `bs` is shorter than `as`, `f` is
applied to `none` for the remaining `aᵢ`. Returns the results of the `f`
applications and the remaining `bs`.

```
map₂_left' prod.mk [1, 2] ['a'] = ([(1, some 'a'), (2, none)], [])

map₂_left' prod.mk [1] ['a', 'b'] = ([(1, some 'a')], ['b'])
```
-/
@[simp]
def map₂Left' (f : α → Option β → γ) : List α → List β → List γ × List β
  | [], bs => ([], bs)
  | a :: as, [] => ((a :: as).map fun a => f a none, [])
  | a :: as, b :: bs =>
    let rec := map₂_left' as bs
    (f a (some b) :: rec.fst, rec.snd)
#align list.map₂_left' List.map₂Left'
-/

#print List.map₂Right' /-
/-- Right-biased version of `list.map₂`. `map₂_right' f as bs` applies `f` to each
pair of elements `aᵢ ∈ as` and `bᵢ ∈ bs`. If `as` is shorter than `bs`, `f` is
applied to `none` for the remaining `bᵢ`. Returns the results of the `f`
applications and the remaining `as`.

```
map₂_right' prod.mk [1] ['a', 'b'] = ([(some 1, 'a'), (none, 'b')], [])

map₂_right' prod.mk [1, 2] ['a'] = ([(some 1, 'a')], [2])
```
-/
def map₂Right' (f : Option α → β → γ) (as : List α) (bs : List β) : List γ × List α :=
  map₂Left' (flip f) bs as
#align list.map₂_right' List.map₂Right'
-/

#print List.zipLeft' /-
/-- Left-biased version of `list.zip`. `zip_left' as bs` returns the list of
pairs `(aᵢ, bᵢ)` for `aᵢ ∈ as` and `bᵢ ∈ bs`. If `bs` is shorter than `as`, the
remaining `aᵢ` are paired with `none`. Also returns the remaining `bs`.

```
zip_left' [1, 2] ['a'] = ([(1, some 'a'), (2, none)], [])

zip_left' [1] ['a', 'b'] = ([(1, some 'a')], ['b'])

zip_left' = map₂_left' prod.mk

```
-/
def zipLeft' : List α → List β → List (α × Option β) × List β :=
  map₂Left' Prod.mk
#align list.zip_left' List.zipLeft'
-/

#print List.zipRight' /-
/-- Right-biased version of `list.zip`. `zip_right' as bs` returns the list of
pairs `(aᵢ, bᵢ)` for `aᵢ ∈ as` and `bᵢ ∈ bs`. If `as` is shorter than `bs`, the
remaining `bᵢ` are paired with `none`. Also returns the remaining `as`.

```
zip_right' [1] ['a', 'b'] = ([(some 1, 'a'), (none, 'b')], [])

zip_right' [1, 2] ['a'] = ([(some 1, 'a')], [2])

zip_right' = map₂_right' prod.mk
```
-/
def zipRight' : List α → List β → List (Option α × β) × List α :=
  map₂Right' Prod.mk
#align list.zip_right' List.zipRight'
-/

#print List.map₂Left /-
/-- Left-biased version of `list.map₂`. `map₂_left f as bs` applies `f` to each pair
`aᵢ ∈ as` and `bᵢ ∈ bs`. If `bs` is shorter than `as`, `f` is applied to `none`
for the remaining `aᵢ`.

```
map₂_left prod.mk [1, 2] ['a'] = [(1, some 'a'), (2, none)]

map₂_left prod.mk [1] ['a', 'b'] = [(1, some 'a')]

map₂_left f as bs = (map₂_left' f as bs).fst
```
-/
@[simp]
def map₂Left (f : α → Option β → γ) : List α → List β → List γ
  | [], _ => []
  | a :: as, [] => (a :: as).map fun a => f a none
  | a :: as, b :: bs => f a (some b) :: map₂_left as bs
#align list.map₂_left List.map₂Left
-/

#print List.map₂Right /-
/-- Right-biased version of `list.map₂`. `map₂_right f as bs` applies `f` to each
pair `aᵢ ∈ as` and `bᵢ ∈ bs`. If `as` is shorter than `bs`, `f` is applied to
`none` for the remaining `bᵢ`.

```
map₂_right prod.mk [1, 2] ['a'] = [(some 1, 'a')]

map₂_right prod.mk [1] ['a', 'b'] = [(some 1, 'a'), (none, 'b')]

map₂_right f as bs = (map₂_right' f as bs).fst
```
-/
def map₂Right (f : Option α → β → γ) (as : List α) (bs : List β) : List γ :=
  map₂Left (flip f) bs as
#align list.map₂_right List.map₂Right
-/

#print List.zipLeft /-
/-- Left-biased version of `list.zip`. `zip_left as bs` returns the list of pairs
`(aᵢ, bᵢ)` for `aᵢ ∈ as` and `bᵢ ∈ bs`. If `bs` is shorter than `as`, the
remaining `aᵢ` are paired with `none`.

```
zip_left [1, 2] ['a'] = [(1, some 'a'), (2, none)]

zip_left [1] ['a', 'b'] = [(1, some 'a')]

zip_left = map₂_left prod.mk
```
-/
def zipLeft : List α → List β → List (α × Option β) :=
  map₂Left Prod.mk
#align list.zip_left List.zipLeft
-/

#print List.zipRight /-
/-- Right-biased version of `list.zip`. `zip_right as bs` returns the list of pairs
`(aᵢ, bᵢ)` for `aᵢ ∈ as` and `bᵢ ∈ bs`. If `as` is shorter than `bs`, the
remaining `bᵢ` are paired with `none`.

```
zip_right [1, 2] ['a'] = [(some 1, 'a')]

zip_right [1] ['a', 'b'] = [(some 1, 'a'), (none, 'b')]

zip_right = map₂_right prod.mk
```
-/
def zipRight : List α → List β → List (Option α × β) :=
  map₂Right Prod.mk
#align list.zip_right List.zipRight
-/

#print List.allSome /-
/-- If all elements of `xs` are `some xᵢ`, `all_some xs` returns the `xᵢ`. Otherwise
it returns `none`.

```
all_some [some 1, some 2] = some [1, 2]
all_some [some 1, none  ] = none
```
-/
def allSome : List (Option α) → Option (List α)
  | [] => some []
  | some a :: as => cons a <$> all_some as
  | none :: as => none
#align list.all_some List.allSome
-/

#print List.fillNones /-
/-- `fill_nones xs ys` replaces the `none`s in `xs` with elements of `ys`. If there
are not enough `ys` to replace all the `none`s, the remaining `none`s are
dropped from `xs`.

```
fill_nones [none, some 1, none, none] [2, 3] = [2, 1, 3]
```
-/
def fillNones {α} : List (Option α) → List α → List α
  | [], _ => []
  | some a :: as, as' => a :: fill_nones as as'
  | none :: as, [] => as.reduceOption
  | none :: as, a :: as' => a :: fill_nones as as'
#align list.fill_nones List.fillNones
-/

#print List.takeList /-
/-- `take_list as ns` extracts successive sublists from `as`. For `ns = n₁ ... nₘ`,
it first takes the `n₁` initial elements from `as`, then the next `n₂` ones,
etc. It returns the sublists of `as` -- one for each `nᵢ` -- and the remaining
elements of `as`. If `as` does not have at least as many elements as the sum of
the `nᵢ`, the corresponding sublists will have less than `nᵢ` elements.

```
take_list ['a', 'b', 'c', 'd', 'e'] [2, 1, 1] = ([['a', 'b'], ['c'], ['d']], ['e'])
take_list ['a', 'b'] [3, 1] = ([['a', 'b'], []], [])
```
-/
def takeList {α} : List α → List ℕ → List (List α) × List α
  | xs, [] => ([], xs)
  | xs, n :: ns =>
    let ⟨xs₁, xs₂⟩ := xs.splitAt n
    let ⟨xss, rest⟩ := take_list xs₂ ns
    (xs₁ :: xss, rest)
#align list.take_list List.takeList
-/

/- warning: list.to_rbmap -> List.toRBMap is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u_1}}, (List.{u_1} α) -> (Rbmap.{0, u_1} Nat α (LT.lt.{0} Nat Nat.hasLt))
but is expected to have type
  forall {α : Type.{u_1}} {ᾰ : Type.{u_2}}, (List.{max u_2 u_1} (Prod.{u_1, u_2} α ᾰ)) -> (forall (cmp : α -> α -> Ordering), Std.RBMap.{u_1, u_2} α ᾰ cmp)
Case conversion may be inaccurate. Consider using '#align list.to_rbmap List.toRBMapₓ'. -/
/-- `to_rbmap as` is the map that associates each index `i` of `as` with the
corresponding element of `as`.

```
to_rbmap ['a', 'b', 'c'] = rbmap_of [(0, 'a'), (1, 'b'), (2, 'c')]
```
-/
def toRBMap {α : Type _} : List α → Rbmap ℕ α :=
  foldlIdx (fun i mapp a => mapp.insert i a) (mkRbmap ℕ α)
#align list.to_rbmap List.toRBMap

#print List.toChunksAux /-
/-- Auxliary definition used to define `to_chunks`.

  `to_chunks_aux n xs i` returns `(xs.take i, (xs.drop i).to_chunks (n+1))`,
  that is, the first `i` elements of `xs`, and the remaining elements chunked into
  sublists of length `n+1`. -/
def toChunksAux {α} (n : ℕ) : List α → ℕ → List α × List (List α)
  | [], i => ([], [])
  | x :: xs, 0 =>
    let (l, L) := to_chunks_aux xs n
    ([], (x :: l) :: L)
  | x :: xs, i + 1 =>
    let (l, L) := to_chunks_aux xs i
    (x :: l, L)
#align list.to_chunks_aux List.toChunksAux
-/

#print List.toChunks /-
/-- `xs.to_chunks n` splits the list into sublists of size at most `n`,
such that `(xs.to_chunks n).join = xs`.

```
[1, 2, 3, 4, 5, 6, 7, 8].to_chunks 10 = [[1, 2, 3, 4, 5, 6, 7, 8]]
[1, 2, 3, 4, 5, 6, 7, 8].to_chunks 3 = [[1, 2, 3], [4, 5, 6], [7, 8]]
[1, 2, 3, 4, 5, 6, 7, 8].to_chunks 2 = [[1, 2], [3, 4], [5, 6], [7, 8]]
[1, 2, 3, 4, 5, 6, 7, 8].to_chunks 0 = [[1, 2, 3, 4, 5, 6, 7, 8]]
```
-/
def toChunks {α} : ℕ → List α → List (List α)
  | _, [] => []
  | 0, xs => [xs]
  | n + 1, x :: xs =>
    let (l, L) := toChunksAux n xs n
    (x :: l) :: L
#align list.to_chunks List.toChunks
-/

#print List.mapAsyncChunked /-
/-- Asynchronous version of `list.map`.
-/
unsafe def mapAsyncChunked {α β} (f : α → β) (xs : List α) (chunk_size := 1024) : List β :=
  ((xs.toChunks chunk_size).map fun xs => task.delay fun _ => List.map f xs).bind task.get
#align list.map_async_chunked List.mapAsyncChunked
-/

/-!
We add some n-ary versions of `list.zip_with` for functions with more than two arguments.
These can also be written in terms of `list.zip` or `list.zip_with`.
For example, `zip_with3 f xs ys zs` could also be written as
`zip_with id (zip_with f xs ys) zs`
or as
`(zip xs $ zip ys zs).map $ λ ⟨x, y, z⟩, f x y z`.
-/

#print List.zipWith3 /-
/-- Ternary version of `list.zip_with`. -/
def zipWith3 (f : α → β → γ → δ) : List α → List β → List γ → List δ
  | x :: xs, y :: ys, z :: zs => f x y z :: zip_with3 xs ys zs
  | _, _, _ => []
#align list.zip_with3 List.zipWith3
-/

#print List.zipWith4 /-
/-- Quaternary version of `list.zip_with`. -/
def zipWith4 (f : α → β → γ → δ → ε) : List α → List β → List γ → List δ → List ε
  | x :: xs, y :: ys, z :: zs, u :: us => f x y z u :: zip_with4 xs ys zs us
  | _, _, _, _ => []
#align list.zip_with4 List.zipWith4
-/

#print List.zipWith5 /-
/-- Quinary version of `list.zip_with`. -/
def zipWith5 (f : α → β → γ → δ → ε → ζ) : List α → List β → List γ → List δ → List ε → List ζ
  | x :: xs, y :: ys, z :: zs, u :: us, v :: vs => f x y z u v :: zip_with5 xs ys zs us vs
  | _, _, _, _, _ => []
#align list.zip_with5 List.zipWith5
-/

#print List.replaceIf /-
/-- Given a starting list `old`, a list of booleans and a replacement list `new`,
read the items in `old` in succession and either replace them with the next element of `new` or
not, according as to whether the corresponding boolean is `tt` or `ff`. -/
def replaceIf : List α → List Bool → List α → List α
  | l, _, [] => l
  | [], _, _ => []
  | l, [], _ => l
  | n :: ns, tf :: bs, e@(c :: cs) => if tf then c :: ns.replaceIf bs cs else n :: ns.replaceIf bs e
#align list.replace_if List.replaceIf
-/

#print List.mapWithPrefixSuffixAux /-
/-- An auxiliary function for `list.map_with_prefix_suffix`. -/
def mapWithPrefixSuffixAux {α β} (f : List α → α → List α → β) : List α → List α → List β
  | prev, [] => []
  | prev, h :: t => f prev h t :: map_with_prefix_suffix_aux (prev.concat h) t
#align list.map_with_prefix_suffix_aux List.mapWithPrefixSuffixAux
-/

#print List.mapWithPrefixSuffix /-
/-- `list.map_with_prefix_suffix f l` maps `f` across a list `l`.
For each `a ∈ l` with `l = pref ++ [a] ++ suff`, `a` is mapped to `f pref a suff`.

Example: if `f : list ℕ → ℕ → list ℕ → β`,
`list.map_with_prefix_suffix f [1, 2, 3]` will produce the list
`[f [] 1 [2, 3], f [1] 2 [3], f [1, 2] 3 []]`.
-/
def mapWithPrefixSuffix {α β} (f : List α → α → List α → β) (l : List α) : List β :=
  mapWithPrefixSuffixAux f [] l
#align list.map_with_prefix_suffix List.mapWithPrefixSuffix
-/

#print List.mapWithComplement /-
/-- `list.map_with_complement f l` is a variant of `list.map_with_prefix_suffix`
that maps `f` across a list `l`.
For each `a ∈ l` with `l = pref ++ [a] ++ suff`, `a` is mapped to `f a (pref ++ suff)`,
i.e., the list input to `f` is `l` with `a` removed.

Example: if `f : ℕ → list ℕ → β`, `list.map_with_complement f [1, 2, 3]` will produce the list
`[f 1 [2, 3], f 2 [1, 3], f 3 [1, 2]]`.
-/
def mapWithComplement {α β} (f : α → List α → β) : List α → List β :=
  mapWithPrefixSuffix fun pref a suff => f a (pref ++ suff)
#align list.map_with_complement List.mapWithComplement
-/

end List


/-
The current plan is to allow instances to have preconditions with registered tactics.
Typeclass resolution will delay these proof obligations, effectively assuming that they will succeed.
If typeclass resolution succeeds, it will return a list of (mvar, lctx) pairs to the elaborator,
which will try to synthesize the proofs and throw a good error message if it fails.
-/

class Foo (b : Bool) : Type

class FooTrue (b : Bool) extends Foo b : Type :=
(H : b = true)

/- This requires the tactic framework and auto_param. -/
-- @[instance] axiom CoeFooFooTrue (b : Bool) (H : b = true · refl) : HasCoe (Foo b) (FooTrue b)

instance BoolToFoo (b : Bool) : Foo b := Foo.mk b

def forceFooTrue (b : Bool) (fooTrue : FooTrue b) : Bool := b

/- Should succeed (once CoeFooFooTrue can be written) -/
#check forceFooTrue true (Foo.mk true)

/- Should fail (even after CoeFooFooTrue can be written -/
#check forceFooTrue false (Foo.mk false)

/-
This plan has one limitation, that has so far been deemed acceptable:
it will not support classes with different instances depending on the provability of preconditions.
The classic example is multiplying two elements of `ℤ/nℤ` where `n` is not prime.
Here is a toy version of this problem:

<<
@[class] axiom Prime (p : Nat) : Prop

@[instance] axiom p2 : Prime 2
@[instance] axiom p3 : Prime 3

@[class] axiom Field : Type → Type
@[class] axiom Ring : Type → Type

@[instance] axiom FieldToDiv (α : Type) [Field α] : Div α
@[instance] axiom FieldToMul (α : Type) [Field α] : Mul α
@[instance] axiom RingToMul (α : Type) [Ring α] : Mul α

axiom mkType : Nat → Type

@[instance] axiom PrimeField (n : Nat) (Hp : Prime n · provePrimality) : Field (mkType n)
@[instance] axiom NonPrimeRing (n : Nat) : Ring (mkType n)

example (α β : mkType 4) : α * β = β * α
>>

The issue is that (depending on the order the instances are tried),
the instance involving `FieldToMul` will succeed in typeclass resolution,
but the proof will fail later on.

I (@dselsam) still thinks this plan is a good compromise.
For examples like this, the definition in question (i.e. `Prime`) can be made a class instead
and taken as an inst-implicit argument to `PrimeField`.
-/


/-
Copyright (c) 2022 Jannis Limperg. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jannis Limperg
-/
import Aesop

set_option aesop.check.all true

@[aesop 50% cases]
inductive FancyAnd (α β : Prop) : Prop
  | dummy (p : Empty)
  | and (a : α) (b : β)

attribute [aesop safe -51 cases] Empty

example {α β} (h : FancyAnd α β) : α ∧ β := by
  aesop

@[aesop safe (cases (patterns := [All _ [], All _ (_ :: _)]))]
inductive All (P : α → Prop) : List α → Prop
  | nil : All P []
  | cons : P x → All P xs → All P (x :: xs)

@[aesop 99% constructors]
structure MyTrue : Prop

-- Without the patterns on the `cases` rule for `All`, this test would loop
-- since the `constructors` rule would never be applied.
example {P : α → Prop} (h : All P (x :: xs)) : MyTrue := by
  aesop


/-
Copyright (c) 2018 Simon Hudon. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Simon Hudon, Johan Commelin
-/
import tactic.wlog

section wlog

example {x y : ℕ} : true :=
begin
  wlog h : x ≤ y,
  { guard_hyp h : ¬x ≤ y,
    guard_hyp this : ∀ {x y : ℕ}, x ≤ y → true, -- `wlog` generalizes by default
    guard_target true,
    trivial },
  { guard_hyp h : x ≤ y,
    guard_target true,
    trivial },
end

example {x y : ℕ} : true :=
begin
  wlog h : x ≤ y generalizing x,
  { guard_hyp h : ¬x ≤ y,
    guard_hyp this : ∀ {x : ℕ}, x ≤ y → true, -- only `x` was generalized
    guard_target true,
    trivial },
  { guard_hyp h : x ≤ y,
    guard_target true,
    trivial },
end

example {x y z : ℕ} : true :=
begin
  wlog h : x ≤ y + z with H,
  { guard_hyp h : ¬ x ≤ y + z,
    guard_hyp H : ∀ {x y z : ℕ}, x ≤ y + z → true, -- wlog-claim is named `H` instead of `this`
    guard_target true,
    trivial },
  { guard_hyp h : x ≤ y + z,
    guard_target true,
    trivial },
end

end wlog


import data.pfun data.sigma data.finmap

namespace roption

def pmap {α β} (o : roption α) (f : o.dom → α → β) : roption β :=
⟨o.dom, λ h, f h (o.get h)⟩

def forall₂ {α β} (R : α → β → Prop) (o₁ : roption α) (o₂ : roption β) : Prop :=
(∀ x ∈ o₁, ∃ y ∈ o₂, R x y) ∧ (∀ y ∈ o₂, ∃ x ∈ o₁, R x y)

theorem forall₂_dom {α β} {R : α → β → Prop} {o₁ : roption α} {o₂ : roption β}
  (H : forall₂ R o₁ o₂) : o₁.dom ↔ o₂.dom :=
⟨λ h, by rcases H.1 _ ⟨h, rfl⟩ with ⟨_, ⟨h', _⟩, _⟩; exact h',
 λ h, by rcases H.2 _ ⟨h, rfl⟩ with ⟨_, ⟨h', _⟩, _⟩; exact h'⟩

@[elab_as_eliminator]
def mem_cases {α} (o : roption α) {C : ∀ a ∈ o, Sort*}
  (H : ∀ h, C (o.get h) ⟨h, rfl⟩) : ∀ a h, C a h :=
λ a h', begin
  have h₂ := h', revert h',
  rw ← h₂.snd; exact λ h₂, H _
end

end roption

namespace option

inductive forall₂ {α α'} (R : α → α' → Prop) : option α → option α' → Prop
| none {} : forall₂ none none
| some {a a'} : R a a' → forall₂ (some a) (some a')

theorem forall₂.imp {α α'} {R S : α → α' → Prop} (H : ∀ a a', R a a' → S a a') :
  ∀ {o o'}, forall₂ R o o' → forall₂ S o o'
| _ _ forall₂.none := forall₂.none
| _ _ (forall₂.some h) := forall₂.some (H _ _ h)

end option

namespace sum

inductive forall₂ {α β α' β'} (R : α → α' → Prop) (S : β → β' → Prop) : α ⊕ β → α' ⊕ β' → Prop
| inl {a a'} : R a a' → forall₂ (sum.inl a) (sum.inl a')
| inr {b b'} : S b b' → forall₂ (sum.inr b) (sum.inr b')

end sum

namespace prod

inductive forall₂ {α β α' β'} (R : α → α' → Prop) (S : β → β' → Prop) :
  α × β → α' × β' → Prop
| mk {a b a' b'} : R a a' → S b b' → forall₂ (a, b) (a', b')

theorem forall₂.imp {α β α' β'} {R R' : α → α' → Prop} {S S' : β → β' → Prop}
  (H₁ : ∀ a a', R a a' → R' a a') (H₂ : ∀ b b', S b b' → S' b b') :
  ∀ {x x'}, forall₂ R S x x' → forall₂ R' S' x x'
| _ _ ⟨h₁, h₂⟩ := ⟨H₁ _ _ h₁, H₂ _ _ h₂⟩

end prod

namespace sigma

inductive forall₂ {ι} {α α' : ι → Type*} (R : ∀ i, α i → α' i → Prop) :
  (Σ i, α i) → (Σ i, α' i) → Prop
| mk {i a a'} : R i a a' → forall₂ ⟨i, a⟩ ⟨i, a'⟩

theorem forall₂.imp {ι} {α α' : ι → Type*}
  {R S : ∀ i, α i → α' i → Prop} (H : ∀ i a a', R i a a' → S i a a') :
  ∀ {s s'}, forall₂ R s s' → forall₂ S s s'
| _ _ ⟨r⟩ := ⟨H _ _ _ r⟩

theorem forall₂.flip {ι} {α α' : ι → Type*}
  {R : ∀ i, α i → α' i → Prop} :
  ∀ {s s'}, forall₂ (λ i, flip (R i)) s s' → forall₂ R s' s
| _ _ ⟨r⟩ := ⟨r⟩

end sigma

namespace list

inductive update_at {α} (R : α → α → Prop) : ℕ → list α → list α → Prop
| one {a b l} : R a b → update_at 0 (a :: l) (b :: l)
| cons {n a l l'} : update_at n l l' → update_at (n+1) (a :: l) (a :: l')

theorem update_at_forall₂ {α} {R : α → α → Prop} :
  ∀ {n l₁ l₂}, update_at R n l₁ l₂ → forall₂ (λ x y, R x y ∨ x = y) l₁ l₂
| _ _ _ (@update_at.one _ _ a b l h) :=
  forall₂.cons (or.inl h) (@forall₂_refl _ _ ⟨by exact λ a, or.inr rfl⟩ _)
| _ _ _ (@update_at.cons _ _ n a l₁ l₂ h) :=
  forall₂.cons (or.inr rfl) (update_at_forall₂ h)

theorem update_at_forall₂' {α} {R : α → α → Prop} [is_refl α R]
  {n l₁ l₂} (h : update_at R n l₁ l₂) : forall₂ R l₁ l₂ :=
(update_at_forall₂ h).imp (λ a b, or.rec id (by rintro rfl; apply refl))

lemma forall₂_comm {α β} {r : α → β → Prop} {a b} :
  forall₂ r a b ↔ forall₂ (flip r) b a := ⟨forall₂.flip, forall₂.flip⟩

lemma forall₂_and_right {α β} {r : α → β → Prop} {p : β → Prop}
  {l u} : forall₂ (λa b, r a b ∧ p b) l u ↔ forall₂ r l u ∧ (∀b∈u, p b) :=
by rw [and_comm, forall₂_comm, @forall₂_comm _ _ r, ← forall₂_and_left];
   conv in (_∧_) {rw and_comm}; refl

lemma forall₂.mp_trans {α β γ} {r : α → β → Prop} {q : β → γ → Prop}
  {s : α → γ → Prop} (h : ∀a b c, r a b → q b c → s a c) :
  ∀{l₁ l₂ l₃}, forall₂ r l₁ l₂ → forall₂ q l₂ l₃ → forall₂ s l₁ l₃
| []      []      []      forall₂.nil           forall₂.nil           := forall₂.nil
| (a::l₁) (b::l₂) (c::l₃) (forall₂.cons hr hrs) (forall₂.cons hq hqs) :=
  forall₂.cons (h a b c hr hq) (forall₂.mp_trans hrs hqs)

lemma forall₂.nth {α β} {R : α → β → Prop} :
  ∀{l₁ l₂}, forall₂ R l₁ l₂ → ∀ {a b n}, a ∈ l₁.nth n → b ∈ l₂.nth n → R a b
| (a::l₁) (b::l₂) (forall₂.cons hr hrs) _  _  0     rfl rfl := hr
| (a::l₁) (b::l₂) (forall₂.cons hr hrs) a' b' (n+1) h₁  h₂  := forall₂.nth hrs h₁ h₂

lemma forall₂.nth_right {α β} {R : α → β → Prop} :
  ∀{l₁ l₂}, forall₂ R l₁ l₂ → ∀ {a n}, a ∈ l₁.nth n → ∃ b ∈ l₂.nth n, R a b
| (a::l₁) (b::l₂) (forall₂.cons hr hrs) _  0     rfl := ⟨_, rfl, hr⟩
| (a::l₁) (b::l₂) (forall₂.cons hr hrs) a' (n+1) h₁  := forall₂.nth_right hrs h₁

lemma forall₂_reverse {α β} {R : α → β → Prop} :
  ∀ {l₁ l₂}, forall₂ R (reverse l₁) (reverse l₂) ↔ forall₂ R l₁ l₂ :=
suffices ∀ {l₁ l₂}, forall₂ R l₁ l₂ → forall₂ R (reverse l₁) (reverse l₂),
from λ l₁ l₂, ⟨λ h, by simpa using this h, this⟩,
suffices ∀ {l₁ l₂ r₁ r₂}, forall₂ R l₁ l₂ → forall₂ R r₁ r₂ →
  forall₂ R (reverse_core l₁ r₁) (reverse_core l₂ r₂),
from λ l₁ l₂ h, this h forall₂.nil,
λ l₁, begin
  induction l₁ with a l₁ IH; introv h₁ h₂;
    cases h₁ with _ b _ l₂ r h₁',
  exacts [h₂, IH h₁' (forall₂.cons r h₂)]
end

lemma forall₂_concat {α β} {R : α → β → Prop}
  {a b l₁ l₂} : forall₂ R (l₁ ++ [a]) (l₂ ++ [b]) ↔ forall₂ R l₁ l₂ ∧ R a b :=
by rw ← forall₂_reverse; simp [forall₂_reverse, and_comm]

lemma map_prod_fst_eq_of_forall₂_eq {α β γ} {R : β → γ → Prop} {l₁ l₂}
  (h : forall₂ (prod.forall₂ (@eq α) R) l₁ l₂) :
  l₁.map prod.fst = l₂.map prod.fst :=
begin
  rw [← list.forall₂_eq_eq_eq, list.forall₂_map_left_iff,
    list.forall₂_map_right_iff],
  refine h.imp _, rintro _ _ ⟨rfl, _⟩, refl
end

lemma map_sigma_fst_eq_of_forall₂_eq {α} {β β' : α → Type*}
  {R : ∀ a, β a → β' a → Prop} {l₁ l₂}
  (h : forall₂ (sigma.forall₂ R) l₁ l₂) :
  l₁.map sigma.fst = l₂.map sigma.fst :=
begin
  rw [← list.forall₂_eq_eq_eq, list.forall₂_map_left_iff,
    list.forall₂_map_right_iff],
  refine h.imp _, rintro _ _ ⟨⟩, refl
end

lemma rel_of_forall₂_of_nodup {α β γ} {R : β → γ → Prop} {l₁ l₂}
  (h : forall₂ (prod.forall₂ (@eq α) R) l₁ l₂)
  (nd : (l₁.map prod.fst).nodup)
  {a b c} (h₁ : (a, b) ∈ l₁) (h₂ : (a, c) ∈ l₂) : R b c :=
begin
  have nd' := nd, rw map_prod_fst_eq_of_forall₂_eq h at nd',
  induction h, {cases h₁},
  rcases h₁ with rfl | h₁; rcases h₂ with rfl | h₂,
  { cases h_a_1, assumption },
  { rcases h_a_1 with ⟨i, _, _, τ', rfl, _⟩,
    cases (list.nodup_cons.1 nd').1 (list.mem_map_of_mem prod.fst h₂:_) },
  { rcases h_a_1 with ⟨i, v', _, _, rfl, _⟩,
    cases (list.nodup_cons.1 nd).1 (list.mem_map_of_mem prod.fst h₁:_) },
  { exact h_ih (list.nodup_cons.1 nd).2 h₁ h₂ (list.nodup_cons.1 nd').2 }
end

theorem lookmap_forall₂ {α} (f : α → option α) :
  ∀ l, forall₂ (λ a b, a = b ∨ b ∈ f a) l (lookmap f l)
| []     := forall₂.nil
| (a::l) := begin
  dsimp only [lookmap],
  cases e : f a,
  { exact forall₂.cons (or.inl rfl) (lookmap_forall₂ l) },
  { refine forall₂.cons (or.inr e) (@forall₂_refl _ _ ⟨_⟩ l),
    exact λ _, or.inl rfl },
end

theorem lookmap_forall₂' {α} (f : α → option α)
  {β} (g : α → β) (H : ∀ a a' (b ∈ f a) (b' ∈ f a'), g a = g a') :
  ∀ l : list α, (l.map g).nodup → forall₂ (λ a b, (f a).get_or_else a = b) l (lookmap f l)
| []     nd := forall₂.nil
| (a::l) nd := begin
  cases list.nodup_cons.1 nd with nd₁ nd₂,
  dsimp only [lookmap],
  cases e : f a,
  { exact forall₂.cons (by rw e; refl) (lookmap_forall₂' l nd₂) },
  { refine forall₂.cons (by rw e; refl) (forall₂_same $ λ a' h, _),
    cases e' : f a', {refl},
    rw H _ _ _ e _ e' at nd₁,
    cases nd₁ (mem_map_of_mem _ h) },
end

inductive kreplace_rel {α} {β : α → Type*} (a) (b : β a) : ∀ a, β a → β a → Prop
| repl {} {b'} : kreplace_rel a b' b
| refl {} {a' b'} : a ≠ a' → kreplace_rel a' b' b'

theorem kreplace_forall₂ {α} {β : α → Type*} [decidable_eq α] (a) (b : β a)
  {l} (nd : nodupkeys l) : forall₂ (sigma.forall₂ (kreplace_rel a b)) l (kreplace a b l) :=
begin
  refine (lookmap_forall₂' _ _ _ _ nd).imp _,
  { rintro ⟨a₁, b₁⟩ ⟨a₂, b₂⟩ h,
    split_ifs at h; cases h,
    { cases h_1, exact ⟨kreplace_rel.repl⟩ },
    { exact ⟨kreplace_rel.refl h_1⟩ } },
  { rintro ⟨a₁, b₁⟩ ⟨a₂, b₂⟩ s₁ h₁ s₂ h₂,
    split_ifs at h₁ h₂; cases h₁; cases h₂,
    exact h_1.symm.trans h }
end

theorem mem_erasep {α} (p : α → Prop) [decidable_pred p] {l : list α}
  (H : pairwise (λ a b, p a → p b → false) l)
  {a : α} : a ∈ erasep p l ↔ ¬ p a ∧ a ∈ l :=
⟨λ h, begin
  by_cases pa : p a,
  { rcases exists_of_erasep (mem_of_mem_erasep h) pa
      with ⟨b, l₁, l₂, h₁, pb, rfl, h₂⟩,
    rw h₂ at h,
    cases mem_append.1 h,
    { cases h₁ _ h_1 pa },
    { cases (pairwise_cons.1 (pairwise_append.1 H).2.1).1 _ h_1 pb pa } },
  { exact ⟨pa, (mem_erasep_of_neg pa).1 h⟩ }
end, λ ⟨h₁, h₂⟩, (mem_erasep_of_neg h₁).2 h₂⟩

theorem mem_kerase {α β} [decidable_eq α] {s : list (sigma β)}
  (nd : s.nodupkeys) {a a' : α} {b' : β a'} :
  sigma.mk a' b' ∈ kerase a s ↔ a ≠ a' ∧ sigma.mk a' b' ∈ s :=
mem_erasep _ $ (nodupkeys_iff_pairwise.1 nd).imp $
by rintro x y h rfl; exact h

@[simp] theorem cons_to_finset {α} [decidable_eq α] (a l) :
  (a :: l : list α).to_finset = insert a l.to_finset :=
by ext; simp

end list

def prod.to_sigma {α β} (x : α × β) : Σ a : α, β := ⟨x.1, x.2⟩

namespace alist
open list

theorem mem_lookup_iff {α} {β : α → Type*} [decidable_eq α]
  {a : α} {b : β a} {s : alist β} :
  b ∈ lookup a s ↔ sigma.mk a b ∈ s.entries :=
list.mem_lookup_iff s.2

theorem exists_mem_lookup_iff {α} {β : α → Type*} [decidable_eq α]
  {a : α} {s : alist β} : (∃ b, b ∈ lookup a s) ↔ a ∈ s :=
option.is_some_iff_exists.symm.trans lookup_is_some

theorem to_sigma_nodupkeys {α β} {l : list (α × β)} :
  (l.map prod.to_sigma).nodupkeys ↔ (l.map prod.fst).nodup :=
by rw [list.nodupkeys, list.keys, list.map_map]; refl

def mk' {α β} (l : list (α × β)) (h : (l.map prod.fst).nodup) : alist (λ _, β) :=
⟨l.map prod.to_sigma, to_sigma_nodupkeys.2 h⟩

@[simp] theorem mk'_entries {α β} (l : list (α × β)) (h) :
  (mk' l h).entries = l.map prod.to_sigma := rfl

@[simp] theorem mk'_keys {α β} (l : list (α × β)) (h) :
  (mk' l h).keys = l.map prod.fst :=
by rw [keys, list.keys, mk'_entries, list.map_map]; refl

def cons {α} {β : α → Type*} (s : alist β)
  (a : α) (b : β a) (h : a ∉ s) : alist β :=
⟨⟨a, b⟩ :: s.entries, nodup_cons.2 ⟨mt mem_keys.1 h, s.nodupkeys⟩⟩

theorem insert_eq_cons {α} {β : α → Type*} [decidable_eq α]
  {s : alist β} {a : α} {b : β a} (h : a ∉ s) : insert a b s = cons s a b h :=
ext $ by simp only [insert_entries_of_neg h]; refl

theorem cons_inj {α} {β : α → Type*} [decidable_eq α]
  {s s' : alist β} {a a' : α} {b : β a} {b' : β a'}
  {h : a ∉ s} {h' : a' ∉ s'}
  (eq : cons s a b h = cons s' a' b' h') :
  sigma.mk a b = ⟨a', b'⟩ ∧ s = s' :=
by cases s; cases s'; cases congr_arg alist.entries eq; exact ⟨rfl, rfl⟩

theorem lookup_cons_iff {α β} [decidable_eq α] {s a b h a' b'} :
  b' ∈ lookup a' (@cons α β s a b h) ↔ sigma.mk a' b' = ⟨a, b⟩ ∨ b' ∈ lookup a' s :=
mem_lookup_iff.trans $ or_congr iff.rfl mem_lookup_iff.symm

theorem lookup_cons_of_lookup {α β} [decidable_eq α] {s a b h a' b'}
  (H : b' ∈ lookup a' s) : b' ∈ lookup a' (@cons α β s a b h) :=
lookup_cons_iff.2 $ or.inr H

theorem lookup_cons_self {α β} [decidable_eq α] {s a b h} :
  b ∈ lookup a (@cons α β s a b h) :=
lookup_cons_iff.2 $ or.inl rfl

@[simp] theorem cons_keys {α} {β : α → Type*} (s : alist β)
  (a : α) (b : β a) (h : a ∉ s) : (alist.cons s a b h).keys = a :: s.keys := rfl

def forall₂ {α} {β γ : α → Type*} (R : ∀ a, β a → γ a → Prop)
  (l₁ : alist β) (l₂ : alist γ) : Prop :=
list.forall₂ (sigma.forall₂ R) l₁.entries l₂.entries

theorem forall₂.imp {α} {β β' : α → Type*}
  {R S : ∀ a, β a → β' a → Prop} (H : ∀ a b b', R a b b' → S a b b')
  {s s'} : forall₂ R s s' → forall₂ S s s' :=
list.forall₂.imp $ λ a b, sigma.forall₂.imp H

lemma forall₂_same {α} {β : α → Type*} {r : ∀ a, β a → β a → Prop} {s : alist β}
  (h : ∀ a (b : β a), sigma.mk a b ∈ s.entries → r a b b) : forall₂ r s s :=
forall₂_same $ λ ⟨a, b⟩ m, ⟨h _ _ m⟩

theorem forall₂.flip {α} {β β' : α → Type*}
  {R : ∀ a, β a → β' a → Prop}
  {s s'} (H : forall₂ (λ a, flip (R a)) s s') : forall₂ R s' s :=
H.flip.imp $ λ _ _, sigma.forall₂.flip

theorem forall₂.keys {α} {β β' : α → Type*}
  {R : ∀ a, β a → β' a → Prop} {s s'} :
  forall₂ R s s' → s.keys = s'.keys :=
map_sigma_fst_eq_of_forall₂_eq

theorem forall₂.mem_iff {α} {β β' : α → Type*}
  {R : ∀ a, β a → β' a → Prop} {s s'} (H : forall₂ R s s')
  {a} : a ∈ s ↔ a ∈ s' :=
by rw [mem_keys, H.keys, mem_keys]

@[elab_as_eliminator]
theorem forall₂.induction {α} {β γ : α → Type*}
  {R : ∀ a, β a → γ a → Prop}
  {P : @alist α β → @alist α γ → Prop} {l₁ : alist β} {l₂ : alist γ}
  (H : forall₂ R l₁ l₂) (H0 : P ∅ ∅)
  (H1 : ∀ l₁ l₂ a b c h₁ h₂, R a b c → forall₂ R l₁ l₂ →
     P l₁ l₂ → P (cons l₁ a b h₁) (cons l₂ a c h₂)) :
  P l₁ l₂ :=
begin
  cases l₁ with l₁ nd₁; cases l₂ with l₂ nd₂,
  dsimp [forall₂] at H,
  induction H, {exact H0},
  rcases H_a_1 with ⟨a, b, c, h⟩,
  cases nodupkeys_cons.1 nd₁ with m₁ nd₁,
  cases nodupkeys_cons.1 nd₂ with m₂ nd₂,
  refine H1 ⟨_, _⟩ ⟨_, _⟩ _ _ _ m₁ m₂ h H_a_2 (H_ih nd₁ nd₂),
end

theorem forall₂_cons {α} {β γ : α → Type*} {R : ∀ a, β a → γ a → Prop}
  {l₁ : alist β} {l₂ : alist γ} {a b c h₁ h₂} :
  forall₂ R (cons l₁ a b h₁) (cons l₂ a c h₂) ↔ R a b c ∧ forall₂ R l₁ l₂ :=
⟨by rintro (_|⟨_,_,_,_,⟨_,_,_,r⟩,h⟩); exact ⟨r, h⟩,
 λ ⟨r, h⟩, list.forall₂.cons ⟨r⟩ h⟩

theorem forall₂.rel_of_mem {α} {β₁ β₂ : α → Type*} [decidable_eq α]
  {R : ∀ a, β₁ a → β₂ a → Prop} {s₁ s₂} (H : forall₂ R s₁ s₂)
  {a} {b₁ : β₁ a} {b₂ : β₂ a}
  (h₁ : sigma.mk a b₁ ∈ s₁.entries)
  (h₂ : sigma.mk a b₂ ∈ s₂.entries) : R a b₁ b₂ :=
begin
  cases s₁ with l₁ nd₁, cases s₂ with l₂ nd₂,
  dsimp only [forall₂] at *,
  induction H, {cases h₁},
  rcases h₁ with rfl | h₁; rcases h₂ with rfl | h₂,
  { cases H_a_1, assumption },
  { rcases H_a_1 with ⟨i, _, b₂', _⟩,
    cases (list.nodupkeys_cons.1 nd₂).1 (list.mem_keys.2 ⟨_, h₂⟩) },
  { rcases H_a_1 with ⟨i, b₁', _, _⟩,
    cases (list.nodupkeys_cons.1 nd₁).1 (list.mem_keys.2 ⟨_, h₁⟩) },
  { cases H_a, cases H_b,
    exact H_ih (list.nodupkeys_cons.1 nd₁).2 h₁ (list.nodupkeys_cons.1 nd₂).2 h₂ }
end

theorem forall₂.rel_of_lookup {α} {β β' : α → Type*} [decidable_eq α]
  {R : ∀ a, β a → β' a → Prop} {s s'} (H : forall₂ R s s')
  {a b b'} (h₁ : b ∈ s.lookup a) (h₂ : b' ∈ s'.lookup a) : R a b b' :=
by rw mem_lookup_iff at h₁ h₂; exact H.rel_of_mem h₁ h₂

theorem forall₂.rel_of_lookup_right {α} {β β' : α → Type*} [decidable_eq α]
  {R : ∀ a, β a → β' a → Prop} {s s'} (H : forall₂ R s s')
  {a b} (h : b ∈ s.lookup a) : ∃ b' ∈ s'.lookup a, R a b b' :=
let ⟨b', h'⟩ := exists_mem_lookup_iff.2
  (H.mem_iff.1 (exists_mem_lookup_iff.1 ⟨_, h⟩)) in
⟨b', h', H.rel_of_lookup h h'⟩

theorem replace_forall₂ {α} {β : α → Type*} [decidable_eq α]
  (a) (b : β a) (s : alist β) : forall₂ (kreplace_rel a b) s (replace a b s) :=
kreplace_forall₂ _ _ s.2

def map {α} {β γ : α → Type*}
  (f : ∀ a, β a → γ a) (s : alist β) : alist γ :=
⟨list.map (sigma.map id f) s.entries, by rw [
    list.nodupkeys, list.keys, list.map_map,
    (by ext ⟨a, b⟩; refl : sigma.fst ∘ sigma.map id f = sigma.fst)];
  exact s.2⟩

theorem map_entries {α} {β γ : α → Type*}
  (f : ∀ a, β a → γ a) (s : alist β) :
  (map f s).entries = list.map (sigma.map id f) s.entries := rfl

theorem mem_map_entries {α} {β γ : α → Type*}
  {f : ∀ a, β a → γ a} {s : alist β} {a} {c : γ a} :
  sigma.mk a c ∈ (map f s).entries ↔ ∃ b : β a, sigma.mk a b ∈ s.entries ∧ f a b = c :=
mem_map.trans ⟨
  by rintro ⟨⟨a, b⟩, h, ⟨⟩⟩; exact ⟨_, h, rfl⟩,
  by rintro ⟨b, h, ⟨⟩⟩; exact ⟨_, h, rfl⟩⟩

theorem lookup_map {α} {β γ : α → Type*} [decidable_eq α]
  {f : ∀ a, β a → γ a} {s : alist β} {a} {c : γ a} :
  c ∈ (map f s).lookup a ↔ ∃ b : β a, b ∈ s.lookup a ∧ f a b = c :=
mem_lookup_iff.trans $ mem_map_entries.trans $ by simp only [mem_lookup_iff]

theorem forall₂_map_left_iff {α} {β γ δ : α → Type*}
  {r : ∀ a, γ a → δ a → Prop} {f : ∀ a, β a → γ a} {l : alist β} {u : alist δ} :
  alist.forall₂ r (alist.map f l) u ↔ alist.forall₂ (λ a c d, r a (f a c) d) l u :=
begin
  unfold forall₂, refine list.forall₂_map_left_iff.trans _,
  apply iff_of_eq, congr', ext ⟨a, b⟩ ⟨a', d⟩,
  split; rintro ⟨_, _, d, r⟩; exact ⟨r⟩
end

theorem forall₂_map_right_iff {α} {β γ δ : α → Type*}
  {r : ∀ a, β a → δ a → Prop} {f : ∀ a, γ a → δ a} {l : alist β} {u : alist γ} :
  alist.forall₂ r l (alist.map f u) ↔ alist.forall₂ (λ a b c, r a b (f a c)) l u :=
⟨λ h, (forall₂_map_left_iff.1 h.flip).flip,
 λ h, ((@forall₂_map_left_iff _ _ _ _ (λ a, flip (r a)) _ _ _).2 h.flip).flip⟩

theorem lookup_replace_of_ne {α} {β : α → Type*} [decidable_eq α]
  {a} {b : β a} {s : alist β} {a'} (ne : a ≠ a'):
  lookup a' (replace a b s) = lookup a' s :=
begin
  ext b',
  split; intro h,
  { rcases (replace_forall₂ a b s).flip.rel_of_lookup_right h with ⟨b'', m, _|_⟩;
    [cases ne rfl, exact m] },
  { rcases (replace_forall₂ a b s).rel_of_lookup_right h with ⟨b'', m, _|_⟩;
    [cases ne rfl, exact m] },
end

theorem lookup_replace_self {α} {β : α → Type*} [decidable_eq α]
  {a} {b : β a} {s : alist β} (h : a ∈ s) :
  b ∈ lookup a (replace a b s) :=
by rcases exists_mem_lookup_iff.2 h with ⟨b', h⟩;
  rcases (replace_forall₂ a b s).rel_of_lookup_right h with ⟨b'', m, _|_⟩;
  [exact m, cases h_1_h_a rfl]

theorem replace_cons_self {α} {β : α → Type*} [decidable_eq α]
  {a} {b b' : β a} {s : alist β} (h) : replace a b' (cons s a b h) = cons s a b' h :=
by simp [replace, cons, kreplace]; rw [lookmap_cons_some]; simp

theorem replace_cons_of_ne {α} {β : α → Type*} [decidable_eq α]
  {a} {b : β a} {s : alist β} (h) {a'} {b' : β a'} (ne : a' ≠ a) :
  ∃ h', replace a' b' (cons s a b h) = cons (replace a' b' s) a b h' :=
⟨mt alist.mem_replace.1 h,
  by simp [replace, cons, kreplace]; rw [lookmap_cons_none]; simp [ne]⟩

@[simp] theorem entries_erase {α β} [decidable_eq α] (a : α) (s : alist β) :
  (erase a s).entries = s.entries.kerase a := rfl

theorem lookup_erase' {α β} [decidable_eq α] {s : alist β} {a a' : α} {b' : β a'} :
  b' ∈ lookup a' (erase a s) ↔ a ≠ a' ∧ b' ∈ lookup a' s :=
by rw [mem_lookup_iff, entries_erase, mem_kerase s.2, mem_lookup_iff]

def values {α β} (s : alist (λ _ : α, β)) : list β := s.entries.map sigma.snd

@[elab_as_eliminator]
def rec' {α β} {C : @alist α β → Sort*}
  (H0 : C ∅) (H1 : ∀ s a b h, C s → C (cons s a b h)) (s) : C s :=
begin
  cases s with l nd,
  induction l with ab l IH,
  { exact H0 },
  { cases ab with a b,
    have := list.nodupkeys_cons.1 nd,
    exact H1 ⟨l, this.2⟩ a b this.1 (IH this.2) }
end

@[simp] theorem rec'_empty {α β C H0 H1} : @rec' α β C H0 H1 ∅ = H0 := rfl

@[simp] theorem rec'_cons {α β C H0 H1} : ∀ s a b h,
  @rec' α β C H0 H1 (cons s a b h) = H1 s a b h (@rec' α β C H0 H1 s)
| ⟨l, nd⟩ a b h := rfl

end alist

namespace finset

theorem singleton_subset {α} {a : α} {s : finset α} :
  singleton a ⊆ s ↔ a ∈ s :=
by simp [subset_def]; refl

theorem union_subset_iff {α} [decidable_eq α]
  {s₁ s₂ t : finset α} : s₁ ∪ s₂ ⊆ t ↔ s₁ ⊆ t ∧ s₂ ⊆ t :=
⟨λ h, ⟨
  subset.trans (subset_union_left _ _) h,
  subset.trans (subset_union_right _ _) h⟩,
λ ⟨h₁, h₂⟩, union_subset h₁ h₂⟩

end finset

namespace finmap
open list

theorem mem_lookup_iff {α} {β : α → Type*} [decidable_eq α]
  {a : α} {b : β a} {s : finmap β} :
  b ∈ lookup a s ↔ sigma.mk a b ∈ s.entries :=
induction_on s $ λ s, alist.mem_lookup_iff

theorem exists_mem_lookup_iff {α} {β : α → Type*} [decidable_eq α]
  {a : α} {s : finmap β} : (∃ b, b ∈ lookup a s) ↔ a ∈ s :=
induction_on s $ λ s, alist.exists_mem_lookup_iff

theorem lookup_insert_of_neg {α} {β : α → Type*} [decidable_eq α]
  {a : α} {b : β a} {s : finmap β} (h : a ∉ s) {a' : α} {b' : β a'} :
  b' ∈ (insert a b s).lookup a' ↔
  sigma.mk a' b' = ⟨a, b⟩ ∨ b' ∈ s.lookup a' :=
by rw [mem_lookup_iff, mem_lookup_iff, insert_entries_of_neg h, multiset.mem_cons]

theorem lookup_insert_self {α β} [decidable_eq α] {s a b} :
  a ∉ s → b ∈ lookup a (@insert α β _ a b s) :=
induction_on s $ λ s h,
by simp [insert, alist.insert_eq_cons h]; exact alist.lookup_cons_self

theorem lookup_erase' {α β} [decidable_eq α] {s : finmap β} {a a' : α} {b' : β a'} :
  b' ∈ lookup a' (erase a s) ↔ a ≠ a' ∧ b' ∈ lookup a' s :=
induction_on s $ λ s, alist.lookup_erase'

theorem lookup_replace_of_ne {α} {β : α → Type*} [decidable_eq α]
  {a} {b : β a} {s : finmap β} {a'} : a ≠ a' →
  lookup a' (replace a b s) = lookup a' s :=
induction_on s $ λ s, alist.lookup_replace_of_ne

theorem lookup_replace_self {α} {β : α → Type*} [decidable_eq α]
  {a} {b : β a} {s : finmap β} : a ∈ s →
  b ∈ lookup a (replace a b s) :=
induction_on s $ λ s, alist.lookup_replace_self

@[simp] theorem keys_to_finmap {α} {β : α → Type*} [decidable_eq α]
  (s : alist β) : keys s.to_finmap = s.keys.to_finset :=
to_finset_eq _

@[simp] theorem keys_insert {α} {β : α → Type*} [decidable_eq α]
  (a : α) (b : β a) (s : finmap β) :
  (insert a b s).keys = has_insert.insert a s.keys :=
induction_on s $ λ s, by ext; simp; by_cases a_1 = a; simp [h]

end finmap