闘魂Elixir ── Advent of code 2023 Day 7 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/5361b81407b15e7c2f00

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

https://adventofcode.com/2023

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

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

問題はこちら

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

Part 1 はこちら

https://qiita.com/RyoWakabayashi/items/0dbae9f3b2da5bbab5cd

## セットアップ

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

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

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

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

## 入力の取得

Day 7 の入力を取得します

![スクリーンショット 2023-12-26 18.06.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/24270377-594d-b27a-76de-77ef809d65e3.png)

## 私の答え

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

<details><summary>details</summary>

回答用のモジュールです

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

```elixir
defmodule Resolver do
  @card_strength %{
    "T" => 10,
    "J" => 1,
    "Q" => 12,
    "K" => 13,
    "A" => 14
  }

  def parse(input) do
    input
    |> String.split("\n")
    |> Enum.map(fn line ->
      line
      |> String.split(" ", trim: true)
      |> then(fn [hand, bet] ->
        hand =
          hand
          |> String.codepoints()
          |> Enum.map(fn card ->
            strength = Map.get(@card_strength, card)
            if is_nil(strength) do
              String.to_integer(card)
            else
              strength
            end
          end)

        has_j = Enum.member?(hand, 1)

        {min_freq, max_freq, uniq} =
          hand
          |> Enum.filter(& &1 != 1)
          |> Enum.frequencies()
          |> Map.values()
          |> then(fn frequencies ->
            if frequencies == [] do
              {0, 0, 0}
            else
              {
                Enum.min(frequencies),
                Enum.max(frequencies),
                Enum.count(frequencies)
              }
            end
          end)

        type_strength =
          case {min_freq, max_freq, uniq, has_j} do
            {_, _, 0, _} ->
              6
            {_, _, 1, _} ->
              6
            {2, _, 2, _} ->
              4
            {_, _, 2, _} ->
              5
            {_, 3, 3, false} ->
              3
            {_, 2, 3, false} ->
              2
            {_, _, 3, true} ->
              3
            {_, _, 4, _} ->
              1
            {_, _, 5, _} ->
              0
          end

        hand_strength =
          0..4
          |> Enum.map(fn index ->
            Enum.at(hand, index) * Integer.pow(14, 4 - index)
          end)
          |> Enum.sum()
          |> Kernel.+(type_strength * Integer.pow(14, 5))

        %{
          hand_strength: hand_strength,
          bet: String.to_integer(bet)
        }
      end)
    end)
  end

  def resolve(games) do
    games
    |> Enum.sort(& &1.hand_strength < &2.hand_strength)
    |> Enum.with_index()
    |> Enum.map(fn {game, index} ->
      game.bet * (index + 1)
    end)
    |> Enum.sum()
  end
end
```

カードの強さの計算方法（14進数）は Part 1 と同じです

J を 11 ではなくジョーカー（役を最も強くするように変化するカード）として扱います

ただし、タイプの識別以外では最弱として扱います

役は以下の数の組み合わせで決定できます

- ジョーカー以外の種類毎の最大枚数
- ジョーカー以外の種類毎の最小枚数
- ジョーカー以外の種類数
- ジョーカーの有無

全ての場合分け

- ジョーカー以外のカードが 0 種類の場合、つまり全てジョーカーなので必ずファイブカードです
- ジョーカー以外のカードが 1 種類の場合、他のカードを全て同じカードにするので必ずファイブカードです
- ジョーカー以外のカードが 2 種類の場合
  - ジョーカー以外の種類毎の最小枚数が 2 の場合、つまり XXYYJ か XXYYY の場合はフルハウスしか作れません
  - それ以外の場合、つまり XYJJJ か XYYJJ か XYYYJ の場合、必ずフォーカードになります
- ジョーカー以外のカードが 3 種類の場合
  - ジョーカーがない場合
    - 種類毎の最大枚数が 3 の場合、つまり XYZZZ の場合はスリーカードです
    - それ以外の場合、つまり XYYZZ の場合はツーペアです
  - ジョーカーがある場合
    - XYZJJ か XYZZJ ですが、いずれも必ずスリーカードになります
- ジョーカー以外のカードが 4 種類の場合、 XYZWJ か XYZWW なので必ずワンペアになります
- カードが 5 種類の場合、ハイカードです

`resolve` 関数は Part 1 と同じです

</details>

## まとめ

ジョーカーがあるときの場合分けが更に面白いですね