Advent of Code 2024 - Day 21
Mix.install([
{:kino, github: "livebook-dev/kino"},
{:memoize, "~> 1.4"},
])
kino_input = Kino.Input.textarea("Please paste your input file: ")
Part 1
input = Kino.Input.read(kino_input)
# Gave up, https://github.com/ibarakaiev/advent-of-code-2024/blob/main/lib/advent_of_code2024/day21.ex
defmodule Keypad do
@numeric_keypad %{
"0" => {3, 1},
"A" => {3, 2},
"1" => {2, 0},
"2" => {2, 1},
"3" => {2, 2},
"4" => {1, 0},
"5" => {1, 1},
"6" => {1, 2},
"7" => {0, 0},
"8" => {0, 1},
"9" => {0, 2},
}
@directional_keypad %{
"<" => {1, 0},
"v" => {1, 1},
">" => {1, 2},
"^" => {0, 1},
"A" => {0, 2},
}
def expand_sequence(sequence, [], _level) do
{Enum.count(sequence), []}
end
def expand_sequence(sequence, current_chars, level) do
sequence
|> Enum.reduce({0, current_chars},
fn target_char, {acc, [current_char | rest_chars]} ->
current_char
|> sequence_for_keypad(target_char, if(level == 0, do: :numeric, else: :directional))
|> Enum.map(fn sequence ->
{expanded_sequence, updated_chars} = expand_sequence(sequence, rest_chars, level + 1)
{acc + expanded_sequence, [target_char | updated_chars]}
end)
|> Enum.min_by(fn {sequence_length, _} -> sequence_length end)
end)
end
defp sequence_for_keypad(starting_char, target_char, keypad_type) do
keypad =
case keypad_type do
:numeric -> @numeric_keypad
:directional -> @directional_keypad
end
{starting_i, starting_j} = keypad[starting_char]
{target_i, target_j} = keypad[target_char]
horizontal_moves = List.duplicate(if(target_j > starting_j, do: ">", else: "<"), abs(target_j - starting_j))
vertical_moves = List.duplicate(if(target_i > starting_i, do: "v", else: "^"), abs(target_i - starting_i))
cond do
(keypad_type == :numeric and starting_i == 3 and target_j == 0) or
(keypad_type == :directional and starting_i == 0 and target_j == 0) ->
[vertical_moves ++ horizontal_moves ++ ["A"]]
(keypad_type == :numeric and starting_j == 0 and target_i == 3) or
(keypad_type == :directional and starting_j == 0 and target_i == 0) ->
[horizontal_moves ++ vertical_moves ++ ["A"]]
true ->
[horizontal_moves ++ vertical_moves ++ ["A"], vertical_moves ++ horizontal_moves ++ ["A"]]
end
end
end
input
|> String.split("\n", trim: true)
|> Enum.map(&String.graphemes/1)
|> Enum.map(fn number ->
{sequence, _} = Keypad.expand_sequence(number, List.duplicate("A", 2+1), 0)
numeric_part = number |> Enum.reject(&(&1 == "A")) |> Enum.join() |> String.to_integer()
sequence * numeric_part
end)
|> Enum.sum()
Part 2
input = Kino.Input.read(kino_input)
# Gave up, https://github.com/ibarakaiev/advent-of-code-2024/blob/main/lib/advent_of_code2024/day21.ex
defmodule KeypadPart2 do
use Memoize
@numeric_keypad %{
"0" => {3, 1},
"A" => {3, 2},
"1" => {2, 0},
"2" => {2, 1},
"3" => {2, 2},
"4" => {1, 0},
"5" => {1, 1},
"6" => {1, 2},
"7" => {0, 0},
"8" => {0, 1},
"9" => {0, 2},
}
@directional_keypad %{
"<" => {1, 0},
"v" => {1, 1},
">" => {1, 2},
"^" => {0, 1},
"A" => {0, 2},
}
defmemo expand_sequence(sequence, [], _level) do
{Enum.count(sequence), []}
end
defmemo expand_sequence(sequence, current_chars, level) do
sequence
|> Enum.reduce({0, current_chars},
fn target_char, {acc, [current_char | rest_chars]} ->
current_char
|> sequence_for_keypad(target_char, if(level == 0, do: :numeric, else: :directional))
|> Enum.map(fn sequence ->
{expanded_sequence, updated_chars} = expand_sequence(sequence, rest_chars, level + 1)
{acc + expanded_sequence, [target_char | updated_chars]}
end)
|> Enum.min_by(fn {sequence_length, _} -> sequence_length end)
end)
end
defp sequence_for_keypad(starting_char, target_char, keypad_type) do
keypad =
case keypad_type do
:numeric -> @numeric_keypad
:directional -> @directional_keypad
end
{starting_i, starting_j} = keypad[starting_char]
{target_i, target_j} = keypad[target_char]
horizontal_moves = List.duplicate(if(target_j > starting_j, do: ">", else: "<"), abs(target_j - starting_j))
vertical_moves = List.duplicate(if(target_i > starting_i, do: "v", else: "^"), abs(target_i - starting_i))
cond do
(keypad_type == :numeric and starting_i == 3 and target_j == 0) or
(keypad_type == :directional and starting_i == 0 and target_j == 0) ->
[vertical_moves ++ horizontal_moves ++ ["A"]]
(keypad_type == :numeric and starting_j == 0 and target_i == 3) or
(keypad_type == :directional and starting_j == 0 and target_i == 0) ->
[horizontal_moves ++ vertical_moves ++ ["A"]]
true ->
[horizontal_moves ++ vertical_moves ++ ["A"], vertical_moves ++ horizontal_moves ++ ["A"]]
end
end
end
input
|> String.split("\n", trim: true)
|> Enum.map(&String.graphemes/1)
|> Enum.map(fn number ->
{sequence, _} = KeypadPart2.expand_sequence(number, List.duplicate("A", 25+1), 0)
numeric_part = number |> Enum.reject(&(&1 == "A")) |> Enum.join() |> String.to_integer()
sequence * numeric_part
end)
|> Enum.sum()