Imports
import Mathlib

CLRS Section 16.1 - Activity selection

This file gives a first Lean model for the activity-selection problem from CLRS Section 16.1. Activities are closed-open intervals over natural-number time points, represented only by their start and finish fields. A selected list is feasible when every earlier activity in the list finishes before every later one starts.

Main results:

  • Theorem earliest_finish_minFinish: the executable selector earliest_finish returns an activity whose finish time is minimum in the input list.

  • Theorem finishSorted_head_minFinish: the head of a finish-time-sorted nonempty activity list is the earliest-finishing available activity.

  • Theorem greedy_choice_minFinish_preserves_optimal_tail_feasibility: if the greedy activity is compatible with an optimal tail solution, prepending it is feasible.

  • Theorem greedy_choice_optimal_from_certificate: a certificate-based optimality theorem for the greedy-choice step. The exchange argument is provided as a hypothesis, keeping the theorem honest while still matching the CLRS proof structure.

  • Theorem finishSorted_greedyChoiceCertificate: on a finish-time-sorted candidate list, the CLRS exchange certificate is derived automatically.

  • Theorem greedySelect_cons_eq: the executable selector follows the CLRS recursive cons-case equation: choose the first finishing activity and recurse on the filtered compatible tail.

  • Theorems greedySelect_sublist and greedySelect_feasible: the executable greedy selector always returns activities drawn from the input and arranged feasibly.

  • Theorem greedySelect_maxCardinality: on a finish-time-sorted input, the executable greedy selector has maximum cardinality among feasible sublists.

  • Theorem greedySelect_cons_maxCardinality: the nonempty sorted-input recursion theorem, exposing the greedy choice plus optimal recursive subproblem directly.

  • Theorem greedySelect_after_maxCardinality: the filtered compatible tail is itself solved optimally by the same executable selector.

  • Theorem greedySelect_optimal_length: a direct reader-facing corollary: every feasible sublist of a finish-time-sorted input has length at most the greedy output.

  • Definition activitySelection: the CLRS-facing name for the executable greedy selector.

  • Theorems activitySelection_maxCardinality and activitySelection_cons_maxCardinality: top-level maximum-cardinality certificates for the full input and the nonempty recursive step.

  • Theorems activitySelection_correct and activitySelection_cons_correct: reader-facing correctness bundles for the full sorted-list input and the nonempty recursive step.

  • Theorems greedySelect_cons_recursive_correct and activitySelection_cons_recursive_correct: bundled nonempty recursion theorems that expose the exact cons-case equation, optimal recursive tail, optimal full solution, feasibility, sublist membership, and optimal-length inequality in one statement.

Current gaps:

  • None for the current finite-list model. A lower-level refinement to CLRS array/pseudocode execution and richer interval-validity assumptions remains a future extension.

open Listnamespace CLRSnamespace ActivitySelection

Activities and feasibility

An activity is an interval with a natural-number start time and finish time. The model intentionally does not require start ≤ finish; that assumption can be added by clients that want to rule out degenerate input data.

structure Activity where start : Nat finish : Nat deriving Repr, DecidableEq

Two activities are compatible when one finishes before the other starts. This is the symmetric textbook notion used for unordered sets of selected activities.

def Compatible (a b : Activity) : Prop := a.finish b.start b.finish a.start

Before a b is the oriented compatibility relation used by a selected list: activity a is scheduled before activity b.

def Before (a b : Activity) : Prop := a.finish b.start

A selected list is feasible when it is in chronological order and every head activity finishes before every activity in the tail starts.

def Feasible : List Activity Prop | [] => True | a :: rest => Feasible rest b rest, Before a b

Every activity after a in a feasible a :: rest selection is compatible with a.

theorem compatible_of_before {a b : Activity} (h : Before a b) : Compatible a b :=

The tail of a feasible selected list is feasible.

theorem feasible_tail {a : Activity} {rest : List Activity} (h : Feasible (a :: rest)) : Feasible rest :=

Consing an activity onto a feasible tail preserves feasibility when the new activity finishes before every activity in the tail starts.

theorem feasible_cons {a : Activity} {rest : List Activity} (hrest : Feasible rest) (ha : b rest, Before a b) : Feasible (a :: rest) :=

Earliest finishing activity

MinFinish a xs says that a is an element of xs with minimum finish time among all activities in xs.

def MinFinish (a : Activity) (xs : List Activity) : Prop := a xs b xs, a.finish b.finish

A list is sorted by nondecreasing finish time. On such a list, the head is the CLRS earliest-finishing activity among the currently available activities.

def FinishSorted : List Activity Prop := List.Pairwise fun a b => a.finish b.finish

Filtering a finish-sorted activity list preserves finish-time order.

theorem finishSorted_filter {p : Activity Bool} {xs : List Activity} (hsorted : FinishSorted xs) : FinishSorted (xs.filter p) :=

The head of a nonempty finish-sorted list has minimum finish time.

theorem finishSorted_head_minFinish {a : Activity} {rest : List Activity} (hsorted : FinishSorted (a :: rest)) : MinFinish a (a :: rest) :=

Select an activity with earliest finish time from a finite list, returning none on the empty list.

def earliest_finish : List Activity Option Activity | [] => none | a :: rest => match earliest_finish rest with | none => some a | some b => if a.finish b.finish then some a else some b

The earliest-finish selector returns none exactly for the empty list.

cases hrest : earliest_finish rest with

The executable selector earliest_finish returns a minimum-finish activity.

cases hrest : earliest_finish rest with

Subproblems and greedy selection

The activities still available after choosing a: those whose start time is at least a.finish.

def activitiesAfter (a : Activity) (xs : List Activity) : List Activity := xs.filter fun b => decide (a.finish b.start)

The post-greedy candidate list is a sublist of the original candidate list.

theorem activitiesAfter_sublist (a : Activity) (xs : List Activity) : (activitiesAfter a xs).Sublist xs :=

Membership in activitiesAfter is exactly membership in the source list plus oriented compatibility with the chosen activity.

theorem mem_activitiesAfter {a b : Activity} {xs : List Activity} : b activitiesAfter a xs b xs Before a b :=

The available list after a greedy choice preserves finish-time ordering.

theorem finishSorted_activitiesAfter {a : Activity} {xs : List Activity} (hsorted : FinishSorted xs) : FinishSorted (activitiesAfter a xs) :=

The CLRS recursive greedy algorithm, parameterized by the list order supplied by the caller. On a list sorted by finish time, the head is an earliest-finishing available activity.

def greedySelect : List Activity List Activity | [] => [] | a :: rest => a :: greedySelect (activitiesAfter a rest) termination_by xs => xs.length decreasing_by

Executable recursion equation for the nonempty CLRS activity-selection case: choose the first activity in the finish-time order and recurse on the remaining activities compatible with that choice.

CLRS-facing wrapper around the executable recursive selector. Keeping this name separate lets the proof expose greedySelect as the implementation while readers cite activitySelection as the algorithm.

def activitySelection (xs : List Activity) : List Activity := greedySelect xs

The public algorithm name is definitionally the greedy recursive selector.

theorem activitySelection_eq_greedySelect (xs : List Activity) : activitySelection xs = greedySelect xs :=

CLRS-facing recursion equation for nonempty finish-time ordered input.

theorem activitySelection_cons_eq (a : Activity) (rest : List Activity) : activitySelection (a :: rest) = a :: activitySelection (activitiesAfter a rest) :=

The executable greedy selector returns only activities from the input list.

The executable greedy selector always returns a feasible chronologically ordered activity list.

Maximum-cardinality certificates

MaxCardinality available selected says that selected is a feasible sublist of available and no feasible sublist of available has larger cardinality.

structure MaxCardinality (available selected : List Activity) : Prop where sublist : selected.Sublist available feasible : Feasible selected maximum : other, other.Sublist available Feasible other other.length selected.length

A one-step greedy-choice certificate. The field exchange is the CLRS exchange argument: every feasible competitor can be converted, without losing cardinality, into one that starts with the chosen greedy activity and then uses only the after subproblem.

structure GreedyChoiceCertificate (available after selected : List Activity) (a : Activity) : Prop where chosen_sublist : (a :: selected).Sublist available selected_after : b selected, Before a b exchange : other, other.Sublist available Feasible other tail, tail.Sublist after Feasible tail other.length (a :: tail).length

If a feasible competitor starts with first, and the greedy activity a has minimum finish time in the sorted available list, then the competitor's tail is available after choosing a.

On a finish-time-sorted nonempty candidate list, the textbook exchange argument is no longer an external assumption: every feasible competitor can be rewritten as the greedy activity followed by a feasible tail from the filtered subproblem.

theorem finishSorted_greedyChoiceCertificate {a : Activity} {rest selected : List Activity} (hsorted : FinishSorted (a :: rest)) (hselected_sub : selected.Sublist (activitiesAfter a rest)) : GreedyChoiceCertificate (a :: rest) (activitiesAfter a rest) selected a := cases other with refine [], , , ?_ exact tail, htail_sub, hfeasible.1,

Greedy-choice feasibility. If a has minimum finish time among the available activities and an optimal tail solution is compatible with a, then prepending a preserves feasibility.

The minimum-finish hypothesis records the CLRS greedy choice; feasibility itself uses only the compatibility of the chosen tail.

theorem greedy_choice_minFinish_preserves_optimal_tail_feasibility {available after selected : List Activity} {a : Activity} (hmin : MinFinish a available) (hopt : MaxCardinality after selected) (hafter : b selected, Before a b) : Feasible (a :: selected) :=

If the tail is maximum-cardinality for the post-greedy subproblem, then every chosen-tail competitor has size at most the greedy choice plus that tail.

theorem chosen_tail_bound_of_tail_optimal {after selected tail : List Activity} {a : Activity} (hopt : MaxCardinality after selected) (htail : tail.Sublist after) (hfeasible : Feasible tail) : (a :: tail).length (a :: selected).length :=

Certificate-based greedy-choice optimality. This is the Lean-friendly version of the CLRS exchange step. Given:

  • an optimal solution selected for the after subproblem, and

  • a certificate that every feasible competitor for available can be exchanged for one beginning with a,

the solution a :: selected is maximum-cardinality for available.

theorem greedy_choice_optimal_from_certificate {available after selected : List Activity} {a : Activity} (hopt : MaxCardinality after selected) (hcert : GreedyChoiceCertificate available after selected a) : MaxCardinality available (a :: selected) :=

Full finite-list optimality for sorted inputs. If the candidate activities are sorted by nondecreasing finish time, the executable greedy selector returns a feasible sublist of maximum cardinality.

Top-level CLRS-facing optimality certificate: on finish-time-sorted input, activitySelection is a feasible sublist of maximum cardinality.

theorem activitySelection_maxCardinality {xs : List Activity} (hsorted : FinishSorted xs) : MaxCardinality xs (activitySelection xs) :=

Recursive subproblem optimality. After the greedy choice from a sorted nonempty candidate list, the executable selector is maximum-cardinality for the filtered compatible tail.

theorem greedySelect_after_maxCardinality {a : Activity} {rest : List Activity} (hsorted : FinishSorted (a :: rest)) : MaxCardinality (activitiesAfter a rest) (greedySelect (activitiesAfter a rest)) :=

Nonempty sorted-input recursion theorem. The CLRS greedy choice followed by the recursively optimal compatible tail is itself maximum-cardinality for the whole candidate list.

theorem greedySelect_cons_maxCardinality {a : Activity} {rest : List Activity} (hsorted : FinishSorted (a :: rest)) : MaxCardinality (a :: rest) (a :: greedySelect (activitiesAfter a rest)) :=

CLRS-facing nonempty recursion certificate: choose the first finish-sorted activity, recursively solve its compatible tail, and obtain a maximum-cardinality solution for the original candidate list.

theorem activitySelection_cons_maxCardinality {a : Activity} {rest : List Activity} (hsorted : FinishSorted (a :: rest)) : MaxCardinality (a :: rest) (a :: activitySelection (activitiesAfter a rest)) :=

Reader-facing optimality corollary. On finish-time-sorted inputs, any feasible sublist has cardinality at most the executable greedy selection.

theorem greedySelect_optimal_length {xs other : List Activity} (hsorted : FinishSorted xs) (hsub : other.Sublist xs) (hfeasible : Feasible other) : other.length (greedySelect xs).length := (greedySelect_maxCardinality hsorted).maximum other hsub hfeasible

Reader-facing correctness theorem for the finite sorted-list activity-selection model: the executable greedy selector returns a feasible sublist and no feasible sublist of the input is longer.

theorem activitySelection_correct {xs : List Activity} (hsorted : FinishSorted xs) : (activitySelection xs).Sublist xs Feasible (activitySelection xs) other, other.Sublist xs Feasible other other.length (activitySelection xs).length :=

Reader-facing correctness theorem for the nonempty CLRS recursion step: choose the first finish-sorted activity, solve the compatible tail recursively, and no feasible competitor from the original nonempty list is longer.

theorem activitySelection_cons_correct {a : Activity} {rest : List Activity} (hsorted : FinishSorted (a :: rest)) : (a :: activitySelection (activitiesAfter a rest)).Sublist (a :: rest) Feasible (a :: activitySelection (activitiesAfter a rest)) other, other.Sublist (a :: rest) Feasible other other.length (a :: activitySelection (activitiesAfter a rest)).length :=

Bundled executable recursion theorem for the sorted nonempty greedy selector. It exposes the exact cons-case equation, the optimal recursive subproblem, the optimal whole solution, and the reader-facing correctness facts in one place.

theorem greedySelect_cons_recursive_correct {a : Activity} {rest : List Activity} (hsorted : FinishSorted (a :: rest)) : greedySelect (a :: rest) = a :: greedySelect (activitiesAfter a rest) MaxCardinality (activitiesAfter a rest) (greedySelect (activitiesAfter a rest)) MaxCardinality (a :: rest) (greedySelect (a :: rest)) (greedySelect (a :: rest)).Sublist (a :: rest) Feasible (greedySelect (a :: rest)) other, other.Sublist (a :: rest) Feasible other other.length (greedySelect (a :: rest)).length :=

CLRS-facing bundled recursion theorem for activity selection. On a nonempty finish-time-sorted input, the public algorithm chooses the head, recursively solves the compatible tail, and the resulting executable output is feasible, drawn from the input, and maximum-cardinality.

end ActivitySelectionend CLRS