Skip to content

Commit 8d536bf

Browse files
committed
2024 Day 21
1 parent 9f3358f commit 8d536bf

File tree

2 files changed

+272
-0
lines changed

2 files changed

+272
-0
lines changed

2024/lib/day21.ex

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
defmodule Day21a do
2+
@type action() :: ?A | ?< | ?^ | ?> | ?v
3+
@type dir_state() :: action()
4+
5+
@spec dir_pos(action()) :: TVec.t2()
6+
def dir_pos(?^), do: {1, 0}
7+
def dir_pos(?A), do: {2, 0}
8+
def dir_pos(?<), do: {0, 1}
9+
def dir_pos(?v), do: {1, 1}
10+
def dir_pos(?>), do: {2, 1}
11+
12+
def num_pos(?0), do: {1, 3}
13+
def num_pos(?A), do: {2, 3}
14+
def num_pos(?1), do: {0, 2}
15+
def num_pos(?2), do: {1, 2}
16+
def num_pos(?3), do: {2, 2}
17+
def num_pos(?4), do: {0, 1}
18+
def num_pos(?5), do: {1, 1}
19+
def num_pos(?6), do: {2, 1}
20+
def num_pos(?7), do: {0, 0}
21+
def num_pos(?8), do: {1, 0}
22+
def num_pos(?9), do: {2, 0}
23+
24+
@spec dir_pos(TVec.t2()) :: action()
25+
def dir_action({1, 0}), do: ?>
26+
def dir_action({-1, 0}), do: ?<
27+
def dir_action({0, 1}), do: ?v
28+
def dir_action({0, -1}), do: ?^
29+
30+
@spec cost(list(dir_state()), action()) :: {integer(), list(dir_state())}
31+
def cost(state, action)
32+
33+
def cost([], _action) do
34+
{1, []}
35+
end
36+
37+
def cost([state], action) do
38+
{TVec.manhattan(dir_pos(state), dir_pos(action)) + 1, [action]}
39+
end
40+
41+
# cost for nth dpad robot
42+
def cost([state | state_tail], action) do
43+
{cost, state_tail} =
44+
moves_between(dir_pos(state), dir_pos(action), {0, 0})
45+
|> Enum.map(fn moves ->
46+
(moves ++ [?A])
47+
|> Enum.reduce({0, state_tail}, fn move, {total_cost, state} ->
48+
{cost, state} = cost(state, move)
49+
{total_cost + cost, state}
50+
end)
51+
end)
52+
|> Enum.min_by(&elem(&1, 0))
53+
54+
{cost, [action | state_tail]}
55+
end
56+
57+
def cost_num([state | state_tail], action) do
58+
{cost, state_tail} =
59+
moves_between(num_pos(state), num_pos(action), {0, 3})
60+
|> Enum.map(fn moves ->
61+
(moves ++ [?A])
62+
|> Enum.reduce({0, state_tail}, fn move, {total_cost, state} ->
63+
{cost, state} = cost(state, move)
64+
{total_cost + cost, state}
65+
end)
66+
end)
67+
|> Enum.min_by(&elem(&1, 0))
68+
69+
{cost, [action | state_tail]}
70+
end
71+
72+
def moves_between({x1, y1}, {x2, y2}, exclude) do
73+
cond do
74+
x1 == x2 ->
75+
[y1..y2 |> Enum.map(&{x1, &1})]
76+
77+
y1 == y2 ->
78+
[x1..x2 |> Enum.map(&{&1, y1})]
79+
80+
true ->
81+
[
82+
Enum.map(x1..(x2 + if(x2 > x1, do: -1, else: 1)), &{&1, y1}) ++
83+
Enum.map(y1..y2, &{x2, &1}),
84+
Enum.map(y1..(y2 + if(y2 > y1, do: -1, else: 1)), &{x1, &1}) ++
85+
Enum.map(x1..x2, &{&1, y2})
86+
]
87+
|> Enum.filter(&(not Enum.member?(&1, exclude)))
88+
end
89+
|> Enum.map(fn pos_list ->
90+
Enum.zip(pos_list, pos_list |> Enum.drop(1))
91+
|> Enum.map(fn {a, b} ->
92+
dir_action(TVec.sub(b, a))
93+
end)
94+
end)
95+
end
96+
97+
def run do
98+
File.read!(".input.txt")
99+
|> String.split("\n")
100+
|> Enum.map(fn code ->
101+
state = ~c"AAA"
102+
103+
{cost, _} =
104+
code
105+
|> String.to_charlist()
106+
|> Enum.reduce({0, state}, fn move, {total_cost, state} ->
107+
{cost, state} = cost_num(state, move)
108+
{cost + total_cost, state}
109+
end)
110+
111+
code_num =
112+
code
113+
|> String.trim_leading("0")
114+
|> String.trim_trailing("A")
115+
|> String.to_integer()
116+
117+
code_num * cost
118+
end)
119+
|> Enum.sum()
120+
end
121+
end
122+
123+
defmodule Day21b do
124+
@type action() :: ?A | ?< | ?^ | ?> | ?v
125+
@type dir_state() :: action()
126+
127+
@spec dir_pos(action()) :: TVec.t2()
128+
def dir_pos(?^), do: {1, 0}
129+
def dir_pos(?A), do: {2, 0}
130+
def dir_pos(?<), do: {0, 1}
131+
def dir_pos(?v), do: {1, 1}
132+
def dir_pos(?>), do: {2, 1}
133+
134+
def num_pos(?0), do: {1, 3}
135+
def num_pos(?A), do: {2, 3}
136+
def num_pos(?1), do: {0, 2}
137+
def num_pos(?2), do: {1, 2}
138+
def num_pos(?3), do: {2, 2}
139+
def num_pos(?4), do: {0, 1}
140+
def num_pos(?5), do: {1, 1}
141+
def num_pos(?6), do: {2, 1}
142+
def num_pos(?7), do: {0, 0}
143+
def num_pos(?8), do: {1, 0}
144+
def num_pos(?9), do: {2, 0}
145+
146+
@spec dir_pos(TVec.t2()) :: action()
147+
def dir_action({1, 0}), do: ?>
148+
def dir_action({-1, 0}), do: ?<
149+
def dir_action({0, 1}), do: ?v
150+
def dir_action({0, -1}), do: ?^
151+
152+
@spec cost(list(dir_state()), action(), map()) :: {integer(), list(dir_state()), map()}
153+
def cost(state, action, cache)
154+
155+
def cost([], _action, cache) do
156+
{1, [], cache}
157+
end
158+
159+
def cost([state], action, cache) do
160+
{TVec.manhattan(dir_pos(state), dir_pos(action)) + 1, [action], cache}
161+
end
162+
163+
def cost([state | state_tail] = states, action, cache) do
164+
if cache |> Map.has_key?({states, action}) do
165+
{cost, state} = Map.get(cache, {states, action})
166+
{cost, state, cache}
167+
else
168+
{cost, state_tail, cache} =
169+
moves_between(dir_pos(state), dir_pos(action), {0, 0})
170+
|> Enum.reduce({nil, state_tail, cache}, fn moves, {min, old_state, cache} ->
171+
{val, state, cache} =
172+
(moves ++ [?A])
173+
|> Enum.reduce({0, state_tail, cache}, fn move, {total_cost, state, cache} ->
174+
{cost, state, cache} = cost(state, move, cache)
175+
{total_cost + cost, state, cache}
176+
end)
177+
178+
if val < min do
179+
{val, state, cache}
180+
else
181+
{min, old_state, cache}
182+
end
183+
end)
184+
185+
cache = cache |> Map.put({states, action}, {cost, [action | state_tail]})
186+
187+
{cost, [action | state_tail], cache}
188+
end
189+
end
190+
191+
def cost_num([state | state_tail] = states, action, cache) do
192+
if cache |> Map.has_key?({states, action}) do
193+
{cost, state} = Map.get(cache, {states, action})
194+
{cost, state, cache}
195+
else
196+
{cost, state_tail, cache} =
197+
moves_between(num_pos(state), num_pos(action), {0, 3})
198+
|> Enum.reduce({nil, state_tail, cache}, fn moves, {min, old_state, cache} ->
199+
{val, state, cache} =
200+
(moves ++ [?A])
201+
|> Enum.reduce({0, state_tail, cache}, fn move, {total_cost, state, cache} ->
202+
{cost, state, cache} = cost(state, move, cache)
203+
{total_cost + cost, state, cache}
204+
end)
205+
206+
if val < min do
207+
{val, state, cache}
208+
else
209+
{min, old_state, cache}
210+
end
211+
end)
212+
213+
cache = cache |> Map.put({states, action}, {cost, [action | state_tail]})
214+
215+
{cost, [action | state_tail], cache}
216+
end
217+
end
218+
219+
def moves_between({x1, y1}, {x2, y2}, exclude) do
220+
cond do
221+
x1 == x2 ->
222+
[y1..y2 |> Enum.map(&{x1, &1})]
223+
224+
y1 == y2 ->
225+
[x1..x2 |> Enum.map(&{&1, y1})]
226+
227+
true ->
228+
[
229+
Enum.map(x1..(x2 + if(x2 > x1, do: -1, else: 1)), &{&1, y1}) ++
230+
Enum.map(y1..y2, &{x2, &1}),
231+
Enum.map(y1..(y2 + if(y2 > y1, do: -1, else: 1)), &{x1, &1}) ++
232+
Enum.map(x1..x2, &{&1, y2})
233+
]
234+
|> Enum.filter(&(not Enum.member?(&1, exclude)))
235+
end
236+
|> Enum.map(fn pos_list ->
237+
Enum.zip(pos_list, pos_list |> Enum.drop(1))
238+
|> Enum.map(fn {a, b} ->
239+
dir_action(TVec.sub(b, a))
240+
end)
241+
end)
242+
end
243+
244+
def run do
245+
File.read!(".input.txt")
246+
|> String.split("\n")
247+
|> Enum.map(fn code ->
248+
state = 0..25 |> Enum.map(fn _ -> ?A end)
249+
250+
{cost, _} =
251+
code
252+
|> String.to_charlist()
253+
|> Enum.reduce({0, state}, fn move, {total_cost, state} ->
254+
{cost, state, _} = cost_num(state, move, %{})
255+
{cost + total_cost, state}
256+
end)
257+
258+
code_num =
259+
code
260+
|> String.trim_leading("0")
261+
|> String.trim_trailing("A")
262+
|> String.to_integer()
263+
264+
code_num * cost
265+
end)
266+
|> Enum.sum()
267+
end
268+
end

2024/lib/util.ex

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ defmodule TVec do
3737
def dirs(_) do
3838
[{0, 1}, {-1, 0}, {0, -1}, {1, 0}]
3939
end
40+
41+
def manhattan({x1, y1}, {x2, y2}) do
42+
abs(x1 - x2) + abs(y1 - y2)
43+
end
4044
end
4145

4246
defmodule Grid do

0 commit comments

Comments
 (0)