Skip to content

LongestCommonSublist: TimeM proof#370

Open
kuotsanhsu wants to merge 1 commit intoleanprover:mainfrom
kuotsanhsu:longest_common_sublist
Open

LongestCommonSublist: TimeM proof#370
kuotsanhsu wants to merge 1 commit intoleanprover:mainfrom
kuotsanhsu:longest_common_sublist

Conversation

@kuotsanhsu
Copy link

Time complexity using TimeM is proven, but TimeM is messing up my lemmas aux2_1 and cons_aux0_cons for the correctness proof lcsDP whereas my original non-monadic implementation can utilize definitional equality of List.foldr. Reviewers can start giving feedback while I work on the 2 lemmas with sorry.

Comment on lines +89 to +97
{
sublist₁ := show [] <+ [] from .slnil
sublist₂ := show [] <+ ys from ys.nil_sublist
longest
| cs, ⟨(h : cs <+ []), _⟩ =>
show cs.length ≤ [].length from
suffices cs = [] from this.rec Nat.le.refl
eq_nil_of_sublist_nil h
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually we would format this as

Suggested change
{
sublist₁ := show [] <+ [] from .slnil
sublist₂ := show [] <+ ys from ys.nil_sublist
longest
| cs, ⟨(h : cs <+ []), _⟩ =>
show cs.length ≤ [].length from
suffices cs = [] from this.rec Nat.le.refl
eq_nil_of_sublist_nil h
}
{ sublist₁ := show [] <+ [] from .slnil
sublist₂ := show [] <+ ys from ys.nil_sublist
longest
| cs, ⟨(h : cs <+ []), _⟩ =>
show cs.length ≤ [].length from
suffices cs = [] from this.rec Nat.le.refl
eq_nil_of_sublist_nil h }

(and throughout)

Comment on lines +31 to +32
/-- `cs` is a common sublist of the lists `xs` and `ys`. -/
public structure CommonSublist (xs ys cs : List α) : Prop where
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/-- `cs` is a common sublist of the lists `xs` and `ys`. -/
public structure CommonSublist (xs ys cs : List α) : Prop where
/-- `IsCommonSublist xs ys xs` indicates that `cs` is a common sublist of the lists `xs` and `ys`. -/
public structure IsCommonSublist (xs ys cs : List α) : Prop where

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to put the cs argument first so that you can write cs.IsCommonSublist xs ys too

Comment on lines +80 to +84
theorem nil_lcsBrute : {ys : List α} → lcsBrute [] ys = []
| [] | _ :: _ => lcsBrute.eq_def ..

theorem lcsBrute_nil : {xs : List α} → lcsBrute xs [] = []
| [] | _ :: _ => lcsBrute.eq_def ..
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should be @[simp]

| [] | _ :: _ => lcsBrute.eq_def ..

theorem lcsBrute_correct : {xs ys : List α} → LCS xs ys (lcsBrute xs ys)
| [], ys =>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of doing a manual pattern match here, I think it would be better to use fun_induction, which will aso handle the x = y case split for you.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I am pretty sure simp could handle this case, maybe with some sublist lemmas. Especially if you follow my Prop def suggestion.

Comment on lines +193 to +201
aux3 (x : α) : α × DP α → DPS α × DP α × DP α → TimeM ℕ (DPS α × DP α × DP α)
| (y, xx), (dps, yy, xy) => do
✓ let dp := dp x y xx yy xy
return ((y, dp) :: dps, dp, xx)
dp (x y : α) (xx yy xy : DP α) : DP α :=
if x = y then
⟨x :: xy.lcs, xy.length + 1, xy.length_eq.rec rfl⟩
else
if xx.length ≥ yy.length then xx else yy
Copy link
Collaborator

@eric-wieser eric-wieser Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest

Suggested change
aux3 (x : α) : α × DP α → DPS α × DP α × DP α → TimeM ℕ (DPS α × DP α × DP α)
| (y, xx), (dps, yy, xy) => do
let dp := dp x y xx yy xy
return ((y, dp) :: dps, dp, xx)
dp (x y : α) (xx yy xy : DP α) : DP α :=
if x = y then
⟨x :: xy.lcs, xy.length + 1, xy.length_eq.rec rfl⟩
else
if xx.length ≥ yy.length then xx else yy
aux3 (x : α) : α × DP α → DPS α × DP α × DP α → TimeM ℕ (DPS α × DP α × DP α)
| (y, xx), (dps, yy, xy) => do
let dp dp x y xx yy xy
return ((y, dp) :: dps, dp, xx)
dp (x y : α) (xx yy xy : DP α) : TimeM (DP α) := do
if x = y then
return ⟨x :: xy.lcs, xy.length + 1, xy.length_eq.rec rfl⟩
else
return if xx.length ≥ yy.length then xx else yy

Copy link
Contributor

@Shreyas4991 Shreyas4991 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is my first review. I also wonder why you have this DPS structure. If you want simplicity of proofs and DP table, why not just go for a two parameter function?

@@ -0,0 +1,354 @@
module

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you might want to put @[expose] public section here. This exposes all your definitions by default.

variable {α} [DecidableEq α]

/-- `cs` is a common sublist of the lists `xs` and `ys`. -/
public structure CommonSublist (xs ys cs : List α) : Prop where
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explicit public is not required.

variable {α} [DecidableEq α]

/-- `cs` is a common sublist of the lists `xs` and `ys`. -/
public structure CommonSublist (xs ys cs : List α) : Prop where
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also why not just

abbrev CommonSublist (xs ys cs : List α) : Prop := (cs <+ xs) ∧ (cs <+ ys)

?

sublist₂ : cs <+ ys

/-- `lcs` is a longest common sublist of the lists `xs` and `ys`. -/
public structure LCS (xs ys lcs : List α) : Prop extends CommonSublist xs ys lcs where
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following my suggestion on CommonSubList, this could just be

abbrev LCS (xs ys lcs : List α) := CommonSubList xs ys lcs ∧ ∀ cs, CommonSublist xs ys cs → cs.length ≤ lcs.length

| [] | _ :: _ => lcsBrute.eq_def ..

theorem lcsBrute_correct : {xs ys : List α} → LCS xs ys (lcsBrute xs ys)
| [], ys =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I am pretty sure simp could handle this case, maybe with some sublist lemmas. Especially if you follow my Prop def suggestion.


namespace lcsDP

structure DP α where
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For LCS you should really just take the list length as an input parameter. Even better, solve for a general l which denotes a prefix of the list (including the whole list if l \geq n) to an auxiliary function, and then pass List.length as the argument to this parameter in the actual function


scoped instance : Inhabited (DP α) where default := ⟨[], 0, rfl⟩
example : @Eq (DP α) default ⟨[], 0, rfl⟩ := rfl
abbrev DPS α := List (α × DP α)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this second definition at all?

else
if xx.length ≥ yy.length then xx else yy

section unittest
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This entire section goes in the CslibTests folder. Please don't put tests in the main library folder.


variable {x y : α} {xs ys : List α}

theorem cons_aux0 : ⟪aux0 (x :: xs) ys⟫ = ⟪aux1 x ⟪aux0 xs ys⟫⟫ :=
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The four lemmas about aux0 sound like things you will get free as equational lemmas from lean. You should probably just fun_induction wherever you use these lemmas. I could be wrong so I'll tag @eric-wieser to check. If these lemmas stay, they probably ought to be @[simp]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants