Zulip Chat Archive
Stream: new members
Topic: Guidance on proof methods: pattern matching vs. induction
Newell Jensen (Aug 13 2023 at 05:11):
Questions/guidance on proof methods
I am trying to prove that the boolean AND of two boolean palindromes of the same length are also a palindrome.
I feel a bit lost with the proof method because I am unsure if I am even using the best approach or if there are other approaches that would be better for the way I have things set up. That being said, if there is a better way to set things up I am also interested in what people have to say.
Some uncertainties I have are:
- Should I define
List.And
differently to make this type of proof easier? - Instead of pattern matching on the boolean lists
A
andB
, should I be pattern matching on something else? Should I be pattern matching at all? - If not pattern matching, is using induction in some form or another a better method? Why?
The idea for the proof that I am trying here is pattern matching and then would like to try and show that since A
and B
are boolean palindromes of the same length then both ends of these lists are the same, making the boolean AND of both the ends also the same.
Here is what I have so far:
import Mathlib.Tactic
import Mathlib.Data.List.Palindrome
open List
def List.And : List Bool → List Bool → List Bool
| [], [] => []
| _, [] => []
| [], _ => []
| a::as, b::bs => (a && b) :: List.And as bs
#eval List.And [] [true, true] -- []
#eval List.And [true, false] [true, true] -- [true, false]
#eval List.And [true, false] [true, true, false] -- [true, false]
#eval Palindrome <| List.And [true] [false] -- true
#eval Palindrome <| List.And [true,true] [false,false] -- true
#eval Palindrome <| List.And [true,false,true] [false,true,false] -- true
#eval Palindrome <| List.And [true,true,true] [false,true,true] -- false
theorem reverse_eq' {A B : List Bool} (p : Palindrome (List.And A B))
: reverse (List.And A B) = List.And A B := by
exact Palindrome.reverse_eq p
theorem of_reverse_eq' {A B : List Bool} : reverse (List.And A B) = (List.And A B) → Palindrome (List.And A B) := by
exact Palindrome.of_reverse_eq
theorem iff_reverse_eq' {A B : List Bool} : Palindrome (List.And A B) ↔ reverse (List.And A B) = List.And A B :=
Iff.intro reverse_eq' of_reverse_eq'
lemma bool_palindrome_and_bool_palindrome_is_palindrome {A B : List Bool} (h : A.length = B.length)
: Palindrome A → Palindrome B → Palindrome (List.And A B) := by
intro hA hB
match A, B with
| [], [] => simp
| A, [] =>
have h1 : A = [] := Iff.mp length_eq_zero h
rw [h1]; simp
| [], B =>
have h2 : B = [] := Iff.mp length_eq_zero (id (Eq.symm h))
rw [h2]; simp
| a::as, b::bs =>
have h3 : (a::as) ≠ [] := cons_ne_nil a as
have h4 : (b::bs) ≠ [] := cons_ne_nil b bs
by_cases hn : as = [] ∧ bs = []
· -- as = [] ∧ bs = []
have h5 : (a::as) = (a::[]) := congrArg (cons a) hn.1
have h6 : (b::bs) = (b::[]) := congrArg (cons b) hn.2
rw [h5, h6]
exact of_reverse_eq' rfl
· -- as ≠ [] ∨ bs ≠ []
rw [not_and_or] at hn; push_neg at hn
rcases hn with (hn1 | hn2)
· sorry
· sorry
Damiano Testa (Aug 13 2023 at 05:32):
I'm not at a computer, so my suggestions will be very coarse. I think that List.And
is List.zipWith (And \. \.) _ _
(docs#List.zipWith).
Also, do you really need to do all the trivial cases separately? Couldn't you do the cons, cons
case first and then lump together all the rest in a single case?
Newell Jensen (Aug 13 2023 at 05:49):
Yes, List.zipWith (and \. \.) _ _
does replace what I have for List.And
.
In terms of making the proof more concise, yes I probably can do something as you mention with cons, cons
. I am more interested in seeing what the preferred proof method is between pattern matching, induction, or some other way.
For example, I tried a bunch of different things such as regular induction
and bidirectionalRecOn
etc. but I kept running into the issue that I need to be able to iterate over A
and B
(at least afaict).
Newell Jensen (Aug 13 2023 at 13:00):
So I have thought about this some more and this question is probably rather trivial for most of the bright minds here. This is what I have come up with (which in hindsight I feel I should have known but sometimes asking stupid questions will make you realize it faster) as a rule of thumb, which may be helpful to any other beginners who come upon this in the future:
- Pattern matching is basically doing
cases
on inductive types in your hypothesis during the proof. - If you need an actual induction step in your proof, use
induction
or one its variants. Which one is best for you will depend on the proof at hand.
Basically main difference between the two is the induction step since cases
deconstructs the inductive data types just like we get with induction
, but with induction
we get that added induction hypothesis to help us with the induction step if needed.
Henrik Böving (Aug 13 2023 at 13:14):
There is no actual difference between pattern matching and induction. You can emulate induction with the cases
tactic by doing a recursive call to your theorem and calling the resulting value ih
theorem Nat.example_zero_add (n : Nat) : 0 + n = n := by
cases n with
| zero => rfl
| succ m =>
let ih := Nat.example_zero_add m
rw[Nat.add_succ]
rw[ih]
induction
basically "just" automates this concept for you by using a certain kind of induction scheme (and it has a default one that is automatically generated for each inductive type). That said if you are not willing to come up with the generalized scheme for the proof that you are doing you can still just resolve to using a case + recursive call.
Newell Jensen (Aug 13 2023 at 18:04):
@Henrik Böving thanks for the response. In addition, the example you gave, from what I understand, is strong or complete induction, correct?
Henrik Böving (Aug 13 2023 at 18:05):
I guess you can call it that if you want to yes.
Last updated: Dec 20 2023 at 11:08 UTC