はじめに
Advent of code 2024 の準備として、過去回の Advent of code 2015 を Livebook で楽しみます
本記事では Day 23 の Part 1 と Part 2 を解きます
問題文はこちら
実装したノートブックはこちら
セットアップ
Kino AOC をインストールします
Mix.install([
{:kino_aoc, "~> 0.1"}
])
Kino AOC の使い方はこちらを参照
入力の取得
"Advent of Code Helper" スマートセルを追加し、 Day 23 の入力を取得します
私の答え
私の答えです。
折りたたんでおきます。
▶を押して開いてください。
Part 1
回答
まず小さい入力例で試してみましょう
instructions =
"""
inc a
jio a, +2
tpl a
inc a
"""
|> String.trim()
|> String.split("\n")
各行の命令を Elixir のコードに変換して評価する、という変則的なやり方を思いついてしまったので、それで解きました
正規表現 ~r/(?<func>[a-z]+) (?<var>[a-z]*),* *(?<offset>(\+|\-)*\d*)/
で命令の関数、変数を取得します
命令の内容を Elixir で使える形に変換します(次に評価する行を知る必要があるため、引数に index を加えています)
例
-
inc a
->inc.({a, index})
-
jmp 4
->jmp.({a, index}, 4)
-
jio a, 4
->jio.({a, index}, 4)
実際の入力には変数 b
も存在するため、入出力に入れておきます
hlf
や tpl
の関数を評価するコード内に定義し、
eval_instruction = fn instruction, {a, b, index} ->
Regex.named_captures(
~r/(?<func>[a-z]+) (?<var>[a-z]*),* *(?<offset>(\+|\-)*\d*)/,
instruction
)
|> then(fn %{"func" => func, "var" => var, "offset" => offset} ->
var = if var == "", do: "a", else: var
instruction =
cond do
offset == "" ->
"#{func}.({#{var}, #{index}})"
func == "jmp" ->
"#{func}.({a, #{index}}, #{String.to_integer(offset)})"
true ->
"#{func}.({#{var}, #{index}}, #{String.to_integer(offset)})"
end
|> IO.inspect()
"""
hlf = fn {a, index} -> {div(a, 2), index + 1} end
tpl = fn {a, index} -> {a * 3, index + 1} end
inc = fn {a, index} -> {a + 1, index + 1} end
jmp = fn {a, index}, offset -> {a, index + offset} end
jie = fn {a, index}, offset ->
if rem(a, 2) == 0 do
{a, index + offset}
else
{a, index + 1}
end
end
jio = fn {a, index}, offset ->
case a do
1 -> {a, index + offset}
_ -> {a, index + 1}
end
end
a = #{a}
b = #{b}
{#{var}, index} = #{instruction}
{a, b, index}
"""
end)
|> Code.eval_string()
|> elem(0)
end
各変数が正しく評価されるか確認していきます
実行結果は全て想定通りになります
eval_instruction.("hlf a", {4, 6, 0})
eval_instruction.("hlf b", {4, 6, 0})
eval_instruction.("tpl a", {1, 1, 0})
eval_instruction.("inc a", {0, 1, 0})
eval_instruction.("jmp 5", {0, 1, 0})
eval_instruction.("jmp -5", {0, 1, 10})
eval_instruction.("jie a, 3", {2, 1, 0})
eval_instruction.("jie a, 3", {3, 1, 0})
eval_instruction.("jio a, 3", {1, 1, 0})
eval_instruction.("jio a, 3", {2, 1, 0})
入力例に対して実行してみます
index が命令の範囲外に出るまで繰り返します
0..10
|> Enum.reduce_while({0, 0, 0}, fn _, {a, b, index} ->
{next_a, next_b, next_index} =
instructions
|> Enum.at(index)
|> eval_instruction.({a, b, index})
|> IO.inspect()
if next_index >= length(instructions) - 1 do
{:halt, {next_a, next_b, next_index}}
else
{:cont, {next_a, next_b, next_index}}
end
end)
実行結果
{1, 0, 3}
実際の入力に対して実行します
instructions = String.split(puzzle_input, "\n")
0..1000
|> Enum.reduce_while({0, 0, 0}, fn _, {a, b, index} ->
{next_a, next_b, next_index} =
instructions
|> Enum.at(index)
|> eval_instruction.({a, b, index})
|> IO.inspect()
if next_index >= length(instructions) do
{:halt, {next_a, next_b, next_index}}
else
{:cont, {next_a, next_b, next_index}}
end
end)
Part 2
回答
a の初期値を 1 にして実行するだけです
0..10000
|> Enum.reduce_while({1, 0, 0}, fn _, {a, b, index} ->
{next_a, next_b, next_index} =
instructions
|> Enum.at(index)
|> eval_instruction.({a, b, index})
|> IO.inspect()
if next_index >= length(instructions) do
{:halt, {next_a, next_b, next_index}}
else
{:cont, {next_a, next_b, next_index}}
end
end)
まとめ
問題文から ChatGPT に画像を生成してもらいました
思ったよりすんなり解けました
メタプログラミングも面白いですね