Advent of code 2024 Day 4 Part 2 を Livebook で楽しむ

## はじめに

Advent of code 2024 Day 4 の Part 2 を解きます

問題文はこちら

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

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

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

## セットアップ

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

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

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

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

## 入力の取得

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

![スクリーンショット 2024-12-04 19.17.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/e2318148-82ba-c5a4-9d62-2efae32dad76.png)

## 私の答え

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

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

まずは小さい入力例で解いてみます

```elixir
input =
  """
  MMMSXXMASM
  MSAMXMSMSA
  AMXSXMAAMM
  MSAMASMSMX
  XMASAMXAMM
  XXAMMXXAMA
  SMSMSASXSS
  SAXAMASAAA
  MAMMMXMMMM
  MXMXAXMASX
  """
```

斜めに読んで `MAS` を探すため、入力をずらして回転します

```elixir
turned_input =
  input
  |> String.trim()
  |> String.split("\n")
  |> Enum.with_index()
  |> Enum.map(fn {row, index} ->
    String.duplicate(".", index) <> row <> String.duplicate(".", String.length(row) - index - 1)
  end)
  |> Enum.map(&String.codepoints(&1))
  |> Enum.zip()
  |> Enum.map(fn col -> col |> Tuple.to_list() |> Enum.join() end)
  |> Enum.join("\n")

IO.puts(turned_input)
```

標準出力

```tetx
M.........
MM........
MSA.......
SAMM......
XMXSX.....
XXSAMX....
MMXMAXS...
ASMASAMS..
SMASAMSAM.
MSAMMMMXAM
.AMSXXSAMX
..MMAXAMMM
...XMASAMX
....MMXSXA
.....ASAMX
......SAMM
.......AMA
........MS
.........X
```

斜めの中で `MAS` を `M+S` 、 `SAM` を `S+M` に変換します

```elixir
turned_input
|> String.replace(~r/MAS/, "M+S")
|> String.replace(~r/SAM/, "S+M")
|> IO.puts()
```

標準出力

```
M.........
MM........
MSA.......
S+MM......
XMXSX.....
XXS+MX....
MMXMAXS...
ASM+S+MS..
SM+S+MS+M.
MS+MMMMXAM
.AMSXXS+MX
..MMAXAMMM
...XM+S+MX
....MMXSXA
.....AS+MX
......S+MM
.......AMA
........MS
.........X
```

元の角度に戻して `+` 以外を `.` に変換して見やすくします

```elixir
output_a =
  turned_input
  |> String.replace(~r/MAS/, "M+S")
  |> String.replace(~r/SAM/, "S+M")
  |> String.split("\n")
  |> Enum.map(&String.codepoints(&1))
  |> Enum.zip()
  |> Enum.map(fn col ->
    col
    |> Tuple.to_list()
    |> Enum.join()
    |> String.trim(".")
    |> String.replace(~r/[A-Z]/, ".")
  end)

output_a
|> Enum.join("\n")
|> IO.puts()
```

標準出力

```
..........
..+.......
......++..
..+.+.....
....+.....
..+....+..
..........
.+.+.+.++.
..........
..........
```

逆方向の斜めで読んだとき、同じ位置に `+` があれば、交差したとみなすことができます

逆方向で試す前に、回転と置換を関数化しておきます

```elixir
turn = fn input, direction ->
  input
  |> String.trim()
  |> String.split("\n")
  |> Enum.with_index()
  |> Enum.map(fn {row, index} ->
    case direction do
      :left ->
        String.duplicate(".", index) <> row <> String.duplicate(".", String.length(row) - index - 1)
      _ ->
        String.duplicate(".", String.length(row) - index - 1) <> row <> String.duplicate(".", index)
    end
  end)
  |> Enum.map(&String.codepoints(&1))
  |> Enum.zip()
  |> Enum.map(fn col -> col |> Tuple.to_list() |> Enum.join() end)
  |> Enum.join("\n")
end
```

```elixir
replase_mas = fn turned_input ->
  turned_input
  |> String.replace(~r/MAS/, "M+S")
  |> String.replace(~r/SAM/, "S+M")
  |> String.split("\n")
  |> Enum.map(&String.codepoints(&1))
  |> Enum.zip()
  |> Enum.map(fn col ->
    col
    |> Tuple.to_list()
    |> Enum.join()
    |> String.trim(".")
    |> String.replace(~r/[A-Z]/, ".")
  end)
end
```

関数を使って回転と置換を実行します

```elixir
output_b =
  input
  |> turn.(:right)
  |> replase_mas.()

output_b
|> Enum.join("\n")
|> IO.puts()
```

標準出力

```text
..........
..+.......
......++..
..+.+.....
..+.......
..........
.....+....
.+.+.+.+..
.+........
..........
```

`MAS` が交差した点 = 左回転した方と右回転した方の両方で `+` になった点を数えます

```elixir
Enum.zip(output_a, output_b)
|> Enum.map(fn {row_a, row_b} ->
  Enum.zip(
    String.codepoints(row_a),
    String.codepoints(row_b)
  )
  |> Enum.count(fn ab -> ab == {"+", "+"} end)
end)
|> Enum.sum()
```

実行結果

```elixir
9
```

同じことを実際の入力に対して実行します

```elixir
output_a =
  puzzle_input
  |> turn.(:left)
  |> replase_mas.()

output_b =
  puzzle_input
  |> turn.(:right)
  |> replase_mas.()

Enum.zip(output_a, output_b)
|> Enum.map(fn {row_a, row_b} ->
  Enum.zip(
    String.codepoints(row_a),
    String.codepoints(row_b)
  )
  |> Enum.count(fn ab -> ab == {"+", "+"} end)
end)
|> Enum.sum()
```

</details>

## まとめ

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

![aoc2024_day4_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/5b9db672-2dcd-a4ee-3afc-a10263c2637e.png)

「交差する点」をどう探すかが面白い問題でした

Part 1 もそうですが、出力がアートのようになってキレイですね