defmodule AOC.Day8 do use AOC.Day, day: 8 @start "AAA" @stop "ZZZ" def parse_input([instructions, _ | nodes]) do instructions = instructions |> String.to_charlist() |> Stream.cycle() nodes = Enum.map(nodes, fn s -> %{"id" => id, "left" => left, "right" => right} = Regex.named_captures( ~r/^(?[[:alnum:]]{3}) = \((?[[:alnum:]]{3}), (?[[:alnum:]]{3})\)$/, s ) {id, {left, right}} end) |> Enum.into(%{}) {instructions, nodes} end def part1({instructions, nodes}) do reach_end(instructions, nodes) end def part2({instructions, nodes}) do IO.puts("Use LCM calculator for the following numbers :)") Enum.filter(nodes, fn {k, _v} -> String.ends_with?(k, "A") end) |> Enum.map(fn {k, _v} -> find_cycle(instructions, nodes, k) end) |> Enum.map(&elem(&1, 0)) |> Enum.map(&Integer.to_string/1) |> Enum.join(" ") end def reach_end(instructions, nodes) do Enum.reduce_while(instructions, {@start, 0}, fn instruction, {current_node, step_count} -> if current_node == @stop do {:halt, step_count} else {left, right} = Map.fetch!(nodes, current_node) next_node = case instruction do ?L -> left ?R -> right end {:cont, {next_node, step_count + 1}} end end) end def find_cycle(instructions, nodes, start) do Enum.reduce_while(instructions, {start, 0, nil}, fn instruction, {current_node, step_count, last_end_count} -> if String.ends_with?(current_node, "Z") and last_end_count != nil do {:halt, {last_end_count, step_count - last_end_count}} else last_end_count = if String.ends_with?(current_node, "Z"), do: step_count, else: last_end_count {left, right} = Map.fetch!(nodes, current_node) next_node = case instruction do ?L -> left ?R -> right end {:cont, {next_node, step_count + 1, last_end_count}} end end) end end