11module
22
3- def smallest_change : Unit :=
4- ()
3+ public import Std
4+ import all Init.Data.Range.Polymorphic.UpwardEnumerable
5+ public meta import Lean -- TODO
6+ open Std
7+
8+ /-! ## Implementation -/
9+
10+ public section
11+
12+ instance : PRange.Least? (Fin n) where
13+ least? := if h : 0 < n then some ⟨0 , by grind⟩ else none
14+
15+ instance : PRange.LawfulUpwardEnumerableLeast? (Fin n) where
16+ least?_le x := ⟨⟨0 , by grind [x.isLt]⟩, by
17+ simp [PRange.least?, PRange.UpwardEnumerable.LE]
18+ constructor
19+ · grind [x.isLt]
20+ · refine ⟨x.val, by grind⟩⟩
21+
22+ def smallestChange (xs : Array Nat) : Nat :=
23+ (*...* : Rii (Fin (xs.size / 2 ))).iter.filter (fun i => xs[i.val] ≠ xs[xs.size - 1 - i.val]) |>.length
24+
25+ /-! ## Tests -/
26+
27+ example : smallestChange #[1 ,2 ,3 ,5 ,4 ,7 ,9 ,6 ] = 4 := by native_decide
28+ example : smallestChange #[1 , 2 , 3 , 4 , 3 , 2 , 2 ] = 1 := by native_decide
29+ example : smallestChange #[1 , 4 , 2 ] = 1 := by native_decide
30+ example : smallestChange #[1 , 4 , 4 , 2 ] = 1 := by native_decide
31+ example : smallestChange #[1 , 2 , 3 , 2 , 1 ] = 0 := by native_decide
32+ example : smallestChange #[3 , 1 , 1 , 3 ] = 0 := by native_decide
33+ example : smallestChange #[1 ] = 0 := by native_decide
34+ example : smallestChange #[0 , 1 ] = 1 := by native_decide
35+
36+ /-! ## Missing API -/
37+
38+ theorem Vector.ext_getElem_iff {l₁ l₂ : Vector α n} :
39+ l₁ = l₂ ↔ ∀ (i : Nat) (h₁ : i < n) (h₂ : i < n), l₁[i]'h₁ = l₂[i]'h₂ := by
40+ simp [← Vector.toList_inj, List.ext_getElem_iff]
41+
42+ @[simp, grind =]
43+ theorem Fin.size_rii :
44+ (*...* : Rii (Fin n)).size = n := by
45+ simp only [Rii.size, PRange.least?, Rxi.HasSize.size]
46+ grind
47+
48+ attribute [grind =] Rii.length_toList
49+
50+ @[simp, grind =]
51+ theorem Fin.length_toList_rii :
52+ (*...* : Rii (Fin n)).size = n := by
53+ grind
54+
55+ attribute [grind .] Rii.mem Rii.mem_toList Rii.nodup_toList
56+
57+ theorem List.filter_false {xs : List α} :
58+ xs.filter (fun _ => false) = [] := by
59+ simp
60+
61+ theorem List.Nodup.countP_le_countP_add_one {xs : List α} (h : xs.Nodup)
62+ (h' : ∀ b ∈ xs, b = a ∨ p b = q b) :
63+ xs.countP p ≤ xs.countP q + 1 := by
64+ open Classical in
65+ have : xs.countP p ≤ (xs.erase a).countP p + 1 := by
66+ have := (xs.filter p).le_length_erase (a := a)
67+ grind
68+ have : (xs.erase a).countP p = (xs.erase a).countP q := by
69+ apply List.countP_congr
70+ grind
71+ have : (xs.erase a).countP q ≤ xs.countP q := by
72+ apply List.Sublist.countP_le
73+ apply List.erase_sublist
74+ grind
75+
76+ @[grind .]
77+ theorem List.Nodup.map (f : α → β) (h : ∀ a b : α, ¬ a = b → ¬ (f a) = (f b))
78+ (p : Nodup l) : Nodup (map f l) := by
79+ simp only [nodup_iff_pairwise_ne, ne_eq] at p ⊢
80+ exact Pairwise.map f h p
81+
82+ @[grind ←] theorem List.Nodup.filter (p : α → Bool) : Nodup l → Nodup (filter p l) :=
83+ Pairwise.sublist filter_sublist
84+
85+ theorem Array.getElem !_set {xs : Array Nat} {h} :
86+ (xs.set i v h)[j]! = if j = i then v else xs[j]! := by
87+ grind
88+
89+ theorem Fin.map_val_toList_roi {n : Nat} (m : Fin n) :
90+ (m<...* : Roi (Fin n)).toList.map (·.val) = (m.val<...n).toList := by
91+ induction h : n - m.val generalizing m
92+ · grind
93+ · rename_i ih
94+ have : m.val < n := by grind
95+ rw [Roi.toList_eq_match]
96+ split
97+ · simp only [List.map_nil, List.nil_eq, Nat.toList_roo_eq_nil_iff] at *
98+ grind
99+ · specialize ih ⟨m + 1 , by grind⟩
100+ rw [Nat.toList_roo_eq_cons, ← ih] <;> grind
101+
102+ theorem Fin.map_val_toList_rci {n : Nat} (m : Fin n) :
103+ (m...* : Rci (Fin n)).toList.map (·.val) = (m.val...n).toList := by
104+ induction h : n - m.val generalizing m
105+ · grind
106+ · rename_i ih
107+ have : m.val < n := by grind
108+ rw [Rci.toList_eq_toList_roi, List.map_cons, Fin.map_val_toList_roi, Nat.toList_rco_eq_cons (by grind)]
109+ simp [Nat.toList_roo_eq_toList_rco]
110+
111+ theorem Fin.map_val_toList_rii {n : Nat} :
112+ (*...* : Rii (Fin n)).toList.map (·.val) = (*...n).toList := by
113+ simp only [Rii.toList_eq_match_rci, PRange.least?, Rio.toList_eq_match_rco]
114+ split
115+ · grind [Nat.toList_rco_eq_nil_iff]
116+ · grind [map_val_toList_rci]
117+
118+ /-! ## Verification -/
119+
120+ def IsPalindrome (xs : Vector Nat n) := xs.reverse = xs
121+
122+ theorem getElem_foldl_set {changes : List (Fin n × α)} {v : Vector α n}
123+ (h_nodup : (changes.map Prod.fst).Nodup) {j : Nat} (hj : j < n) :
124+ (changes.foldl (fun v' (i, x) => v'.set i x) v)[j] =
125+ match changes.find? (·.1 .val == j) with
126+ | some (_, x) => x
127+ | none => v[j] := by
128+ rw [← List.reverse_reverse (as := changes)] at h_nodup ⊢
129+ generalize changes.reverse = revChanges at ⊢ h_nodup
130+ induction revChanges
131+ · simp
132+ · rename_i c cs ih
133+ simp only [List.foldl_reverse] at ih
134+ simp only [List.reverse_cons, List.foldl_append, List.foldl_reverse, List.foldl_cons,
135+ List.foldl_nil]
136+ rw [Vector.getElem_set, ih]
137+ split
138+ · split
139+ · simp only [List.reverse_cons, List.map_append] at h_nodup
140+ rw [List.nodup_append] at h_nodup
141+ replace h_nodup := h_nodup.2 .2 c.fst
142+ grind
143+ · grind
144+ · grind
145+ · grind
146+
147+ /--
148+ First direction of the characterization:
149+
150+ There is a finite sequence of modifications that transforms `xs` into a palindrome, and the sequence
151+ consists of exactly `smallestChange xs` modifications..
152+ -/
153+ theorem exists_isPalindrome {xs : Array Nat} :
154+ ∃ changes : List (Fin xs.size × Nat), changes.length = smallestChange xs ∧
155+ IsPalindrome (changes.foldl (fun xs' (i, v) => xs'.set i.val v) xs.toVector) := by
156+ refine ⟨(*...* : Rii (Fin (xs.size / 2 ))).iter.filter (fun i => xs[i.val] ≠ xs[xs.size - 1 - i.val]) |>.map (fun i => (⟨i.val, by grind⟩, xs[xs.size - 1 - i.val])) |>.toList, ?_⟩
157+ constructor
158+ · simp [smallestChange, ← Iter.length_toList_eq_length]
159+ · rw [IsPalindrome, Vector.ext_getElem_iff]
160+ intro i h₁ h₂
161+ simp only [Iter.toList_map, Iter.toList_filter, Rii.toList_iter, Vector.getElem_reverse]
162+ rw [getElem_foldl_set (by grind), getElem_foldl_set (by grind)]
163+ simp only [List.find?_map, Vector.getElem_mk]
164+ split <;> split
165+ · grind
166+ · grind [Option.map_eq_some_iff]
167+ · grind [Option.map_eq_some_iff]
168+ · simp only [Option.map_eq_none_iff, List.find?_eq_none] at *
169+ rename_i heq heq'
170+ match hcmp : compare i (xs.size - 1 - i) with
171+ | .lt =>
172+ specialize heq ⟨i, by grind⟩
173+ grind
174+ | .eq => grind
175+ | .gt =>
176+ specialize heq' ⟨xs.size - 1 - i, by grind⟩
177+ grind
178+
179+ /--
180+ An alternative, less efficient implementation of `smallestChange` that does not use `Fin`.
181+ It is less dependently typed and therefore lends itself better to verification.
182+ -/
183+ private def smallestChangeAux (xs : Array Nat) : Nat :=
184+ (*...(xs.size / 2 )).iter.filter (fun i => xs[i]! ≠ xs[xs.size - 1 - i]!) |>.length
185+
186+ private theorem smallChange_eq_smallChangeAux {xs : Array Nat} :
187+ smallestChange xs = smallestChangeAux xs := by
188+ simp only [smallestChange, ← Iter.length_toList_eq_length, smallestChangeAux,
189+ Iter.toList_filter, Rii.toList_iter, Rio.toList_iter, ← Fin.map_val_toList_rii,
190+ List.filter_map, Function.comp_def]
191+ grind
192+
193+ /--
194+ Second part of the verification:
195+
196+ In order to transform `xs` into a palindrome, one needs at least `smallestChange xs` modifications.
197+ -/
198+ theorem smallestChange_le {xs : Array Nat} {changes : List (Fin xs.size × Nat)}
199+ (h : IsPalindrome (changes.foldl (fun xs' (i, v) => xs'.set i.val v) xs.toVector)) :
200+ smallestChange xs ≤ changes.length := by
201+ suffices ∀ ys : Vector Nat xs.size,
202+ IsPalindrome (changes.foldl (fun xs' (i, v) => xs'.set i.val v) ys) →
203+ (List.filter (fun i => !decide (ys.toArray[i]! = ys.toArray[ys.toArray.size - 1 - i]!))
204+ (*...ys.toArray.size / 2 ).iter.toList).length ≤ changes.length by
205+ rw [smallChange_eq_smallChangeAux, smallestChangeAux]
206+ simp only [ne_eq, decide_not, ← Iter.length_toList_eq_length, Iter.toList_filter]
207+ grind
208+ clear h
209+ intro ys h
210+ induction changes generalizing ys
211+ · simp only [Rio.toList_iter, List.length_nil, Nat.le_zero_eq, List.length_eq_zero_iff,
212+ List.filter_eq_nil_iff]
213+ intro a ha
214+ simp only [Rio.mem_toList_iff_mem, Rio.mem_iff] at ha
215+ grind [Vector.cast_mk, IsPalindrome, List.reverse_nil, Vector.reverse_mk, List.filter_false]
216+ · rename_i c cs ih
217+ specialize ih _ h
218+ simp only [List.foldl_cons] at h
219+ replace ih := Nat.add_le_add_right ih 1
220+ refine le_trans ?_ ih
221+ simp only [← List.countP_eq_length_filter, Vector.size_toArray]
222+ apply List.Nodup.countP_le_countP_add_one (a := min c.fst.val (xs.size - 1 - c.fst.val))
223+ · apply Rio.nodup_toList
224+ · intro b hb
225+ simp only [Vector.toArray_set, Array.getElem!_set]
226+ simp only [Rio.toList_iter, Rio.mem_toList_iff_mem, Rio.mem_iff] at hb
227+ split
228+ · grind
229+ · grind
5230
6231/-!
7232## Prompt
@@ -49,4 +274,4 @@ def check(candidate):
49274 assert candidate([0, 1]) == 1
50275
51276```
52- -/
277+ -/
0 commit comments