tangled
alpha
login
or
join now
hauleth.dev
/
advent-of-code
3
fork
atom
this repo has no description
3
fork
atom
overview
issues
pulls
pipelines
ft: add day 15.2022
hauleth.dev
3 years ago
43d53ae0
e779273c
+310
1 changed file
expand all
collapse all
unified
split
2022
day15.livemd
+310
2022/day15.livemd
···
1
1
+
<!-- livebook:{"persist_outputs":true} -->
2
2
+
3
3
+
# Day 15
4
4
+
5
5
+
```elixir
6
6
+
Mix.install([
7
7
+
{:kino_aoc, git: "https://github.com/ljgago/kino_aoc"}
8
8
+
])
9
9
+
```
10
10
+
11
11
+
<!-- livebook:{"output":true} -->
12
12
+
13
13
+
```
14
14
+
:ok
15
15
+
```
16
16
+
17
17
+
## Section
18
18
+
19
19
+
<!-- livebook:{"attrs":{"day":"15","session_secret":"ADVENT_OF_CODE_SESSION","variable":"puzzle_input","year":"2022"},"kind":"Elixir.KinoAOC.HelperCell","livebook_object":"smart_cell"} -->
20
20
+
21
21
+
```elixir
22
22
+
{:ok, puzzle_input} =
23
23
+
KinoAOC.download_puzzle("2022", "15", System.fetch_env!("LB_ADVENT_OF_CODE_SESSION"))
24
24
+
```
25
25
+
26
26
+
<!-- livebook:{"output":true} -->
27
27
+
28
28
+
```
29
29
+
{:ok,
30
30
+
"Sensor at x=2288642, y=2282562: closest beacon is at x=1581951, y=2271709\nSensor at x=2215505, y=2975419: closest beacon is at x=2229474, y=3709584\nSensor at x=275497, y=3166843: closest beacon is at x=-626874, y=3143870\nSensor at x=1189444, y=2115305: closest beacon is at x=1581951, y=2271709\nSensor at x=172215, y=2327851: closest beacon is at x=-101830, y=2000000\nSensor at x=3953907, y=1957660: closest beacon is at x=2882446, y=1934422\nSensor at x=685737, y=2465261: closest beacon is at x=1581951, y=2271709\nSensor at x=1458348, y=2739442: closest beacon is at x=1581951, y=2271709\nSensor at x=3742876, y=2811554: closest beacon is at x=3133845, y=3162635\nSensor at x=437819, y=638526: closest beacon is at x=-101830, y=2000000\nSensor at x=2537979, y=1762726: closest beacon is at x=2882446, y=1934422\nSensor at x=1368739, y=2222863: closest beacon is at x=1581951, y=2271709\nSensor at x=2743572, y=3976937: closest beacon is at x=2229474, y=3709584\nSensor at x=2180640, y=105414: closest beacon is at x=3011118, y=-101788\nSensor at x=3845753, y=474814: closest beacon is at x=3011118, y=-101788\nSensor at x=2493694, y=3828087: closest beacon is at x=2229474, y=3709584\nSensor at x=2786014, y=3388077: closest beacon is at x=3133845, y=3162635\nSensor at x=3593418, y=3761871: closest beacon is at x=3133845, y=3162635\nSensor at x=856288, y=3880566: closest beacon is at x=2229474, y=3709584\nSensor at x=1757086, y=2518373: closest beacon is at x=1581951, y=2271709\nSensor at x=2853518, y=2939097: closest beacon is at x=3133845, y=3162635\nSensor at x=1682023, y=1449902: closest beacon is at x=1581951, y=2271709\nSensor at x=3360575, y=1739100: closest beacon is at x=2882446, y=1934422\nSensor at x=2904259, y=1465606: closest beacon is at x=2882446, y=1934422\nSensor at x=3078500, y=3564862: closest beacon is at x=3133845, y=3162635\nSensor at x=2835288, y=1011055: closest beacon is at x=2882446, y=1934422\nSensor at x=2998762, y=2414323: closest beacon is at x=2882446, y=1934422\n"}
31
31
+
```
32
32
+
33
33
+
```elixir
34
34
+
points =
35
35
+
puzzle_input
36
36
+
|> String.split("\n", trim: true)
37
37
+
|> Enum.map(fn line ->
38
38
+
%{"sx" => sx, "sy" => sy, "bx" => bx, "by" => by} =
39
39
+
Regex.named_captures(
40
40
+
~r/x=(?<sx>-?\d+), y=(?<sy>-?\d+):.*x=(?<bx>-?\d+), y=(?<by>-?\d+)/,
41
41
+
line
42
42
+
)
43
43
+
44
44
+
%{
45
45
+
sensor: {String.to_integer(sx), String.to_integer(sy)},
46
46
+
beacon: {String.to_integer(bx), String.to_integer(by)}
47
47
+
}
48
48
+
end)
49
49
+
50
50
+
circles =
51
51
+
points
52
52
+
|> Enum.map(fn %{beacon: {bx, by}, sensor: {sx, sy}} ->
53
53
+
radius = abs(bx - sx) + abs(by - sy)
54
54
+
55
55
+
{{sx, sy}, radius}
56
56
+
end)
57
57
+
```
58
58
+
59
59
+
<!-- livebook:{"output":true} -->
60
60
+
61
61
+
```
62
62
+
[
63
63
+
{{2288642, 2282562}, 717544},
64
64
+
{{2215505, 2975419}, 748134},
65
65
+
{{275497, 3166843}, 925344},
66
66
+
{{1189444, 2115305}, 548911},
67
67
+
{{172215, 2327851}, 601896},
68
68
+
{{3953907, 1957660}, 1094699},
69
69
+
{{685737, 2465261}, 1089766},
70
70
+
{{1458348, 2739442}, 591336},
71
71
+
{{3742876, 2811554}, 960112},
72
72
+
{{437819, 638526}, 1901123},
73
73
+
{{2537979, 1762726}, 516163},
74
74
+
{{1368739, 2222863}, 262058},
75
75
+
{{2743572, 3976937}, 781451},
76
76
+
{{2180640, 105414}, 1037680},
77
77
+
{{3845753, 474814}, 1411237},
78
78
+
{{2493694, 3828087}, 382723},
79
79
+
{{2786014, 3388077}, 573273},
80
80
+
{{3593418, 3761871}, 1058809},
81
81
+
{{856288, 3880566}, 1544168},
82
82
+
{{1757086, 2518373}, 421799},
83
83
+
{{2853518, 2939097}, 503865},
84
84
+
{{1682023, 1449902}, 921879},
85
85
+
{{3360575, 1739100}, 673451},
86
86
+
{{2904259, 1465606}, 490629},
87
87
+
{{3078500, 3564862}, 457572},
88
88
+
{{2835288, 1011055}, 970525},
89
89
+
{{2998762, 2414323}, 596217}
90
90
+
]
91
91
+
```
92
92
+
93
93
+
```elixir
94
94
+
defmodule RangeSet do
95
95
+
defstruct ranges: []
96
96
+
97
97
+
def new(), do: %__MODULE__{}
98
98
+
def new(%__MODULE__{} = set), do: set
99
99
+
def new(a..b//1) when a <= b, do: %__MODULE__{ranges: [a..b//1]}
100
100
+
def new(b..a//-1) when b > a, do: %__MODULE__{ranges: [a..b//1]}
101
101
+
def new(list), do: %__MODULE__{ranges: list |> Enum.sort_by(& &1.first) |> squash()}
102
102
+
103
103
+
def min(%__MODULE__{ranges: [a.._ | _]}), do: a
104
104
+
105
105
+
def max(%__MODULE__{ranges: ranges}) do
106
106
+
_..a = List.last(ranges)
107
107
+
108
108
+
a
109
109
+
end
110
110
+
111
111
+
def gaps(%__MODULE__{ranges: ranges}) do
112
112
+
gaps =
113
113
+
ranges
114
114
+
|> Enum.chunk_every(2, 1, :discard)
115
115
+
|> Enum.flat_map(fn [_..a, b.._] -> [(a + 1)..(b - 1)] end)
116
116
+
|> squash()
117
117
+
118
118
+
%__MODULE__{ranges: gaps}
119
119
+
end
120
120
+
121
121
+
def empty?(%__MODULE__{ranges: ranges}), do: ranges == []
122
122
+
123
123
+
def has?(%__MODULE__{ranges: ranges}, value), do: Enum.any?(ranges, &(value in &1))
124
124
+
125
125
+
def length(%__MODULE__{ranges: ranges}),
126
126
+
do: Enum.reduce(ranges, 0, fn r, acc -> acc + Range.size(r) end)
127
127
+
128
128
+
def to_list(%__MODULE__{ranges: ranges}),
129
129
+
do: Enum.flat_map(ranges, & &1)
130
130
+
131
131
+
def remove(%__MODULE__{ranges: ranges}, int) when is_integer(int) do
132
132
+
new_ranges =
133
133
+
Enum.flat_map(ranges, fn a..b ->
134
134
+
cond do
135
135
+
a == int -> [(int + 1)..b]
136
136
+
b == int -> [a..(int - 1)]
137
137
+
int in a..b -> [a..(int - 1), (int + 1)..b]
138
138
+
true -> [a..b]
139
139
+
end
140
140
+
end)
141
141
+
142
142
+
%__MODULE__{ranges: new_ranges}
143
143
+
end
144
144
+
145
145
+
def remove(%__MODULE__{ranges: ranges}, c..d//1) when c <= d do
146
146
+
new_ranges =
147
147
+
Enum.flat_map(ranges, fn a..b ->
148
148
+
cond do
149
149
+
a in c..d and b in c..d -> []
150
150
+
a < c and b in c..d -> [a..(c - 1)]
151
151
+
a in c..d and b > d -> [(d + 1)..b]
152
152
+
c in a..b and d in a..b -> [a..(c - 1), (d + 1)..b]
153
153
+
true -> [a..b]
154
154
+
end
155
155
+
end)
156
156
+
157
157
+
%__MODULE__{ranges: new_ranges}
158
158
+
end
159
159
+
160
160
+
def remove(%__MODULE__{} = set, %__MODULE__{ranges: ranges}),
161
161
+
do: Enum.reduce(ranges, set, &remove(&2, &1))
162
162
+
163
163
+
def remove(%__MODULE__{} = set, list),
164
164
+
do: Enum.reduce(list, set, &remove(&2, &1))
165
165
+
166
166
+
def add(set, int) when is_integer(int), do: add(set, int..int//1)
167
167
+
168
168
+
def add(set, a..b//-1), do: add(set, b..a//1)
169
169
+
170
170
+
def add(%__MODULE__{} = set, a..b//1) when a > b, do: set
171
171
+
172
172
+
def add(%__MODULE__{ranges: ranges}, a..b//1 = range) when a <= b do
173
173
+
ranges =
174
174
+
ranges
175
175
+
|> insert_sorted(range)
176
176
+
|> squash()
177
177
+
178
178
+
%__MODULE__{ranges: ranges}
179
179
+
end
180
180
+
181
181
+
defp insert_sorted([], val), do: [val]
182
182
+
183
183
+
defp insert_sorted([a.._ = x | rest], b.._ = y) when a < b,
184
184
+
do: [x | insert_sorted(rest, y)]
185
185
+
186
186
+
defp insert_sorted(rest, y),
187
187
+
do: [y | rest]
188
188
+
189
189
+
defp squash([]), do: []
190
190
+
defp squash([_] = list), do: list
191
191
+
192
192
+
defp squash([a..b, c..d | rest]) when b >= c - 1,
193
193
+
do: squash([a..max(b, d) | rest])
194
194
+
195
195
+
defp squash([x | rest]), do: [x | squash(rest)]
196
196
+
end
197
197
+
```
198
198
+
199
199
+
<!-- livebook:{"output":true} -->
200
200
+
201
201
+
```
202
202
+
{:module, RangeSet, <<70, 79, 82, 49, 0, 0, 37, ...>>, {:squash, 1}}
203
203
+
```
204
204
+
205
205
+
```elixir
206
206
+
RangeSet.new([6..10, -8..26])
207
207
+
```
208
208
+
209
209
+
<!-- livebook:{"output":true} -->
210
210
+
211
211
+
```
212
212
+
%RangeSet{ranges: [-8..26]}
213
213
+
```
214
214
+
215
215
+
Our list can be change into list of radii of the circles (in Taxicab metric these will be visually squares), so then using inequality for circle
216
216
+
217
217
+
$$
218
218
+
r \ge d(C, a)
219
219
+
$$
220
220
+
221
221
+
where $d(a, b)$ is distance function for given metric, we can compute possible ranges of values at given line in constant time. In Taxicab metric $d$ is defined as:
222
222
+
223
223
+
$$
224
224
+
d(a, b) = |a_x - b_x| + |a_y - b_y|
225
225
+
$$
226
226
+
227
227
+
So after substitution at the above inequality we get:
228
228
+
229
229
+
$$
230
230
+
r \ge |C_x - a_x| + |C_y - a_y|
231
231
+
$$
232
232
+
233
233
+
In the task $a_y = const$ and $C = const$, so we need to solve only for $a_x$. So we can define $d_y = |C_y - a_y| = const$ as well.
234
234
+
235
235
+
That gives us:
236
236
+
237
237
+
$$
238
238
+
|a_x - C_x| \le r - d_y
239
239
+
$$
240
240
+
241
241
+
$$
242
242
+
\begin{align*}
243
243
+
\therefore&\ -r + d_y &\le&\ a_x - C_x &\le&\ r - d_y \\
244
244
+
\therefore&\ -r + d_y + C_x &\le&\ a_x &\le&\ r - d_y + C_x
245
245
+
\end{align*}
246
246
+
$$
247
247
+
248
248
+
```elixir
249
249
+
defmodule Beacons do
250
250
+
def covered_at_line(circles, a_y) do
251
251
+
Enum.reduce(circles, RangeSet.new(), fn {{c_x, c_y}, r}, set ->
252
252
+
d_y = abs(c_y - a_y)
253
253
+
RangeSet.add(set, (-r + d_y + c_x)..(r - d_y + c_x)//1)
254
254
+
end)
255
255
+
end
256
256
+
end
257
257
+
```
258
258
+
259
259
+
<!-- livebook:{"output":true} -->
260
260
+
261
261
+
```
262
262
+
{:module, Beacons, <<70, 79, 82, 49, 0, 0, 9, ...>>, {:covered_at_line, 2}}
263
263
+
```
264
264
+
265
265
+
## Task 1
266
266
+
267
267
+
```elixir
268
268
+
a_y = 2_000_000
269
269
+
270
270
+
beacons_at_line =
271
271
+
Enum.flat_map(points, fn %{beacon: {x, y}} -> if y == a_y, do: [x], else: [] end)
272
272
+
273
273
+
circles
274
274
+
|> Beacons.covered_at_line(a_y)
275
275
+
|> RangeSet.remove(beacons_at_line)
276
276
+
|> RangeSet.length()
277
277
+
```
278
278
+
279
279
+
<!-- livebook:{"output":true} -->
280
280
+
281
281
+
```
282
282
+
5108096
283
283
+
```
284
284
+
285
285
+
## Task 2
286
286
+
287
287
+
```elixir
288
288
+
range = 4_000_000..0//-1
289
289
+
# range = 0..4_000_000
290
290
+
291
291
+
{y, [x]} =
292
292
+
range
293
293
+
|> Enum.find_value(fn y ->
294
294
+
covered = Beacons.covered_at_line(circles, y)
295
295
+
296
296
+
gaps = RangeSet.gaps(covered)
297
297
+
298
298
+
if not RangeSet.empty?(gaps) do
299
299
+
{y, RangeSet.to_list(gaps)}
300
300
+
end
301
301
+
end)
302
302
+
303
303
+
x * 4_000_000 + y
304
304
+
```
305
305
+
306
306
+
<!-- livebook:{"output":true} -->
307
307
+
308
308
+
```
309
309
+
10553942650264
310
310
+
```