This commit is contained in:
Pim Kunis 2023-04-17 20:18:20 +02:00
commit 77064a9563
79 changed files with 10533 additions and 0 deletions

46
2022/lib/day.ex Normal file
View file

@ -0,0 +1,46 @@
defmodule AOC.Day do
defmacro __using__(opts) do
day = Keyword.get(opts, :day)
debug = Keyword.get(opts, :debug, false)
trim = Keyword.get(opts, :trim, true)
input_file = Keyword.get(opts, :input, nil)
quote do
def solve do
input =
AOC.Day.input_lines(unquote(day), unquote(trim), unquote(input_file))
|> parse_input()
if unquote(debug) do
IO.inspect(input)
end
part1_solution = part1(input)
IO.puts("Part 1: #{part1_solution}")
part2_solution = part2(input)
IO.puts("Part 2: #{part2_solution}")
end
end
end
def input_lines(day, trim, input_file) do
lines =
input_file_name(day, input_file)
|> File.stream!()
if trim do
Enum.map(lines, &String.trim/1)
else
lines
end
end
def input_file_name(day, input_file) do
if input_file do
Path.join([File.cwd!(), "inputs", "day#{day}_#{input_file}.txt"])
else
Path.join([File.cwd!(), "inputs", "day#{day}.txt"])
end
end
end

24
2022/lib/days/day1.ex Normal file
View file

@ -0,0 +1,24 @@
defmodule AOC.Day1 do
use AOC.Day, day: 1
def parse_input(lines) do
lines
|> Enum.chunk_by(&(&1 == ""))
|> Enum.reject(&(&1 == [""]))
|> Enum.map(fn l -> Enum.map(l, &String.to_integer/1) end)
end
def part1(input) do
input
|> Enum.map(&Enum.sum/1)
|> Enum.max()
end
def part2(input) do
input
|> Enum.map(&Enum.sum/1)
|> Enum.sort(:desc)
|> Enum.take(3)
|> Enum.sum()
end
end

93
2022/lib/days/day11.ex Normal file
View file

@ -0,0 +1,93 @@
defmodule AOC.Day11 do
use AOC.Day, day: 11
alias AOC.Day11.{MonkeyRegistry, Monkey}
def parse_input(lines) do
monkeys =
lines
|> Enum.chunk_by(&(&1 == ""))
|> Enum.reject(&(&1 == [""]))
|> Enum.map(&parse_monkey/1)
supermod =
monkeys
|> Enum.map(& &1.modulo)
|> Enum.reduce(&Kernel.*/2)
{:ok, _} = Registry.start_link(keys: :unique, name: MonkeyRegistry)
Enum.each(monkeys, fn %{id: id} = monkey ->
GenServer.start_link(Monkey, Map.put(monkey, :supermod, supermod),
name: {:via, Registry, {MonkeyRegistry, id}}
)
end)
Enum.count(monkeys)
end
def parse_monkey([
"Monkey " <> monkey_id,
"Starting items: " <> items,
"Operation: " <> operation,
"Test: divisible by " <> modulo,
"If true: throw to monkey " <> true_monkey_id,
"If false: throw to monkey " <> false_monkey_id
]) do
%{
id: monkey_id |> String.slice(0..-2) |> String.to_integer(),
items: items |> String.split(", ") |> Enum.map(&String.to_integer/1),
operation: parse_operation(operation),
modulo: String.to_integer(modulo),
true_monkey_id: String.to_integer(true_monkey_id),
false_monkey_id: String.to_integer(false_monkey_id)
}
end
def parse_operation(operation) do
["new", "=", lhs, op, rhs] = String.split(operation)
fn old ->
lhs = if lhs == "old", do: old, else: String.to_integer(lhs)
rhs = if rhs == "old", do: old, else: String.to_integer(rhs)
op =
case op do
"*" -> &Kernel.*/2
"+" -> &Kernel.+/2
end
op.(lhs, rhs)
end
end
def part1(monkey_count) do
# monkey_business(monkey_count, 20, false)
end
def part2(monkey_count) do
monkey_business(monkey_count, 10000, true)
end
def monkey_business(monkey_count, rounds, ridiculous) do
execute_rounds(monkey_count, rounds, ridiculous)
Enum.map(0..(monkey_count - 1), fn id ->
[{pid, _}] = Registry.lookup(MonkeyRegistry, id)
Monkey.get_activeness(pid)
end)
|> Enum.sort(:desc)
|> Enum.take(2)
|> Enum.reduce(&Kernel.*/2)
end
def execute_rounds(monkey_count, rounds, ridiculous) do
Enum.each(0..(rounds - 1), fn _ -> execute_round(monkey_count, ridiculous) end)
end
def execute_round(monkey_count, ridiculous) do
Enum.each(0..(monkey_count - 1), fn id ->
[{pid, _}] = Registry.lookup(MonkeyRegistry, id)
Monkey.execute_turn(pid, ridiculous)
end)
end
end

View file

@ -0,0 +1,63 @@
defmodule AOC.Day11.Monkey do
use GenServer
alias AOC.Day11.{MonkeyRegistry, Monkey}
@impl true
def init(state) do
{:ok, Map.put(state, :activeness, 0)}
end
def execute_turn(pid, ridiculous) do
GenServer.call(pid, {:execute_turn, ridiculous}, :infinity)
end
def throw(pid, item) do
GenServer.call(pid, {:throw, item})
end
def get_items(pid) do
GenServer.call(pid, :get_items)
end
def get_activeness(pid) do
GenServer.call(pid, :get_activeness)
end
@impl true
def handle_call(
{:execute_turn, ridiculous},
_from,
%{
items: items,
operation: operation,
modulo: modulo,
true_monkey_id: true_monkey_id,
false_monkey_id: false_monkey_id,
activeness: activeness,
supermod: supermod
} = state
) do
Enum.each(items, fn item ->
item = operation.(item)
item = if ridiculous, do: item, else: Integer.floor_div(item, 3)
item = Integer.mod(item, supermod)
recipient = if Integer.mod(item, modulo) == 0, do: true_monkey_id, else: false_monkey_id
[{pid, _}] = Registry.lookup(MonkeyRegistry, recipient)
Monkey.throw(pid, item)
end)
{:reply, :ok, %{state | items: [], activeness: activeness + length(items)}}
end
def handle_call({:throw, item}, _from, %{items: items} = state) do
{:reply, :ok, Map.put(state, :items, items ++ [item])}
end
def handle_call(:get_items, _from, %{items: items} = state) do
{:reply, items, state}
end
def handle_call(:get_activeness, _from, %{activeness: activeness} = state) do
{:reply, activeness, state}
end
end

67
2022/lib/days/day13.ex Normal file
View file

@ -0,0 +1,67 @@
defmodule AOC.Day13 do
use AOC.Day, day: 13
alias AOC.Day13.Packet
defmodule Packet do
def compare({p1, p2}) do
compare(p1, p2)
end
def compare([], []), do: :eq
def compare([], _l), do: :lt
def compare(_l, []), do: :gt
def compare(x, l) when is_number(x) and is_list(l), do: compare([x], l)
def compare(l, x) when is_number(x) and is_list(l), do: compare(l, [x])
def compare([h1 | tl1], [h2 | tl2]) do
case compare(h1, h2) do
:eq -> compare(tl1, tl2)
x -> x
end
end
def compare(x, y) when x < y, do: :lt
def compare(x, y) when x == y, do: :eq
def compare(x, y) when x > y, do: :gt
end
def parse_input(lines) do
lines
|> Enum.chunk_by(&(&1 == ""))
|> Enum.reject(&(&1 == [""]))
|> Enum.map(fn [p1, p2] ->
{parse_packet(p1), parse_packet(p2)}
end)
end
def parse_packet(packet) do
packet
|> String.to_charlist()
|> Code.string_to_quoted()
|> elem(1)
end
def part1(pairs) do
pairs
|> Enum.map(&Packet.compare/1)
|> Enum.with_index()
|> Enum.filter(&(elem(&1, 0) == :lt))
|> Enum.map(&elem(&1, 1))
|> Enum.map(&Kernel.+(&1, 1))
|> Enum.sum()
end
@divider_packets [[[2]], [[6]]]
def part2(pairs) do
pairs
|> Enum.flat_map(fn {p1, p2} -> [p1, p2] end)
|> Kernel.++(@divider_packets)
|> Enum.sort(Packet)
|> Enum.with_index()
|> Enum.filter(&(elem(&1, 0) in @divider_packets))
|> Enum.map(&elem(&1, 1))
|> Enum.map(&Kernel.+(&1, 1))
|> Enum.reduce(&Kernel.*/2)
end
end

81
2022/lib/days/day16.ex Normal file
View file

@ -0,0 +1,81 @@
defmodule AOC.Day16 do
use AOC.Day, day: 16, debug: true, input: "example"
alias AOC.Day16.{ValveRegistry, Valve}
defmodule Valve do
use GenServer
@impl true
def init(state) do
state = Map.put(state, :released, 0)
state = Map.put(state, :open?, false)
{:ok, state}
end
def get_rate(valve) do
GenServer.call(valve, :get_rate)
end
def tick(valve) do
GenServer.call(valve, :tick)
end
def open(valve) do
GenServer.call(valve, :open)
end
@impl true
def handle_call(:get_rate, _from, %{rate: rate} = state) do
{:reply, rate, state}
end
def handle_call(:tick, _from, %{open?: open?, released: released, rate: rate} = state) do
released = if open?, do: released + rate, else: released
{:reply, :ok, %{state | released: released}}
end
def handle_call(:open, _from, state) do
{:reply, :ok, %{state | open?: true}}
end
end
@line_regex ~r/Valve (?<name>[A-Z]{2}) has flow rate=(?<rate>\d+); tunnels? leads? to valves? (?<tunnels>.*)/
def parse_input(lines) do
{:ok, _} = Registry.start_link(keys: :unique, name: ValveRegistry)
lines
|> Enum.map(fn line ->
%{"name" => name, "rate" => rate, "tunnels" => tunnels} =
Regex.named_captures(@line_regex, line)
%{name: name, rate: String.to_integer(rate), tunnels: String.split(tunnels, ", ")}
end)
|> Enum.into(%{}, fn %{name: name} = valve ->
{:ok, pid} =
GenServer.start_link(Valve, valve, name: {:via, Registry, {ValveRegistry, name}})
{name, pid}
end)
end
def part1(valves) do
Enum.map(valves, fn {name, valve} ->
rate = Valve.get_rate(valve)
{name, rate}
end)
|> Enum.sort_by(&elem(&1, 1), :desc)
|> IO.inspect()
:ok
end
def tick(valves) do
Enum.each(valves, fn {_, valve} ->
Valve.tick(valve)
end)
end
def part2(_input) do
end
end

68
2022/lib/days/day2.ex Normal file
View file

@ -0,0 +1,68 @@
defmodule AOC.Day2 do
use AOC.Day, day: 2
def parse_input(lines) do
Enum.map(lines, fn line ->
[move1, move2] =
line
|> String.split(" ")
|> Enum.map(fn c ->
c
|> String.to_charlist()
|> hd()
end)
{move1, move2}
end)
end
def part1(input) do
input
|> Enum.map(fn {move1, move2} ->
score =
case move2 do
?X -> 1
?Y -> 2
?Z -> 3
end
score +
case {move1, move2} do
{?A, ?Y} -> 6
{?B, ?Z} -> 6
{?C, ?X} -> 6
{?A, ?X} -> 3
{?B, ?Y} -> 3
{?C, ?Z} -> 3
_ -> 0
end
end)
|> Enum.sum()
end
def part2(input) do
input
|> Enum.map(fn {move1, result} ->
score =
case result do
?X -> 0
?Y -> 3
?Z -> 6
end
score +
case {move1, result} do
{?A, ?X} -> 3
{?A, ?Y} -> 1
{?A, ?Z} -> 2
{?B, ?X} -> 1
{?B, ?Y} -> 2
{?B, ?Z} -> 3
{?C, ?X} -> 2
{?C, ?Y} -> 3
{?C, ?Z} -> 1
end
end)
|> Enum.sum()
end
end

39
2022/lib/days/day3.ex Normal file
View file

@ -0,0 +1,39 @@
defmodule AOC.Day3 do
use AOC.Day, day: 3
def parse_input(lines), do: lines
def part1(input) do
input
|> Enum.map(fn line ->
comp_size = line |> String.length() |> div(2)
{comp1, comp2} = line |> String.to_charlist() |> Enum.split(comp_size)
comp1 = MapSet.new(comp1)
comp2 = MapSet.new(comp2)
MapSet.intersection(comp1, comp2)
|> MapSet.to_list()
|> hd()
|> get_priority()
end)
|> Enum.sum()
end
def part2(input) do
input
|> Enum.chunk_every(3)
|> Enum.map(fn group ->
group
|> Enum.map(&String.to_charlist/1)
|> Enum.map(&MapSet.new/1)
|> Enum.reduce(&MapSet.intersection/2)
|> MapSet.to_list()
|> hd()
|> get_priority()
end)
|> Enum.sum()
end
def get_priority(char) when char >= ?a, do: char - 96
def get_priority(char), do: char - 38
end

38
2022/lib/days/day4.ex Normal file
View file

@ -0,0 +1,38 @@
defmodule AOC.Day4 do
use AOC.Day, day: 4
def parse_input(lines) do
Enum.map(lines, fn line ->
[assign1, assign2] =
line
|> String.split(",")
|> Enum.map(fn assign ->
[from, to] =
assign
|> String.split("-")
|> Enum.map(&String.to_integer/1)
MapSet.new(from..to)
end)
{assign1, assign2}
end)
end
def part1(input) do
input
|> Enum.map(fn {set1, set2} ->
MapSet.subset?(set1, set2) or MapSet.subset?(set2, set1)
end)
|> Enum.count(& &1)
end
def part2(input) do
input
|> Enum.map(fn {set1, set2} ->
MapSet.intersection(set1, set2)
|> Enum.any?()
end)
|> Enum.count(& &1)
end
end

70
2022/lib/days/day5.ex Normal file
View file

@ -0,0 +1,70 @@
defmodule AOC.Day5 do
use AOC.Day, day: 5, trim: false
def parse_input(lines) do
[init, ops] =
lines
|> Enum.chunk_by(&(&1 == "\n"))
|> Enum.reject(&(&1 == ["\n"]))
{parse_init(init), parse_ops(ops)}
end
def parse_init(init) do
init
|> Enum.split(-1)
|> elem(0)
|> Enum.map(fn line ->
line
|> String.to_charlist()
|> tl()
|> Enum.take_every(4)
end)
|> Enum.zip_with(&Function.identity/1)
|> Enum.map(fn stack ->
Enum.reject(stack, &(&1 == ?\s))
end)
end
def parse_ops(ops) do
Enum.map(ops, fn op ->
%{"count" => count, "from" => from, "to" => to} =
Regex.named_captures(~r/move (?<count>\d+) from (?<from>\d+) to (?<to>\d+)\n/, op)
%{
count: String.to_integer(count),
from: String.to_integer(from) - 1,
to: String.to_integer(to) - 1
}
end)
end
def part1(input) do
move(input, true)
end
def part2(input) do
move(input, false)
end
def move({init, ops}, reverse) do
ops
|> Enum.reduce(init, fn %{count: count, from: from, to: to}, acc ->
{acc, removed} = remove(acc, from, count)
removed = if reverse, do: Enum.reverse(removed), else: removed
add(acc, to, removed)
end)
|> Enum.map(&hd/1)
end
def remove(stacks, from, count) do
{removed, new} = Enum.split(Enum.at(stacks, from), count)
stacks = List.update_at(stacks, from, fn _ -> new end)
{stacks, removed}
end
def add(stacks, to, removed) do
added = Enum.concat(removed, Enum.at(stacks, to))
List.update_at(stacks, to, fn _ -> added end)
end
end

29
2022/lib/days/day6.ex Normal file
View file

@ -0,0 +1,29 @@
defmodule AOC.Day6 do
use AOC.Day, day: 6
def parse_input(lines), do: lines |> hd() |> String.to_charlist()
def part1(input) do
find_marker(input, 0, 4)
end
def part2(input) do
find_marker(input, 0, 14)
end
def find_marker(signal, index, length) do
distinct =
signal
|> Enum.take(length)
|> MapSet.new()
|> Enum.count()
if distinct == length do
index + length
else
signal
|> tl()
|> find_marker(index + 1, length)
end
end
end

82
2022/lib/days/day7.ex Normal file
View file

@ -0,0 +1,82 @@
defmodule AOC.Day7 do
use AOC.Day, day: 7
def parse_input(lines) do
Enum.map(lines, fn
"$ cd " <> arg ->
{:cd, arg}
"$ ls" ->
:ls
"dir " <> arg ->
{:dir, arg}
line ->
[size, file] = String.split(line)
{:file, String.to_integer(size), file}
end)
end
def part1(input) do
input
|> get_file_sizes()
|> get_directory_sizes()
|> Enum.map(fn {_, size} -> size end)
|> Enum.filter(&(&1 <= 100_000))
|> Enum.sum()
end
def part2(input) do
file_sizes = get_file_sizes(input)
total_size =
file_sizes
|> Enum.map(&elem(&1, 1))
|> Enum.sum()
size_needed = total_size - 40_000_000
file_sizes
|> get_directory_sizes()
|> Enum.map(&elem(&1, 1))
|> Enum.filter(fn size -> size >= size_needed end)
|> Enum.min()
end
def get_file_sizes(input) do
Enum.reduce(input, {[], []}, fn
{:cd, "/"}, {_, sizes} ->
{["/"], sizes}
{:cd, ".."}, {[_ | path], sizes} ->
{path, sizes}
{:cd, dir}, {path, sizes} ->
{[dir | path], sizes}
{:file, size, file}, {path, sizes} ->
{path, [{[file | path], size} | sizes]}
_, acc ->
acc
end)
|> elem(1)
end
def get_directory_sizes(file_sizes) do
file_sizes
|> Enum.reduce(%{}, fn {[_ | path], size}, acc ->
path = Enum.reverse(path)
update_directory_size(acc, size, path)
end)
end
def update_directory_size(sizes, file_size, file_path) do
Enum.reduce(file_path, {sizes, []}, fn dir, {sizes, cur_path} ->
cur_path = [dir | cur_path]
{Map.update(sizes, cur_path, file_size, &(&1 + file_size)), cur_path}
end)
|> elem(0)
end
end

53
2022/lib/days/day8.ex Normal file
View file

@ -0,0 +1,53 @@
defmodule AOC.Day8 do
use AOC.Day, day: 8, debug: true
def parse_input(lines) do
Enum.map(lines, fn line ->
line
|> String.split("", trim: true)
|> Enum.map(&String.to_integer/1)
end)
end
def part1(rows) do
rows = with_index(rows)
cols = Enum.zip(rows) |> Enum.map(&Tuple.to_list/1)
rrows = Enum.map(rows, &Enum.reverse/1)
rcols = Enum.map(cols, &Enum.reverse/1)
[rows, cols, rrows, rcols]
|> Enum.map(&count_visible/1)
|> Enum.reduce(&MapSet.union/2)
|> Enum.count()
end
def part2(rows) do
rows
end
def with_index(rows) do
Enum.with_index(rows, fn row, y ->
Enum.with_index(row, fn el, x ->
%{height: el, x: x, y: y}
end)
end)
end
def count_visible(rows) do
rows
|> Enum.map(&count_visible_row/1)
|> Enum.map(&elem(&1, 0))
|> Enum.reduce(&MapSet.union/2)
end
def count_visible_row(row) do
Enum.reduce(row, {MapSet.new(), -1}, fn
%{height: h, x: x, y: y}, {trees, highest} when h > highest ->
{MapSet.put(trees, {x, y}), h}
_, acc ->
acc
end)
end
end