Advent of code 2015 Day 23 Part 1 & Part 2 を Livebook で楽しむ

## はじめに

Advent of code 2024 の準備として、過去回の Advent of code 2015 を Livebook で楽しみます

本記事では Day 23 の Part 1 と Part 2 を解きます

問題文はこちら

https://adventofcode.com/2015/day/23

実装したノートブックはこちら

https://github.com/RyoWakabayashi/elixir-learning/blob/main/livebooks/aoc/2015/2015_day23_part1.livemd

https://github.com/RyoWakabayashi/elixir-learning/blob/main/livebooks/aoc/2015/2015_day23_part2.livemd

## セットアップ

Kino AOC をインストールします

```elixir
Mix.install([
  {:kino_aoc, "~> 0.1"}
])
```

Kino AOC の使い方はこちらを参照

https://qiita.com/RyoWakabayashi/items/3d3abcf9134cee4dff05

## 入力の取得

"Advent of Code Helper" スマートセルを追加し、 Day 23 の入力を取得します

![スクリーンショット 2024-12-04 21.42.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/a7bd2bc6-8d80-70ea-cb6d-15e26153d386.png)

## 私の答え

私の答えです。
折りたたんでおきます。
▶を押して開いてください。

### Part 1

<details><summary>回答</summary>

まず小さい入力例で試してみましょう

```elixir
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` の関数を評価するコード内に定義し、

```elixir
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
```

各変数が正しく評価されるか確認していきます

:::note info
実行結果は全て想定通りになります
:::

```elixir
eval_instruction.("hlf a", {4, 6, 0})
```

```elixir
eval_instruction.("hlf b", {4, 6, 0})
```

```elixir
eval_instruction.("tpl a", {1, 1, 0})
```

```elixir
eval_instruction.("inc a", {0, 1, 0})
```

```elixir
eval_instruction.("jmp 5", {0, 1, 0})
```

```elixir
eval_instruction.("jmp -5", {0, 1, 10})
```

```elixir
eval_instruction.("jie a, 3", {2, 1, 0})
```

```elixir
eval_instruction.("jie a, 3", {3, 1, 0})
```

```elixir
eval_instruction.("jio a, 3", {1, 1, 0})
```

```elixir
eval_instruction.("jio a, 3", {2, 1, 0})
```

入力例に対して実行してみます

index が命令の範囲外に出るまで繰り返します

```elixir
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)
```

実行結果

```elixir
{1, 0, 3}
```

実際の入力に対して実行します

```elixir
instructions = String.split(puzzle_input, "\n")
```

```elixir
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)
```

</details>

### Part 2

<details><summary>回答</summary>

a の初期値を 1 にして実行するだけです

```elixir
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)
```

</details>

## まとめ

問題文から ChatGPT に画像を生成してもらいました

![aoc2015_day23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/f7239407-ebb2-35bb-43f5-14384e39699d.png)

思ったよりすんなり解けました

メタプログラミングも面白いですね