LoginSignup
10
1

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

Last updated at Posted at 2023-12-26

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

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

はじめに

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

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

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

問題はこちら

セットアップ

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" => 11,
    "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)

        {min_freq, max_freq, uniq} =
          hand
          |> Enum.frequencies()
          |> Map.values()
          |> then(fn frequencies ->
            {
              Enum.min(frequencies),
              Enum.max(frequencies),
              Enum.count(frequencies)
            }
          end)

        type_strength =
          case {min_freq, max_freq, uniq} do
            {_, 1, _} ->
              0
            {_, 2, 4} ->
              1
            {_, 2, 3} ->
              2
            {1, 3, _} ->
              3
            {2, 3, _} ->
              4
            {_, 4, _} ->
              5
            {_, 5, _} ->
              6
          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: hand,
          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

ハンドの強さを数値化しています

ドラゴンボールでいうところの戦闘力ですね

まず、ハンドのタイプを識別します

ハンドのタイプは以下の要素で決定できます

  • 種類毎の最大枚数
  • 種類毎の最小枚数
  • 種類数

最強のファイブカードを 6 、最弱のハイカードを 0 として振り分けます

タイプが同じ場合は同じ位置のカードの強さで勝敗を決します

カードは 2 〜 A の順に強いので、数字はそのまま、文字は以下のマップで数値に変換します

  @card_strength %{
    "T" => 10,
    "J" => 11,
    "Q" => 12,
    "K" => 13,
    "A" => 14
  }

最大値が 14 なので、各カードの強さを 14 進数の各桁として扱えば、カード全体の強さとして扱えそうです

タイプの強さは更に上の桁として扱いましょう

つまり以下の合計値がハンドの強さになります

  • タイプの強さ * 14 ^ 5
  • 0 番目のカードの強さ * 14 ^ 4
  • 1 番目のカードの強さ * 14 ^ 3
  • 2 番目のカードの強さ * 14 ^ 2
  • 3 番目のカードの強さ * 14 ^ 1
  • 4 番目のカードの強さ * 14 ^ 0

パースの例は以下のようになります

入力

32T3K 765
T55J5 684
KK677 28
KTJJT 220
QQQJA 483

パース結果

[
  %{bet: 765, hand_strength: 660575},
  %{bet: 684, hand_strength: 2012491},
  %{bet: 28, hand_strength: 1612009},
  %{bet: 220, hand_strength: 1604816},
  %{bet: 483, hand_strength: 2109912}
]

resolve では単純に強い順に並び替え、 bet とランクを掛けて合計しています

まとめ

ハンドの強さを数値化するのは面白かったです

Part 2 はこちら

10
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
10
1