LeetCode 1171, with a specification and a proof in Lean.
In September 2026, OpenAI reported that about 10,000 of its AI agents had
found a singularity in the three-dimensional Navier–Stokes equations, the
subject of one of the Millennium Prize Problems, and that another AI model had
formalised the proof in Lean, which checked it
(Quanta Magazine).
Mathematicians are still examining the result, and who deserves credit is
disputed. I wondered: if AI is this capable, why does vibe coding still
produce buggy code?
Most of the advice I found was to review the code that AI writes more
carefully and to test it better. Given my background in formal methods, I
asked a different question: why not have the AI go the extra mile and prove
that its code is correct? This idea already has a name, vericoding: an AI
writes code from a formal specification, together with a proof that the code
meets it, and a machine checks the proof
(Bursuc et al., 2025). Lean is both a
programming language and a theorem prover, so it can play both parts.
This post is a small experiment in that spirit. Together with Claude Code,
Anthropic's coding assistant, I wrote a specification for a LeetCode problem
in Lean. Claude then wrote a solution and a proof that it meets the
specification, and Lean checked the proof.
An Exercise in Vericoding
The problem we study is LeetCode 1171: Remove Zero Sum Consecutive Nodes from Linked List: given the head
of a linked list of integers, we must repeatedly delete a consecutive sequence
of numbers that adds up to zero, until no such sequence remains. For example,
the list [1, 2, -3, 3, 1] can reduce either to [3, 1] or to [1, 2, 1].
A small simplification for this blog entry: LeetCode's input is a linked list whose nodes can be
changed, and solutions relink the nodes to cut runs out. We use Lean's List
instead.
The Specification
Since we are vericoding, we should first try to capture the specification formally.
/-- `t` results from `s` by deleting runs that sum to zero. Read `s` from
the left: each element is either kept, or starts a run that sums to zero
and is deleted. Core Lean's `List.Sublist` is defined the same way, except
that it may delete any element. -/inductiveDeletesZeroSumRuns:ListInt→ListInt→Prop|nil:DeletesZeroSumRuns[][]|keep(a:Int)(st:ListInt):DeletesZeroSumRunsst→DeletesZeroSumRuns(a::s)(a::t)|delete(wst:ListInt):w.sum=0→DeletesZeroSumRunsst→DeletesZeroSumRuns(w++s)t/-- LeetCode 1171 for one input `s` and output `t`: `t` has the sum of
`s`, results from `s` by deleting runs that sum to zero, and has no run
other than `[]` that sums to zero. `w <:+: t` says that `w` is a run of
`t`: `t = u ++ w ++ v` for some `u` and `v`. -/defRemoveZeroSumSpec(st:ListInt):Prop:=t.sum=s.sum∧DeletesZeroSumRunsst∧∀w,w<:+:t→w≠[]→w.sum≠0/-- A function solves LeetCode 1171 if it meets the spec on every input. -/defSolvesLeetCode1171(f:ListInt→ListInt):Prop:=∀s,RemoveZeroSumSpecs(fs)
We had several back-and-forth interactions with Claude Code to find a proper specification.
This is probably the hardest of the tasks: simpler conditions are too weak; conditions on sums alone
would allow a function to turn [1, 2] into [3]; it must support several deletions of zero-sum runs,
and the output must have no zero-sum run of its own.
From that specification, it was relatively easy for Claude to find a candidate function:
defremoveZeroSumSublists(s:ListInt):ListInt:=matchswith|[]=>[]|a::rest=>/- `rest.scanl (· + ·) a` lists the sums of the nonempty prefixes of
`a :: rest`; `j` is the position of the first that is zero. -/match(rest.scanl(·+·)a).findIdx?(·==0)with/- The first `j + 1` elements sum to zero: delete them. -/|somej=>removeZeroSumSublists(rest.dropj)/- No nonempty prefix sums to zero: keep `a`. -/|none=>a::removeZeroSumSublistsrest/- Lean accepts a recursive function only if it can see that the
recursion ends. `termination_by` names a number that decreases at each
call: both calls are on lists shorter than `s`. -/termination_bys.length
Lean accepts the solution only with termination_by s.length, which tells
it why the recursion ends: on its own, it can't see that rest.drop j is
shorter than s.
We can run a quick test,
[3,1]#evalremoveZeroSumSublists[1,2,-3,3,1]
[3,1]
but we should focus our efforts on proving the goal SolvesLeetCode1171 removeZeroSumSublists. To do so, Claude suggested proving
a couple of facts before. These facts are related to the expressions used in the implementation of removeZeroSumSublists.
One of the most fascinating advantages about doing vericoding for the average developer is that they don't need to be too careful about the proofs generated by AI;
as long as those proofs "don't cheat" (i.e., they don't say sorry or use "cheating axioms"), they are valid, and any valid proof of the specification suffices!
In that spirit, I invite you to skip the next section and continue at section The Goal; unless you are curious about the lemmas that Claude proposed.
Lemmas
sum_cons_eq_foldl says that the sum of a :: l is l folded with +,
starting from a, which is how scanl computes it. The proofs below
need it to read the sums that removeZeroSumSublists checks as sums of
prefixes.
/-- The sum of `a :: l` is `l` folded with `+`, starting from `a`: the
form in which `scanl` gives the sums of prefixes. -/theoremsum_cons_eq_foldl(a:Int)(l:ListInt):(a::l).sum=l.foldl(·+·)a:=a:Intl:ListInt⊢ (a::l).sum=List.foldl(funx1x2=>x1+x2)ala:Intl:ListInt⊢ List.foldl(funx1x2=>x1+x2)0(a::l)=List.foldl(funx1x2=>x1+x2)alsimpAll goals completed! 🐙
DeletesZeroSumRuns.sum_eq says that deleting runs that sum to zero
keeps the sum of a list. It gives the sum part of the specification.
/-- Deleting runs that sum to zero keeps the sum. -/theoremDeletesZeroSumRuns.sum_eq{st:ListInt}(h:DeletesZeroSumRunsst):t.sum=s.sum:=bys:ListIntt:ListInth:DeletesZeroSumRunsst⊢ t.sum=s.suminductionhwith|nil=>nils:ListIntt:ListInt⊢ [].sum=[].sumrflAll goals completed! 🐙|keepast_ih=>keeps✝:ListIntt✝:ListInta:Ints:ListIntt:ListInta✝:DeletesZeroSumRunsstih:t.sum=s.sum⊢ (a::t).sum=(a::s).sumsimp[ih]All goals completed! 🐙|deletewsthw_ih=>deletes✝:ListIntt✝:ListIntw:ListInts:ListIntt:ListInthw:w.sum=0a✝:DeletesZeroSumRunsstih:t.sum=s.sum⊢ t.sum=(w++s).sumsimp[List.sum_append,hw,ih]All goals completed! 🐙
DeletesZeroSumRuns.prefix_sum says that after deleting runs that sum to
zero, every prefix of the output has the sum of some prefix of the input.
removeZeroSumSublists_noZeroRun uses it to show that a kept element
can't start a run that sums to zero.
/-- Every prefix of the output has the sum of some prefix of the input. -/theoremDeletesZeroSumRuns.prefix_sum{st:ListInt}(h:DeletesZeroSumRunsst):∀u,u<+:t→∃u',u'<+:s∧u.sum=u'.sum:=bys:ListIntt:ListInth:DeletesZeroSumRunsst⊢ ∀(u:ListInt),u<+:t→∃u',u'<+:s∧u.sum=u'.suminductionhwith|nil=>nils:ListIntt:ListInt⊢ ∀(u:ListInt),u<+:[]→∃u',u'<+:[]∧u.sum=u'.sum/- The only prefix of `[]` is `[]`. -/introuhunils:ListIntt:ListIntu:ListInthu:u<+:[]⊢ ∃u',u'<+:[]∧u.sum=u'.sumrw[List.prefix_nilnils:ListIntt:ListIntu:ListInthu:u=[]⊢ ∃u',u'<+:[]∧u.sum=u'.sum]athunils:ListIntt:ListIntu:ListInthu:u=[]⊢ ∃u',u'<+:[]∧u.sum=u'.sumexact⟨[],List.nil_prefix,bys:ListIntt:ListIntu:ListInthu:u=[]⊢ u.sum=[].sumsimp[hu]All goals completed! 🐙⟩|keepast_ih=>keeps✝:ListIntt✝:ListInta:Ints:ListIntt:ListInta✝:DeletesZeroSumRunsstih:∀(u:ListInt),u<+:t→∃u',u'<+:s∧u.sum=u'.sum⊢ ∀(u:ListInt),u<+:a::t→∃u',u'<+:a::s∧u.sum=u'.sum/- A prefix of `a :: t` is `[]`, or `a` followed by a prefix of `t`. -/introuhukeeps✝:ListIntt✝:ListInta:Ints:ListIntt:ListInta✝:DeletesZeroSumRunsstih:∀(u:ListInt),u<+:t→∃u',u'<+:s∧u.sum=u'.sumu:ListInthu:u<+:a::t⊢ ∃u',u'<+:a::s∧u.sum=u'.sumrcasesList.prefix_cons_iff.mphuwithrfl|⟨u₁,rfl,hu₁⟩keep.inls✝:ListIntt✝:ListInta:Ints:ListIntt:ListInta✝:DeletesZeroSumRunsstih:∀(u:ListInt),u<+:t→∃u',u'<+:s∧u.sum=u'.sumhu:[]<+:a::t⊢ ∃u',u'<+:a::s∧[].sum=u'.sumkeep.inrs✝:ListIntt✝:ListInta:Ints:ListIntt:ListInta✝:DeletesZeroSumRunsstih:∀(u:ListInt),u<+:t→∃u',u'<+:s∧u.sum=u'.sumu₁:ListInthu₁:u₁<+:thu:a::u₁<+:a::t⊢ ∃u',u'<+:a::s∧(a::u₁).sum=u'.sum·keep.inls✝:ListIntt✝:ListInta:Ints:ListIntt:ListInta✝:DeletesZeroSumRunsstih:∀(u:ListInt),u<+:t→∃u',u'<+:s∧u.sum=u'.sumhu:[]<+:a::t⊢ ∃u',u'<+:a::s∧[].sum=u'.sumexact⟨[],List.nil_prefix,rfl⟩All goals completed! 🐙·keep.inrs✝:ListIntt✝:ListInta:Ints:ListIntt:ListInta✝:DeletesZeroSumRunsstih:∀(u:ListInt),u<+:t→∃u',u'<+:s∧u.sum=u'.sumu₁:ListInthu₁:u₁<+:thu:a::u₁<+:a::t⊢ ∃u',u'<+:a::s∧(a::u₁).sum=u'.sumobtain⟨u',hu',hsum⟩:=ihu₁hu₁keep.inrs✝:ListIntt✝:ListInta:Ints:ListIntt:ListInta✝:DeletesZeroSumRunsstih:∀(u:ListInt),u<+:t→∃u',u'<+:s∧u.sum=u'.sumu₁:ListInthu₁:u₁<+:thu:a::u₁<+:a::tu':ListInthu':u'<+:shsum:u₁.sum=u'.sum⊢ ∃u',u'<+:a::s∧(a::u₁).sum=u'.sumexact⟨a::u',List.cons_prefix_cons.mpr⟨rfl,hu'⟩,bys✝:ListIntt✝:ListInta:Ints:ListIntt:ListInta✝:DeletesZeroSumRunsstih:∀(u:ListInt),u<+:t→∃u',u'<+:s∧u.sum=u'.sumu₁:ListInthu₁:u₁<+:thu:a::u₁<+:a::tu':ListInthu':u'<+:shsum:u₁.sum=u'.sum⊢ (a::u₁).sum=(a::u').sumsimp[hsum]All goals completed! 🐙⟩|deletewsthw_ih=>deletes✝:ListIntt✝:ListIntw:ListInts:ListIntt:ListInthw:w.sum=0a✝:DeletesZeroSumRunsstih:∀(u:ListInt),u<+:t→∃u',u'<+:s∧u.sum=u'.sum⊢ ∀(u:ListInt),u<+:t→∃u',u'<+:w++s∧u.sum=u'.sum/- Put the deleted run `w` in front: it adds zero to the sum. -/introuhudeletes✝:ListIntt✝:ListIntw:ListInts:ListIntt:ListInthw:w.sum=0a✝:DeletesZeroSumRunsstih:∀(u:ListInt),u<+:t→∃u',u'<+:s∧u.sum=u'.sumu:ListInthu:u<+:t⊢ ∃u',u'<+:w++s∧u.sum=u'.sumobtain⟨u',hu',hsum⟩:=ihuhudeletes✝:ListIntt✝:ListIntw:ListInts:ListIntt:ListInthw:w.sum=0a✝:DeletesZeroSumRunsstih:∀(u:ListInt),u<+:t→∃u',u'<+:s∧u.sum=u'.sumu:ListInthu:u<+:tu':ListInthu':u'<+:shsum:u.sum=u'.sum⊢ ∃u',u'<+:w++s∧u.sum=u'.sumexact⟨w++u',(List.prefix_append_right_injw).mprhu',bys✝:ListIntt✝:ListIntw:ListInts:ListIntt:ListInthw:w.sum=0a✝:DeletesZeroSumRunsstih:∀(u:ListInt),u<+:t→∃u',u'<+:s∧u.sum=u'.sumu:ListInthu:u<+:tu':ListInthu':u'<+:shsum:u.sum=u'.sum⊢ u.sum=(w++u').sumsimp[List.sum_append,hw,hsum]All goals completed! 🐙⟩
removeZeroSumSublists_deletes says that removeZeroSumSublists only
deletes runs that sum to zero. Each case of the function is one case of
DeletesZeroSumRuns, so the proof follows the function's recursion.
/-- `removeZeroSumSublists` deletes only runs that sum to zero. -/theoremremoveZeroSumSublists_deletes(s:ListInt):DeletesZeroSumRunss(removeZeroSumSublistss):=bys:ListInt⊢ DeletesZeroSumRunss(removeZeroSumSublistss)/- Induction that follows the recursion of `removeZeroSumSublists`: each case
of the function is one case of `DeletesZeroSumRuns`. -/fun_inductionremoveZeroSumSublistsswith|case1=>case1⊢ DeletesZeroSumRuns[][]exact.nilAll goals completed! 🐙|case2arestjhih=>case2a:Intrest:ListIntj:Nath:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=somejih:DeletesZeroSumRuns(List.dropjrest)(removeZeroSumSublists(List.dropjrest))⊢ DeletesZeroSumRuns(a::rest)(removeZeroSumSublists(List.dropjrest))/- The `j`-th sum in the list is zero, and it is the sum of the first
`j + 1` elements. -/obtain⟨hj,hzero,_⟩:=List.findIdx?_eq_some_iff_getElem.mphcase2a:Intrest:ListIntj:Nath:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=somejih:DeletesZeroSumRuns(List.dropjrest)(removeZeroSumSublists(List.dropjrest))hj:j<(List.scanl(funx1x2=>x1+x2)arest).lengthhzero:((List.scanl(funx1x2=>x1+x2)arest)[j]==0)=trueright✝:∀(j_1:Nat)(hji:j_1<j),¬((List.scanl(funx1x2=>x1+x2)arest)[j_1]==0)=true⊢ DeletesZeroSumRuns(a::rest)(removeZeroSumSublists(List.dropjrest))havehsum:(a::rest.takej).sum=0:=bys:ListInt⊢ DeletesZeroSumRunss(removeZeroSumSublistss)rw[sum_cons_eq_foldl,a:Intrest:ListIntj:Nath:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=somejih:DeletesZeroSumRuns(List.dropjrest)(removeZeroSumSublists(List.dropjrest))hj:j<(List.scanl(funx1x2=>x1+x2)arest).lengthhzero:((List.scanl(funx1x2=>x1+x2)arest)[j]==0)=trueright✝:∀(j_1:Nat)(hji:j_1<j),¬((List.scanl(funx1x2=>x1+x2)arest)[j_1]==0)=true⊢ List.foldl(funx1x2=>x1+x2)a(List.takejrest)=0←List.getElem_scanlhja:Intrest:ListIntj:Nath:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=somejih:DeletesZeroSumRuns(List.dropjrest)(removeZeroSumSublists(List.dropjrest))hj:j<(List.scanl(funx1x2=>x1+x2)arest).lengthhzero:((List.scanl(funx1x2=>x1+x2)arest)[j]==0)=trueright✝:∀(j_1:Nat)(hji:j_1<j),¬((List.scanl(funx1x2=>x1+x2)arest)[j_1]==0)=true⊢ (List.scanl(funx1x2=>x1+x2)arest)[j]=0]a:Intrest:ListIntj:Nath:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=somejih:DeletesZeroSumRuns(List.dropjrest)(removeZeroSumSublists(List.dropjrest))hj:j<(List.scanl(funx1x2=>x1+x2)arest).lengthhzero:((List.scanl(funx1x2=>x1+x2)arest)[j]==0)=trueright✝:∀(j_1:Nat)(hji:j_1<j),¬((List.scanl(funx1x2=>x1+x2)arest)[j_1]==0)=true⊢ (List.scanl(funx1x2=>x1+x2)arest)[j]=0simpausinghzerocase2a:Intrest:ListIntj:Nath:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=somejih:DeletesZeroSumRuns(List.dropjrest)(removeZeroSumSublists(List.dropjrest))hj:j<(List.scanl(funx1x2=>x1+x2)arest).lengthhzero:((List.scanl(funx1x2=>x1+x2)arest)[j]==0)=trueright✝:∀(j_1:Nat)(hji:j_1<j),¬((List.scanl(funx1x2=>x1+x2)arest)[j_1]==0)=truehsum:(a::List.takejrest).sum=0⊢ DeletesZeroSumRuns(a::rest)(removeZeroSumSublists(List.dropjrest))havee:a::rest=(a::rest.takej)++rest.dropj:=bys:ListInt⊢ DeletesZeroSumRunss(removeZeroSumSublistss)simpcase2a:Intrest:ListIntj:Nath:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=somejih:DeletesZeroSumRuns(List.dropjrest)(removeZeroSumSublists(List.dropjrest))hj:j<(List.scanl(funx1x2=>x1+x2)arest).lengthhzero:((List.scanl(funx1x2=>x1+x2)arest)[j]==0)=trueright✝:∀(j_1:Nat)(hji:j_1<j),¬((List.scanl(funx1x2=>x1+x2)arest)[j_1]==0)=truehsum:(a::List.takejrest).sum=0e:a::rest=a::List.takejrest++List.dropjrest⊢ DeletesZeroSumRuns(a::rest)(removeZeroSumSublists(List.dropjrest))rw[ecase2a:Intrest:ListIntj:Nath:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=somejih:DeletesZeroSumRuns(List.dropjrest)(removeZeroSumSublists(List.dropjrest))hj:j<(List.scanl(funx1x2=>x1+x2)arest).lengthhzero:((List.scanl(funx1x2=>x1+x2)arest)[j]==0)=trueright✝:∀(j_1:Nat)(hji:j_1<j),¬((List.scanl(funx1x2=>x1+x2)arest)[j_1]==0)=truehsum:(a::List.takejrest).sum=0e:a::rest=a::List.takejrest++List.dropjrest⊢ DeletesZeroSumRuns(a::List.takejrest++List.dropjrest)(removeZeroSumSublists(List.dropjrest))]case2a:Intrest:ListIntj:Nath:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=somejih:DeletesZeroSumRuns(List.dropjrest)(removeZeroSumSublists(List.dropjrest))hj:j<(List.scanl(funx1x2=>x1+x2)arest).lengthhzero:((List.scanl(funx1x2=>x1+x2)arest)[j]==0)=trueright✝:∀(j_1:Nat)(hji:j_1<j),¬((List.scanl(funx1x2=>x1+x2)arest)[j_1]==0)=truehsum:(a::List.takejrest).sum=0e:a::rest=a::List.takejrest++List.dropjrest⊢ DeletesZeroSumRuns(a::List.takejrest++List.dropjrest)(removeZeroSumSublists(List.dropjrest))exact.delete___hsumihAll goals completed! 🐙|case3aresthih=>case3a:Intrest:ListInth:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=noneih:DeletesZeroSumRunsrest(removeZeroSumSublistsrest)⊢ DeletesZeroSumRuns(a::rest)(a::removeZeroSumSublistsrest)exact.keepa__ihAll goals completed! 🐙
removeZeroSumSublists_noZeroRun says that no run of the output, other
than [], sums to zero. A run that starts with a kept element a has the
sum of a nonempty prefix of a :: rest, and the function keeps a only
when none of those sums to zero.
/-- No run of the output of `removeZeroSumSublists`, other than `[]`, sums to
zero. -/theoremremoveZeroSumSublists_noZeroRun(s:ListInt):∀w,w<:+:removeZeroSumSublistss→w≠[]→w.sum≠0:=bys:ListInt⊢ ∀(w:ListInt),w<:+:removeZeroSumSublistss→w≠[]→w.sum≠0/- Induction that follows the recursion of `removeZeroSumSublists`. -/fun_inductionremoveZeroSumSublistsswith|case1=>case1⊢ ∀(w:ListInt),w<:+:[]→w≠[]→w.sum≠0/- The only run of `[]` is `[]`. -/introwhwhnecase1w:ListInthw:w<:+:[]hne:w≠[]⊢ w.sum≠0simp_allAll goals completed! 🐙|case2arestjhih=>case2a:Intrest:ListIntj:Nath:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=somejih:∀(w:ListInt),w<:+:removeZeroSumSublists(List.dropjrest)→w≠[]→w.sum≠0⊢ ∀(w:ListInt),w<:+:removeZeroSumSublists(List.dropjrest)→w≠[]→w.sum≠0exactihAll goals completed! 🐙|case3aresthih=>case3a:Intrest:ListInth:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=noneih:∀(w:ListInt),w<:+:removeZeroSumSublistsrest→w≠[]→w.sum≠0⊢ ∀(w:ListInt),w<:+:a::removeZeroSumSublistsrest→w≠[]→w.sum≠0/- A run of `a :: t` is a prefix of `a :: t`, or a run of `t`. -/introwhwhnecase3a:Intrest:ListInth:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=noneih:∀(w:ListInt),w<:+:removeZeroSumSublistsrest→w≠[]→w.sum≠0w:ListInthw:w<:+:a::removeZeroSumSublistsresthne:w≠[]⊢ w.sum≠0rcasesList.infix_cons_iff.mphwwithhp|hicase3.inla:Intrest:ListInth:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=noneih:∀(w:ListInt),w<:+:removeZeroSumSublistsrest→w≠[]→w.sum≠0w:ListInthw:w<:+:a::removeZeroSumSublistsresthne:w≠[]hp:w<+:a::removeZeroSumSublistsrest⊢ w.sum≠0case3.inra:Intrest:ListInth:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=noneih:∀(w:ListInt),w<:+:removeZeroSumSublistsrest→w≠[]→w.sum≠0w:ListInthw:w<:+:a::removeZeroSumSublistsresthne:w≠[]hi:w<:+:removeZeroSumSublistsrest⊢ w.sum≠0·case3.inla:Intrest:ListInth:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=noneih:∀(w:ListInt),w<:+:removeZeroSumSublistsrest→w≠[]→w.sum≠0w:ListInthw:w<:+:a::removeZeroSumSublistsresthne:w≠[]hp:w<+:a::removeZeroSumSublistsrest⊢ w.sum≠0rcasesList.prefix_cons_iff.mphpwithrfl|⟨w₁,rfl,hw₁⟩case3.inl.inla:Intrest:ListInth:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=noneih:∀(w:ListInt),w<:+:removeZeroSumSublistsrest→w≠[]→w.sum≠0hw:[]<:+:a::removeZeroSumSublistsresthne:[]≠[]hp:[]<+:a::removeZeroSumSublistsrest⊢ [].sum≠0case3.inl.inra:Intrest:ListInth:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=noneih:∀(w:ListInt),w<:+:removeZeroSumSublistsrest→w≠[]→w.sum≠0w₁:ListInthw₁:w₁<+:removeZeroSumSublistsresthw:a::w₁<:+:a::removeZeroSumSublistsresthne:a::w₁≠[]hp:a::w₁<+:a::removeZeroSumSublistsrest⊢ (a::w₁).sum≠0·case3.inl.inla:Intrest:ListInth:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=noneih:∀(w:ListInt),w<:+:removeZeroSumSublistsrest→w≠[]→w.sum≠0hw:[]<:+:a::removeZeroSumSublistsresthne:[]≠[]hp:[]<+:a::removeZeroSumSublistsrest⊢ [].sum≠0exactabsurdrflhneAll goals completed! 🐙·case3.inl.inra:Intrest:ListInth:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=noneih:∀(w:ListInt),w<:+:removeZeroSumSublistsrest→w≠[]→w.sum≠0w₁:ListInthw₁:w₁<+:removeZeroSumSublistsresthw:a::w₁<:+:a::removeZeroSumSublistsresthne:a::w₁≠[]hp:a::w₁<+:a::removeZeroSumSublistsrest⊢ (a::w₁).sum≠0/- `w` is `a :: w₁`, and `w₁` has the sum of a prefix `u'` of
`rest`. So `w` has the sum of `a :: u'`, a nonempty prefix of
`a :: rest`, and none of those sums to zero. -/obtain⟨u',hu',hsum⟩:=(removeZeroSumSublists_deletesrest).prefix_sumw₁hw₁case3.inl.inra:Intrest:ListInth:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=noneih:∀(w:ListInt),w<:+:removeZeroSumSublistsrest→w≠[]→w.sum≠0w₁:ListInthw₁:w₁<+:removeZeroSumSublistsresthw:a::w₁<:+:a::removeZeroSumSublistsresthne:a::w₁≠[]hp:a::w₁<+:a::removeZeroSumSublistsrestu':ListInthu':u'<+:resthsum:w₁.sum=u'.sum⊢ (a::w₁).sum≠0havehlen:u'.length<(rest.scanl(·+·)a).length:=bys:ListInt⊢ ∀(w:ListInt),w<:+:removeZeroSumSublistss→w≠[]→w.sum≠0have:=hu'.length_lea:Intrest:ListInth:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=noneih:∀(w:ListInt),w<:+:removeZeroSumSublistsrest→w≠[]→w.sum≠0w₁:ListInthw₁:w₁<+:removeZeroSumSublistsresthw:a::w₁<:+:a::removeZeroSumSublistsresthne:a::w₁≠[]hp:a::w₁<+:a::removeZeroSumSublistsrestu':ListInthu':u'<+:resthsum:w₁.sum=u'.sumthis:u'.length≤rest.length⊢ u'.length<(List.scanl(funx1x2=>x1+x2)arest).lengthsimpa:Intrest:ListInth:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=noneih:∀(w:ListInt),w<:+:removeZeroSumSublistsrest→w≠[]→w.sum≠0w₁:ListInthw₁:w₁<+:removeZeroSumSublistsresthw:a::w₁<:+:a::removeZeroSumSublistsresthne:a::w₁≠[]hp:a::w₁<+:a::removeZeroSumSublistsrestu':ListInthu':u'<+:resthsum:w₁.sum=u'.sumthis:u'.length≤rest.length⊢ u'.length<rest.length+1omegacase3.inl.inra:Intrest:ListInth:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=noneih:∀(w:ListInt),w<:+:removeZeroSumSublistsrest→w≠[]→w.sum≠0w₁:ListInthw₁:w₁<+:removeZeroSumSublistsresthw:a::w₁<:+:a::removeZeroSumSublistsresthne:a::w₁≠[]hp:a::w₁<+:a::removeZeroSumSublistsrestu':ListInthu':u'<+:resthsum:w₁.sum=u'.sumhlen:u'.length<(List.scanl(funx1x2=>x1+x2)arest).length⊢ (a::w₁).sum≠0havehmem:=List.getElem_memhlencase3.inl.inra:Intrest:ListInth:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=noneih:∀(w:ListInt),w<:+:removeZeroSumSublistsrest→w≠[]→w.sum≠0w₁:ListInthw₁:w₁<+:removeZeroSumSublistsresthw:a::w₁<:+:a::removeZeroSumSublistsresthne:a::w₁≠[]hp:a::w₁<+:a::removeZeroSumSublistsrestu':ListInthu':u'<+:resthsum:w₁.sum=u'.sumhlen:u'.length<(List.scanl(funx1x2=>x1+x2)arest).lengthhmem:(List.scanl(funx1x2=>x1+x2)arest)[u'.length]∈List.scanl(funx1x2=>x1+x2)arest⊢ (a::w₁).sum≠0rw[List.getElem_scanl,case3.inl.inra:Intrest:ListInth:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=noneih:∀(w:ListInt),w<:+:removeZeroSumSublistsrest→w≠[]→w.sum≠0w₁:ListInthw₁:w₁<+:removeZeroSumSublistsresthw:a::w₁<:+:a::removeZeroSumSublistsresthne:a::w₁≠[]hp:a::w₁<+:a::removeZeroSumSublistsrestu':ListInthu':u'<+:resthsum:w₁.sum=u'.sumhlen:u'.length<(List.scanl(funx1x2=>x1+x2)arest).lengthhmem:List.foldl(funx1x2=>x1+x2)a(List.takeu'.lengthrest)∈List.scanl(funx1x2=>x1+x2)arest⊢ (a::w₁).sum≠0←List.prefix_iff_eq_take.mphu',case3.inl.inra:Intrest:ListInth:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=noneih:∀(w:ListInt),w<:+:removeZeroSumSublistsrest→w≠[]→w.sum≠0w₁:ListInthw₁:w₁<+:removeZeroSumSublistsresthw:a::w₁<:+:a::removeZeroSumSublistsresthne:a::w₁≠[]hp:a::w₁<+:a::removeZeroSumSublistsrestu':ListInthu':u'<+:resthsum:w₁.sum=u'.sumhlen:u'.length<(List.scanl(funx1x2=>x1+x2)arest).lengthhmem:List.foldl(funx1x2=>x1+x2)au'∈List.scanl(funx1x2=>x1+x2)arest⊢ (a::w₁).sum≠0←sum_cons_eq_foldlcase3.inl.inra:Intrest:ListInth:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=noneih:∀(w:ListInt),w<:+:removeZeroSumSublistsrest→w≠[]→w.sum≠0w₁:ListInthw₁:w₁<+:removeZeroSumSublistsresthw:a::w₁<:+:a::removeZeroSumSublistsresthne:a::w₁≠[]hp:a::w₁<+:a::removeZeroSumSublistsrestu':ListInthu':u'<+:resthsum:w₁.sum=u'.sumhlen:u'.length<(List.scanl(funx1x2=>x1+x2)arest).lengthhmem:(a::u').sum∈List.scanl(funx1x2=>x1+x2)arest⊢ (a::w₁).sum≠0]athmemcase3.inl.inra:Intrest:ListInth:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=noneih:∀(w:ListInt),w<:+:removeZeroSumSublistsrest→w≠[]→w.sum≠0w₁:ListInthw₁:w₁<+:removeZeroSumSublistsresthw:a::w₁<:+:a::removeZeroSumSublistsresthne:a::w₁≠[]hp:a::w₁<+:a::removeZeroSumSublistsrestu':ListInthu':u'<+:resthsum:w₁.sum=u'.sumhlen:u'.length<(List.scanl(funx1x2=>x1+x2)arest).lengthhmem:(a::u').sum∈List.scanl(funx1x2=>x1+x2)arest⊢ (a::w₁).sum≠0havehne0:=List.findIdx?_eq_none_iff.mph_hmemcase3.inl.inra:Intrest:ListInth:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=noneih:∀(w:ListInt),w<:+:removeZeroSumSublistsrest→w≠[]→w.sum≠0w₁:ListInthw₁:w₁<+:removeZeroSumSublistsresthw:a::w₁<:+:a::removeZeroSumSublistsresthne:a::w₁≠[]hp:a::w₁<+:a::removeZeroSumSublistsrestu':ListInthu':u'<+:resthsum:w₁.sum=u'.sumhlen:u'.length<(List.scanl(funx1x2=>x1+x2)arest).lengthhmem:(a::u').sum∈List.scanl(funx1x2=>x1+x2)aresthne0:((a::u').sum==0)=false⊢ (a::w₁).sum≠0simpathne0case3.inl.inra:Intrest:ListInth:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=noneih:∀(w:ListInt),w<:+:removeZeroSumSublistsrest→w≠[]→w.sum≠0w₁:ListInthw₁:w₁<+:removeZeroSumSublistsresthw:a::w₁<:+:a::removeZeroSumSublistsresthne:a::w₁≠[]hp:a::w₁<+:a::removeZeroSumSublistsrestu':ListInthu':u'<+:resthsum:w₁.sum=u'.sumhlen:u'.length<(List.scanl(funx1x2=>x1+x2)arest).lengthhmem:(a::u').sum∈List.scanl(funx1x2=>x1+x2)aresthne0:¬a+u'.sum=0⊢ (a::w₁).sum≠0simpa[hsum]usinghne0All goals completed! 🐙·case3.inra:Intrest:ListInth:List.findIdx?(funx=>x==0)(List.scanl(funx1x2=>x1+x2)arest)=noneih:∀(w:ListInt),w<:+:removeZeroSumSublistsrest→w≠[]→w.sum≠0w:ListInthw:w<:+:a::removeZeroSumSublistsresthne:w≠[]hi:w<:+:removeZeroSumSublistsrest⊢ w.sum≠0exactihwhihneAll goals completed! 🐙
The Goal
We need a proof for the proposition SolvesLeetCode1171 removeZeroSumSublists.
That would prove that removeZeroSumSublists meets the specification on every input.
The following proof combines the lemmas above.
/-- `removeZeroSumSublists` meets the specification of LeetCode 1171. -/example:SolvesLeetCode1171removeZeroSumSublists:=by⊢ SolvesLeetCode1171removeZeroSumSublistsintross:ListInt⊢ RemoveZeroSumSpecs(removeZeroSumSublistss)/- Deleting runs that sum to zero keeps the sum. -/havehd:=removeZeroSumSublists_deletesss:ListInthd:DeletesZeroSumRunss(removeZeroSumSublistss)⊢ RemoveZeroSumSpecs(removeZeroSumSublistss)exact⟨hd.sum_eq,hd,removeZeroSumSublists_noZeroRuns⟩All goals completed! 🐙
That's the whole exercise: a specification, a solution, and a proof that the
solution meets the specification, all checked by Lean. The hardest part was
not the proof but the specification.