LoginSignup
9
1

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

Last updated at Posted at 2023-12-26

$\huge{元氣ですかーーーーッ!!!}$
$\huge{元氣があればなんでもできる!}$

$\huge{闘魂とは己に打ち克つこと。}$
$\huge{そして闘いを通じて己の魂を磨いていく}$
$\huge{ことだと思います}$

はじめに

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

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

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

問題はこちら

Part 1 はこちら

セットアップ

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

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

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

入力の取得

Day 7 の入力を取得します

スクリーンショット 2023-12-26 18.06.09.png

私の答え

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

details

回答用のモジュールです

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

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 と同じです

まとめ

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

9
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
1