summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Day10.lean344
1 files changed, 299 insertions, 45 deletions
diff --git a/Day10.lean b/Day10.lean
index 27c3c14..edda1a2 100644
--- a/Day10.lean
+++ b/Day10.lean
@@ -35,6 +35,9 @@ inductive Tile
| start : Tile
deriving BEq
+instance : Inhabited Tile where
+ default := Tile.ground
+
-- The invariants are maybe overdoing it a bit, but (in the voice of king Leonidas) "This is Lean4!"
structure Area where
width : Nat
@@ -43,7 +46,6 @@ structure Area where
fields : Array Tile
size_invariant : fields.size = width * height
start_invariant : fields[start.toIndex] = Tile.start
- start_invariant2 : ∀ (index : Fin (width*height)), (index ≠ start.toIndex) → fields[index] ≠ Tile.start
inductive Area.ParseError
| NoInput
@@ -55,53 +57,35 @@ inductive Area.ParseError
private structure Area.Raw where
width : Nat
height : Nat
- start : Nat
+ start : Option $ Fin (width * height)
fields : Array Tile
-private def Area.ParseRaw (input : String) : Except Area.ParseError Area.Raw := do
- let lines := input.splitTrim (· == '\n')
- -- Treat the first line special. We use it to establish width
- match lines with
- | [] => throw Area.ParseError.NoInput
- | l :: ls =>
- let mut width := 0
- let mut arr : Array Tile := Array.empty
- let mut start : Option Nat := none
- for c in l.toSubstring do
- let tile ← parse_character c |> Option.toExcept Area.ParseError.UnexpectedCharacter
- arr := arr.push tile
- if tile == Tile.start then
- if start.isNone then
- start := some width
- else
- throw Area.ParseError.MoreThanOneStart
- width := width + 1
- let mut height := 1
- for line in ls do
- let mut thisWidth := 0
- for c in line.toSubstring do
- let tile ← parse_character c |> Option.toExcept Area.ParseError.UnexpectedCharacter
- arr := arr.push tile
- if tile == Tile.start then
- if start.isNone then
- start := some (thisWidth * height)
- else
- throw Area.ParseError.MoreThanOneStart
- thisWidth := thisWidth +1
- height := height + 1
- if thisWidth != width then
- throw Area.ParseError.NotRectangular
- if let some s := start then
- return {
- width := width,
- height := height,
- start := s,
- fields := arr
- }
+
+private def Area.parseLine (previous : Area.Raw) (pos : Nat) (line : Substring) (hh : previous.height > 0) : Except Area.ParseError (Nat × Area.Raw) :=
+ if h : line.isEmpty then
+ Except.ok (pos, previous)
+ else if h₀ : pos ≥ previous.width then
+ throw Area.ParseError.NotRectangular
+ else do
+ let tile ← Option.toExcept Area.ParseError.UnexpectedCharacter $ parseCharacter line.front
+ let rest := line.drop 1
+ if tile == Tile.start then
+ if previous.start.isSome then
+ throw Area.ParseError.MoreThanOneStart
+ else
+ have : previous.width * (previous.height - 1) + pos < previous.width * previous.height := by
+ have := Nat.mul_pred previous.width previous.height
+ simp only [Nat.pred_eq_sub_one] at this
+ rw[this]
+ have : previous.width ≤ previous.width*previous.height := Nat.le_mul_of_pos_right _ hh
+ rw[←Nat.sub_add_comm this]
+ omega
+ let start := ⟨previous.width * (previous.height - 1) + pos, this⟩
+ Area.parseLine {previous with fields := previous.fields.push tile, start := some start} (pos + 1) rest hh
else
- throw Area.ParseError.NoStart
-where
- parse_character : Char → Option Tile := λ c ↦ match c with
+ Area.parseLine {previous with fields := previous.fields.push tile} (pos + 1) rest hh
+termination_by previous.width - pos
+where parseCharacter : Char → Option Tile := λ c ↦ match c with
| '|' => some $ Tile.pipe Pipe.NS
| '-' => some $ Tile.pipe Pipe.WE
| 'L' => some $ Tile.pipe Pipe.NE
@@ -111,3 +95,273 @@ where
| 'S' => some Tile.start
| '.' => some Tile.ground
| _ => none
+
+private theorem Nat.mul_le_succ_right : ∀ (n m : Nat), n*m ≤ n*m.succ := by
+ intro n m
+ rw[Nat.mul_succ]
+ omega
+
+private def Area.parseLines (previous : Area.Raw) (input : List String) : Except Area.ParseError Area.Raw :=
+ match input with
+ | [] => pure previous
+ | l :: ls => do
+ let current := {
+ previous with
+ height := previous.height + 1
+ start := Fin.castLE (Nat.mul_le_succ_right _ _) <$> previous.start
+ }
+ let (parsed_width, parsed_line) ← Area.parseLine current 0 l.toSubstring (by simp only [gt_iff_lt, Nat.zero_lt_succ])
+ if parsed_width = previous.width then
+ Area.parseLines parsed_line ls
+ else
+ throw Area.ParseError.NotRectangular
+
+private def Area.parseRaw (input : String) : Except Area.ParseError Area.Raw :=
+ let lines := input.splitTrim (· == '\n')
+ if h₁ : lines.isEmpty then
+ throw Area.ParseError.NoInput
+ else
+ have : 0 < lines.length := by
+ cases hl : lines
+ case nil => exact absurd (List.isEmpty_nil) (hl.subst (motive := λx↦¬x.isEmpty = true) h₁)
+ case cons => exact Nat.succ_pos _
+ let width := lines[0].length
+ if width > 0 then
+ let initial : Area.Raw := {
+ width,
+ height := 0,
+ start := none,
+ fields := Array.empty
+ }
+ Area.parseLines initial lines
+ else
+ throw Area.ParseError.NoInput
+
+private def Except.get (e : Except ε α) (_ : e.isOk) : α :=
+ match e with
+ | .ok a => a
+
+private theorem Area.ParseLine_adds_returned_count (previous : Area.Raw) (pos : Nat) (line : Substring) (h₁ : previous.height > 0) : (h : (Area.parseLine previous pos line h₁).isOk) → (Except.get (Area.parseLine previous pos line h₁) h).2.fields.size = previous.fields.size + (Except.get (Area.parseLine previous pos line h₁) h).1 - pos := by
+ intros h₂
+ generalize h₃ : Day10.Area.parseLine previous pos line h₁ = r at *
+ unfold parseLine at h₃
+ simp at h₃
+ split at h₃
+ case isTrue =>
+ subst r
+ unfold Except.get
+ simp
+ case isFalse =>
+ split at h₃
+ case isTrue => rw[←h₃] at h₂; contradiction
+ case isFalse =>
+ unfold bind Monad.toBind Except.instMonad at h₃
+ simp at h₃
+ cases h₄ : (Day10.Area.parseLine.parseCharacter line.front)
+ <;> simp[h₄, Option.toExcept, Except.bind] at h₃
+ case none => rw[←h₃] at h₂; contradiction
+ case some =>
+ split at h₃
+ case h_1 => rw[←h₃] at h₂; contradiction
+ case h_2 d1 h₅ char₂ d2 char h₆ =>
+ simp[Except.pure] at h₆
+ subst char₂
+ clear d1 d2
+ split at h₃
+ case' isTrue =>
+ split at h₃
+ case isTrue.isTrue => rw[←h₃] at h₂; contradiction
+ have : previous.width * (previous.height - 1) + pos < previous.width * previous.height := by
+ have := Nat.mul_pred previous.width previous.height
+ simp only [Nat.pred_eq_sub_one] at this
+ rw[this]
+ have : previous.width ≤ previous.width*previous.height := Nat.le_mul_of_pos_right _ h₁
+ rw[←Nat.sub_add_comm this]
+ omega
+ generalize hc :{ width := previous.width, height := previous.height, start := some ⟨previous.width * (previous.height - 1) + pos, this⟩, fields := previous.fields.push char : Area.Raw} = c
+ case' isFalse =>
+ generalize hc :{ width := previous.width, height := previous.height, start := previous.start, fields := previous.fields.push char : Area.Raw} = c
+ case isTrue | isFalse =>
+ simp[hc] at h₃
+ have : c.height = previous.height := by rw[←hc]
+ have h₆ := Area.ParseLine_adds_returned_count c (pos+1) (line.drop 1) (this▸h₁) (by simp_all)
+ simp[h₃] at h₆
+ have h₇ : c.fields.size = previous.fields.size + 1 := by
+ rw[←hc]
+ simp
+ rw[h₇] at h₆
+ omega
+termination_by previous.width - pos
+
+private theorem Area.ParseLine_leaves_width_and_height (previous : Area.Raw) (pos : Nat) (line : Substring) (h₁ : previous.height > 0) : (h : (Area.parseLine previous pos line h₁).isOk) → (Except.get (Area.parseLine previous pos line h₁) h).2.width = previous.width ∧ (Except.get (Area.parseLine previous pos line h₁) h).2.height = previous.height := by
+ intros h₂
+ generalize h₃ : Day10.Area.parseLine previous pos line h₁ = r at *
+ unfold parseLine at h₃
+ simp at h₃
+ split at h₃
+ case isTrue =>
+ subst r
+ unfold Except.get
+ simp
+ case isFalse =>
+ split at h₃
+ case isTrue => rw[←h₃] at h₂; contradiction
+ case isFalse =>
+ unfold bind Monad.toBind Except.instMonad at h₃
+ simp at h₃
+ cases h₄ : (Day10.Area.parseLine.parseCharacter line.front)
+ <;> simp[h₄, Option.toExcept, Except.bind] at h₃
+ case none => rw[←h₃] at h₂; contradiction
+ case some =>
+ split at h₃
+ case h_1 => rw[←h₃] at h₂; contradiction
+ case h_2 d1 h₅ char₂ d2 char h₆ =>
+ simp[Except.pure] at h₆
+ subst char₂
+ clear d1 d2
+ split at h₃
+ case' isTrue =>
+ split at h₃
+ case isTrue.isTrue => rw[←h₃] at h₂; contradiction
+ have : previous.width * (previous.height - 1) + pos < previous.width * previous.height := by
+ have := Nat.mul_pred previous.width previous.height
+ simp only [Nat.pred_eq_sub_one] at this
+ rw[this]
+ have : previous.width ≤ previous.width*previous.height := Nat.le_mul_of_pos_right _ h₁
+ rw[←Nat.sub_add_comm this]
+ omega
+ generalize hc :{ width := previous.width, height := previous.height, start := some ⟨previous.width * (previous.height - 1) + pos, this⟩, fields := previous.fields.push char : Area.Raw} = c
+ case' isFalse =>
+ generalize hc :{ width := previous.width, height := previous.height, start := previous.start, fields := previous.fields.push char : Area.Raw} = c
+ case isTrue | isFalse =>
+ simp[hc] at h₃
+ have h₇ : c.height = previous.height := by rw[←hc]
+ have h₆ := Area.ParseLine_leaves_width_and_height c (pos+1) (line.drop 1) (h₇▸h₁) (by simp_all)
+ simp[h₃] at h₆
+ have h₈ : c.width = previous.width := by rw[←hc]
+ omega
+termination_by previous.width - pos
+
+private theorem Area.ParseLines_array_size (input : Area.Raw) (lines : List String) (h₁ : input.fields.size = input.width * input.height) : (h : (Area.parseLines input lines).isOk) → (Except.get (Area.parseLines input lines) h).fields.size = (Except.get (Area.parseLines input lines) h).width * (Except.get (Area.parseLines input lines) h).height := by
+ intro h₂
+ generalize h₃ : Day10.Area.parseLines input lines = r at *
+ unfold Area.parseLines at h₃
+ cases lines <;> simp at h₃
+ case nil =>
+ simp[←h₃]
+ exact h₁
+ case cons l ls =>
+ unfold bind Monad.toBind Except.instMonad at h₃
+ simp at h₃
+ unfold Except.bind at h₃
+ split at h₃
+ case h_1 => rw[←h₃] at h₂; contradiction
+ case h_2 d lineResult h₄ =>
+ clear d
+ simp at h₃
+ split at h₃
+ case isFalse => rw[←h₃] at h₂; contradiction
+ case isTrue h₅ =>
+ simp[←h₃]
+ apply Area.ParseLines_array_size
+ have : (Area.parseLine { width := input.width, height := input.height + 1, start := Option.map (Fin.castLE (Nat.mul_le_succ_right _ _)) input.start, fields := input.fields } 0 l.toSubstring (Nat.succ_pos _)).isOk := by
+ cases hx : (Area.parseLine { width := input.width, height := input.height + 1, start := Option.map (Fin.castLE (Nat.mul_le_succ_right _ _)) input.start, fields := input.fields } 0 l.toSubstring (Nat.succ_pos _))
+ case error => rw[hx] at h₄; contradiction
+ case ok => rfl
+ have h₆ : lineResult.snd.width = input.width := by
+ have := Area.ParseLine_leaves_width_and_height { width := input.width, height := input.height + 1, start := Option.map (Fin.castLE (Nat.mul_le_succ_right _ _)) input.start, fields := input.fields } 0 l.toSubstring (Nat.succ_pos _) this
+ simp[Except.get] at this
+ split at this
+ rename_i lineResult₂ _ h₄₂ _
+ simp[h₄] at h₄₂
+ subst lineResult₂
+ exact this.left
+ have h₇ : lineResult.snd.height = input.height + 1 := by
+ have := Area.ParseLine_leaves_width_and_height { width := input.width, height := input.height + 1, start := Option.map (Fin.castLE (Nat.mul_le_succ_right _ _)) input.start, fields := input.fields } 0 l.toSubstring (Nat.succ_pos _) this
+ simp[Except.get] at this
+ split at this
+ rename_i lineResult₂ _ h₄₂ _
+ simp[h₄] at h₄₂
+ subst lineResult₂
+ exact this.right
+ have h₈ : lineResult.snd.fields.size = input.fields.size + lineResult.fst := by
+ have := Area.ParseLine_adds_returned_count { width := input.width, height := input.height + 1, start := Option.map (Fin.castLE (Nat.mul_le_succ_right _ _)) input.start, fields := input.fields } 0 l.toSubstring (Nat.succ_pos _) this
+ simp[Except.get] at this
+ split at this
+ rename_i lineResult₂ _ h₄₂ _
+ simp[h₄] at h₄₂
+ subst lineResult₂
+ assumption
+ rw[h₈, h₇, h₆, h₁, h₅]
+ exact Nat.mul_succ _ _
+
+
+private theorem Area.ParseRaw_array_size :
+ ∀ (input : String), (h : (Area.parseRaw input).isOk) → (Except.get (Area.parseRaw input) h).fields.size = (Except.get (Area.parseRaw input) h).width * (Except.get (Area.parseRaw input) h).height := by
+ intros input h₁
+ generalize h₂ : Day10.Area.parseRaw input = r at *
+ unfold Area.parseRaw at h₂
+ simp at h₂
+ split at h₂
+ case isTrue => rw[←h₂] at h₁; contradiction
+ split at h₂
+ case isFalse.isFalse => rw[←h₂] at h₁; contradiction
+ case isFalse.isTrue =>
+ simp[←h₂]
+ apply Area.ParseLines_array_size
+ rfl
+
+private theorem Except.isOk_exists {e : Except ε α} : (e.isOk = true) ↔ ∃a, e = Except.ok a := by
+ constructor
+ <;> intro h₁
+ case mp =>
+ match e with
+ | .ok v => exists v
+ case mpr =>
+ cases h₁
+ subst e
+ rfl
+
+private theorem Except.get_unfold {α : Type u1} {ε : Type u2} (v : α) : Except.get (α := α) (ε := ε) (Except.ok v) (rfl) = v := rfl
+
+private theorem Except.get_unfold' {α : Type u1} {ε : Type u2} {e : Except ε α} {v : α} (h₁ : e = Except.ok v) (h₂ : e.isOk) : Except.get e h₂ = v := by
+ simp[h₁]
+ apply Except.get_unfold
+
+private theorem Option.get_unfold {α : Type u} {v : α} : (some v).get (rfl) = v := rfl
+
+private theorem Option.get_unfold' {α : Type u} {o : Option α} {v : α} (h₁ : o = some v) (h₂ : o.isSome) : o.get h₂ = v := by
+ simp[h₁]
+
+private theorem Area.ParseRaw_start_position_aux :
+ ∀ (input : String), (h₁ : (Area.parseRaw input).isOk) →
+ match (Except.get (Area.parseRaw input) h₁).start with
+ | .some index => (Except.get (Area.parseRaw input) h₁).fields[index]! = Tile.start
+ | .none => True
+ := by
+ intros input h₁
+ split
+ case h_2 => trivial
+ case h_1 =>
+ generalize Day10.Area.parseRaw input = r at *
+ sorry
+
+private theorem Array.getElem!_eq_getElem {α : Type u} [Inhabited α] {a : Array α} {index : Nat} (h : index < a.size ): a[index] = a[index]! := by
+ unfold getElem getElem! instGetElem?OfGetElemOfDecidable Array.instGetElemNatLtSize decidableGetElem?
+ simp
+ split
+ <;> rename_i blah
+ <;> simp[h] at blah
+ assumption
+
+private theorem Area.ParseRaw_start_position :
+ ∀ (input : String), (h₁ : (Area.parseRaw input).isOk) → (h₂ : (Except.get (Area.parseRaw input) h₁).start.isSome) → (Except.get (Area.parseRaw input) h₁).fields[(Except.get (Area.parseRaw input) h₁).start.get h₂]'((Area.ParseRaw_array_size input h₁).substr ((Except.get (Area.parseRaw input) h₁).start.get h₂).isLt) = Tile.start := by
+ intros input h₁ h₂
+ have := Area.ParseRaw_start_position_aux input h₁
+ split at this
+ case h_2 hx => exact absurd h₂ (Option.not_isSome_iff_eq_none.mpr hx)
+ case h_1 maybeIndex index he =>
+ simp at this
+ simp_all
+ rw[←this]
+ apply Array.getElem!_eq_getElem