summaryrefslogtreecommitdiff
path: root/Day5.lean
blob: 18f9aeb5e6b3240b638349fdca4caebeb934553e (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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
import Common.DayPart
import Common.List

namespace Day5

structure Seed where
  id : Nat
  deriving BEq, Ord, Repr

structure Soil where
  id : Nat
  deriving BEq, Ord, Repr

structure Fertilizer where
  id : Nat
  deriving BEq, Ord, Repr

structure Water where
  id : Nat
  deriving BEq, Ord, Repr

structure Light where
  id : Nat
  deriving BEq, Ord, Repr

structure Temperature where
  id : Nat
  deriving BEq, Ord, Repr

structure Humidity where
  id : Nat
  deriving BEq, Ord, Repr

structure Location where
  id : Nat
  deriving BEq, Ord, Repr

private class NatId (α : Type) where
  fromNat : Nat  α
  toNat : α  Nat

private instance : NatId Seed where
  fromNat := Seed.mk
  toNat := Seed.id

private instance : NatId Soil where
  fromNat := Soil.mk
  toNat := Soil.id

private instance : NatId Fertilizer where
  fromNat := Fertilizer.mk
  toNat := Fertilizer.id

private instance : NatId Water where
  fromNat := Water.mk
  toNat := Water.id

private instance : NatId Light where
  fromNat := Light.mk
  toNat := Light.id

private instance : NatId Temperature where
  fromNat := Temperature.mk
  toNat := Temperature.id

private instance : NatId Humidity where
  fromNat := Humidity.mk
  toNat := Humidity.id

private instance : NatId Location where
  fromNat := Location.mk
  toNat := Location.id

private instance : Min Location where
  min a b := if Ord.compare a b == Ordering.lt then a else b

structure Mapping (α β : Type) where
  inputStart : α
  outputStart : β
  length : Nat
  deriving Repr

structure Mappings (α β : Type) where
  mappings : List $ Mapping α β
  deriving Repr

private def Mapping.apply? {α β : Type} [NatId α] [NatId β] (mapping : Mapping α β) (input : α) : Option β :=
  let input := NatId.toNat input
  let fromStart := NatId.toNat mapping.inputStart
  let toStart := NatId.toNat mapping.outputStart
  if input  fromStart  input < fromStart + mapping.length then
    some $ NatId.fromNat $ toStart + input - fromStart
  else
    none

private def Mappings.apply {α β : Type} [NatId α] [NatId β] (mappings : Mappings α β) (input : α) : β :=
  let applied := mappings.mappings.findSome? $ flip Mapping.apply? input
  applied.getD $ NatId.fromNat $ NatId.toNat input

structure Almanach where
  seedsToSoil : Mappings Seed Soil
  soilToFertilizer : Mappings Soil Fertilizer
  fertilizerToWater : Mappings Fertilizer Water
  waterToLight : Mappings Water Light
  lightToTemperature : Mappings Light Temperature
  temperatureToHumidity : Mappings Temperature Humidity
  humidityToLocation : Mappings Humidity Location
  deriving Repr

private def parseSeeds (input : String) : Option (List Seed) :=
  if input.startsWith "seeds: " then
    let input := input.drop 7
    let input := String.trim <$> input.split Char.isWhitespace
    let numbers := input.mapM String.toNat?
    List.map NatId.fromNat <$> numbers
  else
    none

private def parseMapping {α β : Type} [NatId α] [NatId β] (input : String) : Option $ Mapping α β := do
  let input := String.trim <$> input.split Char.isWhitespace
  let nums  input.mapM String.toNat?
  match nums with
  | [a,b,c] => some $ {inputStart := NatId.fromNat b, outputStart := NatId.fromNat a, length := c}
  | _ => none

private def parseMappings (α β : Type) [NatId α] [NatId β] (input : String) (header : String) : Option $ Mappings α β :=
  if input.startsWith header then
    let lines := String.trim <$> input.splitOn "\n" |> List.drop 1 |> List.filter (not  String.isEmpty)
    let mappings := lines.mapM parseMapping
    Mappings.mk <$> mappings
  else
   none

def parse (input : String) : Option ((List Seed) × Almanach) := do
  let blocks := input.splitOn "\n\n" |> List.filter (not  String.isEmpty)
  let blocks := String.trim <$> blocks
  if let [seeds, seedToSoil, soilToFertilizer, fertilizerToWater, waterToLight, lightToTemperature, temperatureToHumidity, humidityToLocation] := blocks then
    let seeds  parseSeeds seeds
    let seedToSoil  parseMappings Seed Soil seedToSoil "seed-to-soil map:"
    let soilToFertilizer  parseMappings Soil Fertilizer soilToFertilizer "soil-to-fertilizer map:"
    let fertilizerToWater  parseMappings Fertilizer Water fertilizerToWater "fertilizer-to-water map:"
    let waterToLight  parseMappings Water Light waterToLight "water-to-light map:"
    let lightToTemperature  parseMappings Light Temperature lightToTemperature "light-to-temperature map:"
    let temperatureToHumidity  parseMappings Temperature Humidity temperatureToHumidity "temperature-to-humidity map:"
    let humidityToLocation  parseMappings Humidity Location humidityToLocation "humidity-to-location map:"
    (seeds, {
      seedsToSoil := seedToSoil
      soilToFertilizer := soilToFertilizer
      fertilizerToWater := fertilizerToWater
      waterToLight := waterToLight
      lightToTemperature := lightToTemperature
      temperatureToHumidity := temperatureToHumidity
      humidityToLocation := humidityToLocation
    : Almanach})
  else
    none

def part1 (input : ((List Seed) × Almanach)) : Option Nat :=
  let a := input.snd
  let seedToLocation  := a.humidityToLocation.apply
                       a.temperatureToHumidity.apply
                       a.lightToTemperature.apply
                       a.waterToLight.apply
                       a.fertilizerToWater.apply
                       a.soilToFertilizer.apply
                       a.seedsToSoil.apply
  let locations := input.fst.map seedToLocation
  NatId.toNat <$> locations.minimum?


-- Part 2 seems unmanageable by brute force.
-- So, let's rather combine the mappings in a smart way, and then invert them.
-- Then we can do binary search if any seed is in the domain of the combined mapping.
-- if not, the answer is just the smallest seed.

private def Mappings.combine {α β γ : Type} [NatId α] [NatId β] [NatId γ] (a : Mappings α β) (b : Mappings β γ) : Mappings α γ :=
  -- the combined mapping contains all ranges in b that aren't "shielded" by ranges of a.
  -- then it contains all ranges of a, but their codomain needs to be checked if it overlaps with
  --    any ranges in b, and if yes, those ranges need to be split accordingly.
  let domain := λ {δ ε : Type} [NatId δ] [NatId ε] (e :Mapping δ ε)  (NatId.toNat e.inputStart, NatId.toNat e.inputStart + e.length)
  let codomain := λ {δ ε : Type} [NatId δ] [NatId ε] (e :Mapping δ ε)  (NatId.toNat e.outputStart, NatId.toNat e.outputStart + e.length)
  let unshieldedB : List (Mapping α γ) := b.mappings.bind λ bm 
    let bmd := domain bm
    let bmcd := codomain bm
    let shielded := a.mappings.filterMap λ am 
      let amd := domain am
      let s := Nat.max amd.fst bmd.fst
      let e := Nat.min amd.snd bmd.snd
      if e > s then some (s,e) else none
    -- add a range before and after to shielded, then we can take those between
    -- we anyhow need to sort shielded, sooo
    let shielded := (bmd.fst, bmd.fst) :: (bmd.snd, bmd.snd) :: shielded
    let shielded := shielded.quicksortBy (·.fst < ·.fst)
    let rs := shielded.zip (shielded.drop 1)
    let rs : List (Nat × Nat):= rs.map λ x  (x.fst.snd, x.snd.fst)
    let rs := rs.filter λ l  l.snd > l.fst -- skip emtpy, they are just noise
    rs.map λ (x : Nat × Nat) 
      let is : α := NatId.fromNat x.fst
      let os : γ := NatId.fromNat $ bmcd.fst + x.fst - bmd.fst
      let l : Nat := (x.snd - x.fst)
      { inputStart := is, outputStart := os, length := l}
  --TODO: Unify the onlyAffectedByA and affectedByBoth computations.
  let onlyAffectedByA : List (Mapping α γ) := a.mappings.bind λ am 
    let amd := domain am
    let amcd := codomain am
    let overlapping := b.mappings.filterMap λ bm 
      let bmd := domain bm
      let s := Nat.max amcd.fst bmd.fst
      let e := Nat.min amcd.snd bmd.snd
      if e > s then some (s,e) else none
    -- overlapping is in the **codomain** of a. Transform to domain:
    let overlapping := overlapping.map λ p  (p.fst + amd.fst - amcd.fst, p.snd + amd.fst - amcd.fst)
    let overlapping := (amd.fst, amd.fst) :: (amd.snd, amd.snd) :: overlapping
    let overlapping := overlapping.quicksortBy (·.fst < ·.fst)
    let rs := overlapping.zip (overlapping.drop 1)
    let rs : List (Nat × Nat):= rs.map λ x  (x.fst.snd, x.snd.fst)
    let rs := rs.filter λ l  l.snd > l.fst
    rs.map λ (x : Nat × Nat) 
      let is : α := NatId.fromNat x.fst
      let os : γ := NatId.fromNat $ amcd.fst + x.fst - amd.fst
      let l : Nat := (x.snd - x.fst)
      { inputStart := is, outputStart := os, length := l}
  let affectedByBoth : List (Mapping α γ) := sorry
  sorry


open DayPart
instance : Parse 5, by simp (ι := (List Seed) × Almanach) where
  parse := parse

instance : Part 5, _⟩ Parts.One (ι := (List Seed) × Almanach) (ρ := Nat) where
  run := part1


private def TestInput := "seeds: 79 14 55 13

seed-to-soil map:
50 98 2
52 50 48

soil-to-fertilizer map:
0 15 37
37 52 2
39 0 15

fertilizer-to-water map:
49 53 8
0 11 42
42 0 7
57 7 4

water-to-light map:
88 18 7
18 25 70

light-to-temperature map:
45 77 23
81 45 19
68 64 13

temperature-to-humidity map:
0 69 1
1 0 69

humidity-to-location map:
60 56 37
56 93 4
"

#eval parse TestInput
#eval parse TestInput >>= part1