AoC 2023 Day 2
Mix.install([
{:kino_aoc, "~> 0.1.5"},
{:nimble_parsec, "~> 1.4"}
])
Input
{:ok, puzzle_input} =
KinoAOC.download_puzzle("2023", "2", System.fetch_env!("LB_AOC_COOKIE_SECRET"))
Parsing
defmodule CubeGame do
import NimbleParsec
defstruct [:id, :sets]
spacing =
ignore(optional(string(",")))
|> ignore(string(" "))
cube_set =
spacing
|> integer(min: 1)
|> ignore(string(" "))
|> ascii_string([?a..?z], min: 1)
|> tag(:cubes)
game_set =
times(cube_set, min: 1)
|> ignore(optional(string(";")))
|> tag(:set)
game =
ignore(string("Game "))
|> integer(min: 1)
|> unwrap_and_tag(:id)
|> ignore(string(":"))
|> times(game_set, min: 1)
|> tag(:game)
games =
repeat(
concat(
game,
ignore(optional(ascii_char([?\n])))
)
)
defparsecp(:parse, games)
def parse_input(input) do
{:ok, games, _, _, _, _} = parse(input)
games
|> Enum.map(fn {:game, g} ->
sets =
Keyword.get_values(g, :set)
|> Enum.map(fn s ->
Keyword.get_values(s, :cubes)
|> Enum.map(fn [n, name] -> {String.to_atom(name), n} end)
end)
%CubeGame{
id: g[:id],
sets: sets
}
end)
end
end
games = CubeGame.parse_input(puzzle_input)
Part 1
limits = %{
red: 12,
green: 13,
blue: 14
}
amount_under_limit? = fn {colour, n} ->
limits[colour] >= n
end
set_possible? = fn set ->
Enum.all?(set, amount_under_limit?)
end
game_possible? = fn %CubeGame{sets: sets} ->
Enum.all?(sets, set_possible?)
end
games
|> Enum.filter(game_possible?)
|> Enum.map(&Map.get(&1, :id))
|> Enum.sum()
Part 2
games
|> Enum.map(&Map.get(&1, :sets))
|> Enum.map(fn sets ->
sets
|> List.flatten()
|> Enum.group_by(&elem(&1, 0), &elem(&1, 1))
|> Enum.map(fn {colour, counts} ->
{colour, Enum.max(counts)}
end)
end)
|> Enum.map(fn maximums ->
maximums
|> Keyword.values()
|> Enum.product()
end)
|> Enum.sum()