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(&ScratchCard.parse/1)
|> Enum.map(&ScratchCard.points/1)
|> Enum.sum()
Part 2
cards =
puzzle_input
|> String.split("\n")
|> Enum.map(&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(&Map.get(lookup, &1))
end
end
Evaluator.evaluate(cards, lookup)