Theory Maximum_Segment_Sum
section ‹Maximum Segment Sum›
theory Maximum_Segment_Sum
imports Main
begin
text ‹The \emph{maximum segment sum} problem is to compute, given a list of numbers,
the largest of the sums of the contiguous segments of that list. It is also known
as the \emph{maximum sum subarray} problem and has been considered many times in the literature;
the Wikipedia article
\href{https://en.wikipedia.org/wiki/Maximum\_subarray\_problem}{Maximum subarray problem} is a good starting point.
We assume that the elements of the list are not necessarily numbers but just elements
of some linearly ordered group.›
class linordered_group_add = linorder + group_add +
assumes add_left_mono: "a ≤ b ⟹ c + a ≤ c + b"
assumes add_right_mono: "a ≤ b ⟹ a + c ≤ b + c"
begin
lemma max_add_distrib_left: "max y z + x = max (y+x) (z+x)"
by (metis add_right_mono max.absorb_iff1 max_def)
lemma max_add_distrib_right: "x + max y z = max (x+y) (x+z)"
by (metis add_left_mono max.absorb1 max.cobounded2 max_def)
subsection ‹Naive Solution›
fun mss_rec_naive_aux :: "'a list ⇒ 'a" where
"mss_rec_naive_aux [] = 0"
| "mss_rec_naive_aux (x#xs) = max 0 (x + mss_rec_naive_aux xs)"
fun mss_rec_naive :: "'a list ⇒ 'a" where
"mss_rec_naive [] = 0"
| "mss_rec_naive (x#xs) = max (mss_rec_naive_aux (x#xs)) (mss_rec_naive xs)"
definition fronts :: "'a list ⇒ 'a list set" where
"fronts xs = {as. ∃bs. xs = as @ bs}"
definition "front_sums xs ≡ sum_list ` fronts xs"
lemma fronts_cons: "fronts (x#xs) = ((#) x) ` fronts xs ∪ {[]}" (is "?l = ?r")
proof
show "?l ⊆ ?r"
proof
fix as assume "as ∈ ?l"
then show "as ∈ ?r" by (cases as) (auto simp: fronts_def)
qed
show "?r ⊆ ?l" unfolding fronts_def by auto
qed
lemma front_sums_cons: "front_sums (x#xs) = (+) x ` front_sums xs ∪ {0}"
proof -
have "sum_list ` ((#) x) ` fronts xs = (+) x ` front_sums xs" unfolding front_sums_def by force
then show ?thesis by (simp add: front_sums_def fronts_cons)
qed
lemma finite_fronts: "finite (fronts xs)"
by (induction xs) (simp add: fronts_def, simp add: fronts_cons)
lemma finite_front_sums: "finite (front_sums xs)"
using front_sums_def finite_fronts by simp
lemma front_sums_not_empty: "front_sums xs ≠ {}"
unfolding front_sums_def fronts_def using image_iff by fastforce
lemma max_front_sum: "Max (front_sums (x#xs)) = max 0 (x + Max (front_sums xs))"
using finite_front_sums front_sums_not_empty
by (auto simp add: front_sums_cons hom_Max_commute max_add_distrib_right)
lemma mss_rec_naive_aux_front_sums: "mss_rec_naive_aux xs = Max (front_sums xs)"
by (induction xs) (simp add: front_sums_def fronts_def, auto simp: max_front_sum)
lemma front_sums: "front_sums xs = {s. ∃as bs. xs = as @ bs ∧ s = sum_list as}"
unfolding front_sums_def fronts_def by auto
lemma mss_rec_naive_aux: "mss_rec_naive_aux xs = Max {s. ∃as bs. xs = as @ bs ∧ s = sum_list as}"
using front_sums mss_rec_naive_aux_front_sums by simp
definition mids :: "'a list ⇒ 'a list set" where
"mids xs ≡ {bs. ∃as cs. xs = as @ bs @ cs}"
definition "mid_sums xs ≡ sum_list ` mids xs"
lemma fronts_mids: "bs ∈ fronts xs ⟹ bs ∈ mids xs"
unfolding fronts_def mids_def by auto
lemma mids_mids_cons: "bs ∈ mids xs ⟹ bs ∈ mids (x#xs)"
proof-
fix bs assume "bs ∈ mids xs"
then obtain as cs where "xs = as @ bs @ cs" unfolding mids_def by blast
then have "x # xs = (x#as) @ bs @ cs" by simp
then show "bs ∈ mids (x#xs)" unfolding mids_def by blast
qed
lemma mids_cons: "mids (x#xs) = fronts (x#xs) ∪ mids xs" (is "?l = ?r")
proof
show "?l ⊆ ?r"
proof
fix bs assume "bs ∈ ?l"
then obtain as cs where as_bs_cs: "(x#xs) = as @ bs @ cs" unfolding mids_def by blast
then show "bs ∈ ?r"
proof (cases as)
case Nil
then have "bs ∈ fronts (x#xs)" by (simp add: fronts_def as_bs_cs)
then show ?thesis by simp
next
case (Cons a as')
then have "xs = as' @ bs @ cs" using as_bs_cs by simp
then show ?thesis unfolding mids_def by auto
qed
qed
show "?r ⊆ ?l" using fronts_mids mids_mids_cons by auto
qed
lemma mid_sums_cons: "mid_sums (x#xs) = front_sums (x#xs) ∪ mid_sums xs"
unfolding mid_sums_def by (auto simp: mids_cons front_sums_def)
lemma finite_mids: "finite (mids xs)"
by (induction xs) (simp add: mids_def, simp add: mids_cons finite_fronts)
lemma finite_mid_sums: "finite (mid_sums xs)"
by (simp add: mid_sums_def finite_mids)
lemma mid_sums_not_empty: "mid_sums xs ≠ {}"
unfolding mid_sums_def mids_def by blast
lemma max_mid_sums_cons: "Max (mid_sums (x#xs)) = max (Max (front_sums (x#xs))) (Max (mid_sums xs))"
by (auto simp: mid_sums_cons Max_Un finite_front_sums finite_mid_sums front_sums_not_empty mid_sums_not_empty)
lemma mss_rec_naive_max_mid_sum: "mss_rec_naive xs = Max (mid_sums xs)"
by (induction xs) (simp add: mid_sums_def mids_def, auto simp: max_mid_sums_cons mss_rec_naive_aux front_sums)
lemma mid_sums: "mid_sums xs = {s. ∃as bs cs. xs = as @ bs @ cs ∧ s = sum_list bs}"
by (auto simp: mid_sums_def mids_def)
theorem mss_rec_naive: "mss_rec_naive xs = Max {s. ∃as bs cs. xs = as @ bs @ cs ∧ s = sum_list bs}"
unfolding mss_rec_naive_max_mid_sum mid_sums by simp
subsection ‹Kadane's Algorithms›
fun kadane :: "'a list ⇒ 'a ⇒ 'a ⇒ 'a" where
"kadane [] cur m = m"
| "kadane (x#xs) cur m =
(let cur' = max (cur + x) x in
kadane xs cur' (max m cur'))"
definition "mss_kadane xs ≡ kadane xs 0 0"
lemma Max_front_sums_geq_0: "Max (front_sums xs) ≥ 0"
proof-
have "[] ∈ fronts xs" unfolding fronts_def by blast
then have "0 ∈ front_sums xs" unfolding front_sums_def by force
then show ?thesis using finite_front_sums Max_ge by simp
qed
lemma Max_mid_sums_geq_0: "Max (mid_sums xs) ≥ 0"
proof-
have "0 ∈ mid_sums xs" unfolding mid_sums_def mids_def by force
then show ?thesis using finite_mid_sums Max_ge by simp
qed
lemma kadane: "m ≥ cur ⟹ m ≥ 0 ⟹ kadane xs cur m = max m (max (cur + Max (front_sums xs)) (Max (mid_sums xs)))"
proof (induction xs cur m rule: kadane.induct)
case (1 cur m)
then show ?case unfolding front_sums_def fronts_def mid_sums_def mids_def by auto
next
case (2 x xs cur m)
then show ?case
apply (auto simp: max_front_sum max_mid_sums_cons Let_def)
by (smt (verit, ccfv_threshold) Max_front_sums_geq_0 add_assoc add_0_right max.assoc max.coboundedI1 max.left_commute max.orderE max_add_distrib_left max_add_distrib_right)
qed
lemma Max_front_sums_leq_Max_mid_sums: "Max (front_sums xs) ≤ Max (mid_sums xs)"
proof-
have "front_sums xs ⊆ mid_sums xs" unfolding front_sums_def mid_sums_def using fronts_mids subset_iff by blast
then show ?thesis using front_sums_not_empty finite_mid_sums Max_mono by blast
qed
lemma mss_kadane_mid_sums: "mss_kadane xs = Max (mid_sums xs)"
unfolding mss_kadane_def using kadane Max_mid_sums_geq_0 Max_front_sums_leq_Max_mid_sums by auto
theorem mss_kadane: "mss_kadane xs = Max {s. ∃as bs cs. xs = as @ bs @ cs ∧ s = sum_list bs}"
using mss_kadane_mid_sums mid_sums by auto
end
end