Zulip Chat Archive
Stream: general
Topic: palindromes
Kevin Buzzard (Apr 03 2018 at 20:23):
For recreational reasons I was interested in working with lists which were palindromes, i.e. lists G
satisfying G = list.reverse G
. I wanted to prove a bunch of stuff about these things but I couldn't prove anything by induction because lists don't decompose like that. I wanted to write a general G
of length 2 or more as G=[head G] ++ middle G ++ [head G]
and have a recursor of the form C [] -> C [x] -> forall palindromes H, C H -> C ([a] ++ H ++ [a]) -> forall palindromes G, C G
Kevin Buzzard (Apr 03 2018 at 20:23):
What is the idiomatic way to do this in Lean? I am at the stage now where I could probably get several methods to go through
Kevin Buzzard (Apr 03 2018 at 20:23):
but I would like to choose the one with the least pain.
Kevin Buzzard (Apr 03 2018 at 20:23):
Should I actually make a new inductive type?
Mario Carneiro (Apr 03 2018 at 20:50):
You should look at list.reverse_rec_on
for a similar eliminator. You could encode it as an inductive predicate, and then prove that it is equivalent to g = list.reverse g
, or you could prove the eliminator you wrote with palindromes
defined using reverse
Chris Hughes (Apr 03 2018 at 20:50):
I stopped as soon as it got hard
inductive palindrome : list α → Prop | nil : palindrome [] | singleton : ∀ a, palindrome [a] | cons : ∀ (a) (l), palindrome l → palindrome ([a] ++ l ++ [a]) lemma palindrome_iff_eq_reverse {l : list α} : palindrome l ↔ l = list.reverse l := ⟨λ h, palindrome.rec_on h rfl (λ a, rfl) (λ a l h₁ h₂, begin conv {to_lhs, rw h₂}, rw ← list.reverse_singleton a, simp, end), list.rec_on l (λ h, palindrome.nil) (λ a l h, begin apply list.reverse_rec_on l, end)⟩
Mario Carneiro (Apr 03 2018 at 20:58):
In the cons case you have a::l = list.reverse (a::l) = l.reverse ++ [a]
. By cases on l.reverse
, if l.reverse = []
then a::l = [a]
is a palindrome, and if l.reverse = b::l'
then a::l = b::l'++[a]
so a = b
and l = l' ++ [a]
, so a :: l'.reverse = l.reverse = a::l'
and hence l' = l'.reverse
, so l'
is a palindrome by IH and hence a::l
is a palindrome
Chris Hughes (Apr 03 2018 at 21:18):
Is there a lemma saying a :: l = b :: m -> a = b
?
Kevin Buzzard (Apr 03 2018 at 21:18):
no_confusion
Kevin Buzzard (Apr 03 2018 at 21:19):
or cons_inj or whatever
Chris Hughes (Apr 03 2018 at 21:20):
cons_inj tells me about the lists being equal.
Sebastian Ullrich (Apr 03 2018 at 21:21):
or the injection
tactic
Chris Hughes (Apr 03 2018 at 21:22):
Thanks @Sebastian Ullrich I'd never used injection successfully before.
Kevin Buzzard (Apr 03 2018 at 21:35):
example {β : Type*} (a b : β) (l m : list β) : a :: l = b :: m -> a = b := λ H, (list.cons.inj H).1
Kevin Buzzard (Apr 03 2018 at 21:36):
I feel confident with this sort of stuff now I've seen how it all works.
Kevin Buzzard (Apr 03 2018 at 21:38):
example {β : Type*} (a b : β) (l m : list β) : a :: l = b :: m -> a = b := λ H, @list.no_confusion _ _ (a::l) (b::m) H (λ x y,x)
Kevin Buzzard (Apr 03 2018 at 21:39):
Chris, it was your questioning a week last Thurs which pushed me to learn this stuff. We didn't quite go as far as we should have done. We looked at no_confusion for bool and nat, but if you look at it for list you see how all the terms involved in the constructors are used.
Kevin Buzzard (Apr 03 2018 at 21:41):
variables {a b : β} {L M : list β} {P : Type} #reduce list.no_confusion_type P (a::L) (b::M) -- (a = b → L = M → P) → P
Kevin Buzzard (Apr 03 2018 at 21:42):
You use no_confusion to make an instance of that type.
Kevin Buzzard (Apr 03 2018 at 21:50):
In the cons case you have
a::l = list.reverse (a::l) = l.reverse ++ [a]
. By cases onl.reverse
, ifl.reverse = []
thena::l = [a]
is a palindrome, and ifl.reverse = b::l'
thena::l = b::l'++[a]
soa = b
andl = l' ++ [a]
, soa :: l'.reverse = l.reverse = a::l'
and hencel' = l'.reverse
, sol'
is a palindrome by IH and hencea::l
is a palindrome
For me, I can prove l' = l'.reverse but the inductive hypothesis doesn't let me conclude l' is a palindrome, the inductive hypothesis the way I've set it up says at this point that if (a::l') is its own reverse then (a::l') is a palindrome.
Kevin Buzzard (Apr 03 2018 at 21:52):
Somehow this is exactly the issue I keep running into: list has the wrong recursor for me.
Kevin Buzzard (Apr 03 2018 at 21:53):
I can see that because I have list.rec_on
and list.reverse_rec_on
I should have all I need, but I don't know what C
should be in some sense (what is C
called? The motive?)
Chris Hughes (Apr 03 2018 at 22:09):
I've been struggling with this too. I think the best would be some sort of strong induction on the list.
Mario Carneiro (Apr 03 2018 at 22:23):
You should do strong induction on the length of the list
Kenny Lau (Apr 03 2018 at 22:33):
@Kevin Buzzard you could use a custom recursor
Kenny Lau (Apr 03 2018 at 22:33):
see https://github.com/kckennylau/Lean/blob/master/recursion.lean
Kenny Lau (Apr 03 2018 at 22:33):
for an example
Ching-Tsun Chou (Apr 03 2018 at 23:26):
The trick to prove the lemma Chris wants to prove is to consider the two cases (a) length l = 2n and (b) length l = 2n + 1 separately and then use induction on n in each case.
Kenny Lau (Apr 03 2018 at 23:26):
omg someone from hong kong
Chris Hughes (Apr 04 2018 at 19:41):
Finally did it.
inductive palindrome : list α → Prop | nil : palindrome [] | singleton : ∀ a, palindrome [a] | cons : ∀ (a) (l), palindrome l → palindrome ([a] ++ l ++ [a]) lemma eq_reverse_of_palindrome {l : list α} : palindrome l → l = list.reverse l := λ h, palindrome.rec_on h rfl (λ a, rfl) (λ a l h₁ h₂, begin conv {to_lhs, rw h₂}, rw ← list.reverse_singleton a, simp, end) open list lemma palindrome_of_eq_reverse : ∀ {l : list α}, l = list.reverse l → palindrome l | list.nil := λ _, palindrome.nil | (a :: l) := begin rw reverse_cons', generalize hl' : l.reverse = l', cases l' with b l', { assume h, rw h, exact palindrome.singleton _ }, { assume h, have : a = b := by injection h, rw this at *, have h₁ : l = l' ++ [b] := by injection h, have h₂ : b :: l' = reverse [b] ++ reverse l' := by rwa [← reverse_inj, hl', reverse_append] at h₁, have : l' = l'.reverse := by injection h₂, rw h, exact have h₂ : length l' < 1 + length l := by rw [h₁, add_comm, length_append]; exact lt_trans (lt_succ_self _) (lt_succ_self _), palindrome.cons _ _ (palindrome_of_eq_reverse this) } end using_well_founded {rel_tac := λ _ _, `[exact ⟨_, measure_wf length⟩]}
Kevin Buzzard (Apr 04 2018 at 19:47):
Yes, I did it too. Here's the reason I was asking: Q1(c) of http://www.olympiad.org.uk/papers/2015/bio/round_one.html
Kevin Buzzard (Apr 04 2018 at 19:47):
My son wrote code to do Q1(a) so I thought I'd write code to do Q1(c) because I thought that the idea of writing code to do 1c would be interesting to him.
Kevin Buzzard (Apr 04 2018 at 20:19):
FWIW:
import data.list open list universes u v variables {α : Type u} {β : Type v} inductive palindrome : list α → Prop | nil : palindrome [] | one : ∀ a, palindrome [a] | cons : ∀ a L, palindrome L → palindrome ((a :: L) ++ [a]) namespace palindrome open palindrome def is_rev_self (L : list β) : Prop := L = reverse L lemma rev_aux (a b : β) (L : list β) : is_rev_self (a :: (L ++ [b])) → a = b ∧ is_rev_self L := begin unfold is_rev_self,intro H, rw reverse_cons' at H, rw reverse_append at H, have H2 : a = b ∧ L ++ [b] = reverse L ++ [a], simpa using H, split,exact H2.1, rw H2.1 at H2, have H3 := H2.2, rw H2.1 at H3, exact append_inj_left' H3 rfl, end theorem palindrome_of_is_rev_self.aux (n : ℕ) : ∀ L : list β, L.length = n → is_rev_self L → palindrome L := begin unfold is_rev_self, apply nat.strong_induction_on n, clear n,intro n, intro IH, intro L, cases L with a M, { intros _ _,exact nil }, apply list.reverse_rec_on M, { intros _ _, exact one a}, intros L b X,clear X, intros HLL HRL, have H2 := rev_aux a b L HRL, rw ←cons_append, rw H2.1, apply cons, have H3 : 1 + (1 + length L) = n, simpa using HLL, rw [←add_assoc,add_comm] at H3, have H4 : length L < n, rw ←H3,exact nat.lt_add_of_zero_lt_left (length L) 2 dec_trivial, exact IH (length L) H4 L rfl H2.2, end theorem palindrome_of_rev_self (L : list α) : is_rev_self L → palindrome L := palindrome_of_is_rev_self.aux (length L) L rfl theorem is_rev_self_of_palindrome (L : list α) : palindrome L → is_rev_self L := begin intro H, induction H with a b M HPM HRM, { unfold is_rev_self,refl}, { unfold is_rev_self,refl}, { unfold is_rev_self,unfold is_rev_self at HRM, rw reverse_append, rw reverse_singleton, rw cons_append, rw reverse_cons', rw ←HRM, simp, } end end palindrome
Kevin Buzzard (Apr 04 2018 at 20:20):
My code didn't get highlighted. Possibly because the FWIW was in the same post.
Kevin Buzzard (Apr 04 2018 at 20:20):
No wait Chris also didn't post a pure code post
Kevin Buzzard (Apr 04 2018 at 20:21):
Aah got it, you have to write lean
after the three backticks
Kevin Buzzard (Apr 04 2018 at 20:26):
Oh Chris you didn't use strong induction for the main theorem! (palindrome_of_eq_reverse
). I made an aux lemma saying "forall n, if list length is n then blah" and applied strong induction to n, and then deduced the statement we wanted as a trivial corollary.
Chris Hughes (Apr 04 2018 at 20:34):
I did use strong induction, note using_well_founded {rel_tac := λ _ _, ``[exact ⟨_, measure_wf length⟩]}
I also did question 1a, although in 2^(2^n) time, so I might lose some points for that.
instance [decidable_eq α] (l : list α) : decidable (palindrome l) := decidable_of_iff (l = l.reverse) ⟨λ h, palindrome_of_eq_reverse h, λ h, eq_reverse_of_palindrome h⟩ def block_palindromes (l : list α) [decidable_eq α] := (sublists (sublists l)).filter (λ m : list (list α), palindrome m ∧ m.foldl (++) nil = l)
Mario Carneiro (Apr 04 2018 at 21:13):
haha that solution
Mario Carneiro (Apr 04 2018 at 21:14):
You should write the deterministic bogosort, which enumerates permutations until it finds the sorted one
Kevin Buzzard (Apr 04 2018 at 21:14):
The mark scheme is just a bunch of tests, and you have to pass each one in under a second to get the marks.
Kevin Buzzard (Apr 04 2018 at 22:29):
So I finished the proof that the word has to have an even number of letters.
Kevin Buzzard (Apr 04 2018 at 22:29):
My proof was 240 lines, longer than my son's program to do 1(a).
Kevin Buzzard (Apr 04 2018 at 22:30):
But I had to develop some concepts from scratch; in a parallel universe they would have already been in some library.
Kevin Buzzard (Apr 04 2018 at 22:31):
It was quite an interesting task. The question is about the following definition:
Kevin Buzzard (Apr 04 2018 at 22:31):
definition listunfold : list (list α) → list α | [] := [] | (a :: L) := a ++ (listunfold L)
Kevin Buzzard (Apr 04 2018 at 22:32):
but then I had to write listunfold_append : listunfold (G1 ++ G2) = listunfold G1 ++ listunfold G2
and listunfold_singleton
and so on
Kevin Buzzard (Apr 04 2018 at 22:33):
And similarly for palindrome
(the inductive prop) I had to prove palindrome iff L = reverse L
Chris Hughes (Apr 04 2018 at 22:33):
Listunfold could also be defined as a fold. Then those two lemmas are probably there already because of associativity
Kevin Buzzard (Apr 04 2018 at 22:34):
Is that right?
Kevin Buzzard (Apr 04 2018 at 22:35):
I feel like if I defined it as a fold then I would then have to prove the two things I've used for my definition immediately afterwards
Chris Hughes (Apr 04 2018 at 22:35):
Not in that exact form, but they'd be quite easy.
Kevin Buzzard (Apr 04 2018 at 22:35):
and then the same proof for my append and singleton :-)
Kevin Buzzard (Apr 04 2018 at 22:35):
Oh everything was easy, but of course because this was recreational I did it all in tactic mode so my proofs go on for ages :-)
Chris Hughes (Apr 04 2018 at 22:35):
Those two things are lemmas about fold probably
Kevin Buzzard (Apr 04 2018 at 22:36):
You might well be right. I feel like I know enough Lean to write the definitions and basic properties of these new concepts like listunfold or palindrome
Kevin Buzzard (Apr 04 2018 at 22:36):
but
Kevin Buzzard (Apr 04 2018 at 22:36):
I feel like if I really knew everything that was already there, properly, then I would write things far more efficiently.
Chris Hughes (Apr 04 2018 at 22:37):
It's not about knowing what's there, it's knowing what's probably there.
Mario Carneiro (Apr 05 2018 at 01:21):
Your listunfold
is defined in core and proven in mathlib, by the name list.join
. It is the monad "flattening" operation for lists
Last updated: Dec 20 2023 at 11:08 UTC