Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Day 7

d07.livemd

Day 7

Section

defmodule D07 do
  def parse_input(input) when is_binary(input) do
    input
    |> String.trim()
    |> String.split("\n")
    |> Enum.map(fn line ->
      [hand, bid] = String.split(line)
      {hand, String.to_integer(bid)}
    end)
  end

  def hand_strength(hand, opts \\ []) when is_binary(hand) do
    joker_rule = Keyword.get(opts, :joker_rule, false)

    cards = String.graphemes(hand)
    freqs = card_frequencies(cards, joker_rule)

    hand_type_strength =
      case freqs do
        [5] -> 7
        [4, 1] -> 6
        [3, 2] -> 5
        [3, 1, 1] -> 4
        [2, 2, 1] -> 3
        [2, 1, 1, 1] -> 2
        [1, 1, 1, 1, 1] -> 1
      end

    card_strengths =
      Enum.map(cards, fn
        "A" -> 14
        "K" -> 13
        "Q" -> 12
        "J" when joker_rule -> 1
        "J" when not joker_rule -> 11
        "T" -> 10
        card -> String.to_integer(card)
      end)

    {hand_type_strength, card_strengths}
  end

  def card_frequencies(cards, _joker_rule = false) when is_list(cards) do
    cards
    |> Enum.frequencies()
    |> Map.values()
    |> Enum.sort(:desc)
  end

  def card_frequencies(cards, _joker_rule = true) when is_list(cards) do
    groups = Enum.group_by(cards, fn x -> x end)

    {groups, joker_bonus} =
      if Map.has_key?(groups, "J") && map_size(groups) > 1 do
        {Map.delete(groups, "J"), groups["J"] |> length()}
      else
        {groups, 0}
      end

    [highest_freq | rest] =
      groups
      |> Map.values()
      |> Enum.map(&length/1)
      |> Enum.sort(:desc)

    [highest_freq + joker_bonus | rest]
  end

  def get_winnings(hands, opts \\ []) when is_list(hands) do
    hands
    |> Enum.sort_by(fn {hand, _bid} -> hand_strength(hand, opts) end)
    |> Enum.with_index(1)
    |> Enum.map(fn {{_hand, bid}, rank} -> bid * rank end)
    |> Enum.sum()
  end

  def p1(hands) when is_list(hands), do: get_winnings(hands)
  def p2(hands) when is_list(hands), do: get_winnings(hands, joker_rule: true)
end
{:module, D07, <<70, 79, 82, 49, 0, 0, 23, ...>>, {:p2, 1}}
ExUnit.start(autorun: false)

defmodule D07Test do
  use ExUnit.Case, async: true

  @test_input D07.parse_input("""
              32T3K 765
              T55J5 684
              KK677 28
              KTJJT 220
              QQQJA 483
              """)

  @input Path.join(__DIR__, "inputs/d07") |> File.read!() |> D07.parse_input()

  test "part 1 works" do
    assert D07.p1(@test_input) == 6440
    assert D07.p1(@input) == 250_453_939
  end

  test "part 2 works" do
    assert D07.p2(@test_input) == 5905
    assert D07.p2(@input) == 248_652_697
  end
end

ExUnit.run()
..
Finished in 0.00 seconds (0.00s async, 0.00s sync)
2 tests, 0 failures

Randomized with seed 504885
%{total: 2, failures: 0, excluded: 0, skipped: 0}