Day 8
Solution
defmodule D08 do
def parse_input(input) do
[moves | network] = input |> String.trim() |> String.split("\n", trim: true)
network =
Enum.map(network, fn node ->
[node, paths] = String.split(node, " = ")
[left, right] = paths |> String.trim("(") |> String.trim(")") |> String.split(", ")
{node, %{left: left, right: right}}
end)
|> Enum.into(%{})
{String.graphemes(moves), network}
end
def do_move(network, pos, "L"), do: network[pos][:left]
def do_move(network, pos, "R"), do: network[pos][:right]
def p1(moves, network) when is_list(moves) and is_map(network) do
Stream.cycle(moves)
|> Enum.reduce_while({"AAA", 0}, fn move, {pos, steps_taken} ->
pos = do_move(network, pos, move)
steps_taken = steps_taken + 1
if pos == "ZZZ", do: {:halt, steps_taken}, else: {:cont, {pos, steps_taken}}
end)
end
def p2(moves, network) when is_list(moves) and is_map(network) do
ghost_steps(moves, network) |> lcm()
end
def ghost_steps(moves, network) do
nodes = Map.keys(network)
states =
nodes
|> Enum.filter(&String.ends_with?(&1, "A"))
|> Enum.map(fn node -> {node, {node, 0}} end)
|> Enum.into(%{})
z_nodes = nodes |> Enum.filter(&String.ends_with?(&1, "Z")) |> MapSet.new()
Stream.cycle(moves)
|> Enum.reduce_while(
{states, z_nodes, []},
fn move, {states, z_nodes, steps} ->
if map_size(states) == 0 do
{:halt, steps}
else
states =
Enum.map(states, fn {node, {pos, step}} ->
pos = do_move(network, pos, move)
{node, {pos, step + 1}}
end)
|> Enum.into(states)
{states, steps} =
Enum.filter(states, fn {_node, {pos, _}} -> pos in z_nodes end)
|> Enum.reduce(
{states, steps},
fn {finished_node, {_pos, step}}, {states, steps} ->
{Map.delete(states, finished_node), [step | steps]}
end
)
{:cont, {states, z_nodes, steps}}
end
end
)
end
def lcm(nums) do
expr = "x=lcm(#{inspect(nums)}); println(x)"
{res, 0} = System.cmd("julia", ["--eval", expr], stderr_to_stdout: true)
String.trim(res) |> String.to_integer()
end
end
{:module, D08, <<70, 79, 82, 49, 0, 0, 26, ...>>, {:lcm, 1}}
Tests
ExUnit.start(autorun: false)
defmodule D08Test do
use ExUnit.Case, async: true
@test_input1 D08.parse_input("""
RL
AAA = (BBB, CCC)
BBB = (DDD, EEE)
CCC = (ZZZ, GGG)
DDD = (DDD, DDD)
EEE = (EEE, EEE)
GGG = (GGG, GGG)
ZZZ = (ZZZ, ZZZ)
""")
@test_input2 D08.parse_input("""
LLR
AAA = (BBB, BBB)
BBB = (AAA, ZZZ)
ZZZ = (ZZZ, ZZZ)
""")
@test_input3 D08.parse_input("""
LR
11A = (11B, XXX)
11B = (XXX, 11Z)
11Z = (11B, XXX)
22A = (22B, XXX)
22B = (22C, 22C)
22C = (22Z, 22Z)
22Z = (22B, 22B)
XXX = (XXX, XXX)
""")
@input Path.join(__DIR__, "inputs/d08") |> File.read!() |> D08.parse_input()
test "part 1 works" do
{moves, network} = @test_input1
assert D08.p1(moves, network) == 2
{moves, network} = @test_input2
assert D08.p1(moves, network) == 6
{moves, network} = @input
assert D08.p1(moves, network) == 19783
end
test "part 2 works" do
{moves, network} = @test_input3
assert D08.p2(moves, network) == 6
{moves, network} = @input
assert D08.p2(moves, network) == 9_177_460_370_549
end
end
ExUnit.run()
..
Finished in 0.3 seconds (0.3s async, 0.00s sync)
2 tests, 0 failures
Randomized with seed 158468
%{excluded: 0, failures: 0, skipped: 0, total: 2}