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
|