summaryrefslogtreecommitdiff
path: root/Day10.lean
blob: 27c3c1410e13998c316f6f6efdb9c403b68b370c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import «Common»

namespace Day10

structure Coordinate (width : Nat) (height : Nat) where
  x : Fin width
  y : Fin height

def Coordinate.toIndex {w h : Nat} (c : Coordinate w h) : Fin (w*h) :=
  Fin.mk (c.x.val * c.y.val) (Nat.mul_lt_mul_of_lt_of_lt c.x.isLt c.y.isLt)

def Coordinate.fromIndex {w h : Nat} (index : Fin (w*h)) : Coordinate w h :=
  have : w > 0 :=
    have := index.isLt
    match w with
    | Nat.zero => absurd ((Nat.zero_mul h).subst this) (Nat.not_lt_zero index.val)
    | Nat.succ ww => Nat.succ_pos ww
  {
    x := index.val % w, Nat.mod_lt index.val this
    y := index.val / w, Nat.div_lt_of_lt_mul index.isLt
  }

inductive Pipe
| NS : Pipe
| WE : Pipe
| NE : Pipe
| ES : Pipe
| SW : Pipe
| WN : Pipe
deriving BEq

inductive Tile
| pipe : Pipe  Tile
| ground : Tile
| start : Tile
deriving BEq

-- The invariants are maybe overdoing it a bit, but (in the voice of king Leonidas) "This is Lean4!"
structure Area where
  width : Nat
  height : Nat
  start : Coordinate width height
  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
| UnexpectedCharacter
| NoStart
| MoreThanOneStart
| NotRectangular

private structure Area.Raw where
  width : Nat
  height : Nat
  start : Nat
  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
      }
    else
      throw Area.ParseError.NoStart
where
  parse_character : Char  Option Tile := λ c  match c with
  | '|' => some $ Tile.pipe Pipe.NS
  | '-' => some $ Tile.pipe Pipe.WE
  | 'L' => some $ Tile.pipe Pipe.NE
  | 'J' => some $ Tile.pipe Pipe.WN
  | '7' => some $ Tile.pipe Pipe.SW
  | 'F' => some $ Tile.pipe Pipe.ES
  | 'S' => some Tile.start
  | '.' => some Tile.ground
  | _ => none