Advent of Code 2023 Day 2 (with yecc)
Mix.install([
{:kino_aoc, "~> 0.1.5"}
])
Prep
{:ok, puzzle_input} =
KinoAOC.download_puzzle("2023", "2", System.fetch_env!("LB_AOC_SESSION"))
defmodule AoC2023.Day2.Lexer do
def tokenize(input) do
do_tokenize(input, [])
end
defp do_tokenize("", acc) do
Enum.reverse([{:"$end", []} | acc])
end
defp do_tokenize("Game" <> rest, acc) do
do_tokenize(rest, [{:game, []} | acc])
end
defp do_tokenize("red" <> rest, acc) do
do_tokenize(rest, [{:red, []} | acc])
end
defp do_tokenize("green" <> rest, acc) do
do_tokenize(rest, [{:green, []} | acc])
end
defp do_tokenize("blue" <> rest, acc) do
do_tokenize(rest, [{:blue, []} | acc])
end
defp do_tokenize("," <> rest, acc) do
do_tokenize(rest, [{:",", []} | acc])
end
defp do_tokenize(";" <> rest, acc) do
do_tokenize(rest, [{:";", []} | acc])
end
defp do_tokenize(":" <> rest, acc) do
do_tokenize(rest, [{:":", []} | acc])
end
defp do_tokenize(<<char, rest::binary>>, [{:num, _, num} | acc])
when char in ?0..?9 do
do_tokenize(rest, [{:num, [], num * 10 + char - ?0} | acc])
end
defp do_tokenize(<<char, rest::binary>>, acc)
when char in ?0..?9 do
do_tokenize(rest, [{:num, [], char - ?0} | acc])
end
defp do_tokenize(<<_, rest::binary>>, acc) do
do_tokenize(rest, acc)
end
end
rules = """
Terminals game num red green blue ':' ',' ';'.
Nonterminals color games one_game set sets.
Rootsymbol games.
color -> num red : {red, element(3, '$1')}.
color -> num green : {green, element(3, '$1')}.
color -> num blue : {blue, element(3, '$1')}.
set -> color : ['$1'].
set -> color ',' set : ['$1' | '$3'].
sets -> set : ['$1'].
sets -> set ';' sets : ['$1' | '$3'].
one_game -> game num ':' sets : {element(3, '$2'), '$4'}.
games -> one_game : ['$1'].
games -> one_game games : ['$1' | '$2'].
"""
path = "/tmp/aoc2023_day02_parser.yrl"
File.write!(path, rules)
{:ok, parser_path} = :yecc.file(String.to_charlist(path))
:compile.file(parser_path)
tokens = AoC2023.Day2.Lexer.tokenize(puzzle_input)
{:ok, games} = :aoc2023_day02_parser.parse(tokens)
Part 1
games
|> Stream.filter(fn {_id, sets} ->
Enum.all?(sets, fn set ->
Keyword.get(set, :red, 0) <= 12 and
Keyword.get(set, :green, 0) <= 13 and
Keyword.get(set, :blue, 0) <= 14
end)
end)
|> Stream.map(fn {id, _sets} -> id end)
|> Enum.sum()
Part 2
games
|> Stream.map(fn {_id, sets} ->
Enum.reduce(sets, %{}, fn set, acc ->
Map.merge(acc, Map.new(set), fn _color, count1, count2 ->
max(count1, count2)
end)
end)
end)
|> Stream.map(fn map ->
map |> Map.values() |> Enum.product()
end)
|> Enum.sum()
Cleanup
File.rm(parser_path)
File.rm(path)