Palindromes

Palindromes are lists that read the same from left to right and from right to left. For example, [a, b, b, a] and [a, h, a] are palindromes.

We use an inductive predicate to specify whether a list is a palindrome or not. Recall that inductive predicates, or inductively defined propositions, are a convenient way to specify functions of type ... → Prop.

This example is a based on an example from the book "The Hitchhiker's Guide to Logical Verification".

inductive 
Palindrome: {α : Type u_1} → List α → Prop
Palindrome
:
List: Type u_1 → Type u_1
List
α: Type u_1
α
Prop: Type
Prop
where |
nil: ∀ {α : Type u_1}, Palindrome []
nil
:
Palindrome: {α : Type u_1} → List α → Prop
Palindrome
[]: List ?m.17
[]
|
single: ∀ {α : Type u_1} (a : α), Palindrome [a]
single
: (
a: α
a
:
α: Type u_1
α
)
Palindrome: {α : Type u_1} → List α → Prop
Palindrome
[
a: α
a
] |
sandwich: ∀ {α : Type u_1} {as : List α} (a : α), Palindrome as → Palindrome ([a] ++ as ++ [a])
sandwich
: (
a: α
a
:
α: Type u_1
α
)
Palindrome: {α : Type u_1} → List α → Prop
Palindrome
as: List α
as
Palindrome: {α : Type u_1} → List α → Prop
Palindrome
([
a: α
a
] ++
as: List α
as
++ [
a: α
a
])

The definition distinguishes three cases: (1) [] is a palindrome; (2) for any element a, the singleton list [a] is a palindrome; (3) for any element a and any palindrome [b₁, . . ., bₙ], the list [a, b₁, . . ., bₙ, a] is a palindrome.

We now prove that the reverse of a palindrome is a palindrome using induction on the inductive predicate h : Palindrome as.

Goals accomplished!
Goals accomplished!

Goals accomplished! 🐙
Goals accomplished!
α✝: Type u_1
as: List α
h: Palindrome as

Palindrome as.reverse
Goals accomplished!

Goals accomplished! 🐙
Goals accomplished!

Goals accomplished! 🐙
Goals accomplished!

Goals accomplished! 🐙
Goals accomplished!

Goals accomplished! 🐙
Goals accomplished!

Goals accomplished! 🐙
Goals accomplished!
α✝: Type u_1
as, as✝: List α
a: α
h: Palindrome as
ih: Palindrome as✝.reverse

sandwich
Palindrome (a :: (as✝.reverse ++ [a]))
;
Goals accomplished!

Goals accomplished! 🐙

If a list as is a palindrome, then the reverse of as is equal to itself.

Goals accomplished!
Goals accomplished!

Goals accomplished! 🐙
Goals accomplished!
α✝: Type u_1
as: List α
h: Palindrome as

as.reverse = as
Goals accomplished!

Goals accomplished! 🐙
Goals accomplished!

Goals accomplished! 🐙
Goals accomplished!

Goals accomplished! 🐙
Goals accomplished!

Goals accomplished! 🐙
Goals accomplished!

Goals accomplished! 🐙
Goals accomplished!

Goals accomplished! 🐙

Note that you can also easily prove palindrome_reverse using reverse_eq_of_palindrome.

Goals accomplished!
Goals accomplished!

Goals accomplished! 🐙
Goals accomplished!

Goals accomplished! 🐙

Given a nonempty list, the function List.last returns its element. Note that we use (by simp) to prove that a₂ :: as ≠ [] in the recursive application.

def 
List.last: {α : Type u_1} → (as : List α) → as ≠ [] → α
List.last
: (
as: List α
as
:
List: Type u_1 → Type u_1
List
α: Type u_1
α
)
as: List α
as
[]: List α
[]
α: Type u_1
α
| [
a: α
a
], _ =>
a: α
a
| _::
a₂: α
a₂
::
as: List α
as
, _ => (
a₂: α
a₂
::
as: List α
as
).
last: {α : Type u_1} → (as : List α) → as ≠ [] → α
last
(

Goals accomplished! 🐙

Goals accomplished! 🐙
)

We use the function List.last to prove the following theorem that says that if a list as is not empty, then removing the last element from as and appending it back is equal to as. We use the attribute @[simp] to instruct the simp tactic to use this theorem as a simplification rule.

@[simp] 
Goals accomplished!
Goals accomplished!

Goals accomplished! 🐙
Goals accomplished!
α✝: Type u_1
as: List α
h: as []

as.dropLast ++ [as.last h] = as
Goals accomplished!
α✝: Type u_1
as: List α
h: [] []

[].dropLast ++ [[].last h] = []
Goals accomplished!

Goals accomplished! 🐙
Goals accomplished!
α✝: Type u_1
as: List α
a: α
h: [a] []

[a].dropLast ++ [[a].last h] = [a]
Goals accomplished!

Goals accomplished! 🐙
Goals accomplished!
α✝: Type u_1
as✝: List α
a₁, a₂: α
as: List α
h: a₁ :: a₂ :: as []

(a₁ :: a₂ :: as).dropLast ++ [(a₁ :: a₂ :: as).last h] = a₁ :: a₂ :: as
Goals accomplished!
α✝: Type u_1
as✝: List α
a₁, a₂: α
as: List α
h: a₁ :: a₂ :: as []

(a₂ :: as).dropLast ++ [(a₂ :: as).last ] = a₂ :: as
Goals accomplished!
α✝: Type u_1
as✝: List α
a₁, a₂: α
as: List α
h: a₁ :: a₂ :: as []

(a₂ :: as).dropLast ++ [(a₂ :: as).last ] = a₂ :: as
Goals accomplished!

Goals accomplished! 🐙
Goals accomplished!

Goals accomplished! 🐙
Goals accomplished!

Goals accomplished! 🐙

We now define the following auxiliary induction principle for lists using well-founded recursion on as.length. We can read it as follows, to prove motive as, it suffices to show that: (1) motive []; (2) motive [a] for any a; (3) if motive as holds, then motive ([a] ++ as ++ [b]) also holds for any a, b, and as. Note that the structure of this induction principle is very similar to the Palindrome inductive predicate.

Goals accomplished!
Goals accomplished!

Goals accomplished! 🐙
Goals accomplished!

Goals accomplished! 🐙
Goals accomplished!
Goals accomplished!

Goals accomplished! 🐙
Goals accomplished!

Goals accomplished! 🐙
this: [a₁] ++ (a₂ :: as').dropLast ++ [(a₂ :: as').last ⋯] = a₁ :: a₂ :: as'
Goals accomplished!

We use our new induction principle to prove that if as.reverse = as, then Palindrome as holds. Note that we use the using modifier to instruct the induction tactic to use this induction principle instead of the default one for lists.

Goals accomplished!
Goals accomplished!

Goals accomplished! 🐙
Goals accomplished!
α✝: Type u_1
h: [].reverse = []

h₁
Palindrome []
α✝: Type u_1
a✝: α
h: [a].reverse = [a]
Palindrome [a]
α✝: Type u_1
a✝¹, b✝: α
as✝: List α
a✝: as✝.reverse = as Palindrome as
h: ([a✝¹] ++ as ++ [b]).reverse = [a✝¹] ++ as ++ [b]
Palindrome ([a✝¹] ++ as ++ [b])
Goals accomplished!
α✝: Type u_1
h: [].reverse = []

h₁
Palindrome []
α✝: Type u_1
a✝: α
h: [a].reverse = [a]
Palindrome [a]
α✝: Type u_1
a✝¹, b✝: α
as✝: List α
a✝: as✝.reverse = as Palindrome as
h: ([a✝¹] ++ as ++ [b]).reverse = [a✝¹] ++ as ++ [b]
Palindrome ([a✝¹] ++ as ++ [b])
Goals accomplished!

Goals accomplished! 🐙
Goals accomplished!
α✝: Type u_1
a✝: α
h: [a].reverse = [a]

h₂
Palindrome [a]
α✝: Type u_1
a✝¹, b✝: α
as✝: List α
a✝: as✝.reverse = as Palindrome as
h: ([a✝¹] ++ as ++ [b]).reverse = [a✝¹] ++ as ++ [b]
Palindrome ([a✝¹] ++ as ++ [b])
Goals accomplished!

Goals accomplished! 🐙
Goals accomplished!
α✝: Type u_1
a✝¹, b✝: α
as✝: List α
a✝: as✝.reverse = as Palindrome as
h: ([a✝¹] ++ as ++ [b]).reverse = [a✝¹] ++ as ++ [b]

h₃
Palindrome ([a✝¹] ++ as ++ [b])
Goals accomplished!
α✝: Type u_1
a, b: α
as: List α
ih: as.reverse = as Palindrome as
h: ([a] ++ as ++ [b]).reverse = [a] ++ as ++ [b]

Palindrome ([a] ++ as ++ [b])
Goals accomplished!

Goals accomplished! 🐙
Goals accomplished!

Goals accomplished! 🐙
Goals accomplished!
α✝: Type u_1
a: α
as: List α
ih: as.reverse = as Palindrome as
h: ([a] ++ as ++ [a]).reverse = [a] ++ as ++ [a]

Palindrome ([a] ++ as ++ [a])
Goals accomplished!
α✝: Type u_1
a: α
as: List α
ih: as.reverse = as Palindrome as
h: ([a] ++ as ++ [a]).reverse = [a] ++ as ++ [a]

Palindrome ([a] ++ as ++ [a])
Goals accomplished!

Goals accomplished! 🐙
Goals accomplished!

Goals accomplished! 🐙
Goals accomplished!

Goals accomplished! 🐙

We now define a function that returns true iff as is a palindrome. The function assumes that the type α has decidable equality. We need this assumption because we need to compare the list elements.

def 
List.isPalindrome: {α : Type u_1} → [inst : DecidableEq α] → List α → Bool
List.isPalindrome
[
DecidableEq: Type u_1 → Type (max 0 u_1)
DecidableEq
α: Type u_1
α
] (
as: List α
as
:
List: Type u_1 → Type u_1
List
α: Type u_1
α
) :
Bool: Type
Bool
:=
as: List α
as
.
reverse: {α : Type u_1} → List α → List α
reverse
=
as: List α
as

It is straightforward to prove that isPalindrome is correct using the previously proved theorems.

Goals accomplished!
Goals accomplished!

Goals accomplished! 🐙
Goals accomplished!
α: Type u_1
inst✝: DecidableEq α
as: List α

as.reverse = as Palindrome as
Goals accomplished!

Goals accomplished! 🐙
true
[
1: Nat
1
,
2: Nat
2
,
1: Nat
1
].
isPalindrome: {α : Type} → [inst : DecidableEq α] → List α → Bool
isPalindrome
false
[
1: Nat
1
,
2: Nat
2
,
3: Nat
3
,
1: Nat
1
].
isPalindrome: {α : Type} → [inst : DecidableEq α] → List α → Bool
isPalindrome
Goals accomplished!
Goals accomplished!
Goals accomplished!