summaryrefslogtreecommitdiff
path: root/Day12.lean
blob: 13f3b76d7d22326b42cbc14c83ff67614aa3b790 (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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
import «Common»

namespace Day12

------------------------------------------------------------------------------------

inductive SpringState
| operational
| damaged
| unknown
deriving Repr, BEq

instance : ToString (List SpringState) where
  toString := λl 
    let rec w := λ
    | .operational :: as => "." ++ (w as)
    | .damaged :: as => "#" ++ (w as)
    | .unknown :: as => "?" ++ (w as)
    | [] => ""
  w l


structure SpringArrangement where
  mask : List SpringState
  damagedGroups : List Nat

instance : ToString SpringArrangement where
  toString := λ
  | {mask, damagedGroups} => (inferInstance : ToString (List SpringState)).toString mask ++ " " ++ damagedGroups.toString

inductive ParsingError
| unknownSymbolInLeftBlock : Char  ParsingError
| nonNumericSymbolInRightBlock : Substring  ParsingError
| invalidNumberOfBlockSeparators : Nat  ParsingError
| noInput : ParsingError
| missingLeftBlock : ParsingError
| missingRightBlock : ParsingError

instance : ToString ParsingError where
  toString := λ
  | .unknownSymbolInLeftBlock c => s!"Unkown character in left part of input: '{c}'. Expected '#', '.' or '?'."
  | .nonNumericSymbolInRightBlock s => s!"The right block contained an unknown symbol. Expected ',' or numeric digits. Found {s}"
  | .invalidNumberOfBlockSeparators n => s!"An input line has an unexpected number of whitespace characters. Expected 1, found {n}."
  | .noInput => "The input file is empty."
  | .missingLeftBlock => "The left block, which should contain a map of operational ('.') or damaged ('#') springs, or the question mark ('?') is empty."
  | .missingRightBlock => "The right block, which should contain a comma-separted list of numbers, is empty."



def parse (input : String) : Except ParsingError (List SpringArrangement) :=
  let input := input.toSubstring.trim
  let lines := input.splitOn "\n"
  let lines := lines.filterMap λx 
    let r : Substring := x.trim
    (Function.const Unit r) <$> Option.ofBool !r.isEmpty
  if lines.isEmpty then
    Except.error ParsingError.noInput
  else
    lines.mapM λl 
      match l.splitOn " " with
      | l :: r :: [] => parseLeftBlock l [] >>= λmask parseRightBlock r <&> λdamagedGroups  {mask, damagedGroups}
      | _ :: [] => .error $ ParsingError.invalidNumberOfBlockSeparators 0
      | l => .error $ ParsingError.invalidNumberOfBlockSeparators (l.length - 1)
where
  parseLeftBlock : Substring  List SpringState  Except ParsingError (List SpringState) := λs p 
    if s.isEmpty then
      Except.ok p.reverse
    else
      match parseMaskChar s.front with
      | .error e => .error e
      | .ok v => parseLeftBlock (s.drop 1) $ v :: p
  termination_by s => s.bsize
  decreasing_by
    simp_wf
    rename_i h₁
    apply Substring.drop_bsize_dec _ _ h₁ Nat.one_ne_zero
  parseMaskChar : Char  Except ParsingError SpringState := λ
  | '#' => .ok .damaged
  | '?' => .ok .unknown
  | '.' => .ok .operational
  | c => Except.error $ .unknownSymbolInLeftBlock c
  parseRightBlock : Substring  Except ParsingError (List Nat) := λs 
    s.splitOn ","
    |> List.mapM λs  (Substring.toNat? s).toExcept (ParsingError.nonNumericSymbolInRightBlock s)


----------------------------------------------------------------------------------------------------

-- There is probably some nice closed form, but I don't have the brain for that right now.
-- Brute force, it is.

inductive DefiniteSpringState
| operational
| damaged

def canFirstNBeDamaged (states : List SpringState) (n : Nat) : Bool :=
  match n, states with
  | 0, _ => true
  | (_ + 1), [] => false
  | (_ + 1), .operational :: _ => false
  | (nn + 1), _ :: ss => canFirstNBeDamaged ss nn

def canFirstBeOperational : (states : List SpringState)  Bool
| .operational :: _ => true
| .unknown :: _ => true
| _ => false

def mustFirstBeDamaged : (states : List SpringState)  Bool
| .damaged :: _ => true
| _ => false

def countPossiblePositionsWithDamaged (remainingSpace : List SpringState) (remainingDamagedGroups : List Nat) (h₁ : remainingDamagedGroups  []) : Nat :=
  if h₂ : remainingSpace.isEmpty then
    0
  else
    match remainingDamagedGroups with
    | g :: gs =>
      let fromCurrentPosition :=
        if canFirstNBeDamaged remainingSpace g then
          if h₃ : gs.isEmpty then
            if (remainingSpace.drop g).any (· == .damaged) then
              0
            else
              1
          else
            let d := (remainingSpace.drop g)
            if canFirstBeOperational d then
              countPossiblePositionsWithDamaged (d.drop 1) gs (List.ne_nil_of_not_empty.mp $ (Bool.not_eq_true _).mp h₃)
            else
              0
        else
          0
      have  : remainingSpace.length > 0 := List.not_empty_iff_size_gt_zero.mp $ (Bool.not_eq_true _).mp h₂
      --have _termination : (List.dropWhile (fun x => x == SpringState.damaged) remainingSpace).length - 1 < remainingSpace.length := by
      --  have := List.drop_while_length_le remainingSpace (· == .damaged)
      --  omega
      let fromOtherPositions :=
        if mustFirstBeDamaged remainingSpace then
          0
        else
          --let remainingSpace := remainingSpace.dropWhile (· == .damaged) --before the damaged group there must be an operational one.
          countPossiblePositionsWithDamaged (remainingSpace.drop 1) (g :: gs) h₁
      fromCurrentPosition + fromOtherPositions
termination_by remainingSpace.length + remainingDamagedGroups.length

def countPossiblePositions (remainingSpace : List SpringState) (remainingDamagedGroups : List Nat) : Nat :=
  if h : remainingDamagedGroups.isEmpty then
    let canAllBeOperational := remainingSpace.all λ
    | .operational | .unknown => true
    | _ => false
    if canAllBeOperational then
      1
    else
      0
  else
    countPossiblePositionsWithDamaged remainingSpace remainingDamagedGroups (List.ne_nil_of_not_empty.mp $ (Bool.not_eq_true _).mp h)


def part1 (springs : List SpringArrangement) : Nat :=
  let possiblePositions := springs.map λ{mask, damagedGroups}  countPossiblePositions mask damagedGroups
  possiblePositions.foldl (· + ·) 0

----------------------------------------------------------------------------------------------------

open DayPart
instance : Parse 12, by simp (ι := List SpringArrangement) where
  parse := Except.mapError toString  parse

instance : Part 12,_⟩ Parts.One (ι := List SpringArrangement) (ρ := Nat) where
  run := some  part1

----------------------------------------------------------------------------------------------------

def testData := "???.### 1,1,3
.??..??...?##. 1,1,3
?#?#?#?#?#?#?#? 1,3,1,6
????.#...#... 4,1,1
????.######..#####. 1,6,5
?###???????? 3,2,1"

#eval parse testData <&> part1

def testData2 := "?##?.##???????#??#.. 2,10,1"

#eval parse testData2 <&> part1