The title is an homage of the Natural Number Game, which is a nice interactive tutorial into proving certain properties of natural numbers in Lean.
The definitions used in Lv1 will be reused here. I've done some improvements:
- Now it starts with
import tactic
, which means you can freely use powerful mathlib tactics. - I noticed that I can define the general list notation
[a, b, ..., z]
without problems.
import tactic
universe u
variables {A : Type u}
namespace mylist
inductive list (T : Type u) : Type u
| nil : list
| cons : T → list → list
infixr ` :: `:67 := list.cons
notation `[]` := list.nil
notation `[` l:(foldr `, ` (h t, list.cons h t) list.nil `]`) := l
def append : list A → list A → list A
| [] t := t
| (h :: s) t := h :: (append s t)
instance : has_append (list A) := ⟨@append A⟩
@[simp] lemma nil_append (s : list A) : [] ++ s = s := rfl
@[simp] lemma cons_append (x : A) (s t : list A) : (x :: s) ++ t = x :: (s ++ t) := rfl
@[simp] def rev : list A → list A
| [] := []
| (h :: t) := rev t ++ [h]
Now, here's a new definition we will use from now on: a membership relationship between an element and a list. It is defined recursively using two cases: A value x
is in a list l
if
x
is the head ofl
, orx
is in the tail ofl
.
inductive is_in : A → list A → Prop
| in_hd {x : A} {l : list A} : is_in x (x :: l)
| in_tl {x y : A} {l : list A} : is_in x l → is_in x (y :: l)
open is_in
Now your task is to prove the following two statements: (think of these as parts of a chapter, sharing a few lemmas)
is_in_append
:x
appears inl1 ++ l2
if and only ifx
appears inl1
orx
appears inl2
.is_in_rev
: ifx
appears inl
,x
appears inrev l
.
theorem is_in_append : ∀ (x : A) (l1 l2 : list A), is_in x (l1 ++ l2) ↔ is_in x l1 \/ is_in x l2 := sorry
theorem is_in_rev : ∀ (x : A) (l : list A), is_in x l → is_in x (rev l) := sorry
You can change the name of each statement and golf its definition, as long as its type is correct. Any kind of sidestepping is not allowed. Due to the nature of this challenge, adding imports is also not allowed (i.e. no mathlib, except for tactics).
The entire boilerplate is provided here. Your score is the length of the code between the two dashed lines, measured in bytes. The shortest code wins.
Tips for beginners
The new beast called "inductive Prop" is a particularly hard concept to grasp. Simply put, a value of is_in x l
contains a concrete proof that x
appears in l
. It is an unknown value, but we can still reason about it:
- Consider all the possible paths
is_in x l
could have been constructed (i.e. proven). This is done withcases
tactic. When used on a hypothesis ofis_in x l
, it generates two subgoals, one assumingin_hd
and the other assumingin_tl
.- If you use
cases
onis_in x []
, there is no way it could have been constructed (since both possibilities result in a cons), the goal is immediately closed by contradiction.
- If you use
- Apply induction on the proof. It works just like induction on a nat or a list: to prove a theorem in the form of
∀ (x:A) (l:list A), is_in x l → some_prop
, you can prove it by induction onis_in x l
and prove the "base case"is_in x (x::l)
and "inductive case"(is_in x l → some_prop) → is_in x (y::l) → some_prop
.
It takes some practice to see which argument (an inductive data or an inductive prop - in this challenge, list
or is_in
) works best with induction for a given statement. You can start by proving parts of the main statement separately as lemmas:
lemma is_in_append_l : ∀ (x : A) (l l2 : list A), is_in x l → is_in x (l ++ l2) := sorry lemma is_in_append_r : ∀ (x : A) (l l2 : list A), is_in x l → is_in x (l2 ++ l) := sorry
(Hint: One works best by induction on
l2
, and the other works best onis_in x l
.)