Powered by AppSignal & Oban Pro

Day 7

notebooks/day07.livemd

Day 7

Mix.install([
  {:req, "~> 0.4.5"},
  {:kino, "~> 0.11.3"}
])

Input

session = System.fetch_env!("LB_AOC_SESSION")

input =
  Req.get!(
    "https://adventofcode.com/2023/day/7/input",
    headers: [{"Cookie", ~s"session=#{session}"}]
  ).body

Kino.Text.new(input, terminal: true)

Part 1

hands =
  for line <- String.split(input, "\n", trim: true) do
    [hand, bid] = String.split(line)
    {String.graphemes(hand), String.to_integer(bid)}
  end
defmodule Part1 do
  @powers (2..9 |> Enum.map(&amp;Integer.to_string/1)) ++ ["T", "J", "Q", "K", "A"]
  def card_power(card), do: Enum.find_index(@powers, &amp;(&amp;1 == card))

  def hand_power(cards) do
    hand = Enum.frequencies(cards)
    size = map_size(hand)
    sets = Map.values(hand)

    cond do
      size == 1 -> 6
      size == 2 and Enum.any?(sets, &amp;(&amp;1 == 1 or &amp;1 == 4)) -> 5
      size == 2 and Enum.any?(sets, &amp;(&amp;1 == 2 or &amp;1 == 3)) -> 4
      size == 3 and Enum.any?(sets, &amp;(&amp;1 == 3)) -> 3
      size == 3 and Enum.any?(sets, &amp;(&amp;1 == 2)) -> 2
      size == 4 -> 1
      true -> 0
    end
  end

  def total_winnings(hands, hp_fun, cp_fun) do
    hands
    |> Enum.map(fn {cards, bid} ->
      {hp_fun.(cards), Enum.map(cards, cp_fun), bid}
    end)
    |> Enum.sort(fn {hp1, cp1, _}, {hp2, cp2, _} ->
      if hp1 == hp2, do: cp1 <= cp2, else: hp1 < hp2
    end)
    |> Enum.with_index(1)
    |> Enum.map(fn {{_, _, bid}, rank} -> bid * rank end)
    |> Enum.sum()
  end
end

Part1.total_winnings(hands, &amp;Part1.hand_power/1, &amp;Part1.card_power/1)

Part 2

defmodule Part2 do
  @five_of_akind 6
  @four_of_a_kind 5
  @full_house 4
  @three_of_a_kind 3
  @two_pair 2
  @one_pair 1
  @high_card 0

  @joker_powers ["J"] ++ (2..9 |> Enum.map(&amp;Integer.to_string/1)) ++ ["T", "Q", "K", "A"]

  def joker_card_power(card), do: Enum.find_index(@joker_powers, &amp;(&amp;1 == card))

  def joker_hand_power(cards) do
    hand = Enum.frequencies(cards)
    size = map_size(hand)
    sets = Map.values(hand)
    jokers = Map.get(hand, "J", 0)

    cond do
      size == 1 ->
        @five_of_akind

      size == 2 and Enum.any?(sets, &amp;(&amp;1 == 1 or &amp;1 == 4)) ->
        if jokers > 0, do: @five_of_akind, else: @four_of_a_kind

      size == 2 and Enum.any?(sets, &amp;(&amp;1 == 2 or &amp;1 == 3)) ->
        if jokers > 0, do: @five_of_akind, else: @full_house

      size == 3 and Enum.any?(sets, &amp;(&amp;1 == 3)) ->
        if jokers > 0, do: @four_of_a_kind, else: @three_of_a_kind

      size == 3 and Enum.any?(sets, &amp;(&amp;1 == 2)) ->
        case jokers do
          2 -> @four_of_a_kind
          1 -> @full_house
          _ -> @two_pair
        end

      size == 4 ->
        if jokers > 0, do: @three_of_a_kind, else: @one_pair

      true ->
        if jokers == 1, do: @one_pair, else: @high_card
    end
  end
end

Part1.total_winnings(hands, &amp;Part2.joker_hand_power/1, &amp;Part2.joker_card_power/1)