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

prelude
public import Init.SimpLemmas
public import Init.Data.Nat.Basic
public import Init.Data.List.Notation
public import Init.Data.Nat.Div.Basic

public section

@[expose] section

/-!
# Basic operations on `List`.

We define
* basic operations on `List`,
* simp lemmas for applying the operations on `.nil` and `.cons` arguments
  (in the cases where the right hand side is simple to state; otherwise these are deferred to `Init.Data.List.Lemmas`),
* the minimal lemmas which are required for setting up `Init.Data.Array.Basic`.

In `Init.Data.List.Impl` we give tail-recursive versions of these operations
along with `@[csimp]` lemmas,

In `Init.Data.List.Lemmas` we develop the full API for these functions.

Recall that `length`, `get`, `set`, `foldl`, and `concat` have already been defined in `Init.Prelude`.

The operations are organized as follow:
* Equality: `beq`, `isEqv`.
* Lexicographic ordering: `lt`, `le`, and instances.
* Head and tail operators: `head`, `head?`, `headD?`, `tail`, `tail?`, `tailD`.
* Basic operations:
  `map`, `filter`, `filterMap`, `foldr`, `append`, `flatten`, `pure`, `flatMap`, `replicate`, and
  `reverse`.
* Additional functions defined in terms of these: `leftpad`, `rightPad`, and `reduceOption`.
* Operations using indexes: `mapIdx`.
* List membership: `isEmpty`, `elem`, `contains`, `mem` (and the `∈` notation),
  and decidability for predicates quantifying over membership in a `List`.
* Sublists: `take`, `drop`, `takeWhile`, `dropWhile`, `partition`, `dropLast`,
  `isPrefixOf`, `isPrefixOf?`, `isSuffixOf`, `isSuffixOf?`, `Subset`, `Sublist`,
  `rotateLeft` and `rotateRight`.
* Manipulating elements: `replace`, `modify`, `insert`, `insertIdx`, `erase`, `eraseP`, `eraseIdx`.
* Finding elements: `find?`, `findSome?`, `findIdx`, `indexOf`, `findIdx?`, `indexOf?`,
 `countP`, `count`, and `lookup`.
* Logic: `any`, `all`, `or`, and `and`.
* Zippers: `zipWith`, `zip`, `zipWithAll`, and `unzip`.
* Ranges and enumeration: `range`, `zipIdx`.
* Minima and maxima: `min?` and `max?`.
* Other functions: `intersperse`, `intercalate`, `eraseDups`, `eraseReps`, `span`, `splitBy`,
  `removeAll`
  (currently these functions are mostly only used in meta code,
  and do not have API suitable for verification).

Further operations are defined in `Init.Data.List.BasicAux`
(because they use `Array` in their implementations), namely:
* Variant getters: `get!`, `get?`, `getD`, `getLast`, `getLast!`, `getLast?`, and `getLastD`.
* Head and tail: `head!`, `tail!`.
* Other operations on sublists: `partitionMap`, `rotateLeft`, and `rotateRight`.
-/

set_option linter.missingDocs true -- keep it documented
set_option linter.listVariables true -- Enforce naming conventions for `List`/`Array`/`Vector` variables.
set_option linter.indexVariables true -- Enforce naming conventions for index variables.

open Decidable List

universe u v w

variable {α : Type u} {β : Type v} {γ : Type w}

namespace List

/-! ## Preliminaries from `Init.Prelude` -/

/-! ### length -/

@[simp, grind] theorem length_nil : length ([] : List α) = 0 :=
  rfl

@[simp] theorem length_singleton {a : α} : length [a] = 1 := rfl

@[simp, grind] theorem length_cons {a : α} {as : List α} : (cons a as).length = as.length + 1 :=
  rfl

/-! ### set -/

@[simp, grind] theorem length_set {as : List α} {i : Nat} {a : α} : (as.set i a).length = as.length := by
  induction as generalizing i with
  | nil => rfl
  | cons x xs ih =>
    cases i with
    | zero => rfl
    | succ i => simp [set, ih]

/-! ### foldl -/

-- As `List.foldl` is defined in `Init.Prelude`, we write the basic simplification lemmas here.
@[simp, grind] theorem foldl_nil : [].foldl f b = b := rfl
@[simp, grind] theorem foldl_cons {l : List α} {f : β → α → β} {b : β} : (a :: l).foldl f b = l.foldl f (f b a) := rfl

/-! ### concat -/

theorem length_concat {as : List α} {a : α} : (concat as a).length = as.length + 1 := by
  induction as with
  | nil => rfl
  | cons _ xs ih => simp [concat, ih]

theorem of_concat_eq_concat {as bs : List α} {a b : α} (h : as.concat a = bs.concat b) : as = bs ∧ a = b := by
  match as, bs with
  | [], [] => simp [concat] at h; simp [h]
  | [_], [] => simp [concat] at h
  | _::_::_, [] => simp [concat] at h
  | [], [_] => simp [concat] at h
  | [], _::_::_ => simp [concat] at h
  | _::_, _::_ => simp [concat] at h; simp [h]; apply of_concat_eq_concat h.2

/-! ## Equality -/

/--
Checks whether two lists have the same length and their elements are pairwise `BEq`. Normally used
via the `==` operator.
-/
protected def beq [BEq α] : List α → List α → Bool
  | [],    []    => true
  | a::as, b::bs => a == b && List.beq as bs
  | _,     _     => false

@[simp] theorem beq_nil_nil [BEq α] : List.beq ([] : List α) ([] : List α) = true := rfl
@[simp] theorem beq_cons_nil [BEq α] {a : α} {as : List α} : List.beq (a::as) [] = false := rfl
@[simp] theorem beq_nil_cons [BEq α] {a : α} {as : List α} : List.beq [] (a::as) = false := rfl
theorem beq_cons₂ [BEq α] {a b : α} {as bs : List α} : List.beq (a::as) (b::bs) = (a == b && List.beq as bs) := rfl

instance [BEq α] : BEq (List α) := ⟨List.beq⟩

instance [BEq α] [ReflBEq α] : ReflBEq (List α) where
  rfl {as} := by
    induction as with
    | nil => rfl
    | cons a as ih => simp [BEq.beq, List.beq]; exact ih

instance [BEq α] [LawfulBEq α] : LawfulBEq (List α) where
  eq_of_beq {as bs} := by
    induction as generalizing bs with
    | nil => intro h; cases bs <;> first | rfl | contradiction
    | cons a as ih =>
      cases bs with
      | nil => intro h; contradiction
      | cons b bs =>
        simp [show (a::as == b::bs) = (a == b && as == bs) from rfl, -and_imp]
        intro ⟨h₁, h₂⟩
        exact ⟨h₁, ih h₂⟩

/--
Returns `true` if `as` and `bs` have the same length and they are pairwise related by `eqv`.

`O(min |as| |bs|)`. Short-circuits at the first non-related pair of elements.

Examples:
* `[1, 2, 3].isEqv [2, 3, 4] (· < ·) = true`
* `[1, 2, 3].isEqv [2, 2, 4] (· < ·) = false`
* `[1, 2, 3].isEqv [2, 3] (· < ·) = false`
-/
@[specialize] def isEqv : (as bs : List α) → (eqv : α → α → Bool) → Bool
  | [],    [],    _   => true
  | a::as, b::bs, eqv => eqv a b && isEqv as bs eqv
  | _,     _,     _   => false

@[simp] theorem isEqv_nil_nil : isEqv ([] : List α) [] eqv = true := rfl
@[simp] theorem isEqv_nil_cons : isEqv ([] : List α) (a::as) eqv = false := rfl
@[simp] theorem isEqv_cons_nil : isEqv (a::as : List α) [] eqv = false := rfl
theorem isEqv_cons₂ : isEqv (a::as) (b::bs) eqv = (eqv a b && isEqv as bs eqv) := rfl


/-! ## Lexicographic ordering -/

/--
Lexicographic ordering for lists with respect to an ordering of elements.

`as` is lexicographically smaller than `bs` if
 * `as` is empty and `bs` is non-empty, or
 * both `as` and `bs` are non-empty, and the head of `as` is less than the head of `bs` according to
   `r`, or
 * both `as` and `bs` are non-empty, their heads are equal, and the tail of `as` is less than the
   tail of `bs`.
-/
inductive Lex (r : α → α → Prop) : (as : List α) → (bs : List α) → Prop
  /-- `[]` is the smallest element in the lexicographic order. -/
  | nil {a l} : Lex r [] (a :: l)
  /--
  If the head of the first list is smaller than the head of the second, then the first list is
  lexicographically smaller than the second list.
  -/
  | rel {a₁ l₁ a₂ l₂} (h : r a₁ a₂) : Lex r (a₁ :: l₁) (a₂ :: l₂)
  /--
  If two lists have the same head, then their tails determine their lexicographic order. If the tail
  of the first list is lexicographically smaller than the tail of the second list, then the entire
  first list is lexicographically smaller than the entire second list.
  -/
  | cons {a l₁ l₂} (h : Lex r l₁ l₂) : Lex r (a :: l₁) (a :: l₂)


instance decidableLex [DecidableEq α] (r : α → α → Prop) [h : DecidableRel r] :
    (l₁ l₂ : List α) → Decidable (Lex r l₁ l₂)
  | [], [] => isFalse nofun
  | [], _::_ => isTrue Lex.nil
  | _::_, [] => isFalse nofun
  | a::as, b::bs =>
    match h a b with
    | isTrue h₁ => isTrue (Lex.rel h₁)
    | isFalse h₁ =>
      if h₂ : a = b then
        match decidableLex r as bs with
        | isTrue h₃ => isTrue (h₂ ▸ Lex.cons h₃)
        | isFalse h₃ => isFalse (fun h => match h with
          | Lex.rel h₁' => absurd h₁' h₁
          | Lex.cons h₃' => absurd h₃' h₃)
      else
        isFalse (fun h => match h with
          | Lex.rel h₁' => absurd h₁' h₁
          | Lex.cons h₂' => h₂ rfl)

/--
Lexicographic ordering of lists with respect to an ordering on their elements.

`as < bs` if
 * `as` is empty and `bs` is non-empty, or
 * both `as` and `bs` are non-empty, and the head of `as` is less than the head of `bs`, or
 * both `as` and `bs` are non-empty, their heads are equal, and the tail of `as` is less than the
   tail of `bs`.
-/
protected abbrev lt [LT α] : List α → List α → Prop := Lex (· < ·)

instance instLT [LT α] : LT (List α) := ⟨List.lt⟩

/-- Decidability of lexicographic ordering. -/
instance decidableLT [DecidableEq α] [LT α] [DecidableLT α] (l₁ l₂ : List α) :
    Decidable (l₁ < l₂) := decidableLex (· < ·) l₁ l₂

@[deprecated decidableLT (since := "2024-12-13"), inherit_doc decidableLT]
abbrev hasDecidableLt := @decidableLT

/--
Non-strict ordering of lists with respect to a strict ordering of their elements.

`as ≤ bs` if `¬ bs < as`.

This relation can be treated as a lexicographic order if the underlying `LT α` instance is
well-behaved. In particular, it should be irreflexive, asymmetric, and antisymmetric. These
requirements are precisely formulated in `List.cons_le_cons_iff`. If these hold, then `as ≤ bs` if
and only if:
 * `as` is empty, or
 * both `as` and `bs` are non-empty, and the head of `as` is less than the head of `bs`, or
 * both `as` and `bs` are non-empty, their heads are equal, and the tail of `as` is less than or
   equal to the tail of `bs`.
-/
@[reducible] protected def le [LT α] (as bs : List α) : Prop := ¬ bs < as

instance instLE [LT α] : LE (List α) := ⟨List.le⟩

instance decidableLE [DecidableEq α] [LT α] [DecidableLT α] (l₁ l₂ : List α) :
    Decidable (l₁ ≤ l₂) :=
  inferInstanceAs (Decidable (Not _))

/--
Compares lists lexicographically with respect to a comparison on their elements.

The lexicographic order with respect to `lt` is:
* `[].lex (b :: bs)` is `true`
* `as.lex [] = false` is `false`
* `(a :: as).lex (b :: bs)` is true if `lt a b` or `a == b` and `lex lt as bs` is true.
-/
def lex [BEq α] (l₁ l₂ : List α) (lt : α → α → Bool := by exact (· < ·)) : Bool :=
  match l₁, l₂ with
  | [],      _ :: _  => true
  | _,      []       => false
  | a :: as, b :: bs => lt a b || (a == b && lex as bs lt)

theorem nil_lex_nil [BEq α] : lex ([] : List α) [] lt = false := rfl
@[simp] theorem nil_lex_cons [BEq α] {b} {bs : List α} : lex [] (b :: bs) lt = true := rfl
theorem cons_lex_nil [BEq α] {a} {as : List α} : lex (a :: as) [] lt = false := rfl
@[simp] theorem cons_lex_cons [BEq α] {a b} {as bs : List α} :
    lex (a :: as) (b :: bs) lt = (lt a b || (a == b && lex as bs lt)) := rfl

@[simp] theorem lex_nil [BEq α] {as : List α} : lex as [] lt = false := by
  cases as <;> simp [nil_lex_nil, cons_lex_nil]

@[deprecated nil_lex_nil (since := "2025-02-10")]
theorem lex_nil_nil [BEq α] : lex ([] : List α) [] lt = false := rfl
@[deprecated nil_lex_cons (since := "2025-02-10")]
theorem lex_nil_cons [BEq α] {b} {bs : List α} : lex [] (b :: bs) lt = true := rfl
@[deprecated cons_lex_nil (since := "2025-02-10")]
theorem lex_cons_nil [BEq α] {a} {as : List α} : lex (a :: as) [] lt = false := rfl
@[deprecated cons_lex_cons (since := "2025-02-10")]
theorem lex_cons_cons [BEq α] {a b} {as bs : List α} :
    lex (a :: as) (b :: bs) lt = (lt a b || (a == b && lex as bs lt)) := rfl

/-! ## Alternative getters -/

/-! ### getLast -/

/--
Returns the last element of a non-empty list.

Examples:
 * `["circle", "rectangle"].getLast (by decide) = "rectangle"`
 * `["circle"].getLast (by decide) = "circle"`
-/
def getLast : ∀ (as : List α), as ≠ [] → α
  | [],       h => absurd rfl h
  | [a],      _ => a
  | _::b::as, _ => getLast (b::as) (fun h => List.noConfusion h)

/-! ### getLast? -/

/--
Returns the last element in the list, or `none` if the list is empty.

Alternatives include `List.getLastD`, which takes a fallback value for empty lists, and
`List.getLast!`, which panics on empty lists.

Examples:
 * `["circle", "rectangle"].getLast? = some "rectangle"`
 * `["circle"].getLast? = some "circle"`
 * `([] : List String).getLast? = none`
-/
def getLast? : List α → Option α
  | []    => none
  | a::as => some (getLast (a::as) (fun h => List.noConfusion h))

@[simp, grind] theorem getLast?_nil : @getLast? α [] = none := rfl

/-! ### getLastD -/

/--
Returns the last element in the list, or `fallback` if the list is empty.

Alternatives include `List.getLast?`, which returns an `Option`, and `List.getLast!`, which panics
on empty lists.

Examples:
 * `["circle", "rectangle"].getLastD "oval" = "rectangle"`
 * `["circle"].getLastD "oval" = "circle"`
 * `([] : List String).getLastD "oval" = "oval"`
-/
def getLastD : (as : List α) → (fallback : α) → α
  | [],   a₀ => a₀
  | a::as, _ => getLast (a::as) (fun h => List.noConfusion h)

-- These aren't `simp` lemmas since we always simplify `getLastD` in terms of `getLast?`.
theorem getLastD_nil {a : α} : getLastD [] a = a := rfl
theorem getLastD_cons {a b : α} {l} : getLastD (b::l) a = getLastD l b := by cases l <;> rfl

/-! ## Head and tail -/

/-! ### head -/

/--
Returns the first element of a non-empty list.
-/
def head : (as : List α) → as ≠ [] → α
  | a::_, _ => a

@[simp, grind] theorem head_cons {a : α} {l : List α} {h} : head (a::l) h = a := rfl

/-! ### head? -/

/--
Returns the first element in the list, if there is one. Returns `none` if the list is empty.

Use `List.headD` to provide a fallback value for empty lists, or `List.head!` to panic on empty
lists.

Examples:
 * `([] : List Nat).head? = none`
 * `[3, 2, 1].head? = some 3`
-/
def head? : List α → Option α
  | []   => none
  | a::_ => some a

@[simp, grind] theorem head?_nil : head? ([] : List α) = none := rfl
@[simp, grind] theorem head?_cons {a : α} {l : List α} : head? (a::l) = some a := rfl

/-! ### headD -/

/--
Returns the first element in the list if there is one, or `fallback` if the list is empty.

Use `List.head?` to return an `Option`, and `List.head!` to panic on empty lists.

Examples:
 * `[].headD "empty" = "empty"`
 * `[].headD 2 = 2`
 * `["head", "shoulders", "knees"].headD "toes" = "head"`
-/
def headD : (as : List α) → (fallback : α) → α
  | [],   fallback => fallback
  | a::_, _  => a

@[simp] theorem headD_nil {d : α} : headD [] d = d := rfl
@[simp] theorem headD_cons {a : α} {l : List α} {d : α} : headD (a::l) d = a := rfl

/-! ### tail -/

/--
Drops the first element of a nonempty list, returning the tail. Returns `[]` when the argument is
empty.

Examples:
 * `["apple", "banana", "grape"].tail = ["banana", "grape"]`
 * `["apple"].tail = []`
 * `([] : List String).tail = []`
-/
def tail : List α → List α
  | []    => []
  | _::as => as

@[simp, grind] theorem tail_nil : tail ([] : List α) = [] := rfl
@[simp, grind] theorem tail_cons {a : α} {as : List α} : tail (a::as) = as := rfl

/-! ### tail? -/

/--
Drops the first element of a nonempty list, returning the tail. Returns `none` when the argument is
empty.

Alternatives include `List.tail`, which returns the empty list on failure, `List.tailD`, which
returns an explicit fallback value, and `List.tail!`, which panics on the empty list.

Examples:
 * `["apple", "banana", "grape"].tail? = some ["banana", "grape"]`
 * `["apple"].tail? = some []`
 * `([] : List String).tail = none`
-/
def tail? : List α → Option (List α)
  | []    => none
  | _::as => some as

@[simp, grind] theorem tail?_nil : tail? ([] : List α) = none := rfl
@[simp, grind] theorem tail?_cons {a : α} {l : List α} : tail? (a::l) = some l := rfl

/-! ### tailD -/

set_option linter.listVariables false in
/--
Drops the first element of a nonempty list, returning the tail. Returns `none` when the argument is
empty.

Alternatives include `List.tail`, which returns the empty list on failure, `List.tail?`, which
returns an `Option`, and `List.tail!`, which panics on the empty list.

Examples:
 * `["apple", "banana", "grape"].tailD ["orange"] = ["banana", "grape"]`
 * `["apple"].tailD ["orange"] = []`
 * `[].tailD ["orange"] = ["orange"]`
-/
def tailD (l fallback : List α) : List α :=
  match l with
  | [] => fallback
  | _ :: tl => tl

@[simp] theorem tailD_nil {l' : List α} : tailD [] l' = l' := rfl
@[simp] theorem tailD_cons {a : α} {l : List α} {l' : List α} : tailD (a::l) l' = l := rfl

/-! ## Basic `List` operations.

We define the basic functional programming operations on `List`:
`map`, `filter`, `filterMap`, `foldr`, `append`, `flatten`, `pure`, `bind`, `replicate`, and `reverse`.
-/

/-! ### map -/

/--
Applies a function to each element of the list, returning the resulting list of values.

`O(|l|)`.

Examples:
* `[a, b, c].map f = [f a, f b, f c]`
* `[].map Nat.succ = []`
* `["one", "two", "three"].map (·.length) = [3, 3, 5]`
* `["one", "two", "three"].map (·.reverse) = ["eno", "owt", "eerht"]`
-/
@[specialize] def map (f : α → β) : (l : List α) → List β
  | []    => []
  | a::as => f a :: map f as

@[simp, grind] theorem map_nil {f : α → β} : map f [] = [] := rfl
@[simp, grind] theorem map_cons {f : α → β} {a : α} {l : List α} : map f (a :: l) = f a :: map f l := rfl

/-! ### filter -/

/--
Returns the list of elements in `l` for which `p` returns `true`.

`O(|l|)`.

Examples:
* `[1, 2, 5, 2, 7, 7].filter (· > 2) = [5, 7, 7]`
* `[1, 2, 5, 2, 7, 7].filter (fun _ => false) = []`
* `[1, 2, 5, 2, 7, 7].filter (fun _ => true) = [1, 2, 5, 2, 7, 7]`
-/
def filter (p : α → Bool) : (l : List α) → List α
  | [] => []
  | a::as => match p a with
    | true => a :: filter p as
    | false => filter p as

@[simp, grind] theorem filter_nil {p : α → Bool} : filter p [] = [] := rfl

/-! ### filterMap -/

/--
Applies a function that returns an `Option` to each element of a list, collecting the non-`none`
values.

`O(|l|)`.

Example:
```lean example
#eval [1, 2, 5, 2, 7, 7].filterMap fun x =>
  if x > 2 then some (2 * x) else none
```
```output
[10, 14, 14]
```
-/
@[specialize] def filterMap (f : α → Option β) : List α → List β
  | []   => []
  | a::as =>
    match f a with
    | none   => filterMap f as
    | some b => b :: filterMap f as

@[simp, grind] theorem filterMap_nil {f : α → Option β} : filterMap f [] = [] := rfl
@[grind] theorem filterMap_cons {f : α → Option β} {a : α} {l : List α} :
    filterMap f (a :: l) =
      match f a with
      | none => filterMap f l
      | some b => b :: filterMap f l := rfl

/-! ### foldr -/

/--
Folds a function over a list from the right, accumulating a value starting with `init`. The
accumulated value is combined with the each element of the list in reverse order, using `f`.

`O(|l|)`. Replaced at runtime with `List.foldrTR`.

Examples:
 * `[a, b, c].foldr f init  = f a (f b (f c init))`
 * `[1, 2, 3].foldr (toString · ++ ·) "" = "123"`
 * `[1, 2, 3].foldr (s!"({·} {·})") "!" = "(1 (2 (3 !)))"`
-/
@[specialize] def foldr (f : α → β → β) (init : β) : (l : List α) → β
  | []     => init
  | a :: l => f a (foldr f init l)

@[simp, grind] theorem foldr_nil : [].foldr f b = b := rfl
@[simp, grind] theorem foldr_cons {a} {l : List α} {f : α → β → β} {b} :
    (a :: l).foldr f b = f a (l.foldr f b) := rfl

/-! ### reverse -/

/-- Auxiliary for `List.reverse`. `List.reverseAux l r = l.reverse ++ r`, but it is defined directly. -/
def reverseAux : List α → List α → List α
  | [],   r => r
  | a::l, r => reverseAux l (a::r)

@[simp] theorem reverseAux_nil : reverseAux [] r = r := rfl
@[simp] theorem reverseAux_cons : reverseAux (a::l) r = reverseAux l (a::r) := rfl

/--
Reverses a list.

`O(|as|)`.

Because of the “functional but in place” optimization implemented by Lean's compiler, this function
does not allocate a new list when its reference to the input list is unshared: it simply walks the
linked list and reverses all the node pointers.

Examples:
* `[1, 2, 3, 4].reverse = [4, 3, 2, 1]`
* `[].reverse = []`
-/
@[expose] def reverse (as : List α) : List α :=
  reverseAux as []

@[simp, grind] theorem reverse_nil : reverse ([] : List α) = [] := rfl

theorem reverseAux_reverseAux {as bs cs : List α} :
    reverseAux (reverseAux as bs) cs = reverseAux bs (reverseAux (reverseAux as []) cs) := by
  induction as generalizing bs cs with
  | nil => rfl
  | cons a as ih => simp [reverseAux, ih (bs := a::bs), ih (bs := [a])]

/-! ### append -/

/--
Appends two lists. Normally used via the `++` operator.

Appending lists takes time proportional to the length of the first list: `O(|xs|)`.

Examples:
  * `[1, 2, 3] ++ [4, 5] = [1, 2, 3, 4, 5]`.
  * `[] ++ [4, 5] = [4, 5]`.
  * `[1, 2, 3] ++ [] = [1, 2, 3]`.
-/
protected def append : (xs ys : List α) → List α
  | [],    bs => bs
  | a::as, bs => a :: List.append as bs

/--
Appends two lists. Normally used via the `++` operator.

Appending lists takes time proportional to the length of the first list: `O(|xs|)`.

This is a tail-recursive version of `List.append`.

Examples:
  * `[1, 2, 3] ++ [4, 5] = [1, 2, 3, 4, 5]`.
  * `[] ++ [4, 5] = [4, 5]`.
  * `[1, 2, 3] ++ [] = [1, 2, 3]`.
-/
-- The @[csimp] lemma for `appendTR` must be set up immediately, because otherwise `Append (List α)`
-- instance below will not use it.
def appendTR (as bs : List α) : List α :=
  reverseAux as.reverse bs

@[csimp] theorem append_eq_appendTR : @List.append = @appendTR := by
  apply funext; intro α; apply funext; intro as; apply funext; intro bs
  simp [appendTR, reverse]
  induction as with
  | nil  => rfl
  | cons a as ih =>
    rw [reverseAux, reverseAux_reverseAux]
    simp [List.append, ih, reverseAux]

instance : Append (List α) := ⟨List.append⟩

@[simp] theorem append_eq {as bs : List α} : List.append as bs = as ++ bs := rfl

@[simp, grind] theorem nil_append (as : List α) : [] ++ as = as := rfl
@[simp, grind _=_] theorem cons_append {a : α} {as bs : List α} : (a::as) ++ bs = a::(as ++ bs) := rfl

@[simp, grind] theorem append_nil (as : List α) : as ++ [] = as := by
  induction as with
  | nil => rfl
  | cons a as ih =>
    simp_all only [HAppend.hAppend, Append.append, List.append]

instance : Std.LawfulIdentity (α := List α) (· ++ ·) [] where
  left_id := nil_append
  right_id := append_nil

@[simp, grind] theorem length_append {as bs : List α} : (as ++ bs).length = as.length + bs.length := by
  induction as with
  | nil => simp
  | cons _ as ih => simp [ih, Nat.succ_add]

@[simp, grind _=_] theorem append_assoc (as bs cs : List α) : (as ++ bs) ++ cs = as ++ (bs ++ cs) := by
  induction as with
  | nil => rfl
  | cons a as ih => simp [ih]

instance : Std.Associative (α := List α) (· ++ ·) := ⟨append_assoc⟩

-- Arguments are explicit as there is often ambiguity inferring the arguments.
theorem append_cons (as : List α) (b : α) (bs : List α) : as ++ b :: bs = as ++ [b] ++ bs := by
  simp

@[simp, grind =] theorem concat_eq_append {as : List α} {a : α} : as.concat a = as ++ [a] := by
  induction as <;> simp [concat, *]

theorem reverseAux_eq_append {as bs : List α} : reverseAux as bs = reverseAux as [] ++ bs := by
  induction as generalizing bs with
  | nil => simp [reverseAux]
  | cons a as ih =>
    simp [reverseAux]
    rw [ih (bs := a :: bs), ih (bs := [a]), append_assoc]
    rfl

@[simp, grind] theorem reverse_cons {a : α} {as : List α} : reverse (a :: as) = reverse as ++ [a] := by
  simp [reverse, reverseAux]
  rw [← reverseAux_eq_append]

/-! ### flatten -/

/--
Concatenates a list of lists into a single list, preserving the order of the elements.

`O(|flatten L|)`.

Examples:
* `[["a"], ["b", "c"]].flatten = ["a", "b", "c"]`
* `[["a"], [], ["b", "c"], ["d", "e", "f"]].flatten = ["a", "b", "c", "d", "e", "f"]`
-/
def flatten : List (List α) → List α
  | []      => []
  | l :: L => l ++ flatten L

@[simp, grind] theorem flatten_nil : List.flatten ([] : List (List α)) = [] := rfl
@[simp, grind] theorem flatten_cons : (l :: L).flatten = l ++ L.flatten := rfl

/-! ### singleton -/

/--
Constructs a single-element list.

Examples:
 * `List.singleton 5 = [5]`.
 * `List.singleton "green" = ["green"]`.
 * `List.singleton [1, 2, 3] = [[1, 2, 3]]`
-/
@[inline, expose] protected def singleton {α : Type u} (a : α) : List α := [a]

/-! ### flatMap -/

/--
Applies a function that returns a list to each element of a list, and concatenates the resulting
lists.

Examples:
* `[2, 3, 2].flatMap List.range = [0, 1, 0, 1, 2, 0, 1]`
* `["red", "blue"].flatMap String.toList = ['r', 'e', 'd', 'b', 'l', 'u', 'e']`
-/
@[inline] def flatMap {α : Type u} {β : Type v} (b : α → List β) (as : List α) : List β := flatten (map b as)

@[simp, grind] theorem flatMap_nil {f : α → List β} : List.flatMap f [] = [] := by simp [List.flatMap]
@[simp, grind] theorem flatMap_cons {x : α} {xs : List α} {f : α → List β} :
  List.flatMap f (x :: xs) = f x ++ List.flatMap f xs := by simp [List.flatMap]

/-! ### replicate -/

/--
Creates a list that contains `n` copies of `a`.

* `List.replicate 5 "five" = ["five", "five", "five", "five", "five"]`
* `List.replicate 0 "zero" = []`
* `List.replicate 2 ' ' = [' ', ' ']`
-/
def replicate : (n : Nat) → (a : α) → List α
  | 0,   _ => []
  | n+1, a => a :: replicate n a

@[simp, grind] theorem replicate_zero {a : α} : replicate 0 a = [] := rfl
@[grind] theorem replicate_succ {a : α} {n : Nat} : replicate (n+1) a = a :: replicate n a := rfl

@[simp, grind] theorem length_replicate {n : Nat} {a : α} : (replicate n a).length = n := by
  induction n with
  | zero => simp
  | succ n ih => simp only [ih, replicate_succ, length_cons]

/-! ## Additional functions -/

/-! ### leftpad and rightpad -/

/--
Pads `l : List α` on the left with repeated occurrences of `a : α` until it is of length `n`. If `l`
already has at least `n` elements, it is returned unmodified.

Examples:
 * `[1, 2, 3].leftpad 5 0 = [0, 0, 1, 2, 3]`
 * `["red", "green", "blue"].leftpad 4 "blank" = ["blank", "red", "green", "blue"]`
 * `["red", "green", "blue"].leftpad 3 "blank" = ["red", "green", "blue"]`
 * `["red", "green", "blue"].leftpad 1 "blank" = ["red", "green", "blue"]`
-/
def leftpad (n : Nat) (a : α) (l : List α) : List α := replicate (n - length l) a ++ l


/--
Pads `l : List α` on the right with repeated occurrences of `a : α` until it is of length `n`. If
`l` already has at least `n` elements, it is returned unmodified.

Examples:
 * `[1, 2, 3].rightpad 5 0 = [1, 2, 3, 0, 0]`
 * `["red", "green", "blue"].rightpad 4 "blank" = ["red", "green", "blue", "blank"]`
 * `["red", "green", "blue"].rightpad 3 "blank" = ["red", "green", "blue"]`
 * `["red", "green", "blue"].rightpad 1 "blank" = ["red", "green", "blue"]`
-/
def rightpad (n : Nat) (a : α) (l : List α) : List α := l ++ replicate (n - length l) a

/-! ### reduceOption -/

/-- Drop `none`s from a list, and replace each remaining `some a` with `a`. -/
@[inline] def reduceOption {α} : List (Option α) → List α :=
  List.filterMap id

/-! ## List membership

* `L.contains a : Bool` determines, using a `[BEq α]` instance, whether `L` contains an element `· == a`.
* `a ∈ L : Prop` is the proposition (only decidable if `α` has decidable equality) that `L` contains an element `· = a`.
-/

/-! ### EmptyCollection -/

instance : EmptyCollection (List α) := ⟨List.nil⟩

@[simp] theorem empty_eq : (∅ : List α) = [] := rfl

/-! ### isEmpty -/

/--
Checks whether a list is empty.

`O(1)`.

Examples:
* `[].isEmpty = true`
* `["grape"].isEmpty = false`
* `["apple", "banana"].isEmpty = false`
-/
def isEmpty : List α → Bool
  | []     => true
  | _ :: _ => false

@[simp, grind] theorem isEmpty_nil : ([] : List α).isEmpty = true := rfl
@[simp, grind] theorem isEmpty_cons : (x :: xs : List α).isEmpty = false := rfl

/-! ### elem -/

/--
Checks whether `a` is an element of `l`, using `==` to compare elements.

`O(|l|)`. `List.contains` is a synonym that takes the list before the element.

The preferred simp normal form is `l.contains a`. When `LawfulBEq α` is available,
`l.contains a = true ↔ a ∈ l` and `l.contains a = false ↔ a ∉ l`.

Example:
* `List.elem 3 [1, 4, 2, 3, 3, 7] = true`
* `List.elem 5 [1, 4, 2, 3, 3, 7] = false`
-/
def elem [BEq α] (a : α) : (l : List α) → Bool
  | []    => false
  | b::bs => match a == b with
    | true  => true
    | false => elem a bs

@[simp, grind] theorem elem_nil [BEq α] : ([] : List α).elem a = false := rfl
theorem elem_cons [BEq α] {a : α} :
    (b::bs).elem a = match a == b with | true => true | false => bs.elem a := rfl

/-! ### contains -/

/--
Checks whether `a` is an element of `as`, using `==` to compare elements.

`O(|as|)`. `List.elem` is a synonym that takes the element before the list.

The preferred simp normal form is `l.contains a`, and when `LawfulBEq α` is available,
`l.contains a = true ↔ a ∈ l` and `l.contains a = false ↔ a ∉ l`.

Examples:
* `[1, 4, 2, 3, 3, 7].contains 3 = true`
* `List.contains [1, 4, 2, 3, 3, 7] 5 = false`
-/
abbrev contains [BEq α] (as : List α) (a : α) : Bool :=
  elem a as

@[simp] theorem contains_nil [BEq α] : ([] : List α).contains a = false := rfl

/-! ### Mem -/

/--
List membership, typically accessed via the `∈` operator.

`a ∈ l` means that `a` is an element of the list `l`. Elements are compared according to Lean's
logical equality.

The related function `List.elem` is a Boolean membership test that uses a `BEq α` instance.

Examples:
* `a ∈ [x, y, z] ↔ a = x ∨ a = y ∨ a = z`
-/
inductive Mem (a : α) : List α → Prop
  /-- The head of a list is a member: `a ∈ a :: as`. -/
  | head (as : List α) : Mem a (a::as)
  /-- A member of the tail of a list is a member of the list: `a ∈ l → a ∈ b :: l`. -/
  | tail (b : α) {as : List α} : Mem a as → Mem a (b::as)

instance : Membership α (List α) where
  mem l a := Mem a l

theorem mem_of_elem_eq_true [BEq α] [LawfulBEq α] {a : α} {as : List α} : elem a as = true → a ∈ as := by
  match as with
  | [] => simp [elem]
  | a'::as =>
    simp [elem]
    split
    next h => intros; simp at h; subst h; apply Mem.head
    next _ => intro h; exact Mem.tail _ (mem_of_elem_eq_true h)

theorem elem_eq_true_of_mem [BEq α] [ReflBEq α] {a : α} {as : List α} (h : a ∈ as) : elem a as = true := by
  induction h with
  | head _ => simp [elem]
  | tail _ _ ih => simp only [elem]; split; rfl; assumption

instance [BEq α] [LawfulBEq α] (a : α) (as : List α) : Decidable (a ∈ as) :=
  decidable_of_decidable_of_iff (Iff.intro mem_of_elem_eq_true elem_eq_true_of_mem)

theorem mem_append_left {a : α} {as : List α} (bs : List α) : a ∈ as → a ∈ as ++ bs := by
  intro h
  induction h with
  | head => apply Mem.head
  | tail => apply Mem.tail; assumption

theorem mem_append_right {b : α} (as : List α) {bs : List α} : b ∈ bs → b ∈ as ++ bs := by
  intro h
  induction as with
  | nil  => simp [h]
  | cons => apply Mem.tail; assumption

instance decidableBEx (p : α → Prop) [DecidablePred p] :
    ∀ l : List α, Decidable (Exists fun x => x ∈ l ∧ p x)
  | [] => isFalse nofun
  | x :: xs =>
    if h₁ : p x then isTrue ⟨x, .head .., h₁⟩ else
      match decidableBEx p xs with
      | isTrue h₂ => isTrue <| let ⟨y, hm, hp⟩ := h₂; ⟨y, .tail _ hm, hp⟩
      | isFalse h₂ => isFalse fun
        | ⟨y, .tail _ h, hp⟩ => h₂ ⟨y, h, hp⟩
        | ⟨_, .head .., hp⟩ => h₁ hp

instance decidableBAll (p : α → Prop) [DecidablePred p] :
    ∀ l : List α, Decidable (∀ x, x ∈ l → p x)
  | [] => isTrue nofun
  | x :: xs =>
    if h₁ : p x then
      match decidableBAll p xs with
      | isTrue h₂ => isTrue fun
        | y, .tail _ h => h₂ y h
        | _, .head .. => h₁
      | isFalse h₂ => isFalse fun H => h₂ fun y hm => H y (.tail _ hm)
    else isFalse fun H => h₁ <| H x (.head ..)

/-! ## Sublists -/

/-! ### take -/

/--
Extracts the first `n` elements of `xs`, or the whole list if `n` is greater than `xs.length`.

`O(min n |xs|)`.

Examples:
* `[a, b, c, d, e].take 0 = []`
* `[a, b, c, d, e].take 3 = [a, b, c]`
* `[a, b, c, d, e].take 6 = [a, b, c, d, e]`
-/
def take : (n : Nat) → (xs : List α) → List α
  | 0,   _     => []
  | _+1, []    => []
  | n+1, a::as => a :: take n as

@[simp, grind] theorem take_nil {i : Nat} : ([] : List α).take i = [] := by cases i <;> rfl
@[simp, grind] theorem take_zero {l : List α} : l.take 0 = [] := rfl
@[simp, grind] theorem take_succ_cons {a : α} {as : List α} {i : Nat} : (a::as).take (i+1) = a :: as.take i := rfl

/-! ### drop -/

/--
Removes the first `n` elements of the list `xs`. Returns the empty list if `n` is greater than the
length of the list.

`O(min n |xs|)`.

Examples:
* `[0, 1, 2, 3, 4].drop 0 = [0, 1, 2, 3, 4]`
* `[0, 1, 2, 3, 4].drop 3 = [3, 4]`
* `[0, 1, 2, 3, 4].drop 6 = []`
-/
def drop : (n : Nat) → (xs : List α) → List α
  | 0,   as     => as
  | _+1, []    => []
  | n+1, _::as => drop n as

@[simp, grind] theorem drop_nil : ([] : List α).drop i = [] := by
  cases i <;> rfl
@[simp, grind] theorem drop_zero {l : List α} : l.drop 0 = l := rfl
@[simp, grind] theorem drop_succ_cons {a : α} {l : List α} {i : Nat} : (a :: l).drop (i + 1) = l.drop i := rfl

theorem drop_eq_nil_of_le {as : List α} {i : Nat} (h : as.length ≤ i) : as.drop i = [] := by
  match as, i with
  | [],    i   => simp
  | _::_,  0   => simp at h
  | _::as, i+1 => simp only [length_cons] at h; exact @drop_eq_nil_of_le as i (Nat.le_of_succ_le_succ h)

/-! ### extract -/

/--
Returns the slice of `l` from indices `start` (inclusive) to `stop` (exclusive).

Examples:
* [0, 1, 2, 3, 4, 5].extract 1 2 = [1]
* [0, 1, 2, 3, 4, 5].extract 2 2 = []
* [0, 1, 2, 3, 4, 5].extract 2 4 = [2, 3]
* [0, 1, 2, 3, 4, 5].extract 2 = [2, 3, 4, 5]
* [0, 1, 2, 3, 4, 5].extract (stop := 2) = [0, 1]
-/
-- This is only an abbreviation for the operation in terms of `drop` and `take`.
-- We do not prove properties of extract itself.
abbrev extract (l : List α) (start : Nat := 0) (stop : Nat := l.length) : List α :=
  (l.drop start).take (stop - start)

@[simp] theorem extract_eq_drop_take {l : List α} {start stop : Nat} :
    l.extract start stop = (l.drop start).take (stop - start) := rfl

/-! ### takeWhile -/

/--
 Returns the longest initial segment of `xs` for which `p` returns true.

`O(|xs|)`.

Examples:
* `[7, 6, 4, 8].takeWhile (· > 5) = [7, 6]`
* `[7, 6, 6, 5].takeWhile (· > 5) = [7, 6, 6]`
* `[7, 6, 6, 8].takeWhile (· > 5) = [7, 6, 6, 8]`
-/
def takeWhile (p : α → Bool) : (xs : List α) → List α
  | []       => []
  | hd :: tl => match p hd with
   | true  => hd :: takeWhile p tl
   | false => []

@[simp] theorem takeWhile_nil : ([] : List α).takeWhile p = [] := rfl

/-! ### dropWhile -/

/--
Removes the longest prefix of a list for which `p` returns `true`.

Elements are removed from the list until one is encountered for which `p` returns `false`. This
element and the remainder of the list are returned.

`O(|l|)`.

Examples:
 * `[1, 3, 2, 4, 2, 7, 4].dropWhile (· < 4) = [4, 2, 7, 4]`
 * `[8, 3, 2, 4, 2, 7, 4].dropWhile (· < 4) = [8, 3, 2, 4, 2, 7, 4]`
 * `[8, 3, 2, 4, 2, 7, 4].dropWhile (· < 100) = []`
-/
def dropWhile (p : α → Bool) : List α → List α
  | []   => []
  | a::l => match p a with
    | true  => dropWhile p l
    | false => a::l

@[simp] theorem dropWhile_nil : ([] : List α).dropWhile p = [] := rfl

/-! ### partition -/

/--
Returns a pair of lists that together contain all the elements of `as`. The first list contains
those elements for which `p` returns `true`, and the second contains those for which `p` returns
`false`.

`O(|l|)`. `as.partition p` is equivalent to `(as.filter p, as.filter (not ∘ p))`, but it is slightly
more efficient since it only has to do one pass over the list.

Examples:
 * `[1, 2, 5, 2, 7, 7].partition (· > 2) = ([5, 7, 7], [1, 2, 2])`
 * `[1, 2, 5, 2, 7, 7].partition (fun _ => false) = ([], [1, 2, 5, 2, 7, 7])`
 * `[1, 2, 5, 2, 7, 7].partition (fun _ => true) = ([1, 2, 5, 2, 7, 7], [])`
-/
@[inline] def partition (p : α → Bool) (as : List α) : List α × List α :=
  loop as ([], [])
where
  @[specialize] loop : List α → List α × List α → List α × List α
  | [],    (bs, cs) => (bs.reverse, cs.reverse)
  | a::as, (bs, cs) =>
    match p a with
    | true  => loop as (a::bs, cs)
    | false => loop as (bs, a::cs)

/-! ### dropLast -/

/--
Removes the last element of the list, if one exists.

Examples:
* `[].dropLast = []`
* `["tea"].dropLast = []`
* `["tea", "coffee", "juice"].dropLast = ["tea", "coffee"]`
-/
def dropLast {α} : List α → List α
  | []    => []
  | [_]   => []
  | a::as => a :: dropLast as

@[simp, grind] theorem dropLast_nil : ([] : List α).dropLast = [] := rfl
@[simp, grind] theorem dropLast_singleton : [x].dropLast = [] := rfl

@[deprecated dropLast_singleton (since := "2025-04-16")]
theorem dropLast_single : [x].dropLast = [] := dropLast_singleton

@[simp, grind] theorem dropLast_cons₂ :
    (x::y::zs).dropLast = x :: (y::zs).dropLast := rfl

-- Later this can be proved by `simp` via `[List.length_dropLast, List.length_cons, Nat.add_sub_cancel]`,
-- but we need this while bootstrapping `Array`.
@[simp] theorem length_dropLast_cons {a : α} {as : List α} : (a :: as).dropLast.length = as.length := by
  match as with
  | []       => rfl
  | b::bs => simp [dropLast, length_dropLast_cons]

/-! ### Subset -/

/--
`l₁ ⊆ l₂` means that every element of `l₁` is also an element of `l₂`, ignoring multiplicity.
-/
protected def Subset (l₁ l₂ : List α) := ∀ ⦃a : α⦄, a ∈ l₁ → a ∈ l₂

instance : HasSubset (List α) := ⟨List.Subset⟩

instance [DecidableEq α] : DecidableRel (Subset : List α → List α → Prop) :=
  fun _ _ => decidableBAll _ _

/-! ### Sublist and isSublist -/

/--
The first list is a non-contiguous sub-list of the second list. Typically written with the `<+`
operator.

In other words, `l₁ <+ l₂` means that `l₁` can be transformed into `l₂` by repeatedly inserting new
elements.
-/
inductive Sublist {α} : List α → List α → Prop
  /-- The base case: `[]` is a sublist of `[]` -/
  | slnil : Sublist [] []
  /-- If `l₁` is a subsequence of `l₂`, then it is also a subsequence of `a :: l₂`. -/
  | cons a : Sublist l₁ l₂ → Sublist l₁ (a :: l₂)
  /-- If `l₁` is a subsequence of `l₂`, then `a :: l₁` is a subsequence of `a :: l₂`. -/
  | cons₂ a : Sublist l₁ l₂ → Sublist (a :: l₁) (a :: l₂)

@[inherit_doc] scoped infixl:50 " <+ " => Sublist

/--
True if the first list is a potentially non-contiguous sub-sequence of the second list, comparing
elements with the `==` operator.

The relation `List.Sublist` is a logical characterization of this property.

Examples:
* `[1, 3].isSublist [0, 1, 2, 3, 4] = true`
* `[1, 3].isSublist [0, 1, 2, 4] = false`
-/
def isSublist [BEq α] : List α → List α → Bool
  | [], _ => true
  | _, [] => false
  | l₁@(hd₁::tl₁), hd₂::tl₂ =>
    if hd₁ == hd₂
    then tl₁.isSublist tl₂
    else l₁.isSublist tl₂

/-! ### IsPrefix / isPrefixOf / isPrefixOf? -/

/--
The first list is a prefix of the second.

`IsPrefix l₁ l₂`, written `l₁ <+: l₂`, means that there exists some `t : List α` such that `l₂` has
the form `l₁ ++ t`.

The function `List.isPrefixOf` is a Boolean equivalent.
-/
def IsPrefix (l₁ : List α) (l₂ : List α) : Prop := Exists fun t => l₁ ++ t = l₂

@[inherit_doc] infixl:50 " <+: " => IsPrefix

/-- not `isPrefix` -/
recommended_spelling "prefix" for "<+:" in [IsPrefix, «term_<+:_»]

/--
Checks whether the first list is a prefix of the second.

The relation `List.IsPrefixOf` expresses this property with respect to logical equality.

Examples:
 * `[1, 2].isPrefixOf [1, 2, 3] = true`
 * `[1, 2].isPrefixOf [1, 2] = true`
 * `[1, 2].isPrefixOf [1] = false`
 * `[1, 2].isPrefixOf [1, 1, 2, 3] = false`
-/
def isPrefixOf [BEq α] : List α → List α → Bool
  | [],    _     => true
  | _,     []    => false
  | a::as, b::bs => a == b && isPrefixOf as bs

@[simp, grind =] theorem isPrefixOf_nil_left [BEq α] : isPrefixOf ([] : List α) l = true := by
  simp [isPrefixOf]
@[simp, grind =] theorem isPrefixOf_cons_nil [BEq α] : isPrefixOf (a::as) ([] : List α) = false := rfl
@[grind =] theorem isPrefixOf_cons₂ [BEq α] {a : α} :
    isPrefixOf (a::as) (b::bs) = (a == b && isPrefixOf as bs) := rfl

/--
If the first list is a prefix of the second, returns the result of dropping the prefix.

In other words, `isPrefixOf? l₁ l₂` returns `some t` when `l₂ == l₁ ++ t`.

Examples:
 * `[1, 2].isPrefixOf? [1, 2, 3] = some [3]`
 * `[1, 2].isPrefixOf? [1, 2] = some []`
 * `[1, 2].isPrefixOf? [1] = none`
 * `[1, 2].isPrefixOf? [1, 1, 2, 3] = none`
-/
def isPrefixOf? [BEq α] : (l₁ : List α) → (l₂ : List α) → Option (List α)
  | [], l₂ => some l₂
  | _, [] => none
  | (x₁ :: l₁), (x₂ :: l₂) =>
    if x₁ == x₂ then isPrefixOf? l₁ l₂ else none

/-! ### IsSuffix / isSuffixOf / isSuffixOf? -/

/--
Checks whether the first list is a suffix of the second.

The relation `List.IsSuffixOf` expresses this property with respect to logical equality.

Examples:
 * `[2, 3].isSuffixOf [1, 2, 3] = true`
 * `[2, 3].isSuffixOf [1, 2, 3, 4] = false`
 * `[2, 3].isSuffixOf [1, 2] = false`
 * `[2, 3].isSuffixOf [1, 1, 2, 3] = true`
-/
def isSuffixOf [BEq α] (l₁ l₂ : List α) : Bool :=
  isPrefixOf l₁.reverse l₂.reverse

@[simp, grind =] theorem isSuffixOf_nil_left [BEq α] : isSuffixOf ([] : List α) l = true := by
  simp [isSuffixOf]

/--
If the first list is a suffix of the second, returns the result of dropping the suffix from the
second.

In other words, `isSuffixOf? l₁ l₂` returns `some t` when `l₂ == t ++ l₁`.

Examples:
 * `[2, 3].isSuffixOf? [1, 2, 3] = some [1]`
 * `[2, 3].isSuffixOf? [1, 2, 3, 4] = none`
 * `[2, 3].isSuffixOf? [1, 2] = none`
 * `[2, 3].isSuffixOf? [1, 1, 2, 3] = some [1, 1]`
-/
def isSuffixOf? [BEq α] (l₁ l₂ : List α) : Option (List α) :=
  Option.map List.reverse <| isPrefixOf? l₁.reverse l₂.reverse

/--
The first list is a suffix of the second.

`IsSuffix l₁ l₂`, written `l₁ <:+ l₂`, means that there exists some `t : List α` such that `l₂` has
the form `t ++ l₁`.

The function `List.isSuffixOf` is a Boolean equivalent.
-/
def IsSuffix (l₁ : List α) (l₂ : List α) : Prop := Exists fun t => t ++ l₁ = l₂

@[inherit_doc] infixl:50 " <:+ " => IsSuffix

/-- not `isSuffix` -/
recommended_spelling "suffix" for "<:+" in [IsSuffix, «term_<:+_»]

/-! ### IsInfix -/

/--
The first list is a contiguous sub-list of the second list. Typically written with the `<:+:`
operator.

In other words, `l₁ <:+: l₂` means that there exist lists `s : List α` and `t : List α` such that
`l₂` has the form `s ++ l₁ ++ t`.
-/
def IsInfix (l₁ : List α) (l₂ : List α) : Prop := Exists fun s => Exists fun t => s ++ l₁ ++ t = l₂

@[inherit_doc] infixl:50 " <:+: " => IsInfix

/-- not `isInfix` -/
recommended_spelling "infix" for "<:+:" in [IsInfix, «term_<:+:_»]

/-! ### splitAt -/

/--
Splits a list at an index, resulting in the first `n` elements of `l` paired with the remaining
elements.

If `n` is greater than the length of `l`, then the resulting pair consists of `l` and the empty
list. `List.splitAt` is equivalent to a combination of `List.take` and `List.drop`, but it is more
efficient.

Examples:
* `["red", "green", "blue"].splitAt 2 = (["red", "green"], ["blue"])`
* `["red", "green", "blue"].splitAt 3 = (["red", "green", "blue], [])`
* `["red", "green", "blue"].splitAt 4 = (["red", "green", "blue], [])`
-/
def splitAt (n : Nat) (l : List α) : List α × List α := go l n [] where
  /--
  Auxiliary for `splitAt`:
  `splitAt.go l xs n acc = (acc.reverse ++ take n xs, drop n xs)` if `n < xs.length`,
  and `(l, [])` otherwise.
  -/
  go : List α → Nat → List α → List α × List α
  | [], _, _ => (l, []) -- This branch ensures the pointer equality of the result with the input
                        -- without any runtime branching cost.
  | x :: xs, n+1, acc => go xs n (x :: acc)
  | xs, _, acc => (acc.reverse, xs)

/-! ### rotateLeft -/

/--
Rotates the elements of `xs` to the left, moving `i % xs.length` elements from the start of the list
to the end.

`O(|xs|)`.

Examples:
* `[1, 2, 3, 4, 5].rotateLeft 3 = [4, 5, 1, 2, 3]`
* `[1, 2, 3, 4, 5].rotateLeft 5 = [1, 2, 3, 4, 5]`
* `[1, 2, 3, 4, 5].rotateLeft 1 = [2, 3, 4, 5, 1]`
-/
def rotateLeft (xs : List α) (i : Nat := 1) : List α :=
  let len := xs.length
  if len ≤ 1 then
    xs
  else
    let i := i % len
    let ys := xs.take i
    let zs := xs.drop i
    zs ++ ys

@[simp] theorem rotateLeft_nil : ([] : List α).rotateLeft n = [] := rfl

/-! ### rotateRight -/
/--
Rotates the elements of `xs` to the right, moving `i % xs.length` elements from the end of the list
to the start.

After rotation, the element at `xs[n]` is at index `(i + n) % l.length`. `O(|xs|)`.

Examples:
* `[1, 2, 3, 4, 5].rotateRight 3 = [3, 4, 5, 1, 2]`
* `[1, 2, 3, 4, 5].rotateRight 5 = [1, 2, 3, 4, 5]`
* `[1, 2, 3, 4, 5].rotateRight 1 = [5, 1, 2, 3, 4]`
-/
def rotateRight (xs : List α) (i : Nat := 1) : List α :=
  let len := xs.length
  if len ≤ 1 then
    xs
  else
    let i := len - i % len
    let ys := xs.take i
    let zs := xs.drop i
    zs ++ ys

@[simp] theorem rotateRight_nil : ([] : List α).rotateRight n = [] := rfl

/-! ## Pairwise, Nodup -/

section Pairwise

variable (R : α → α → Prop)

/--
Each element of a list is related to all later elements of the list by `R`.

`Pairwise R l` means that all the elements of `l` with earlier indexes are `R`-related to all the
elements with later indexes.

For example, `Pairwise (· ≠ ·) l` asserts that `l` has no duplicates, and if `Pairwise (· < ·) l`
asserts that `l` is (strictly) sorted.

Examples:
 * `Pairwise (· < ·) [1, 2, 3] ↔ (1 < 2 ∧ 1 < 3) ∧ 2 < 3`
 * `Pairwise (· = ·) [1, 2, 3] = False`
 * `Pairwise (· ≠ ·) [1, 2, 3] = True`
-/
inductive Pairwise : List α → Prop
  /-- All elements of the empty list are vacuously pairwise related. -/
  | nil : Pairwise []
  /--
  A nonempty list is pairwise related with `R` if the head is related to every element of the tail
  and the tail is itself pairwise related.

  That is, `a :: l` is `Pairwise R` if:
   * `R` relates `a` to every element of `l`
   * `l` is `Pairwise R`.
  -/
  | cons : ∀ {a : α} {l : List α}, (∀ a', a' ∈ l → R a a') → Pairwise l → Pairwise (a :: l)

attribute [simp] Pairwise.nil

variable {R}

@[simp] theorem pairwise_cons : Pairwise R (a::l) ↔ (∀ a', a' ∈ l → R a a') ∧ Pairwise R l :=
  ⟨fun | .cons h₁ h₂ => ⟨h₁, h₂⟩, fun ⟨h₁, h₂⟩ => h₂.cons h₁⟩

instance instDecidablePairwise [DecidableRel R] :
    (l : List α) → Decidable (Pairwise R l)
  | [] => isTrue .nil
  | hd :: tl =>
    match instDecidablePairwise tl with
    | isTrue ht =>
      match decidableBAll (R hd) tl with
      | isFalse hf => isFalse fun hf' => hf (pairwise_cons.1 hf').1
      | isTrue ht' => isTrue <| pairwise_cons.mpr (And.intro ht' ht)
    | isFalse hf => isFalse fun | .cons _ ih => hf ih

end Pairwise

/--
The list has no duplicates: it contains every element at most once.

It is defined as `Pairwise (· ≠ ·)`: each element is unequal to all other elements.
-/
def Nodup : List α → Prop := Pairwise (· ≠ ·)

instance nodupDecidable [DecidableEq α] : ∀ l : List α, Decidable (Nodup l) :=
  instDecidablePairwise

/-! ## Manipulating elements -/

/-! ### replace -/

/--
Replaces the first element of the list `l` that is equal to `a` with `b`. If no element is equal to
`a`, then the list is returned unchanged.

`O(|l|)`.

Examples:
* `[1, 4, 2, 3, 3, 7].replace 3 6 = [1, 4, 2, 6, 3, 7]`
* `[1, 4, 2, 3, 3, 7].replace 5 6 = [1, 4, 2, 3, 3, 7]`
-/
def replace [BEq α] : (l : List α) → (a : α) → (b : α) → List α
  | [],    _, _ => []
  | a::as, b, c => match b == a with
    | true  => c::as
    | false => a :: replace as b c

@[simp, grind] theorem replace_nil [BEq α] : ([] : List α).replace a b = [] := rfl
@[grind] theorem replace_cons [BEq α] {a : α} :
    (a::as).replace b c = match b == a with | true => c::as | false => a :: replace as b c :=
  rfl

/-! ### modify -/

/--
Replaces the `n`th tail of `l` with the result of applying `f` to it. Returns the input without
using `f` if the index is larger than the length of the List.

Examples:
```lean example
["circle", "square", "triangle"].modifyTailIdx 1 List.reverse
```
```output
["circle", "triangle", "square"]
```
```lean example
["circle", "square", "triangle"].modifyTailIdx 1 (fun xs => xs ++ xs)
```
```output
["circle", "square", "triangle", "square", "triangle"]
```
```lean example
["circle", "square", "triangle"].modifyTailIdx 2 (fun xs => xs ++ xs)
```
```output
["circle", "square", "triangle", "triangle"]
```
```lean example
["circle", "square", "triangle"].modifyTailIdx 5 (fun xs => xs ++ xs)
```
```output
["circle", "square", "triangle"]
```
-/
def modifyTailIdx (l : List α) (i : Nat) (f : List α → List α) : List α :=
  go i l
where
  go : Nat → List α → List α
  | 0, l => f l
  | _+1, [] => []
  | i+1, a :: l => a :: go i l

@[simp] theorem modifyTailIdx_zero {l : List α} : l.modifyTailIdx 0 f = f l := rfl
@[simp] theorem modifyTailIdx_succ_nil {i : Nat} : ([] : List α).modifyTailIdx (i + 1) f = [] := rfl
@[simp] theorem modifyTailIdx_succ_cons {i : Nat} {a : α} {l : List α} :
    (a :: l).modifyTailIdx (i + 1) f = a :: l.modifyTailIdx i f := rfl

/--
Replace the head of the list with the result of applying `f` to it. Returns the empty list if the
list is empty.

Examples:
 * `[1, 2, 3].modifyHead (· * 10) = [10, 2, 3]`
 * `[].modifyHead (· * 10) = []`
-/
@[inline] def modifyHead (f : α → α) : List α → List α
  | [] => []
  | a :: l => f a :: l

@[simp] theorem modifyHead_nil {f : α → α} : [].modifyHead f = [] := by rw [modifyHead]
@[simp] theorem modifyHead_cons {a : α} {l : List α} {f : α → α} :
    (a :: l).modifyHead f = f a :: l := by rw [modifyHead]

/--
Replaces the element at the given index, if it exists, with the result of applying `f` to it. If the
index is invalid, the list is returned unmodified.

Examples:
 * `[1, 2, 3].modify 0 (· * 10) = [10, 2, 3]`
 * `[1, 2, 3].modify 2 (· * 10) = [1, 2, 30]`
 * `[1, 2, 3].modify 3 (· * 10) = [1, 2, 3]`
-/
def modify (l : List α) (i : Nat) (f : α → α) : List α :=
  l.modifyTailIdx i (modifyHead f)

/-! ### insert -/

/--
Inserts an element into a list without duplication.

If the element is present in the list, the list is returned unmodified. Otherwise, the new element
is inserted at the head of the list.

Examples:
 * `[1, 2, 3].insert 0 = [0, 1, 2, 3]`
 * `[1, 2, 3].insert 4 = [4, 1, 2, 3]`
 * `[1, 2, 3].insert 2 = [1, 2, 3]`
-/
@[inline] protected def insert [BEq α] (a : α) (l : List α) : List α :=
  if l.contains a then l else a :: l

/--
Inserts an element into a list at the specified index. If the index is greater than the length of
the list, then the list is returned unmodified.

In other words, the new element is inserted into the list `l` after the first `i` elements of `l`.

Examples:
 * `["tues", "thur", "sat"].insertIdx 1 "wed" = ["tues", "wed", "thur", "sat"]`
 * `["tues", "thur", "sat"].insertIdx 2 "wed" = ["tues", "thur", "wed", "sat"]`
 * `["tues", "thur", "sat"].insertIdx 3 "wed" = ["tues", "thur", "sat", "wed"]`
 * `["tues", "thur", "sat"].insertIdx 4 "wed" = ["tues", "thur", "sat"]`
-/
def insertIdx (xs : List α) (i : Nat) (a : α) : List α :=
  xs.modifyTailIdx i (cons a)

/-! ### erase -/

/--
Removes the first occurrence of `a` from `l`. If `a` does not occur in `l`, the list is returned
unmodified.

`O(|l|)`.

Examples:
* `[1, 5, 3, 2, 5].erase 5 = [1, 3, 2, 5]`
* `[1, 5, 3, 2, 5].erase 6 = [1, 5, 3, 2, 5]`
-/
protected def erase {α} [BEq α] : List α → α → List α
  | [],    _ => []
  | a::as, b => match a == b with
    | true  => as
    | false => a :: List.erase as b

@[simp, grind =] theorem erase_nil [BEq α] (a : α) : [].erase a = [] := rfl
@[grind =] theorem erase_cons [BEq α] {a b : α} {l : List α} :
    (b :: l).erase a = if b == a then l else b :: l.erase a := by
  simp only [List.erase]; split <;> simp_all

/--
Removes the first element of a list for which `p` returns `true`. If no element satisfies `p`, then
the list is returned unchanged.

Examples:
  * `[2, 1, 2, 1, 3, 4].eraseP (· < 2) = [2, 2, 1, 3, 4]`
  * `[2, 1, 2, 1, 3, 4].eraseP (· > 2) = [2, 1, 2, 1, 4]`
  * `[2, 1, 2, 1, 3, 4].eraseP (· > 8) = [2, 1, 2, 1, 3, 4]`
-/
def eraseP (p : α → Bool) : List α → List α
  | [] => []
  | a :: l => bif p a then l else a :: eraseP p l

/-! ### eraseIdx -/

/--
Removes the element at the specified index. If the index is out of bounds, the list is returned
unmodified.

`O(i)`.

Examples:
* `[0, 1, 2, 3, 4].eraseIdx 0 = [1, 2, 3, 4]`
* `[0, 1, 2, 3, 4].eraseIdx 1 = [0, 2, 3, 4]`
* `[0, 1, 2, 3, 4].eraseIdx 5 = [0, 1, 2, 3, 4]`
-/
def eraseIdx : (l : List α) → (i : Nat) → List α
  | [],    _   => []
  | _::as, 0   => as
  | a::as, n+1 => a :: eraseIdx as n

@[simp] theorem eraseIdx_nil : ([] : List α).eraseIdx i = [] := rfl
@[simp] theorem eraseIdx_cons_zero : (a::as).eraseIdx 0 = as := rfl
@[simp] theorem eraseIdx_cons_succ : (a::as).eraseIdx (i+1) = a :: as.eraseIdx i := rfl

/-! Finding elements -/

/-! ### find? -/

/--
Returns the first element of the list for which the predicate `p` returns `true`, or `none` if no
such element is found.

`O(|l|)`.

Examples:
* `[7, 6, 5, 8, 1, 2, 6].find? (· < 5) = some 1`
* `[7, 6, 5, 8, 1, 2, 6].find? (· < 1) = none`
-/
def find? (p : α → Bool) : List α → Option α
  | []    => none
  | a::as => match p a with
    | true  => some a
    | false => find? p as

@[simp, grind =] theorem find?_nil : ([] : List α).find? p = none := rfl
@[grind =]theorem find?_cons : (a::as).find? p = match p a with | true => some a | false => as.find? p :=
  rfl

/-! ### findSome? -/

/--
Returns the first non-`none` result of applying `f` to each element of the list in order. Returns
`none` if `f` returns `none` for all elements of the list.

`O(|l|)`.

Examples:
 * `[7, 6, 5, 8, 1, 2, 6].findSome? (fun x => if x < 5 then some (10 * x) else none) = some 10`
 * `[7, 6, 5, 8, 1, 2, 6].findSome? (fun x => if x < 1 then some (10 * x) else none) = none`
-/
def findSome? (f : α → Option β) : List α → Option β
  | []    => none
  | a::as => match f a with
    | some b => some b
    | none   => findSome? f as

@[simp, grind] theorem findSome?_nil : ([] : List α).findSome? f = none := rfl
@[grind] theorem findSome?_cons {f : α → Option β} :
    (a::as).findSome? f = match f a with | some b => some b | none => as.findSome? f :=
  rfl

/-! ### findRev? -/

/--
Returns the last element of the list for which the predicate `p` returns `true`, or `none` if no
such element is found.

`O(|l|)`.

Examples:
* `[7, 6, 5, 8, 1, 2, 6].find? (· < 5) = some 2`
* `[7, 6, 5, 8, 1, 2, 6].find? (· < 1) = none`
-/
def findRev? (p : α → Bool) : List α → Option α
  | []    => none
  | a::as => match findRev? p as with
    | some b => some b
    | none   => if p a then some a else none

/-! ### findSomeRev? -/

/--
Returns the last non-`none` result of applying `f` to each element of the list in order. Returns
`none` if `f` returns `none` for all elements of the list.

`O(|l|)`.

Examples:
 * `[7, 6, 5, 8, 1, 2, 6].findSomeRev? (fun x => if x < 5 then some (10 * x) else none) = some 20`
 * `[7, 6, 5, 8, 1, 2, 6].findSomeRev? (fun x => if x < 1 then some (10 * x) else none) = none`
-/
def findSomeRev? (f : α → Option β) : List α → Option β
  | []    => none
  | a::as => match findSomeRev? f as with
    | some b => some b
    | none   => f a

/-! ### findIdx -/

/--
Returns the index of the first element for which `p` returns `true`, or the length of the list if
there is no such element.

Examples:
* `[7, 6, 5, 8, 1, 2, 6].findIdx (· < 5) = 4`
* `[7, 6, 5, 8, 1, 2, 6].findIdx (· < 1) = 7`
-/
@[inline] def findIdx (p : α → Bool) (l : List α) : Nat := go l 0 where
  /-- Auxiliary for `findIdx`: `findIdx.go p l n = findIdx p l + n` -/
  @[specialize] go : List α → Nat → Nat
  | [], n => n
  | a :: l, n => bif p a then n else go l (n + 1)

@[simp] theorem findIdx_nil {p : α → Bool} : [].findIdx p = 0 := rfl

/-! ### idxOf -/

/--
Returns the index of the first element equal to `a`, or the length of the list if no element is
equal to `a`.

Examples:
 * `["carrot", "potato", "broccoli"].idxOf "carrot" = 0`
 * `["carrot", "potato", "broccoli"].idxOf "broccoli" = 2`
 * `["carrot", "potato", "broccoli"].idxOf "tomato" = 3`
 * `["carrot", "potato", "broccoli"].idxOf "anything else" = 3`
-/
def idxOf [BEq α] (a : α) : List α → Nat := findIdx (· == a)

/-- Returns the index of the first element equal to `a`, or the length of the list otherwise. -/
@[deprecated idxOf (since := "2025-01-29")] abbrev indexOf := @idxOf

@[simp] theorem idxOf_nil [BEq α] : ([] : List α).idxOf x = 0 := rfl

@[deprecated idxOf_nil (since := "2025-01-29")]
theorem indexOf_nil [BEq α] : ([] : List α).idxOf x = 0 := rfl

/-! ### findIdx? -/

/--
Returns the index of the first element for which `p` returns `true`, or `none` if there is no such
element.

Examples:
* `[7, 6, 5, 8, 1, 2, 6].findIdx (· < 5) = some 4`
* `[7, 6, 5, 8, 1, 2, 6].findIdx (· < 1) = none`
-/
def findIdx? (p : α → Bool) (l : List α) : Option Nat :=
  go l 0
where
  go : List α → Nat → Option Nat
  | [], _ => none
  | a :: l, i => if p a then some i else go l (i + 1)

/-! ### idxOf? -/

/--
Returns the index of the first element equal to `a`, or `none` if no element is equal to `a`.

Examples:
 * `["carrot", "potato", "broccoli"].idxOf? "carrot" = some 0`
 * `["carrot", "potato", "broccoli"].idxOf? "broccoli" = some 2`
 * `["carrot", "potato", "broccoli"].idxOf? "tomato" = none`
 * `["carrot", "potato", "broccoli"].idxOf? "anything else" = none`
-/
@[inline] def idxOf? [BEq α] (a : α) : List α → Option Nat := findIdx? (· == a)

/-- Return the index of the first occurrence of `a` in the list. -/
@[deprecated idxOf? (since := "2025-01-29")]
abbrev indexOf? := @idxOf?

/-! ### findFinIdx? -/

/--
Returns the index of the first element for which `p` returns `true`, or `none` if there is no such
element. The index is returned as a `Fin`, which guarantees that it is in bounds.

Examples:
* `[7, 6, 5, 8, 1, 2, 6].findFinIdx? (· < 5) = some (4 : Fin 7)`
* `[7, 6, 5, 8, 1, 2, 6].findFinIdx? (· < 1) = none`
-/
@[inline] def findFinIdx? (p : α → Bool) (l : List α) : Option (Fin l.length) :=
  go l 0 (by simp)
where
  go : (l' : List α) → (i : Nat) → (h : l'.length + i = l.length) → Option (Fin l.length)
  | [], _, _ => none
  | a :: l, i, h =>
    if p a then
      some ⟨i, by
        simp only [Nat.add_comm _ i] at h
        exact Nat.lt_of_add_right_lt (Nat.lt_of_succ_le (Nat.le_of_eq h))⟩
    else
      go l (i + 1) (by simp at h; simpa [← Nat.add_assoc, Nat.add_right_comm] using h)

/-! ### finIdxOf? -/

/--
Returns the index of the first element equal to `a`, or the length of the list if no element is
equal to `a`. The index is returned as a `Fin`, which guarantees that it is in bounds.

Examples:
 * `["carrot", "potato", "broccoli"].finIdxOf? "carrot" = some 0`
 * `["carrot", "potato", "broccoli"].finIdxOf? "broccoli" = some 2`
 * `["carrot", "potato", "broccoli"].finIdxOf? "tomato" = none`
 * `["carrot", "potato", "broccoli"].finIdxOf? "anything else" = none`
-/
@[inline] def finIdxOf? [BEq α] (a : α) : (l : List α) → Option (Fin l.length) :=
  findFinIdx? (· == a)

/-! ### countP -/

/--
Counts the number of elements in the list `l` that satisfy the Boolean predicate `p`.

Examples:
 * `[1, 2, 3, 4, 5].countP (· % 2 == 0) = 2`
 * `[1, 2, 3, 4, 5].countP (· < 5) = 4`
 * `[1, 2, 3, 4, 5].countP (· > 5) = 0`
-/
@[inline] def countP (p : α → Bool) (l : List α) : Nat := go l 0 where
  /-- Auxiliary for `countP`: `countP.go p l acc = countP p l + acc`. -/
  @[specialize] go : List α → Nat → Nat
  | [], acc => acc
  | x :: xs, acc => bif p x then go xs (acc + 1) else go xs acc

/-! ### count -/

/--
Counts the number of times an element occurs in a list.

Examples:
 * `[1, 1, 2, 3, 5].count 1 = 2`
 * `[1, 1, 2, 3, 5].count 5 = 1`
 * `[1, 1, 2, 3, 5].count 4 = 0`
-/
@[inline] def count [BEq α] (a : α) : List α → Nat := countP (· == a)

/-! ### lookup -/

/--
Treats the list as an association list that maps keys to values, returning the first value whose key
is equal to the specified key.

`O(|l|)`.

Examples:
* `[(1, "one"), (3, "three"), (3, "other")].lookup 3 = some "three"`
* `[(1, "one"), (3, "three"), (3, "other")].lookup 2 = none`
-/
def lookup [BEq α] : α → List (α × β) → Option β
  | _, []           => none
  | a, (k, b) :: as => match a == k with
    | true  => some b
    | false => lookup a as

@[simp, grind =] theorem lookup_nil [BEq α] : ([] : List (α × β)).lookup a = none := rfl
@[grind =] theorem lookup_cons [BEq α] {k : α} :
    ((k, b)::as).lookup a = match a == k with | true => some b | false => as.lookup a :=
  rfl

/-! ## Permutations -/

/-! ### Perm -/

/--
Two lists are permutations of each other if they contain the same elements, each occurring the same
number of times but not necessarily in the same order.

One list can be proven to be a permutation of another by showing how to transform one into the other
by repeatedly swapping adjacent elements.

`List.isPerm` is a Boolean equivalent of this relation.
-/
inductive Perm : List α → List α → Prop
  /-- The empty list is a permutation of the empty list: `[] ~ []`. -/
  | nil : Perm [] []
  /--
  If one list is a permutation of the other, adding the same element as the head of each yields
  lists that are permutations of each other: `l₁ ~ l₂ → x::l₁ ~ x::l₂`.
  -/
  | cons (x : α) {l₁ l₂ : List α} : Perm l₁ l₂ → Perm (x :: l₁) (x :: l₂)
  /--
  If two lists are identical except for having their first two elements swapped, then they are
  permutations of each other: `x::y::l ~ y::x::l`.
  -/
  | swap (x y : α) (l : List α) : Perm (y :: x :: l) (x :: y :: l)
  /--
  Permutation is transitive: `l₁ ~ l₂ → l₂ ~ l₃ → l₁ ~ l₃`.
  -/
  | trans {l₁ l₂ l₃ : List α} : Perm l₁ l₂ → Perm l₂ l₃ → Perm l₁ l₃

@[inherit_doc] scoped infixl:50 " ~ " => Perm

/-! ### isPerm -/

/--
Returns `true` if `l₁` and `l₂` are permutations of each other. `O(|l₁| * |l₂|)`.

The relation `List.Perm` is a logical characterization of permutations. When the `BEq α` instance
corresponds to `DecidableEq α`, `isPerm l₁ l₂ ↔ l₁ ~ l₂` (use the theorem `isPerm_iff`).
-/
def isPerm [BEq α] : List α → List α → Bool
  | [], l₂ => l₂.isEmpty
  | a :: l₁, l₂ => l₂.contains a && l₁.isPerm (l₂.erase a)

/-! ## Logical operations -/

/-! ### any -/

/--
Returns `true` if `p` returns `true` for any element of `l`.

`O(|l|)`. Short-circuits upon encountering the first `true`.

Examples:
* `[2, 4, 6].any (· % 2 = 0) = true`
* `[2, 4, 6].any (· % 2 = 1) = false`
* `[2, 4, 5, 6].any (· % 2 = 0) = true`
* `[2, 4, 5, 6].any (· % 2 = 1) = true`
-/
def any : (l : List α) → (p : α → Bool) → Bool
  | [], _ => false
  | h :: t, p => p h || any t p

@[simp, grind] theorem any_nil : [].any f = false := rfl
@[simp, grind] theorem any_cons : (a::l).any f = (f a || l.any f) := rfl

/-! ### all -/

/--
Returns `true` if `p` returns `true` for every element of `l`.

`O(|l|)`. Short-circuits upon encountering the first `false`.

Examples:
* `[a, b, c].all p = (p a && (p b && p c))`
* `[2, 4, 6].all (· % 2 = 0) = true`
* `[2, 4, 5, 6].all (· % 2 = 0) = false`
-/
def all : List α → (α → Bool) → Bool
  | [], _ => true
  | h :: t, p => p h && all t p

@[simp, grind] theorem all_nil : [].all f = true := rfl
@[simp, grind] theorem all_cons : (a::l).all f = (f a && l.all f) := rfl

/-! ### or -/

/--
Returns `true` if `true` is an element of the list `bs`.

`O(|bs|)`. Short-circuits at the first `true` value.

* `[true, true, true].or = true`
* `[true, false, true].or = true`
* `[false, false, false].or = false`
* `[false, false, true].or = true`
* `[].or = false`
-/
def or (bs : List Bool) : Bool := bs.any id

@[simp] theorem or_nil : [].or = false := rfl
@[simp] theorem or_cons : (a::l).or = (a || l.or) := rfl

/-! ### and -/

/--
Returns `true` if every element of `bs` is the value `true`.

`O(|bs|)`. Short-circuits at the first `false` value.

* `[true, true, true].and = true`
* `[true, false, true].and = false`
* `[true, false, false].and = false`
* `[].and = true`
-/
def and (bs : List Bool) : Bool := bs.all id

@[simp] theorem and_nil : [].and = true := rfl
@[simp] theorem and_cons : (a::l).and = (a && l.and) := rfl

/-! ## Zippers -/

/-! ### zipWith -/

/--
Applies a function to the corresponding elements of two lists, stopping at the end of the shorter
list.

`O(min |xs| |ys|)`.

Examples:
* `[1, 2].zipWith (· + ·) [5, 6] = [6, 8]`
* `[1, 2, 3].zipWith (· + ·) [5, 6, 10] = [6, 8, 13]`
* `[].zipWith (· + ·) [5, 6] = []`
* `[x₁, x₂, x₃].zipWith f [y₁, y₂, y₃, y₄] = [f x₁ y₁, f x₂ y₂, f x₃ y₃]`
-/
@[specialize] def zipWith (f : α → β → γ) : (xs : List α) → (ys : List β) → List γ
  | x::xs, y::ys => f x y :: zipWith f xs ys
  | _,     _     => []

@[simp] theorem zipWith_nil_left {f : α → β → γ} : zipWith f [] l = [] := rfl
@[simp] theorem zipWith_nil_right {f : α → β → γ} : zipWith f l [] = [] := by simp [zipWith]
@[simp] theorem zipWith_cons_cons {f : α → β → γ} :
    zipWith f (a :: as) (b :: bs) = f a b :: zipWith f as bs := rfl

/-! ### zip -/

/--
Combines two lists into a list of pairs in which the first and second components are the
corresponding elements of each list. The resulting list is the length of the shorter of the input
lists.

`O(min |xs| |ys|)`.

Examples:
* `["Mon", "Tue", "Wed"].zip [1, 2, 3] = [("Mon", 1), ("Tue", 2), ("Wed", 3)]`
* `["Mon", "Tue", "Wed"].zip [1, 2] = [("Mon", 1), ("Tue", 2)]`
* `[x₁, x₂, x₃].zip [y₁, y₂, y₃, y₄] = [(x₁, y₁), (x₂, y₂), (x₃, y₃)]`
-/
def zip : List α → List β → List (Prod α β) :=
  zipWith Prod.mk

@[simp] theorem zip_nil_left : zip ([] : List α) (l : List β)  = [] := rfl
@[simp] theorem zip_nil_right : zip (l : List α) ([] : List β)  = [] := by simp [zip]
@[simp] theorem zip_cons_cons : zip (a :: as) (b :: bs) = (a, b) :: zip as bs := rfl

/-! ### zipWithAll -/

/--
Applies a function to the corresponding elements of both lists, stopping when there are no more
elements in either list. If one list is shorter than the other, the function is passed `none` for
the missing elements.

Examples:
* `[1, 6].zipWithAll min [5, 2] = [some 1, some 2]`
* `[1, 2, 3].zipWithAll Prod.mk [5, 6] = [(some 1, some 5), (some 2, some 6), (some 3, none)]`
* `[x₁, x₂].zipWithAll f [y] = [f (some x₁) (some y), f (some x₂) none]`
-/
def zipWithAll (f : Option α → Option β → γ) : List α → List β → List γ
  | [], bs => bs.map fun b => f none (some b)
  | a :: as, [] => (a :: as).map fun a => f (some a) none
  | a :: as, b :: bs => f (some a) (some b) :: zipWithAll f as bs

@[simp] theorem zipWithAll_nil :
    zipWithAll f as [] = as.map fun a => f (some a) none := by
  cases as <;> rfl
@[simp] theorem nil_zipWithAll :
    zipWithAll f [] bs = bs.map fun b => f none (some b) := rfl
@[simp] theorem zipWithAll_cons_cons :
    zipWithAll f (a :: as) (b :: bs) = f (some a) (some b) :: zipWithAll f as bs := rfl

/-! ### unzip -/

/--
Separates a list of pairs into two lists that contain the respective first and second components.

`O(|l|)`.

Examples:
* `[("Monday", 1), ("Tuesday", 2)].unzip = (["Monday", "Tuesday"], [1, 2])`
* `[(x₁, y₁), (x₂, y₂), (x₃, y₃)].unzip = ([x₁, x₂, x₃], [y₁, y₂, y₃])`
* `([] : List (Nat × String)).unzip = (([], []) : List Nat × List String)`
-/
def unzip : (l : List (α × β)) → List α × List β
  | []          => ([], [])
  | (a, b) :: t => match unzip t with | (as, bs) => (a::as, b::bs)

@[simp] theorem unzip_nil : ([] : List (α × β)).unzip = ([], []) := rfl
@[simp] theorem unzip_cons {h : α × β} :
    (h :: t).unzip = match unzip t with | (as, bs) => (h.1::as, h.2::bs) := rfl

/-! ## Ranges and enumeration -/

/--
Computes the sum of the elements of a list.

Examples:
 * `[a, b, c].sum = a + (b + (c + 0))`
 * `[1, 2, 5].sum = 8`
-/
def sum {α} [Add α] [Zero α] : List α → α :=
  foldr (· + ·) 0

@[simp, grind] theorem sum_nil [Add α] [Zero α] : ([] : List α).sum = 0 := rfl
@[simp, grind] theorem sum_cons [Add α] [Zero α] {a : α} {l : List α} : (a::l).sum = a + l.sum := rfl

/-! ### range -/

/--
Returns a list of the numbers from `0` to `n` exclusive, in increasing order.

`O(n)`.

Examples:
* `range 5 = [0, 1, 2, 3, 4]`
* `range 0 = []`
* `range 2 = [0, 1]`
-/
def range (n : Nat) : List Nat :=
  loop n []
where
  loop : Nat → List Nat → List Nat
  | 0,   acc => acc
  | n+1, acc => loop n (n::acc)

@[simp, grind =] theorem range_zero : range 0 = [] := rfl

/-! ### range' -/

/--
Returns a list of the numbers with the given length `len`, starting at `start` and increasing by
`step` at each element.

In other words, `List.range' start len step` is `[start, start+step, ..., start+(len-1)*step]`.

Examples:
 * `List.range' 0 3 (step := 1) = [0, 1, 2]`
 * `List.range' 0 3 (step := 2) = [0, 2, 4]`
 * `List.range' 0 4 (step := 2) = [0, 2, 4, 6]`
 * `List.range' 3 4 (step := 2) = [3, 5, 7, 9]`
-/
def range' : (start len : Nat) → (step : Nat := 1) → List Nat
  | _, 0, _ => []
  | s, n+1, step => s :: range' (s+step) n step

/-! ### iota -/

/--
`O(n)`. `iota n` is the numbers from `1` to `n` inclusive, in decreasing order.
* `iota 5 = [5, 4, 3, 2, 1]`
-/
@[deprecated "Use `(List.range' 1 n).reverse` instead of `iota n`." (since := "2025-01-20")]
def iota : Nat → List Nat
  | 0       => []
  | m@(n+1) => m :: iota n

set_option linter.deprecated false in
@[simp] theorem iota_zero : iota 0 = [] := rfl
set_option linter.deprecated false in
@[simp] theorem iota_succ : iota (i+1) = (i+1) :: iota i := rfl

/-! ### zipIdx -/

/--
Pairs each element of a list with its index, optionally starting from an index other than `0`.

`O(|l|)`.

Examples:
* `[a, b, c].zipIdx = [(a, 0), (b, 1), (c, 2)]`
* `[a, b, c].zipIdx 5 = [(a, 5), (b, 6), (c, 7)]`
-/
def zipIdx : (l : List α) → (n : Nat := 0) → List (α × Nat)
  | [], _ => nil
  | x :: xs, n => (x, n) :: zipIdx xs (n + 1)

@[simp] theorem zipIdx_nil : ([] : List α).zipIdx i = [] := rfl
@[simp] theorem zipIdx_cons : (a::as).zipIdx i = (a, i) :: as.zipIdx (i+1) := rfl

/-! ### enumFrom -/

/--
`O(|l|)`. `enumFrom n l` is like `enum` but it allows you to specify the initial index.
* `enumFrom 5 [a, b, c] = [(5, a), (6, b), (7, c)]`
-/
@[deprecated "Use `zipIdx` instead; note the signature change." (since := "2025-01-21")]
def enumFrom : Nat → List α → List (Nat × α)
  | _, [] => nil
  | n, x :: xs   => (n, x) :: enumFrom (n + 1) xs

set_option linter.deprecated false in
@[deprecated zipIdx_nil (since := "2025-01-21"), simp]
theorem enumFrom_nil : ([] : List α).enumFrom i = [] := rfl
set_option linter.deprecated false in
@[deprecated zipIdx_cons (since := "2025-01-21"), simp]
theorem enumFrom_cons : (a::as).enumFrom i = (i, a) :: as.enumFrom (i+1) := rfl

/-! ### enum -/

set_option linter.deprecated false in
/--
`O(|l|)`. `enum l` pairs up each element with its index in the list.
* `enum [a, b, c] = [(0, a), (1, b), (2, c)]`
-/
@[deprecated "Use `zipIdx` instead; note the signature change." (since := "2025-01-21")]
def enum : List α → List (Nat × α) := enumFrom 0

set_option linter.deprecated false in
@[deprecated zipIdx_nil (since := "2025-01-21"), simp]
theorem enum_nil : ([] : List α).enum = [] := rfl

/-! ## Minima and maxima -/

/-! ### min? -/

/--
Returns the smallest element of the list if it is not empty, or `none` if it is empty.

Examples:
* `[].min? = none`
* `[4].min? = some 4`
* `[1, 4, 2, 10, 6].min? = some 1`
-/
def min? [Min α] : List α → Option α
  | []    => none
  | a::as => some <| as.foldl min a

/-! ### max? -/

/--
Returns the largest element of the list if it is not empty, or `none` if it is empty.

Examples:
* `[].max? = none`
* `[4].max? = some 4`
* `[1, 4, 2, 10, 6].max? = some 10`
-/
def max? [Max α] : List α → Option α
  | []    => none
  | a::as => some <| as.foldl max a

/-! ## Other list operations

The functions are currently mostly used in meta code,
and do not have sufficient API developed for verification work.
-/

/-! ### intersperse -/

/--
Alternates the elements of `l` with `sep`.

`O(|l|)`.

`List.intercalate` is a similar function that alternates a separator list with elements of a list of
lists.

Examples:
* `List.intersperse "then" [] = []`
* `List.intersperse "then" ["walk"] = ["walk"]`
* `List.intersperse "then" ["walk", "run"] = ["walk", "then", "run"]`
* `List.intersperse "then" ["walk", "run", "rest"] = ["walk", "then", "run", "then", "rest"]`
-/
def intersperse (sep : α) : (l : List α) → List α
  | []    => []
  | [x]   => [x]
  | x::xs => x :: sep :: intersperse sep xs

@[simp] theorem intersperse_nil {sep : α} : ([] : List α).intersperse sep = [] := rfl
@[simp] theorem intersperse_single {x : α} {sep : α} : [x].intersperse sep = [x] := rfl
@[simp] theorem intersperse_cons₂ {x : α} {y : α} {zs : List α} {sep : α} :
    (x::y::zs).intersperse sep = x::sep::((y::zs).intersperse sep) := rfl

/-! ### intercalate -/

set_option linter.listVariables false in
/--
Alternates the lists in `xs` with the separator `sep`, appending them. The resulting list is
flattened.

`O(|xs|)`.

`List.intersperse` is a similar function that alternates a separator element with the elements of a
list.

Examples:
* `List.intercalate sep [] = []`
* `List.intercalate sep [a] = a`
* `List.intercalate sep [a, b] = a ++ sep ++ b`
* `List.intercalate sep [a, b, c] = a ++ sep ++ b ++ sep ++ c`
-/
def intercalate (sep : List α) (xs : List (List α)) : List α :=
  (intersperse sep xs).flatten

/-! ### eraseDupsBy -/

/--
Erases duplicated elements in the list, using a given comparison function,
keeping the first occurrence of duplicated elements.

`O(|l|^2)`.

Examples:
 * `[1, 3, 4, 2, 1, 5].eraseDupsBy (· % 3 == · % 3) = [1, 3, 2]`
-/
def eraseDupsBy {α} (r : α → α → Bool) (as : List α) : List α :=
  loop as []
where
  loop : List α → List α → List α
  | [],    bs => bs.reverse
  | a::as, bs => match bs.any (r a) with
    | true  => loop as bs
    | false => loop as (a::bs)

/-! ### eraseDups -/

/--
Erases duplicated elements in the list, keeping the first occurrence of duplicated elements.

`O(|l|^2)`.

Examples:
 * `[1, 3, 2, 2, 3, 5].eraseDups = [1, 3, 2, 5]`
 * `["red", "green", "green", "blue"].eraseDups = ["red", "green", "blue"]`
-/
def eraseDups {α} [BEq α] (as : List α) : List α := eraseDupsBy (· == ·) as

/-! ### eraseRepsBy -/

/--
Erases repeated elements, using a given comparison function,
keeping the first element of each run.

`O(|l|)`.

Example:
* `[1, 3, 2, 2, 2, 3, 3, 7].eraseRepsBy (· % 4 == · % 5) = [1, 3, 2, 3]`
-/
def eraseRepsBy {α} (r : α → α → Bool) : List α → List α
  | []    => []
  | a::as => loop a as []
where
  loop : α → List α → List α → List α
  | a, [], acc => (a::acc).reverse
  | a, a'::as, acc => match r a a' with
    | true  => loop a as acc
    | false => loop a' as (a::acc)

/-! ### eraseReps -/

/--
Erases repeated elements, keeping the first element of each run.

`O(|l|)`.

Example:
* `[1, 3, 2, 2, 2, 3, 3, 5].eraseReps = [1, 3, 2, 3, 5]`
-/
def eraseReps {α} [BEq α] (as : List α) : List α := eraseRepsBy (· == ·) as

/-! ### span -/

/--
Splits a list into the the longest initial segment for which `p` returns `true`, paired with the
remainder of the list.

`O(|l|)`.

Examples:
* `[6, 8, 9, 5, 2, 9].span (· > 5) = ([6, 8, 9], [5, 2, 9])`
* `[6, 8, 9, 5, 2, 9].span (· > 10) = ([], [6, 8, 9, 5, 2, 9])`
* `[6, 8, 9, 5, 2, 9].span (· > 0) = ([6, 8, 9, 5, 2, 9], [])`
-/
@[inline] def span (p : α → Bool) (as : List α) : List α × List α :=
  loop as []
where
  @[specialize] loop : List α → List α → List α × List α
  | [],    acc => (acc.reverse, [])
  | a::as, acc => match p a with
    | true  => loop as (a::acc)
    | false => (acc.reverse, a::as)

/-! ### splitBy -/

/--
Splits a list into the longest segments in which each pair of adjacent elements are related by `R`.

`O(|l|)`.

Examples:
* `[1, 1, 2, 2, 2, 3, 2].splitBy (· == ·) = [[1, 1], [2, 2, 2], [3], [2]]`
* `[1, 2, 5, 4, 5, 1, 4].splitBy (· < ·) = [[1, 2, 5], [4, 5], [1, 4]]`
* `[1, 2, 5, 4, 5, 1, 4].splitBy (fun _ _ => true) = [[1, 2, 5, 4, 5, 1, 4]]`
* `[1, 2, 5, 4, 5, 1, 4].splitBy (fun _ _ => false) = [[1], [2], [5], [4], [5], [1], [4]]`
-/
@[specialize] def splitBy (R : α → α → Bool) : List α → List (List α)
  | []    => []
  | a::as => loop as a [] []
where
  /--
  The arguments of `splitBy.loop l b g gs` represent the following:

  - `l : List α` are the elements which we still need to split.
  - `b : α` is the previous element for which a comparison was performed.
  - `r : List α` is the group currently being assembled, in **reverse order**.
  - `acc : List (List α)` is all of the groups that have been completed, in **reverse order**.
  -/
  @[specialize] loop : List α → α → List α → List (List α) → List (List α)
  | a::as, b, r, acc => match R b a with
    | true  => loop as a (b::r) acc
    | false => loop as a [] ((b::r).reverse::acc)
  | [], ag, r, acc => ((ag::r).reverse::acc).reverse

/-! ### removeAll -/

/--
Removes all elements of `xs` that are present in `ys`.

`O(|xs| * |ys|)`.

Examples:
 * `[1, 1, 5, 1, 2, 4, 5].removeAll [1, 2, 2] = [5, 4, 5]`
 * `[1, 2, 3, 2].removeAll [] = [1, 2, 3, 2]`
 * `[1, 2, 3, 2].removeAll [3] = [1, 2, 2]`
-/
def removeAll [BEq α] (xs ys : List α) : List α :=
  xs.filter (fun x => !ys.elem x)

@[simp] theorem nil_removeAll [BEq α] {ys : List α} : [].removeAll ys = [] := rfl

/-!
# Runtime re-implementations using `@[csimp]`

More of these re-implementations are provided in `Init/Data/List/Impl.lean`.
They can not be here, because the remaining ones required `Array` for their implementation.

This leaves a dangerous situation: if you import this file, but not `Init/Data/List/Impl.lean`,
then at runtime you will get non tail-recursive versions.
-/

/-! ### length -/

theorem length_add_eq_lengthTRAux {as : List α} {n : Nat} : as.length + n = as.lengthTRAux n := by
  induction as generalizing n with
  | nil  => simp [length, lengthTRAux]
  | cons a as ih =>
    simp [length, lengthTRAux, ← ih, Nat.succ_add]
    rfl

@[csimp] theorem length_eq_lengthTR : @List.length = @List.lengthTR := by
  apply funext; intro α; apply funext; intro as
  simp [lengthTR, ← length_add_eq_lengthTRAux]

/-! ### map -/

/--
Applies a function to each element of the list, returning the resulting list of values.

`O(|l|)`. This is the tail-recursive variant of `List.map`, used in runtime code.

Examples:
* `[a, b, c].mapTR f = [f a, f b, f c]`
* `[].mapTR Nat.succ = []`
* `["one", "two", "three"].mapTR (·.length) = [3, 3, 5]`
* `["one", "two", "three"].mapTR (·.reverse) = ["eno", "owt", "eerht"]`
-/
@[inline] def mapTR (f : α → β) (as : List α) : List β :=
  loop as []
where
  @[specialize] loop : List α → List β → List β
  | [],    bs => bs.reverse
  | a::as, bs => loop as (f a :: bs)

theorem mapTR_loop_eq {f : α → β} {as : List α} {bs : List β} :
    mapTR.loop f as bs = bs.reverse ++ map f as := by
  induction as generalizing bs with
  | nil => simp [mapTR.loop, map]
  | cons a as ih =>
    simp only [mapTR.loop, map]
    rw [ih (bs := f a :: bs), reverse_cons, append_assoc]
    rfl

@[csimp] theorem map_eq_mapTR : @map = @mapTR :=
  funext fun α => funext fun β => funext fun f => funext fun as => by
    simp [mapTR, mapTR_loop_eq]

/-! ### filter -/

/--
Returns the list of elements in `l` for which `p` returns `true`.

`O(|l|)`. This is a tail-recursive version of `List.filter`, used at runtime.

Examples:
* `[1, 2, 5, 2, 7, 7].filterTR (· > 2)  = [5, 7, 7]`
* `[1, 2, 5, 2, 7, 7].filterTR (fun _ => false) = []`
* `[1, 2, 5, 2, 7, 7].filterTR (fun _ => true) = * [1, 2, 5, 2, 7, 7]`
-/
@[inline] def filterTR (p : α → Bool) (as : List α) : List α :=
  loop as []
where
  @[specialize] loop : List α → List α → List α
  | [],    acc => acc.reverse
  | a::as, acc => match p a with
     | true  => loop as (a::acc)
     | false => loop as acc

theorem filterTR_loop_eq {p : α → Bool} {as : List α} {bs : List α} :
    filterTR.loop p as bs = bs.reverse ++ filter p as := by
  induction as generalizing bs with
  | nil => simp [filterTR.loop, filter]
  | cons a as ih =>
    simp only [filterTR.loop, filter]
    split <;> simp_all

@[csimp] theorem filter_eq_filterTR : @filter = @filterTR := by
  apply funext; intro α; apply funext; intro p; apply funext; intro as
  simp [filterTR, filterTR_loop_eq]

/-! ### replicate -/

/--
Creates a list that contains `n` copies of `a`.

This is a tail-recursive version of `List.replicate`.

* `List.replicateTR 5 "five" = ["five", "five", "five", "five", "five"]`
* `List.replicateTR 0 "zero" = []`
* `List.replicateTR 2 ' ' = [' ', ' ']`
-/
def replicateTR {α : Type u} (n : Nat) (a : α) : List α :=
  let rec loop : Nat → List α → List α
    | 0, as => as
    | n+1, as => loop n (a::as)
  loop n []

theorem replicateTR_loop_replicate_eq {a : α} {m n : Nat} :
  replicateTR.loop a n (replicate m a) = replicate (n + m) a := by
  induction n generalizing m with simp [replicateTR.loop]
  | succ n ih => simp [Nat.succ_add]; exact ih (m := m+1)

theorem replicateTR_loop_eq : ∀ n, replicateTR.loop a n acc = replicate n a ++ acc
  | 0 => rfl
  | n+1 => by rw [← replicateTR_loop_replicate_eq, replicate, replicate,
    replicateTR.loop, replicateTR_loop_eq n, replicateTR_loop_eq n, append_assoc]; rfl

@[csimp] theorem replicate_eq_replicateTR : @List.replicate = @List.replicateTR := by
  apply funext; intro α; apply funext; intro n; apply funext; intro a
  exact (replicateTR_loop_replicate_eq (m := 0)).symm

/-! ## Additional functions -/

/-! ### leftpad -/

/--
Pads `l : List α` on the left with repeated occurrences of `a : α` until it is of length `n`. If `l`
already has at least `n` elements, it is returned unmodified.

This is a tail-recursive version of `List.leftpad`, used at runtime.

Examples:
 * `[1, 2, 3].leftPadTR 5 0 = [0, 0, 1, 2, 3]`
 * `["red", "green", "blue"].leftPadTR 4 "blank" = ["blank", "red", "green", "blue"]`
 * `["red", "green", "blue"].leftPadTR 3 "blank" = ["red", "green", "blue"]`
 * `["red", "green", "blue"].leftPadTR 1 "blank" = ["red", "green", "blue"]`
-/
@[inline] def leftpadTR (n : Nat) (a : α) (l : List α) : List α :=
  replicateTR.loop a (n - length l) l

@[csimp] theorem leftpad_eq_leftpadTR : @leftpad = @leftpadTR := by
  repeat (apply funext; intro)
  simp [leftpad, leftpadTR, replicateTR_loop_eq]


/-! ## Zippers -/

/-! ### unzip -/

/--
Separates a list of pairs into two lists that contain the respective first and second components.

`O(|l|)`. This is a tail-recursive version of `List.unzip` that's used at runtime.

Examples:
* `[("Monday", 1), ("Tuesday", 2)].unzipTR = (["Monday", "Tuesday"], [1, 2])`
* `[(x₁, y₁), (x₂, y₂), (x₃, y₃)].unzipTR = ([x₁, x₂, x₃], [y₁, y₂, y₃])`
* `([] : List (Nat × String)).unzipTR = (([], []) : List Nat × List String)`
-/
def unzipTR (l : List (α × β)) : List α × List β :=
  l.foldr (fun (a, b) (as, bs) => (a::as, b::bs)) ([], [])

@[csimp] theorem unzip_eq_unzipTR : @unzip = @unzipTR := by
  apply funext; intro α; apply funext; intro β; apply funext; intro l
  simp [unzipTR]; induction l <;> simp [*]

/-! ## Ranges and enumeration -/

/-! ### range' -/

/--
Returns a list of the numbers with the given length `len`, starting at `start` and increasing by
`step` at each element.

In other words, `List.range'TR start len step` is `[start, start+step, ..., start+(len-1)*step]`.

This is a tail-recursive version of `List.range'`.

Examples:
 * `List.range'TR 0 3 (step := 1) = [0, 1, 2]`
 * `List.range'TR 0 3 (step := 2) = [0, 2, 4]`
 * `List.range'TR 0 4 (step := 2) = [0, 2, 4, 6]`
 * `List.range'TR 3 4 (step := 2) = [3, 5, 7, 9]`
-/
@[inline] def range'TR (s n : Nat) (step : Nat := 1) : List Nat := go n (s + step * n) [] where
  /-- Auxiliary for `range'TR`: `range'TR.go n e = [e-n, ..., e-1] ++ acc`. -/
  go : Nat → Nat → List Nat → List Nat
  | 0, _, acc => acc
  | n+1, e, acc => go n (e-step) ((e-step) :: acc)

@[csimp] theorem range'_eq_range'TR : @range' = @range'TR := by
  apply funext; intro s; apply funext; intro n; apply funext; intro step
  let rec go (s) : ∀ n m,
    range'TR.go step n (s + step * n) (range' (s + step * n) m step) = range' s (n + m) step
  | 0, m => by simp [range'TR.go]
  | n+1, m => by
    simp [range'TR.go]
    rw [Nat.mul_succ, ← Nat.add_assoc, Nat.add_sub_cancel, Nat.add_right_comm n]
    exact go s n (m + 1)
  exact (go s n 0).symm

/-! ### iota -/

/-- Tail-recursive version of `List.iota`. -/
@[deprecated "Use `List.range' 1 n` instead of `iota n`." (since := "2025-01-20")]
def iotaTR (n : Nat) : List Nat :=
  let rec go : Nat → List Nat → List Nat
    | 0, r => r.reverse
    | m@(n+1), r => go n (m::r)
  go n []

set_option linter.deprecated false in
@[csimp]
theorem iota_eq_iotaTR : @iota = @iotaTR :=
  have aux (n : Nat) (r : List Nat) : iotaTR.go n r = r.reverse ++ iota n := by
    induction n generalizing r with
    | zero => simp [iota, iotaTR.go]
    | succ n ih => simp [iota, iotaTR.go, ih, append_assoc]
  funext fun n => by simp [iotaTR, aux]

/-! ## Other list operations -/

/-! ### intersperse -/

/--
Alternates the elements of `l` with `sep`.

`O(|l|)`.

This is a tail-recursive version of `List.intersperse`, used at runtime.

Examples:
* `List.intersperseTR "then" [] = []`
* `List.intersperseTR "then" ["walk"] = ["walk"]`
* `List.intersperseTR "then" ["walk", "run"] = ["walk", "then", "run"]`
* `List.intersperseTR "then" ["walk", "run", "rest"] = ["walk", "then", "run", "then", "rest"]`
-/
def intersperseTR (sep : α) : (l : List α) → List α
  | [] => []
  | [x] => [x]
  | x::y::xs => x :: sep :: y :: xs.foldr (fun a r => sep :: a :: r) []

@[csimp] theorem intersperse_eq_intersperseTR : @intersperse = @intersperseTR := by
  apply funext; intro α; apply funext; intro sep; apply funext; intro l
  simp [intersperseTR]
  match l with
  | [] | [_] => rfl
  | x::y::xs => simp [intersperse]; induction xs generalizing y <;> simp [*]

end List
