Powered by AppSignal & Oban Pro

Day 7: Camel Cards

2023/day-07.livemd

Day 7: Camel Cards

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

Day 7

sample_input = Kino.Input.textarea("Paste Sample Input")
real_input = Kino.Input.textarea("Paste Real Input")
defmodule Hand do
  defstruct cards: "", card_scores: [], bid: 0, score: 0

  def parse(card_string, part_module) do
    [cards_raw, bid_raw] = String.split(card_string, " ", trim: true)
    card_scores = cards_raw |> String.graphemes() |> Enum.map(&part_module.card_score/1)

    hand = %__MODULE__{
      cards: cards_raw,
      card_scores: card_scores,
      bid: String.to_integer(bid_raw)
    }

    %{hand | score: part_module.hand_score(hand)}
  end

  def sort_score(%__MODULE__{card_scores: card_scores, score: hand_score}) do
    [hand_score | card_scores]
  end
end
defmodule Part1 do
  def card_score("T"), do: 10
  def card_score("J"), do: 11
  def card_score("Q"), do: 12
  def card_score("K"), do: 13
  def card_score("A"), do: 14
  def card_score(card), do: String.to_integer(card)

  def hand_score(%Hand{card_scores: card_scores}) do
    card_scores
    |> Enum.frequencies()
    |> Map.values()
    |> Enum.sort(:desc)
    |> score_from_frequencies()
  end

  def score_from_frequencies([5 | _]), do: 7
  def score_from_frequencies([4 | _]), do: 6
  def score_from_frequencies([3, 2 | _]), do: 5
  def score_from_frequencies([3 | _]), do: 4
  def score_from_frequencies([2, 2 | _]), do: 3
  def score_from_frequencies([2 | _]), do: 2
  def score_from_frequencies(_), do: 1
end
defmodule Part2 do
  defdelegate score_from_frequencies(frequencies), to: Part1

  def card_score("J"), do: 1
  def card_score(card), do: Part1.card_score(card)

  def hand_score(%Hand{card_scores: card_scores}) do
    non_jokers = Enum.filter(card_scores, &(&1 > card_score("J")))
    non_jokers = if length(non_jokers) == 0, do: card_scores, else: non_jokers

    non_jokers
    |> Enum.frequencies()
    |> Map.values()
    |> Enum.sort(:desc)
    |> then(fn [top | rest] -> [top + (5 - length(non_jokers)) | rest] end)
    |> score_from_frequencies()
  end
end
solve = fn input, part_module ->
  input
  |> Kino.Input.read()
  |> String.split("\n", trim: true)
  |> Enum.map(&Hand.parse(&1, part_module))
  |> Enum.sort_by(&Hand.sort_score/1)
  |> Enum.with_index(fn hand, index ->
    (index + 1) * hand.bid
  end)
  |> Enum.sum()
end
solve.(sample_input, Part1)
solve.(real_input, Part1)
solve.(sample_input, Part2)
solve.(real_input, Part2)