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

AoC 2023 Day 4

2023/day4.livemd

AoC 2023 Day 4

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

Input

{:ok, puzzle_input} =
  KinoAOC.download_puzzle("2023", "4", System.fetch_env!("LB_AOC_COOKIE_SECRET"))
defmodule ScratchCard do
  defstruct [:id, :winning, :has]

  defp parse_numbers(numbers) do
    numbers
    |> String.split(" ", trim: true)
    |> Enum.map(&String.to_integer/1)
    |> MapSet.new()
  end

  defp parse_id("Card" <> rest), do: parse_id(rest)
  defp parse_id(" " <> rest), do: parse_id(rest)
  defp parse_id(id), do: Integer.parse(id)

  def parse("Card " <> line) do
    {id, ":" <> numbers} = parse_id(line)
    [winning, has] = String.split(numbers, "|", trim: true)
    winning = parse_numbers(winning)
    has = parse_numbers(has)

    %ScratchCard{
      id: id,
      winning: winning,
      has: has
    }
  end

  def matches(%ScratchCard{has: has, winning: winning}) do
    MapSet.intersection(winning, has)
    |> Enum.count()
  end

  def points(%ScratchCard{} = card) do
    card
    |> matches()
    |> points()
  end

  def points(0), do: 0
  def points(1), do: 1

  def points(n) do
    2 * points(n - 1)
  end
end

Part 1

puzzle_input
|> String.split("\n")
|> Enum.map(&amp;ScratchCard.parse/1)
|> Enum.map(&amp;ScratchCard.points/1)
|> Enum.sum()

Part 2

cards =
  puzzle_input
  |> String.split("\n")
  |> Enum.map(&amp;ScratchCard.parse/1)

lookup = Map.new(cards, fn c -> {c.id, c} end)
defmodule Evaluator do
  def evaluate(cards, lookup), do: evaluate(cards, lookup, 0)

  def evaluate([], _, seen), do: seen

  def evaluate([card | cards], lookup, seen) do
    n = ScratchCard.matches(card)
    cards = new_cards(lookup, card.id, n) ++ cards
    evaluate(cards, lookup, seen + 1)
  end

  def new_cards(_, _, 0), do: []

  def new_cards(lookup, id, n) do
    from = id + 1
    to = from + n - 1
    from..to |> Enum.map(&amp;Map.get(lookup, &amp;1))
  end
end
Evaluator.evaluate(cards, lookup)