Advent of code 2024 Day 9 Part 1 を Livebook で楽しむ

## はじめに

Advent of code 2024 Day 9 の Part 1 を解きます

問題文はこちら

https://adventofcode.com/2024/day/9

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

https://github.com/RyoWakabayashi/elixir-learning/blob/main/livebooks/aoc/2024/2024_day9_part1.livemd

## セットアップ

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

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

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

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

## 入力の取得

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

![スクリーンショット 2024-12-11 13.35.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/f8bdc212-bcd1-4638-2138-4dfd9382c17d.png)

## 私の答え

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

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

まずは入力例で考えます

```elixir
sample_input = "2333133121414131402"
```

入力はデータ使用領域の数と空き領域の数を交互に表しています

データ使用領域に 0 から連番の ID を振り、空き領域の ID を `nil` として領域ブロックの配列を作りましょう

交互になっているので、 2 で割った余りで使用領域か否かを判定でき、 ID は 2 で割った結果になります

後で使いたいので、ついでに空き領域のブロック数もカウントしておきます

```elixir
get_blocks = fn input ->
  input
  |> String.codepoints()
  |> Enum.with_index()
  |> Enum.reduce({[], 0}, fn {num, index}, {acc_list, acc_num_blanck} ->
    num = String.to_integer(num)

    {id, num_blanck} =
      case rem(index, 2) do
        0 ->
          {div(index, 2), 0}
        _ ->
          {nil, num}
      end

    {
      acc_list ++ List.duplicate(id, num),
      acc_num_blanck + num_blanck
    }
  end)
end
```

```elixir
{blocks, num_blank} = get_blocks.(sample_input)
```

実行結果

```elixir
{[0, 0, nil, nil, nil, 1, 1, 1, nil, nil, nil, 2, nil, nil, nil, 3, 3, 3, nil, 4, 4, nil, 5, 5, 5,
  5, nil, 6, 6, 6, 6, nil, 7, 7, 7, nil, 8, 8, 8, 8, 9, 9], 14}
```

空き状態のブロック（`nil` になっている箇所）に後ろから順にファイルで使用いているブロックを詰めていきます

最大でも、ブロックの交換回数は今ある空きブロックの数です

操作しやすいようにブロックを逆順にして、交換先の空きブロックを探し、使用ブロックの ID で埋めていきます

空きブロックと交換するので、先頭（元の末尾）のブロックは不要となるため、 `tl()` で先頭以外を取り出します

空きブロックがなくなったら処理を終了させます

最後に、逆順にしていたのを元に戻します

```elixir
compact = fn blocks, num_blank ->
  1..num_blank
  |> Enum.reduce_while(Enum.reverse(blocks), fn _, acc_blocks ->
    case acc_blocks |> Enum.reverse() |> Enum.find_index(&(is_nil(&1))) do
      nil ->
        {:halt, acc_blocks}
      index ->
        blank_index = length(acc_blocks) - 1 - index
  
        {
          :cont,
          acc_blocks
          |> List.update_at(blank_index, fn _ -> hd(acc_blocks) end)
          |> tl()
        }
    end
  end)
  |> Enum.reverse()
end
```

```elixir
compacted_blocks = compact.(blocks, num_blank)
```

実行結果

```elixir
[0, 0, 9, 9, 8, 1, 1, 1, 8, 8, 8, 2, 7, 7, 7, 3, 3, 3, 6, 4, 4, 6, 5, 5, 5, 5, 6, 6]
```

このブロックについて、チェックサムを計算します

```elixir
get_checksum = fn blocks ->
  blocks
  |> Enum.with_index()
  |> Enum.map(fn {num, index} ->
    num * index
  end)
  |> Enum.sum()
end
```

```elixir
get_checksum.(compacted_blocks)
```

実行結果

```elixir
1928
```

これを実際の入力に対して行えば答えが出ます

```elixir
{blocks, num_blank} = get_blocks.(puzzle_input)
```

```elixir
compacted_blocks = compact.(blocks, num_blank)
```

```elixir
get_checksum.(compacted_blocks)
```

</details>

## まとめ

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

![aoc2024_day9_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/b6cc46aa-222c-cb28-07e9-667cb19fc870.png)

ブロックを一個一個交換する手順にしたため、結構実行時間が長くなってしまいました

Part 2 は全く違う構造で解いています

https://qiita.com/RyoWakabayashi/private/9681174c1fb1e9e7b0d9