闘魂Elixir ── Advent of code 2023 Day 4 Part 2 を Livebook で楽しむ

<b><font color="red">$\huge{元氣ですかーーーーッ！！！}$</font></b>
<b><font color="red">$\huge{元氣があればなんでもできる！}$</font></b>

<b><font color="red">$\huge{闘魂とは己に打ち克つこと。}$</font></b>
<b><font color="red">$\huge{そして闘いを通じて己の魂を磨いていく}$</font></b>
<b><font color="red">$\huge{ことだと思います}$</font></b>

https://qiita.com/advent-calendar/2023/elixir

## はじめに

@torifukukaiou さんの ~~パク~~ リスペクト記事です

https://qiita.com/torifukukaiou/items/ece3f4bc2948ca4ac7e4

Elixir Livebook で Advent of Code 2023 の問題を解いてみます

https://adventofcode.com/2023

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

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

問題はこちら

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

Part 1 はこちら

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

## セットアップ

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

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

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

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

## 入力の取得

Day 4 の入力を取得します

![スクリーンショット 2023-12-26 13.10.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/1cde497d-a54f-f56e-b8b2-bc14d20fb936.png)

## 私の答え

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

<details><summary>details</summary>

回答用のモジュールです

入力を扱いやすい形にするための `parse` と、回答を作るための `resolve` 関数を持っています

```elixir
defmodule Resolver do
  def parse(input) do
    input
    |> String.split("\n")
    |> Enum.with_index()
    |> Enum.into(%{}, fn {line, index} ->
      [_, card] = String.split(line, ":")
      [winning_card, my_card] = String.split(card, "|")
      winning_card = parse_card(winning_card)
      my_card = parse_card(my_card)

      nuber_of_matches =
        winning_card
        |> Enum.count(fn winnig_number ->
          Enum.member?(my_card, winnig_number)
        end)

      copies = get_copies(index + 1, nuber_of_matches)

      {index + 1, copies}
    end)
  end

  defp get_copies(_, 0), do: []
  defp get_copies(current_index, nuber_of_matches) do
    Enum.to_list((current_index + 1)..(current_index + nuber_of_matches))
  end

  defp parse_card(card) do
    card
    |> String.split(" ")
    |> Enum.filter(fn value -> value != "" end)
    |> Enum.map(fn value -> String.to_integer(value) end)
  end

  def resolve(games) do
    games
    |> Enum.map(fn {card_index, _} ->
      get_card(games, card_index, 0)
    end)
    |> Enum.sum()
  end

  defp get_card(games, card_index, acc) do
    games[card_index]
    |> Enum.map(fn next_card_index ->
      get_card(games, next_card_index, acc)
    end)
    |> Enum.sum()
    |> Kernel.+(1)
  end
end
```

今回は `parse` も Part 1 から変えています

パース時点でマッチ数を取得し、カード毎に次に取得できるカードの番号の配列を設定しておきます

例

入力
```
Card 1: 41 48 83 86 17 | 83 86  6 31 17  9 48 53
Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19
Card 3:  1 21 53 59 44 | 69 82 63 72 16 21 14  1
Card 4: 41 92 73 84 69 | 59 84 76 51 58  5 54 83
Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36
Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11
```

パース結果
```elixir
%{1 => [2, 3, 4, 5], 2 => [3, 4], 3 => [4, 5], 4 => [5], 5 => [], 6 => []}
```

1 のカードからは 2, 3, 4, 5 のカードが取得でき、 2 のカードからは 3, 4 のカードが取得できる、という情報担っています

`resolve` でパース結果の情報を再起的に処理し、最終的な枚数を取得します

再起処理の中では、自分から連なるカードの枚数 + 1（自分自身）という計算をしています

```elixir
  defp get_card(games, card_index, acc) do
    games[card_index]
    |> Enum.map(fn next_card_index ->
      get_card(games, next_card_index, acc)
    end)
    |> Enum.sum()
    |> Kernel.+(1)
  end
```

おそらく最後から逆に計算して、計算結果を覚えておいた方が効率的に計算できそうですが、今回はそこまでやらずに愚直に毎回計算しています

</details>

## まとめ

ちょっとややこしい再帰処理が出てきました

徐々に難しくなっている感じがしますね