|
| 1 | +-- PROOF101 Week 6: Theorem Proving in Lean — Programming Assignment 5 |
| 2 | +-- Name: _________________ |
| 3 | +-- Date: _________________ |
| 4 | + |
| 5 | +/- |
| 6 | +Instructions: |
| 7 | +- Replace each `sorry` with a valid proof |
| 8 | +- Make sure all theorems type-check |
| 9 | +- You may use any tactic covered in Weeks 4–5: rfl, exact, decide, intro, |
| 10 | + rw, symm, apply, refine, obtain, constructor, left, right, |
| 11 | + cases, induction, simp, simp_all, omega, calc, have, etc. |
| 12 | +- Do not modify the theorem statements or the provided definitions |
| 13 | +- You ARE allowed (and encouraged!) to use previously proven theorems |
| 14 | + from this file in your proofs |
| 15 | +
|
| 16 | +Useful tactic reminders: |
| 17 | + rfl — closes goals of the form `a = a` |
| 18 | + exact e — closes the goal by providing the exact term `e` |
| 19 | + decide — evaluates decidable propositions (concrete computations) |
| 20 | + intro h — introduces a hypothesis `h` from the goal |
| 21 | + rw [h] — rewrites the goal using equality `h` (left to right) |
| 22 | + rw [← h] — rewrites right to left |
| 23 | + apply f — performs backward reasoning with `f` |
| 24 | + constructor — splits a ∧ goal into two subgoals |
| 25 | + left / right — selects a side of a ∨ goal |
| 26 | + obtain ⟨a, b⟩ — destructures ∧ or ∃ hypotheses |
| 27 | + cases h with — splits on constructors (for inductive types, ∨, etc.) |
| 28 | + induction x with — performs structural induction |
| 29 | + simp [f] — simplifies using the defining equations of `f` |
| 30 | + omega — solves linear arithmetic over ℕ and ℤ |
| 31 | + calc ... — structured chain of equalities/inequalities |
| 32 | +-/ |
| 33 | + |
| 34 | + |
| 35 | +-- ============================================================================ |
| 36 | +-- Provided Definitions (DO NOT MODIFY) |
| 37 | +-- ============================================================================ |
| 38 | + |
| 39 | +/-! ### Custom list operations |
| 40 | +
|
| 41 | +We define our own list operations so you can prove properties about them |
| 42 | +from first principles, without relying on Lean's built-in library lemmas. |
| 43 | +You have implemented similar functions in Assignments 2 and 3, and now you |
| 44 | +will *prove* things about them. |
| 45 | +-/ |
| 46 | + |
| 47 | +def app {α : Type} : List α → List α → List α |
| 48 | + | [], ys => ys |
| 49 | + | x :: xs, ys => x :: app xs ys |
| 50 | + |
| 51 | +def len {α : Type} : List α → Nat |
| 52 | + | [] => 0 |
| 53 | + | _ :: xs => 1 + len xs |
| 54 | + |
| 55 | +def naiveRev {α : Type} : List α → List α |
| 56 | + | [] => [] |
| 57 | + | x :: xs => app (naiveRev xs) [x] |
| 58 | + |
| 59 | +def myMap {α β : Type} (f : α → β) : List α → List β |
| 60 | + | [] => [] |
| 61 | + | x :: xs => f x :: myMap f xs |
| 62 | + |
| 63 | +/-! ### Binary tree type and operations -/ |
| 64 | + |
| 65 | +inductive BTree (α : Type) : Type where |
| 66 | + | empty : BTree α |
| 67 | + | node : α → BTree α → BTree α → BTree α |
| 68 | + deriving Repr |
| 69 | + |
| 70 | +namespace BTree |
| 71 | + |
| 72 | +def size : BTree α → Nat |
| 73 | + | .empty => 0 |
| 74 | + | .node _ l r => 1 + l.size + r.size |
| 75 | + |
| 76 | +def mirror : BTree α → BTree α |
| 77 | + | .empty => .empty |
| 78 | + | .node v l r => .node v r.mirror l.mirror |
| 79 | + |
| 80 | +def depth : BTree α → Nat |
| 81 | + | .empty => 0 |
| 82 | + | .node _ l r => 1 + max l.depth r.depth |
| 83 | + |
| 84 | +def mapTree (f : α → β) : BTree α → BTree β |
| 85 | + | .empty => .empty |
| 86 | + | .node v l r => .node (f v) (l.mapTree f) (r.mapTree f) |
| 87 | + |
| 88 | +def flatten : BTree α → List α |
| 89 | + | .empty => [] |
| 90 | + | .node v l r => app l.flatten (app [v] r.flatten) |
| 91 | + |
| 92 | +end BTree |
| 93 | + |
| 94 | +/-! ### A custom Nat function -/ |
| 95 | + |
| 96 | +def double : Nat → Nat |
| 97 | + | 0 => 0 |
| 98 | + | n + 1 => double n + 2 |
| 99 | + |
| 100 | + |
| 101 | +-- ============================================================================ |
| 102 | +-- Part 1: Tactic Warm-Up — Propositional Logic (20 points) |
| 103 | +-- ============================================================================ |
| 104 | + |
| 105 | +/- |
| 106 | +These exercises warm you up with the basic building-block tactics. |
| 107 | +Using induction here is overkill, careful use of intro, apply, exact, |
| 108 | +constructor, cases, obtain, left, and right is enough. |
| 109 | +-/ |
| 110 | + |
| 111 | +/- Problem 1.1 (5 points) |
| 112 | +Theorem: Transitivity of implication. |
| 113 | +-/ |
| 114 | +theorem imp_trans (P Q R : Prop) : (P → Q) → (Q → R) → P → R := by |
| 115 | + sorry |
| 116 | + |
| 117 | + |
| 118 | +/- Problem 1.2 (5 points) |
| 119 | +Theorem: If P implies both Q and R, then P implies their conjunction. |
| 120 | +Hint: After introducing hypotheses, use `constructor` to split the ∧ goal |
| 121 | +into two separate subgoals. |
| 122 | +-/ |
| 123 | +theorem imp_and_intro (P Q R : Prop) : (P → Q) → (P → R) → P → Q ∧ R := by |
| 124 | + sorry |
| 125 | + |
| 126 | + |
| 127 | +/- Problem 1.3 (5 points) |
| 128 | +Theorem: ∧ distributes over ∨ (left to right). |
| 129 | +Hint: First `obtain` the two parts of the ∧. Then `cases` on the ∨ part. |
| 130 | +-/ |
| 131 | +theorem and_or_distrib (P Q R : Prop) : |
| 132 | + P ∧ (Q ∨ R) → (P ∧ Q) ∨ (P ∧ R) := by |
| 133 | + sorry |
| 134 | + |
| 135 | + |
| 136 | +/- Problem 1.4 (5 points) |
| 137 | +Provide a concrete existential witness. |
| 138 | +Hint: Use `exact ⟨witness, proof⟩`. |
| 139 | +-/ |
| 140 | +theorem exists_witness : ∃ n : Nat, n * n + 3 * n + 2 = 12 := by |
| 141 | + sorry |
| 142 | + |
| 143 | + |
| 144 | +-- ============================================================================ |
| 145 | +-- Part 2: Induction on Natural Numbers (20 points) |
| 146 | +-- ============================================================================ |
| 147 | + |
| 148 | +/- |
| 149 | +These exercises use induction on Nat to prove properties of the `double` |
| 150 | +function defined above. Remember the induction pattern: |
| 151 | +
|
| 152 | + induction n with |
| 153 | + | zero => ... -- base case: n = 0 |
| 154 | + | succ n ih => ... -- inductive step: assuming `ih` for n, prove for n+1 |
| 155 | +
|
| 156 | +Tip: After unfolding the definition with `simp [double]`, you can often close |
| 157 | +the arithmetic with `omega`. |
| 158 | +-/ |
| 159 | + |
| 160 | +/- Problem 2.1 (5 points) |
| 161 | +Theorem: Our custom `double` agrees with multiplication by 2. |
| 162 | +Hint: Induction on n, then `simp [double]` to unfold... |
| 163 | +-/ |
| 164 | +theorem double_eq_two_mul (n : Nat) : double n = 2 * n := by |
| 165 | + sorry |
| 166 | + |
| 167 | + |
| 168 | +/- Problem 2.2 (5 points) |
| 169 | +Theorem: `double` distributes over addition. |
| 170 | +Hint: Think carefully about which variable to induct on. |
| 171 | +Recall that Lean defines addition by recursion on the second argument. |
| 172 | +-/ |
| 173 | +theorem double_add (m n : Nat) : double (m + n) = double m + double n := by |
| 174 | + sorry |
| 175 | + |
| 176 | + |
| 177 | +/- Problem 2.3 (5 points) |
| 178 | +A concrete arithmetic fact using omega. |
| 179 | +Hint: You should not necessarily use induction. |
| 180 | +-/ |
| 181 | +theorem nat_squeeze (n : Nat) (h₁ : n > 5) (h₂ : n < 10) : |
| 182 | + 2 * n + 1 ≥ 13 := by |
| 183 | + sorry |
| 184 | + |
| 185 | + |
| 186 | +/- Problem 2.4 (5 points) |
| 187 | +Theorem: If n is positive then double n is positive. |
| 188 | +Hint: Induction on n. In the zero case, the hypothesis `h : 0 > 0` is |
| 189 | +contradictory — `omega` will close it. |
| 190 | +-/ |
| 191 | +theorem double_pos (n : Nat) (h : n > 0) : double n > 0 := by |
| 192 | + sorry |
| 193 | + |
| 194 | + |
| 195 | +-- ============================================================================ |
| 196 | +-- Part 3: Induction on Lists (35 points) |
| 197 | +-- ============================================================================ |
| 198 | + |
| 199 | +/- |
| 200 | +Now we move to structural induction on lists. The pattern is: |
| 201 | +
|
| 202 | + induction xs with |
| 203 | + | nil => ... -- base case: xs = [] |
| 204 | + | cons x xs ih => ... -- inductive step: assuming `ih` for xs |
| 205 | +
|
| 206 | +The key technique is: unfold the custom function with `simp [f]`, then |
| 207 | +use the inductive hypothesis `ih`, then (if needed) close with `omega` |
| 208 | +for arithmetic or `rfl` for reflexivity. |
| 209 | +
|
| 210 | +IMPORTANT: You may (and should!) use theorems you proved earlier in later |
| 211 | +proofs. For example, after proving `app_nil`, you can write `rw [app_nil]` |
| 212 | +in subsequent proofs. |
| 213 | +-/ |
| 214 | + |
| 215 | +/- Problem 3.1 (5 points) |
| 216 | +Theorem: Appending the empty list on the right is the identity. |
| 217 | +-/ |
| 218 | +theorem app_nil {α : Type} (xs : List α) : app xs [] = xs := by |
| 219 | + sorry |
| 220 | + |
| 221 | + |
| 222 | +/- Problem 3.2 (8 points) |
| 223 | +Theorem: Append is associative. |
| 224 | +-/ |
| 225 | +theorem app_assoc {α : Type} (xs ys zs : List α) : |
| 226 | + app (app xs ys) zs = app xs (app ys zs) := by |
| 227 | + sorry |
| 228 | + |
| 229 | + |
| 230 | +/- Problem 3.3 (7 points) |
| 231 | +Theorem: Length distributes over append. |
| 232 | +Hint: After `simp [...]`, you may need `omega` |
| 233 | +to handle the resulting arithmetic (i.e. associativity of +). |
| 234 | +-/ |
| 235 | +theorem len_app {α : Type} (xs ys : List α) : |
| 236 | + len (app xs ys) = len xs + len ys := by |
| 237 | + sorry |
| 238 | + |
| 239 | + |
| 240 | +/- Problem 3.4 (7 points) |
| 241 | +Theorem: Map distributes over append. |
| 242 | +-/ |
| 243 | +theorem myMap_app {α β : Type} (f : α → β) (xs ys : List α) : |
| 244 | + myMap f (app xs ys) = app (myMap f xs) (myMap f ys) := by |
| 245 | + sorry |
| 246 | + |
| 247 | + |
| 248 | +/- Problem 3.5 (8 points) |
| 249 | +Theorem: Reversing a list preserves its length. |
| 250 | +
|
| 251 | +This might be the hardest problem in Part 3: you must combine induction with a |
| 252 | +previously proven theorem. |
| 253 | +-/ |
| 254 | +theorem len_naiveRev {α : Type} (xs : List α) : |
| 255 | + len (naiveRev xs) = len xs := by |
| 256 | + sorry |
| 257 | + |
| 258 | + |
| 259 | +-- ============================================================================ |
| 260 | +-- Part 4: Induction on Binary Trees (30 points) |
| 261 | +-- ============================================================================ |
| 262 | + |
| 263 | +/- |
| 264 | +Structural induction on binary trees follows the same idea, but now the |
| 265 | +inductive type has *two* recursive children: |
| 266 | +
|
| 267 | + induction t with |
| 268 | + | empty => ... -- base case: the empty tree |
| 269 | + | node v l r ihl ihr => ... -- inductive step: two IHs! |
| 270 | +
|
| 271 | +As with lists, `simp [f, ihl, ihr]` is your main workhorse. |
| 272 | +-/ |
| 273 | + |
| 274 | +/- Problem 4.1 (8 points) |
| 275 | +Theorem: Mirroring a tree twice gives back the original tree (mirror is an involution). |
| 276 | +-/ |
| 277 | +theorem mirror_mirror {α : Type} (t : BTree α) : |
| 278 | + t.mirror.mirror = t := by |
| 279 | + sorry |
| 280 | + |
| 281 | + |
| 282 | +/- Problem 4.2 (8 points) |
| 283 | +Theorem: Mirroring preserves the number of nodes. |
| 284 | +Hint: After `simp [...]`, you may need `omega` to handle the commutativity of addition |
| 285 | +(since mirror swaps left and right, the addition order changes). |
| 286 | +-/ |
| 287 | +theorem size_mirror {α : Type} (t : BTree α) : |
| 288 | + t.mirror.size = t.size := by |
| 289 | + sorry |
| 290 | + |
| 291 | + |
| 292 | +/- Problem 4.3 (7 points) |
| 293 | +Theorem: Mapping the identity function over a tree changes nothing (functor identity law). |
| 294 | +-/ |
| 295 | +theorem mapTree_id {α : Type} (t : BTree α) : |
| 296 | + BTree.mapTree id t = t := by |
| 297 | + sorry |
| 298 | + |
| 299 | + |
| 300 | +/- Problem 4.4 (7 points) |
| 301 | +Theorem: Mapping f after g is the same as mapping (f ∘ g) (functor composition law). |
| 302 | +Hint: On top of the expected tactics you'll use, you may also use `simp [Function.comp]` |
| 303 | +-/ |
| 304 | +theorem mapTree_comp {α β γ : Type} (f : β → γ) (g : α → β) (t : BTree α) : |
| 305 | + BTree.mapTree f (BTree.mapTree g t) = BTree.mapTree (f ∘ g) t := by |
| 306 | + sorry |
| 307 | + |
| 308 | + |
| 309 | +-- ============================================================================ |
| 310 | +-- Part 5: Calc Mode (15 points) |
| 311 | +-- ============================================================================ |
| 312 | + |
| 313 | +/- |
| 314 | +Calc mode lets you write proofs as readable chains of equalities (or |
| 315 | +inequalities). Each step needs a justification after `:= by`. |
| 316 | +
|
| 317 | + calc LHS |
| 318 | + = step₁ := by ... |
| 319 | + _ = step₂ := by ... |
| 320 | + _ = RHS := by ... |
| 321 | +-/ |
| 322 | + |
| 323 | +/- Problem 5.1 (7 points) |
| 324 | +Chain hypotheses together using calc. |
| 325 | +-/ |
| 326 | +theorem calc_chain (a b c d : Nat) |
| 327 | + (h₁ : a = b + 3) (h₂ : b = c * 2) (h₃ : c = d + 1) : |
| 328 | + a = 2 * d + 5 := by |
| 329 | + sorry |
| 330 | + |
| 331 | + |
| 332 | +/- Problem 5.2 (8 points) |
| 333 | +Use calc mode together with previously proven list theorems. |
| 334 | +Hint: Use `rw [len_app]` for each step that distributes len over app. |
| 335 | +-/ |
| 336 | +theorem len_app_three {α : Type} (xs ys zs : List α) : |
| 337 | + len (app (app xs ys) zs) = len xs + len ys + len zs := by |
| 338 | + sorry |
| 339 | + |
| 340 | + |
| 341 | +-- ============================================================================ |
| 342 | +-- BONUS CHALLENGE (25 extra points) |
| 343 | +-- ============================================================================ |
| 344 | + |
| 345 | +/-! |
| 346 | +These problems are designed to be harder than the main assignment. They |
| 347 | +require you to chain multiple lemmas together and think carefully about |
| 348 | +what intermediate facts you need. |
| 349 | +
|
| 350 | +Both bonus problems concern `naiveRev` (our naive list reversal function). |
| 351 | +Bonus 1 is a key lemma, and Bonus 2 *depends* on it — if you solve Bonus 1, |
| 352 | +you may use it in Bonus 2. |
| 353 | +-/ |
| 354 | + |
| 355 | +/- BONUS Problem 1 (15 points) |
| 356 | +Theorem: Reversing distributes over append — but in *reverse order*. |
| 357 | +
|
| 358 | +Hint: |
| 359 | + - Base case: Use `app_nil` (from Problem 3.1). |
| 360 | + - Inductive case: Unfold `naiveRev` and `app` to get a goal about nested `app` calls. |
| 361 | +-/ |
| 362 | +theorem naiveRev_app {α : Type} (xs ys : List α) : |
| 363 | + naiveRev (app xs ys) = app (naiveRev ys) (naiveRev xs) := by |
| 364 | + sorry |
| 365 | + |
| 366 | + |
| 367 | +/- BONUS Problem 2 (10 points) |
| 368 | +Theorem: Reversing a list twice gives back the original list. |
| 369 | +
|
| 370 | +Hint: In the inductive step, unfold `naiveRev` and apply `naiveRev_app` (Bonus 1!). |
| 371 | +-/ |
| 372 | +theorem naiveRev_naiveRev {α : Type} (xs : List α) : |
| 373 | + naiveRev (naiveRev xs) = xs := by |
| 374 | + sorry |
| 375 | + |
| 376 | + |
| 377 | +-- ============================================================================ |
| 378 | +-- END OF ASSIGNMENT |
| 379 | +-- ============================================================================ |
| 380 | + |
| 381 | +/- |
| 382 | +Grading Rubric: |
| 383 | +- Part 1 (20 points): Propositional logic warm-up |
| 384 | +- Part 2 (20 points): Natural number induction |
| 385 | +- Part 3 (35 points): List induction |
| 386 | +- Part 4 (30 points): Binary tree induction |
| 387 | +- Part 5 (15 points): Calc mode |
| 388 | +- Bonus (25 points): Reverse involution |
| 389 | +
|
| 390 | +Total: 120 points (145 with bonus) |
| 391 | +-/ |
0 commit comments