Compare commits
No commits in common. "main" and "cd77059be38e3cbf32d17d44819af544a668815d" have entirely different histories.
main
...
cd77059be3
59 changed files with 43 additions and 5206 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,3 +0,0 @@
|
|||
input
|
||||
sample
|
||||
arena
|
|
@ -1,52 +0,0 @@
|
|||
defmodule Day1 do
|
||||
def part1(<<>>), do: 0
|
||||
def part1(<<?(, rest::binary>>), do: 1 + part1(rest)
|
||||
def part1(<<?), rest::binary>>), do: -1 + part1(rest)
|
||||
|
||||
def part2(input), do: find_basement(input, _floor = 0, _pos = 1)
|
||||
|
||||
def find_basement(<<?), _::binary>>, 0, pos), do: pos
|
||||
def find_basement(<<?), rest::binary>>, floor, pos), do: find_basement(rest, floor - 1, pos + 1)
|
||||
def find_basement(<<?(, rest::binary>>, floor, pos), do: find_basement(rest, floor + 1, pos + 1)
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
input
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{result}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day1.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day1.run()
|
|
@ -1,66 +0,0 @@
|
|||
defmodule Day2 do
|
||||
def part1(input) do
|
||||
input
|
||||
|> Enum.map(fn [x, y, z] ->
|
||||
a = x * y
|
||||
b = x * z
|
||||
c = y * z
|
||||
min = Enum.min([a, b, c])
|
||||
2 * (a + b + c) + min
|
||||
end)
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
def part2(input) do
|
||||
input
|
||||
|> Enum.map(fn box ->
|
||||
[x, y, z] = Enum.sort(box)
|
||||
2 * (x + y) + x * y * z
|
||||
end)
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
input
|
||||
|> String.split(["\n", "x"], trim: true)
|
||||
|> Enum.map(&String.to_integer/1)
|
||||
|> Enum.chunk_every(3)
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{result}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day2.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day2.run()
|
|
@ -1,67 +0,0 @@
|
|||
defmodule Day3 do
|
||||
def part1(input), do: move(input, MapSet.new([{0, 0}]), {0, 0})
|
||||
|
||||
def move(<<?<, rest::binary>>, houses, {x, y}), do: deliver(rest, houses, {x - 1, y})
|
||||
def move(<<?>, rest::binary>>, houses, {x, y}), do: deliver(rest, houses, {x + 1, y})
|
||||
def move(<<?^, rest::binary>>, houses, {x, y}), do: deliver(rest, houses, {x, y - 1})
|
||||
def move(<<?v, rest::binary>>, houses, {x, y}), do: deliver(rest, houses, {x, y + 1})
|
||||
def move(<<>>, houses, _addr), do: MapSet.size(houses)
|
||||
|
||||
def deliver(moves, houses, addr), do: move(moves, MapSet.put(houses, addr), addr)
|
||||
|
||||
def part2(input), do: move2(input, MapSet.new([{0, 0}]), {0, 0}, {0, 0})
|
||||
|
||||
def move2(<<ms, mb, rest::binary>>, houses, santa, bot) do
|
||||
santa = next(ms, santa)
|
||||
bot = next(mb, bot)
|
||||
move2(rest, MapSet.union(houses, MapSet.new([santa, bot])), santa, bot)
|
||||
end
|
||||
|
||||
def move2(<<>>, houses, _santa, _bot), do: MapSet.size(houses)
|
||||
|
||||
def next(?<, {x, y}), do: {x - 1, y}
|
||||
def next(?>, {x, y}), do: {x + 1, y}
|
||||
def next(?^, {x, y}), do: {x, y - 1}
|
||||
def next(?v, {x, y}), do: {x, y + 1}
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
input
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{result}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day3.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day3.run()
|
|
@ -1,50 +0,0 @@
|
|||
defmodule Day4 do
|
||||
def part1(input), do: mine(input, 1, "00000")
|
||||
|
||||
def mine(input, nonce, prefix) do
|
||||
hash = :crypto.hash(:md5, input <> Integer.to_string(nonce)) |> Base.encode16()
|
||||
if String.starts_with?(hash, prefix), do: nonce, else: mine(input, nonce + 1, prefix)
|
||||
end
|
||||
|
||||
def part2(input), do: mine(input, 1, "000000")
|
||||
|
||||
def input do
|
||||
with [input] <- System.argv() do
|
||||
input
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{result}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day4.exs input")
|
||||
end
|
||||
end
|
||||
|
||||
Day4.run()
|
|
@ -1,86 +0,0 @@
|
|||
defmodule Day5 do
|
||||
def part1(input) do
|
||||
Enum.count(input, &nice?/1)
|
||||
end
|
||||
|
||||
def nice?(str) do
|
||||
at_least_three_vowels(str) and
|
||||
twice_in_a_row(str) and
|
||||
not String.contains?(str, ["ab", "cd", "pq", "xy"])
|
||||
end
|
||||
|
||||
def at_least_three_vowels(str) do
|
||||
str
|
||||
|> String.graphemes()
|
||||
|> Enum.frequencies()
|
||||
|> Map.take(["a", "e", "i", "o", "u"])
|
||||
|> Map.values()
|
||||
|> Enum.sum()
|
||||
|> Kernel.>=(3)
|
||||
end
|
||||
|
||||
def twice_in_a_row(<<x, x, _::binary>>), do: true
|
||||
def twice_in_a_row(<<_x, rest::binary>>), do: twice_in_a_row(rest)
|
||||
def twice_in_a_row(<<>>), do: false
|
||||
|
||||
def part2(input) do
|
||||
Enum.count(input, &nice2?/1)
|
||||
end
|
||||
|
||||
def nice2?(str) do
|
||||
pairs = pairs(str)
|
||||
uniq = MapSet.new(pairs)
|
||||
length(pairs) > MapSet.size(uniq) and sandwich(str)
|
||||
end
|
||||
|
||||
def pairs(str, prev \\ nil)
|
||||
def pairs(<<x, x, rest::binary>>, <<x, x>>), do: pairs(<<x, rest::binary>>)
|
||||
def pairs(<<x, y, rest::binary>>, _prev), do: [<<x, y>> | pairs(<<y, rest::binary>>, <<x, y>>)]
|
||||
def pairs(<<_>>, _prev), do: []
|
||||
|
||||
def sandwich(<<x, y, x, _rest::binary>>), do: true
|
||||
def sandwich(<<_x, y, z, rest::binary>>), do: sandwich(<<y, z, rest::binary>>)
|
||||
def sandwich(_), do: false
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
input
|
||||
|> String.split("\n", trim: true)
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{result}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day5.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day5.run()
|
5
2021/README.md
Normal file
5
2021/README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Advent of Code 2021 in Zig!
|
||||
|
||||
I just started learning Zig on Christmas Day 2021, so after running through a few tutorials I'm planning on running through a few of the 2021 puzzles as a way to get to know the language, probably starting sometime in January 2022.
|
||||
|
||||
<img width="943" alt="image" src="https://user-images.githubusercontent.com/498229/147409293-6a855c9c-7d89-4440-9192-240eb47b3d85.png">
|
|
@ -1,95 +0,0 @@
|
|||
defmodule Day10 do
|
||||
def part1(input) do
|
||||
input
|
||||
|> Enum.map(&parse_line(&1, []))
|
||||
|> Enum.filter(&match?({:corrupted, _}, &1))
|
||||
|> Enum.map(fn {:corrupted, char} -> score(char) end)
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
defp parse_line(_, {:corrupted, char}), do: {:corrupted, char}
|
||||
defp parse_line("", stack), do: {:incomplete, stack}
|
||||
defp parse_line(<<char::utf8, rest::binary>>, []), do: parse_line(rest, [char])
|
||||
|
||||
defp parse_line(<<char::utf8, next::binary>>, [prev | popped] = stack) do
|
||||
stack =
|
||||
case char do
|
||||
char when char in '([{<' -> [char | stack]
|
||||
?) when prev == ?( -> popped
|
||||
?] when prev == ?[ -> popped
|
||||
?} when prev == ?{ -> popped
|
||||
?> when prev == ?< -> popped
|
||||
char -> {:corrupted, char}
|
||||
end
|
||||
|
||||
parse_line(next, stack)
|
||||
end
|
||||
|
||||
defp score(?)), do: 3
|
||||
defp score(?]), do: 57
|
||||
defp score(?}), do: 1197
|
||||
defp score(?>), do: 25137
|
||||
|
||||
def part2(input) do
|
||||
scores =
|
||||
input
|
||||
|> Enum.map(&parse_line(&1, []))
|
||||
|> Enum.filter(&match?({:incomplete, _}, &1))
|
||||
|> Enum.map(fn {_, chars} -> score2(chars) end)
|
||||
|> Enum.sort()
|
||||
|
||||
Enum.at(scores, trunc(length(scores) / 2))
|
||||
end
|
||||
|
||||
defp score2(chars) do
|
||||
for char <- chars, reduce: 0 do
|
||||
score -> score * 5 + points(char)
|
||||
end
|
||||
end
|
||||
|
||||
defp points(?(), do: 1
|
||||
defp points(?[), do: 2
|
||||
defp points(?{), do: 3
|
||||
defp points(?<), do: 4
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
String.split(input, "\n", trim: true)
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{result}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day10.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day10.run()
|
106
2021/day13.exs
106
2021/day13.exs
|
@ -1,106 +0,0 @@
|
|||
defmodule Day13 do
|
||||
defmodule Paper do
|
||||
defstruct dots: MapSet.new(), instructions: []
|
||||
end
|
||||
|
||||
def part1(paper) do
|
||||
folded = fold(paper)
|
||||
MapSet.size(folded.dots)
|
||||
end
|
||||
|
||||
defp fold(%Paper{dots: dots, instructions: [{axis, pos} | rest]}) do
|
||||
dots =
|
||||
MapSet.new(dots, fn {x, y} ->
|
||||
cond do
|
||||
axis == "x" and x > pos -> {pos - (x - pos), y}
|
||||
axis == "y" and y > pos -> {x, pos - (y - pos)}
|
||||
true -> {x, y}
|
||||
end
|
||||
end)
|
||||
|
||||
%Paper{dots: dots, instructions: rest}
|
||||
end
|
||||
|
||||
def part2(paper) do
|
||||
paper
|
||||
|> fold_all
|
||||
|> draw
|
||||
end
|
||||
|
||||
defp fold_all(%Paper{instructions: []} = paper), do: paper
|
||||
defp fold_all(%Paper{} = paper), do: paper |> fold |> fold_all
|
||||
|
||||
defp draw(paper) do
|
||||
{max_x, max_y} =
|
||||
for {x, y} <- paper.dots, reduce: {0, 0} do
|
||||
{max_x, max_y} -> {max(x, max_x), max(y, max_y)}
|
||||
end
|
||||
|
||||
for y <- 0..max_y do
|
||||
row =
|
||||
for x <- 0..max_x do
|
||||
if MapSet.member?(paper.dots, {x, y}), do: ?#, else: ?\s
|
||||
end
|
||||
|
||||
# could Enum.join("\n") here, but I want to play with iolists
|
||||
[row | "\n"]
|
||||
end
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
[dots, instructions] = String.split(input, "\n\n")
|
||||
|
||||
dots =
|
||||
dots
|
||||
|> String.split([",", "\n"])
|
||||
|> Enum.map(&String.to_integer/1)
|
||||
|> Enum.chunk_every(2)
|
||||
|> MapSet.new(fn [x, y] -> {x, y} end)
|
||||
|
||||
instructions =
|
||||
instructions
|
||||
|> String.split("\n", trim: true)
|
||||
|> Enum.map(fn <<"fold along ", axis::binary-1, "=", pos::binary>> ->
|
||||
{axis, String.to_integer(pos)}
|
||||
end)
|
||||
|
||||
%Paper{dots: dots, instructions: instructions}
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{result}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day13.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day13.run()
|
140
2021/day16.exs
140
2021/day16.exs
|
@ -1,140 +0,0 @@
|
|||
defmodule Day16 do
|
||||
def part1(input) do
|
||||
{_type, _rest, _count, version_sum} = decode_packet(input, 0, 0)
|
||||
version_sum
|
||||
end
|
||||
|
||||
@literal_type 4
|
||||
@literal_part 1
|
||||
@literal_end 0
|
||||
@total_length_type 0
|
||||
@sub_packets_length_type 1
|
||||
|
||||
defp decode_packet(<<version::3, @literal_type::3, rest::bits>>, count, version_sum) do
|
||||
decode_literal(rest, <<>>, count + 6, version_sum + version)
|
||||
end
|
||||
|
||||
defp decode_packet(<<version::3, operator_type::3, rest::bits>>, count, version_sum) do
|
||||
{sub_packets, rest, count, version_sum} =
|
||||
case rest do
|
||||
<<@total_length_type::1, length::15, rest::bits>> ->
|
||||
decode_sub_packets_by(
|
||||
:length,
|
||||
length,
|
||||
[],
|
||||
rest,
|
||||
count + 3 + 3 + 1 + 15,
|
||||
version_sum + version
|
||||
)
|
||||
|
||||
<<@sub_packets_length_type::1, num_sub_packets::11, rest::bits>> ->
|
||||
decode_sub_packets_by(
|
||||
:quantity,
|
||||
num_sub_packets,
|
||||
[],
|
||||
rest,
|
||||
count + 3 + 3 + 1 + 11,
|
||||
version_sum + version
|
||||
)
|
||||
end
|
||||
|
||||
{{:operator, operator_type, sub_packets}, rest, count, version_sum}
|
||||
end
|
||||
|
||||
defp decode_literal(<<@literal_part::1, group::bits-4, rest::bits>>, acc, count, version_sum) do
|
||||
decode_literal(rest, <<acc::bits, group::bits>>, count + 5, version_sum)
|
||||
end
|
||||
|
||||
defp decode_literal(<<@literal_end::1, group::bits-4, rest::bits>>, acc, count, version_sum) do
|
||||
literal_binary = <<acc::bits, group::bits>>
|
||||
<<literal::size(bit_size(literal_binary))>> = literal_binary
|
||||
{{:literal, literal}, rest, count + 5, version_sum}
|
||||
end
|
||||
|
||||
defp decode_sub_packets_by(_method, 0, decoded_packets, bits, count, version_sum) do
|
||||
{Enum.reverse(decoded_packets), bits, count, version_sum}
|
||||
end
|
||||
|
||||
defp decode_sub_packets_by(method, remaining, decoded_packets, bits, count, version_sum) do
|
||||
{decoded_packet, rest, packet_size, version_sum} = decode_packet(bits, 0, version_sum)
|
||||
|
||||
remaining =
|
||||
case method do
|
||||
:length -> remaining - packet_size
|
||||
:quantity -> remaining - 1
|
||||
end
|
||||
|
||||
decode_sub_packets_by(
|
||||
method,
|
||||
remaining,
|
||||
[decoded_packet | decoded_packets],
|
||||
rest,
|
||||
count + packet_size,
|
||||
version_sum
|
||||
)
|
||||
end
|
||||
|
||||
def part2(input) do
|
||||
{packet, _rest, _count, _version_sum} = decode_packet(input, 0, 0)
|
||||
evaluate(packet)
|
||||
end
|
||||
|
||||
defp evaluate({:literal, literal}), do: literal
|
||||
|
||||
defp evaluate({:operator, op, args}) do
|
||||
args
|
||||
|> Enum.map(&evaluate/1)
|
||||
|> calculate(op)
|
||||
end
|
||||
|
||||
defp calculate(args, 0), do: Enum.sum(args)
|
||||
defp calculate(args, 1), do: Enum.reduce(args, &Kernel.*/2)
|
||||
defp calculate(args, 2), do: Enum.min(args)
|
||||
defp calculate(args, 3), do: Enum.max(args)
|
||||
defp calculate([a, b], 5), do: if(a > b, do: 1, else: 0)
|
||||
defp calculate([a, b], 6), do: if(a < b, do: 1, else: 0)
|
||||
defp calculate([a, b], 7), do: if(a == b, do: 1, else: 0)
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
input
|
||||
|> String.trim()
|
||||
|> Base.decode16!()
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def(run) do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{result}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day16.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day16.run()
|
|
@ -1,98 +0,0 @@
|
|||
defmodule Day3 do
|
||||
def part1(input) do
|
||||
gamma_rating = find_rating(input, 0, :most, <<>>)
|
||||
epsillon_rating = find_rating(input, 0, :least, <<>>)
|
||||
gamma_rating * epsillon_rating
|
||||
end
|
||||
|
||||
defp find_rating([value | _], _, _, bits) when bit_size(value) == bit_size(bits) do
|
||||
<<rating::size(bit_size(bits))>> = bits
|
||||
rating
|
||||
end
|
||||
|
||||
defp find_rating(values, bits_seen, mode, acc) do
|
||||
bit = find_common(values, bits_seen, mode)
|
||||
find_rating(values, bits_seen + 1, mode, <<acc::bits-size(bits_seen), bit::1>>)
|
||||
end
|
||||
|
||||
def part2(input) do
|
||||
oxygen_generator_rating = find_rating2(input, 0, :most)
|
||||
co2_scrubber_rating = find_rating2(input, 0, :least)
|
||||
oxygen_generator_rating * co2_scrubber_rating
|
||||
end
|
||||
|
||||
defp find_rating2([bits], _bits_seen, _mode) do
|
||||
<<value::size(bit_size(bits))>> = bits
|
||||
value
|
||||
end
|
||||
|
||||
defp find_rating2(values, bits_seen, mode) do
|
||||
bit = find_common(values, bits_seen, mode)
|
||||
|
||||
values
|
||||
|> filter_by_bit(bits_seen, bit)
|
||||
|> find_rating2(bits_seen + 1, mode)
|
||||
end
|
||||
|
||||
defp find_common(values, bits_seen, mode) do
|
||||
{zeros, ones} =
|
||||
values
|
||||
|> Enum.map(fn <<_::size(bits_seen), bit::1, _rest::bits>> -> bit end)
|
||||
|> Enum.reduce({_zeros = 0, _ones = 0}, fn
|
||||
0, {zeros, ones} -> {zeros + 1, ones}
|
||||
1, {zeros, ones} -> {zeros, ones + 1}
|
||||
end)
|
||||
|
||||
case mode do
|
||||
:most -> if zeros <= ones, do: 1, else: 0
|
||||
:least -> if zeros <= ones, do: 0, else: 1
|
||||
end
|
||||
end
|
||||
|
||||
defp filter_by_bit(values, num_prev_bits, bit) do
|
||||
Enum.filter(values, &match?(<<_::size(num_prev_bits), ^bit::1, _rest::bits>>, &1))
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
strings = String.split(input, "\n", trim: true)
|
||||
length = byte_size(hd(strings))
|
||||
Enum.map(strings, &<<String.to_integer(&1, 2)::size(length)>>)
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{result}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir dayREPLACE_ME.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day3.run()
|
133
2021/day4.exs
133
2021/day4.exs
|
@ -1,133 +0,0 @@
|
|||
defmodule Day4 do
|
||||
@rows for y <- 0..4, do: for(x <- 0..4, do: {x, y})
|
||||
@cols for x <- 0..4, do: for(y <- 0..4, do: {x, y})
|
||||
|
||||
def part1(input) do
|
||||
{current_number, winning_board} = play(input)
|
||||
sum_unmarked(winning_board) * current_number
|
||||
end
|
||||
|
||||
def part2(input) do
|
||||
{current_number, last_board} = play_until_last(input)
|
||||
sum_unmarked(last_board) * current_number
|
||||
end
|
||||
|
||||
def play({[current_number | next_numbers], boards}) do
|
||||
boards = for board <- boards, do: mark(board, current_number)
|
||||
|
||||
case check_for_winner(boards) do
|
||||
nil -> play({next_numbers, boards})
|
||||
board -> {current_number, board}
|
||||
end
|
||||
end
|
||||
|
||||
def play_until_last({_numbers, [_last_board]} = input), do: play(input)
|
||||
|
||||
def play_until_last({numbers, boards} = input) do
|
||||
{_current_number, current_winner} = play(input)
|
||||
|
||||
current_winner_no_markers = Map.new(current_winner, fn {k, {v, _marker}} -> {k, v} end)
|
||||
|
||||
remaining_boards =
|
||||
Enum.reject(boards, fn board ->
|
||||
current_winner_no_markers == Map.new(board, fn {k, {v, _marker}} -> {k, v} end)
|
||||
end)
|
||||
|
||||
play_until_last({numbers, remaining_boards})
|
||||
end
|
||||
|
||||
def sum_unmarked(board) do
|
||||
board
|
||||
|> Enum.filter(&match?({_key, {_number, false}}, &1))
|
||||
|> Enum.map(fn {_key, {number, _marked}} -> number end)
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
def mark(board, current_number) do
|
||||
case Enum.find(board, &match?({_key, {^current_number, false}}, &1)) do
|
||||
nil -> board
|
||||
{key, _value} -> Map.put(board, key, {current_number, true})
|
||||
end
|
||||
end
|
||||
|
||||
def check_for_winner([]), do: nil
|
||||
|
||||
def check_for_winner([board | boards]) do
|
||||
row_complete? =
|
||||
Enum.any?(@rows, fn row ->
|
||||
values = for key <- row, do: board[key]
|
||||
Enum.all?(values, &match?({_number, true}, &1))
|
||||
end)
|
||||
|
||||
col_complete? =
|
||||
Enum.any?(@cols, fn col ->
|
||||
values = for key <- col, do: board[key]
|
||||
Enum.all?(values, &match?({_number, true}, &1))
|
||||
end)
|
||||
|
||||
if row_complete? or col_complete? do
|
||||
board
|
||||
else
|
||||
check_for_winner(boards)
|
||||
end
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
[numbers | boards] = String.split(input, "\n", trim: true)
|
||||
numbers = numbers |> String.split(",") |> Enum.map(&String.to_integer/1)
|
||||
boards = parse_boards(boards)
|
||||
{numbers, boards}
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_boards([]), do: []
|
||||
|
||||
defp parse_boards(boards) do
|
||||
{board, boards} = Enum.split(boards, 5)
|
||||
|
||||
board = Enum.map(board, &String.split/1)
|
||||
|
||||
board =
|
||||
for {row, y} <- Enum.with_index(board), {value, x} <- Enum.with_index(row), into: %{} do
|
||||
{{x, y}, {String.to_integer(value), false}}
|
||||
end
|
||||
|
||||
[board | parse_boards(boards)]
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{result}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day4.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day4.run()
|
|
@ -1,83 +0,0 @@
|
|||
defmodule Day5 do
|
||||
def part1(input) do
|
||||
input
|
||||
|> Enum.reduce(_points = %{}, fn
|
||||
{{x, y1}, {x, y2}}, points ->
|
||||
for y <- y1..y2, reduce: points, do: (points -> plot(points, {x, y}))
|
||||
|
||||
{{x1, y}, {x2, y}}, points ->
|
||||
for x <- x1..x2, reduce: points, do: (points -> plot(points, {x, y}))
|
||||
|
||||
_point, points ->
|
||||
points
|
||||
end)
|
||||
|> Enum.count(fn {_point, count} -> count >= 2 end)
|
||||
end
|
||||
|
||||
def part2(input) do
|
||||
input
|
||||
|> Enum.reduce(_points = %{}, fn
|
||||
{{x, y1}, {x, y2}}, points ->
|
||||
for y <- y1..y2, reduce: points, do: (points -> plot(points, {x, y}))
|
||||
|
||||
{{x1, y}, {x2, y}}, points ->
|
||||
for x <- x1..x2, reduce: points, do: (points -> plot(points, {x, y}))
|
||||
|
||||
{{x1, x1}, {x2, x2}}, points ->
|
||||
for x <- x1..x2, reduce: points, do: (points -> plot(points, {x, x}))
|
||||
|
||||
{{x1, y1}, {x2, y2}}, points ->
|
||||
for point <- Enum.zip(x1..x2, y1..y2), reduce: points, do: (points -> plot(points, point))
|
||||
end)
|
||||
|> Enum.count(fn {_point, count} -> count >= 2 end)
|
||||
end
|
||||
|
||||
defp plot(points, point) do
|
||||
Map.put(points, point, (points[point] || 0) + 1)
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
input
|
||||
|> String.split(["\n", " -> ", ","], trim: true)
|
||||
|> Enum.map(&String.to_integer/1)
|
||||
|> Enum.chunk_every(4)
|
||||
|> Enum.map(fn [x1, y1, x2, y2] -> {{x1, y1}, {x2, y2}} end)
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{result}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day5.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day5.run()
|
|
@ -1,84 +0,0 @@
|
|||
defmodule Day6 do
|
||||
def part1(input) do
|
||||
input
|
||||
|> run_days(80)
|
||||
|> length()
|
||||
end
|
||||
|
||||
defp run_days(school, 0), do: school
|
||||
defp run_days(school, days), do: run_days(day(school), days - 1)
|
||||
|
||||
defp day(school), do: Enum.flat_map(school, &fish/1)
|
||||
|
||||
defp fish(0), do: [6, 8]
|
||||
defp fish(age), do: [age - 1]
|
||||
|
||||
# Implementing part 2 completely differently to avoid exponential growth
|
||||
|
||||
def part2(input) do
|
||||
counts = Map.new(0..8, fn age -> {age, 0} end)
|
||||
|
||||
input
|
||||
|> Enum.frequencies()
|
||||
|> Enum.into(counts)
|
||||
|> run_days2(256)
|
||||
|> Map.values()
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
defp run_days2(counts, 0), do: counts
|
||||
defp run_days2(counts, days), do: run_days2(day2(counts), days - 1)
|
||||
|
||||
defp day2(counts) do
|
||||
{breeders, counts} = Map.pop(counts, 0)
|
||||
|
||||
counts
|
||||
|> Map.new(fn {age, count} -> {age - 1, count} end)
|
||||
|> Map.update(6, 0, &(&1 + breeders))
|
||||
|> Map.put(8, breeders)
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
input
|
||||
|> String.trim()
|
||||
|> String.split(",")
|
||||
|> Enum.map(&String.to_integer/1)
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{result}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day6.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day6.run()
|
|
@ -1,81 +0,0 @@
|
|||
defmodule Day7 do
|
||||
def part1(input) do
|
||||
input
|
||||
|> Enum.frequencies()
|
||||
|> Enum.into(Map.new(Enum.min(input)..Enum.max(input), &{&1, 0}))
|
||||
|> calculate_costs()
|
||||
|> Enum.min()
|
||||
end
|
||||
|
||||
defp calculate_costs(crabs) do
|
||||
for here <- Map.keys(crabs) do
|
||||
for {there, crabs_there} <- Map.delete(crabs, here), reduce: 0 do
|
||||
cost -> cost + abs(here - there) * crabs_there
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def part2(input) do
|
||||
input
|
||||
|> Enum.frequencies()
|
||||
|> Enum.into(Map.new(Enum.min(input)..Enum.max(input), &{&1, 0}))
|
||||
|> calculate_sum_costs()
|
||||
|> Enum.min()
|
||||
end
|
||||
|
||||
defp calculate_sum_costs(crabs) do
|
||||
for here <- Map.keys(crabs) do
|
||||
for {there, crabs_there} <- Map.delete(crabs, here), reduce: 0 do
|
||||
cost ->
|
||||
distance = abs(here - there)
|
||||
# Mmm maths. Can get the sum of 1..n by doing (n * n+1)/2. Guess you have to know it.
|
||||
sum = div(distance * (distance + 1), 2)
|
||||
cost + sum * crabs_there
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
input
|
||||
|> String.trim()
|
||||
|> String.split(",")
|
||||
|> Enum.map(&String.to_integer/1)
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{result}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day7.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day7.run()
|
111
2021/day9.exs
111
2021/day9.exs
|
@ -1,111 +0,0 @@
|
|||
defmodule Day9 do
|
||||
defmodule Cave do
|
||||
defstruct floor: %{}, max_x: 0, max_y: 0
|
||||
end
|
||||
|
||||
def part1(cave) do
|
||||
for x <- 0..cave.max_x, y <- 0..cave.max_y, lowest_neighbour?(cave, x, y), reduce: 0 do
|
||||
risk_level -> risk_level + cave.floor[{x, y}] + 1
|
||||
end
|
||||
end
|
||||
|
||||
defp neighbours(cave, x, y) do
|
||||
[{x - 1, y}, {x + 1, y}, {x, y - 1}, {x, y + 1}]
|
||||
|> Enum.reject(fn {x, y} -> x < 0 or x > cave.max_x or y < 0 or y > cave.max_y end)
|
||||
end
|
||||
|
||||
defp lowest_neighbour?(cave, x, y) do
|
||||
here = cave.floor[{x, y}]
|
||||
Enum.all?(neighbours(cave, x, y), fn {xn, yn} -> here < cave.floor[{xn, yn}] end)
|
||||
end
|
||||
|
||||
def part2(cave) do
|
||||
cave
|
||||
|> remove_nines()
|
||||
|> find_basins()
|
||||
|> Enum.sort(:desc)
|
||||
|> Enum.take(3)
|
||||
|> Enum.reduce(&Kernel.*/2)
|
||||
end
|
||||
|
||||
defp find_basins(%Cave{floor: floor}) when floor == %{}, do: []
|
||||
|
||||
defp find_basins(cave) do
|
||||
{cave, location} = get_start_location(cave)
|
||||
{cave, basin} = find_basin(cave, [location], [location])
|
||||
[basin | find_basins(cave)]
|
||||
end
|
||||
|
||||
defp find_basin(cave, [], basin), do: {cave, length(basin)}
|
||||
|
||||
defp find_basin(cave, [{x, y} | locations], basin) do
|
||||
neighbours = cave.floor |> Map.take(neighbours(cave, x, y)) |> Map.keys()
|
||||
cave = %Cave{cave | floor: Map.drop(cave.floor, neighbours)}
|
||||
find_basin(cave, locations ++ neighbours, basin ++ neighbours)
|
||||
end
|
||||
|
||||
defp remove_nines(cave) do
|
||||
floor = Map.reject(cave.floor, &match?({_k, 9}, &1))
|
||||
%Cave{cave | floor: floor}
|
||||
end
|
||||
|
||||
defp get_start_location(cave) do
|
||||
[{location, _v}] = Enum.take(cave.floor, 1)
|
||||
cave = %Cave{cave | floor: Map.delete(cave.floor, location)}
|
||||
{cave, location}
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
rows =
|
||||
input
|
||||
|> String.split("\n", trim: true)
|
||||
|> Enum.map(&String.split(&1, "", trim: true))
|
||||
|
||||
floor =
|
||||
for {row, y} <- Enum.with_index(rows), {height, x} <- Enum.with_index(row), into: %{} do
|
||||
{{x, y}, String.to_integer(height)}
|
||||
end
|
||||
|
||||
max_x = length(hd(rows)) - 1
|
||||
max_y = length(rows) - 1
|
||||
|
||||
%Cave{floor: floor, max_x: max_x, max_y: max_y}
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{result}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day9.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day9.run()
|
|
@ -1,11 +0,0 @@
|
|||
# AdventOfCode2022
|
||||
|
||||
My (attempted) solutions to [Advent of Code 2022](https://adventofcode.com/2022) in Elixir.
|
||||
|
||||
<img width="869" alt="image" src="https://user-images.githubusercontent.com/498229/207063420-9e176055-d40c-4b33-a826-368c04a7b75d.png">
|
||||
|
||||
## Running:
|
||||
|
||||
```sh
|
||||
elixir dayN.exs input_filename
|
||||
```
|
|
@ -1,58 +0,0 @@
|
|||
defmodule Day1 do
|
||||
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
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
input
|
||||
|> String.split("\n\n")
|
||||
|> Enum.map(&for elf <- String.split(&1, "\n", trim: true), do: String.to_integer(elf))
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{result}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day1.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day1.run()
|
109
2022/day10.exs
109
2022/day10.exs
|
@ -1,109 +0,0 @@
|
|||
defmodule Day10 do
|
||||
defmodule Instr do
|
||||
defstruct cmd: nil, cycles: 0, amt: nil
|
||||
end
|
||||
|
||||
def part1(input) do
|
||||
process(input, _cycle = 1, _x = 1, _sample_at = 20, _sig = 0)
|
||||
end
|
||||
|
||||
defp process([], _cycle, _x, _sample_at, sig), do: sig
|
||||
|
||||
defp process(instrs, cycle, x, sample_at, sig) when cycle == sample_at do
|
||||
process(instrs, cycle, x, sample_at + 40, sig + cycle * x)
|
||||
end
|
||||
|
||||
defp process([%Instr{cycles: 0} | rest], cycle, x, sample_at, sig) do
|
||||
process(rest, cycle + 1, x, sample_at, sig)
|
||||
end
|
||||
|
||||
defp process([instr = %Instr{cycles: 1, cmd: :noop} | rest], cycle, x, sample_at, sig) do
|
||||
process([%Instr{instr | cycles: 0} | rest], cycle, x, sample_at, sig)
|
||||
end
|
||||
|
||||
defp process([instr = %Instr{cycles: 1, cmd: :addx, amt: amt} | rest], cycle, x, sample_at, sig) do
|
||||
process([%Instr{instr | cycles: 0} | rest], cycle, x + amt, sample_at, sig)
|
||||
end
|
||||
|
||||
defp process([instr = %Instr{cycles: cycles} | rest], cycle, x, sample_at, sig) do
|
||||
process([%Instr{instr | cycles: cycles - 1} | rest], cycle + 1, x, sample_at, sig)
|
||||
end
|
||||
|
||||
def part2(input) do
|
||||
input
|
||||
|> scan(_x = 1, _pixel = 0, _pixels = [[]])
|
||||
|> Enum.map(&Enum.reverse/1)
|
||||
|> Enum.reverse()
|
||||
|> Enum.join("\n")
|
||||
end
|
||||
|
||||
defp scan([], _x, _pixel, pixels), do: pixels
|
||||
|
||||
defp scan(instrs, x, _pixel = 40, pixels) do
|
||||
scan(instrs, x, _pixel = 0, [[] | pixels])
|
||||
end
|
||||
|
||||
defp scan([%Instr{cmd: :noop} | rest], x, pixel, [row | pixels]) do
|
||||
scan(rest, x, pixel + 1, [draw_pixel(pixel, x, row) | pixels])
|
||||
end
|
||||
|
||||
defp scan([%Instr{cycles: 1, cmd: :addx, amt: amt} | rest], x, pixel, [row | pixels]) do
|
||||
scan(rest, x + amt, pixel + 1, [draw_pixel(pixel, x, row) | pixels])
|
||||
end
|
||||
|
||||
defp scan([instr = %Instr{cycles: cycles} | rest], x, pixel, [row | pixels]) do
|
||||
scan([%Instr{instr | cycles: cycles - 1} | rest], x, pixel + 1, [
|
||||
draw_pixel(pixel, x, row) | pixels
|
||||
])
|
||||
end
|
||||
|
||||
defp draw_pixel(pixel, x, row) when pixel in (x - 1)..(x + 1), do: ["#" | row]
|
||||
defp draw_pixel(_pixel, _x, row), do: [" " | row]
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
input
|
||||
|> String.trim()
|
||||
|> String.split("\n")
|
||||
|> Enum.map(fn
|
||||
"noop" -> %Instr{cmd: :noop, cycles: 1}
|
||||
"addx " <> x -> %Instr{cmd: :addx, cycles: 2, amt: String.to_integer(x)}
|
||||
end)
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{result}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day10.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day10.run()
|
188
2022/day11.exs
188
2022/day11.exs
|
@ -1,188 +0,0 @@
|
|||
defmodule Monkey do
|
||||
use GenServer
|
||||
|
||||
defstruct [
|
||||
:num,
|
||||
:items_caught,
|
||||
:operation,
|
||||
:divisor,
|
||||
:true_monkey,
|
||||
:false_monkey,
|
||||
:inspected_count
|
||||
]
|
||||
|
||||
# Server
|
||||
#########
|
||||
|
||||
def init(%Monkey{} = monkey) do
|
||||
{:ok, {monkey, _monkeys = %{}}}
|
||||
end
|
||||
|
||||
def handle_cast({:catch, item}, {monkey, monkies}) do
|
||||
monkey = catch_item(monkey, item)
|
||||
{:noreply, {monkey, monkies}}
|
||||
end
|
||||
|
||||
def handle_call({:register, monkies}, _from, {monkey, _monkies}) do
|
||||
{:reply, :ok, {monkey, monkies}}
|
||||
end
|
||||
|
||||
def handle_call(:turn, _from, {monkey, monkies}) do
|
||||
monkey = do_business(monkey, monkies)
|
||||
{:reply, :done, {%Monkey{monkey | items_caught: []}, monkies}}
|
||||
end
|
||||
|
||||
def handle_call(:get_count, _from, {monkey, monkies}) do
|
||||
{:reply, monkey.inspected_count, {monkey, monkies}}
|
||||
end
|
||||
|
||||
# Client
|
||||
#########
|
||||
def throw(monkey, item), do: GenServer.cast(monkey, {:catch, item})
|
||||
def register(monkey, monkeys), do: GenServer.call(monkey, {:register, monkeys})
|
||||
def take_turn(monkey), do: GenServer.call(monkey, :turn)
|
||||
def report_count(monkey), do: GenServer.call(monkey, :get_count)
|
||||
|
||||
# Monkey business
|
||||
##################
|
||||
|
||||
defp catch_item(monkey, item), do: %Monkey{monkey | items_caught: [item | monkey.items_caught]}
|
||||
|
||||
defp do_business(monkey, monkies) do
|
||||
count =
|
||||
for item <- Enum.reverse(monkey.items_caught), reduce: 0 do
|
||||
count ->
|
||||
item = monkey.operation.(item) |> div(3)
|
||||
|
||||
to_monkey =
|
||||
if rem(item, monkey.divisor) == 0 do
|
||||
monkey.true_monkey
|
||||
else
|
||||
monkey.false_monkey
|
||||
end
|
||||
|
||||
throw(monkies[to_monkey], item)
|
||||
count + 1
|
||||
end
|
||||
|
||||
%Monkey{monkey | items_caught: [], inspected_count: monkey.inspected_count + count}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Day11 do
|
||||
def part1(input) do
|
||||
monkies = setup_monkies(input)
|
||||
order = monkies |> Map.keys() |> Enum.sort()
|
||||
|
||||
run_rounds(20, monkies, order)
|
||||
|
||||
order
|
||||
|> Enum.map(&Monkey.report_count(monkies[&1]))
|
||||
|> Enum.sort(:desc)
|
||||
|> Enum.take(2)
|
||||
|> Enum.reduce(&Kernel.*/2)
|
||||
end
|
||||
|
||||
def setup_monkies(input) do
|
||||
monkies =
|
||||
input
|
||||
|> Enum.with_index()
|
||||
|> Map.new(fn {monkey, idx} ->
|
||||
{:ok, pid} = GenServer.start_link(Monkey, monkey)
|
||||
{idx, pid}
|
||||
end)
|
||||
|
||||
Enum.each(monkies, fn {_k, monkey} -> Monkey.register(monkey, monkies) end)
|
||||
|
||||
monkies
|
||||
end
|
||||
|
||||
defp run_rounds(0, _monkies, _order), do: :ok
|
||||
|
||||
defp run_rounds(rounds, monkies, order) do
|
||||
Enum.each(order, &Monkey.take_turn(monkies[&1]))
|
||||
run_rounds(rounds - 1, monkies, order)
|
||||
end
|
||||
|
||||
def part2(_input) do
|
||||
"not implemented"
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
input
|
||||
|> String.split("\n\n")
|
||||
|> Enum.map(fn monkey ->
|
||||
[
|
||||
<<"Monkey ", monkey_num::binary-1, ":">>,
|
||||
"Starting items: " <> starting_items,
|
||||
<<"Operation: new = old ", op::binary-1, " ", operand::binary>>,
|
||||
"Test: divisible by " <> divisor,
|
||||
"If true: throw to monkey " <> true_monkey,
|
||||
"If false: throw to monkey " <> false_monkey
|
||||
] = monkey |> String.trim() |> String.split("\n") |> Enum.map(&String.trim/1)
|
||||
|
||||
starting_items = String.split(starting_items, ", ")
|
||||
|
||||
[monkey_num, divisor, true_monkey, false_monkey | starting_items] =
|
||||
Enum.map(
|
||||
[monkey_num, divisor, true_monkey, false_monkey] ++ starting_items,
|
||||
&String.to_integer/1
|
||||
)
|
||||
|
||||
operation =
|
||||
case {op, operand} do
|
||||
{"+", "old"} -> fn old -> old + old end
|
||||
{"*", "old"} -> fn old -> old * old end
|
||||
{"+", operand} -> fn old -> old + String.to_integer(operand) end
|
||||
{"*", operand} -> fn old -> old * String.to_integer(operand) end
|
||||
end
|
||||
|
||||
%Monkey{
|
||||
num: monkey_num,
|
||||
items_caught: Enum.reverse(starting_items),
|
||||
operation: operation,
|
||||
divisor: divisor,
|
||||
true_monkey: true_monkey,
|
||||
false_monkey: false_monkey,
|
||||
inspected_count: 0
|
||||
}
|
||||
end)
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{result}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day11.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day11.run()
|
100
2022/day2.exs
100
2022/day2.exs
|
@ -1,100 +0,0 @@
|
|||
defmodule Day2 do
|
||||
def part1(input) do
|
||||
input
|
||||
|> Enum.map(fn
|
||||
{opponent, "X"} -> {opponent, :rock}
|
||||
{opponent, "Y"} -> {opponent, :paper}
|
||||
{opponent, "Z"} -> {opponent, :scissors}
|
||||
end)
|
||||
|> Enum.map(&play/1)
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
@rock 1
|
||||
@paper 2
|
||||
@scissors 3
|
||||
@lose 0
|
||||
@draw 3
|
||||
@win 6
|
||||
|
||||
defp play({:rock, :rock}), do: @rock + @draw
|
||||
defp play({:paper, :rock}), do: @rock + @lose
|
||||
defp play({:scissors, :rock}), do: @rock + @win
|
||||
defp play({:rock, :paper}), do: @paper + @win
|
||||
defp play({:paper, :paper}), do: @paper + @draw
|
||||
defp play({:scissors, :paper}), do: @paper + @lose
|
||||
defp play({:rock, :scissors}), do: @scissors + @lose
|
||||
defp play({:paper, :scissors}), do: @scissors + @win
|
||||
defp play({:scissors, :scissors}), do: @scissors + @draw
|
||||
|
||||
def part2(input) do
|
||||
input
|
||||
|> Enum.map(fn
|
||||
{opponent, "X"} -> {opponent, :lose}
|
||||
{opponent, "Y"} -> {opponent, :draw}
|
||||
{opponent, "Z"} -> {opponent, :win}
|
||||
end)
|
||||
|> Enum.map(&play2/1)
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
defp play2({:rock, :win}), do: @win + @paper
|
||||
defp play2({:paper, :win}), do: @win + @scissors
|
||||
defp play2({:scissors, :win}), do: @win + @rock
|
||||
defp play2({:rock, :draw}), do: @draw + @rock
|
||||
defp play2({:paper, :draw}), do: @draw + @paper
|
||||
defp play2({:scissors, :draw}), do: @draw + @scissors
|
||||
defp play2({:rock, :lose}), do: @lose + @scissors
|
||||
defp play2({:paper, :lose}), do: @lose + @rock
|
||||
defp play2({:scissors, :lose}), do: @lose + @paper
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
input
|
||||
|> String.split([" ", "\n"], trim: true)
|
||||
|> Enum.map(fn
|
||||
"A" -> :rock
|
||||
"B" -> :paper
|
||||
"C" -> :scissors
|
||||
me -> me
|
||||
end)
|
||||
|> Enum.chunk_every(2)
|
||||
|> Enum.map(&List.to_tuple/1)
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{result}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day2.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day2.run()
|
|
@ -1,71 +0,0 @@
|
|||
defmodule Day3 do
|
||||
def part1(input) do
|
||||
input
|
||||
|> Enum.map(fn str ->
|
||||
size = byte_size(str)
|
||||
|
||||
str
|
||||
|> String.split("", trim: true)
|
||||
|> Enum.split(div(size, 2))
|
||||
end)
|
||||
|> Enum.map(fn {a, b} -> hd(a -- a -- b) end)
|
||||
|> Enum.map(&score/1)
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
defp score(<<lower::utf8>>) when lower in ?a..?z, do: lower - ?a + 1
|
||||
defp score(<<upper::utf8>>) when upper in ?A..?Z, do: upper - ?A + 27
|
||||
|
||||
def part2(input) do
|
||||
input
|
||||
|> Enum.map(&String.split(&1, "", trim: true))
|
||||
|> Enum.chunk_every(3)
|
||||
|> Enum.map(fn [a, b, c] ->
|
||||
d = a -- a -- b
|
||||
hd(d -- d -- c)
|
||||
end)
|
||||
|> Enum.map(&score/1)
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
String.split(input, "\n", trim: true)
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{result}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day3.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day3.run()
|
|
@ -1,55 +0,0 @@
|
|||
defmodule Day4 do
|
||||
def part1(input) do
|
||||
Enum.count(input, fn [a, b] -> MapSet.subset?(a, b) or MapSet.subset?(b, a) end)
|
||||
end
|
||||
|
||||
def part2(input) do
|
||||
Enum.count(input, fn [a, b] -> not MapSet.disjoint?(a, b) end)
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
input
|
||||
|> String.split(["-", ",", "\n"], trim: true)
|
||||
|> Enum.map(&String.to_integer/1)
|
||||
|> Enum.chunk_every(2)
|
||||
|> Enum.map(fn [a, b] -> MapSet.new(a..b) end)
|
||||
|> Enum.chunk_every(2)
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{result}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day4.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day4.run()
|
|
@ -1,92 +0,0 @@
|
|||
defmodule Day5 do
|
||||
def part1(input), do: input |> move_with(&move_9000/3) |> top_crates()
|
||||
def part2(input), do: input |> move_with(&move_9001/3) |> top_crates()
|
||||
|
||||
defp move_with({stacks, procedure}, move) do
|
||||
for [number, from_index, to_index] <- procedure, reduce: stacks do
|
||||
stacks ->
|
||||
{moved_from, moved_to} = move.(number, stacks[from_index], stacks[to_index])
|
||||
%{stacks | from_index => moved_from, to_index => moved_to}
|
||||
end
|
||||
end
|
||||
|
||||
defp move_9000(0, from, to), do: {from, to}
|
||||
defp move_9000(number, [crate | from], to), do: move_9000(number - 1, from, [crate | to])
|
||||
|
||||
defp move_9001(number, from, to) do
|
||||
{moving, moved_from} = Enum.split(from, number)
|
||||
{moved_from, moving ++ to}
|
||||
end
|
||||
|
||||
defp top_crates(stacks) do
|
||||
stacks
|
||||
|> Enum.sort_by(fn {label, _stack} -> label end)
|
||||
|> Enum.reduce("", fn {_label, [top | _stack]}, acc -> acc <> top end)
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
[raw_stacks, raw_procedure] = String.split(input, "\n\n")
|
||||
|
||||
stacks =
|
||||
raw_stacks
|
||||
|> String.split("\n")
|
||||
|> parse_stacks()
|
||||
|> Enum.zip()
|
||||
|> Enum.map(fn stack -> stack |> Tuple.to_list() |> Enum.drop_while(&is_nil/1) end)
|
||||
|> Enum.with_index()
|
||||
|> Map.new(fn {stack, index} -> {index + 1, stack} end)
|
||||
|
||||
procedure =
|
||||
raw_procedure
|
||||
|> String.split(["move ", " from ", " to ", "\n"], trim: true)
|
||||
|> Enum.map(&String.to_integer/1)
|
||||
|> Enum.chunk_every(3)
|
||||
|
||||
{stacks, procedure}
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_stacks([_labels_]), do: []
|
||||
defp parse_stacks([line | rest]), do: [parse_line(line) | parse_stacks(rest)]
|
||||
|
||||
defp parse_line(<<"">>), do: []
|
||||
defp parse_line(<<" ", rest::binary>>), do: [nil | parse_line(rest)]
|
||||
defp parse_line(<<"[", crate::binary-1, "]", rest::binary>>), do: [crate | parse_line(rest)]
|
||||
defp parse_line(<<" ", rest::binary>>), do: parse_line(rest)
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{result}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day5.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day5.run()
|
|
@ -1,72 +0,0 @@
|
|||
defmodule Day6 do
|
||||
@packet_marker_size 4
|
||||
@message_marker_size 14
|
||||
|
||||
def part1(input) do
|
||||
find_packet_marker(input)
|
||||
end
|
||||
|
||||
defguardp all_different(a, b, c, d)
|
||||
when a != b and a != c and a != d and b != c and b != d and c != d
|
||||
|
||||
defp find_packet_marker(buffer, seen \\ @packet_marker_size)
|
||||
defp find_packet_marker([a, b, c, d | _rest], seen) when all_different(a, b, c, d), do: seen
|
||||
defp find_packet_marker([_skip | rest], seen), do: find_packet_marker(rest, seen + 1)
|
||||
|
||||
def part2(input) do
|
||||
find_message_marker(input)
|
||||
end
|
||||
|
||||
defp find_message_marker([_skip | rest] = buffer, seen \\ @message_marker_size) do
|
||||
window = Enum.take(buffer, @message_marker_size)
|
||||
|
||||
if Enum.uniq(window) == window do
|
||||
seen
|
||||
else
|
||||
find_message_marker(rest, seen + 1)
|
||||
end
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
input
|
||||
|> String.trim()
|
||||
|> String.codepoints()
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{result}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day6.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day6.run()
|
145
2022/day7.exs
145
2022/day7.exs
|
@ -1,145 +0,0 @@
|
|||
defmodule Day7 do
|
||||
@root ["/"]
|
||||
|
||||
def part1(input) do
|
||||
input
|
||||
|> filetree_with_dir_sizes()
|
||||
|> Enum.filter(fn
|
||||
{_k, {:dir, size, _paths}} -> size <= 100_000
|
||||
{_k, {:file, _size, _paths}} -> false
|
||||
end)
|
||||
|> Enum.map(fn {_k, {_type, size, _paths}} -> size end)
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
defp filetree_with_dir_sizes(input) do
|
||||
tree = build_tree(input)
|
||||
|
||||
tree
|
||||
|> dirs_fewest_descendents_first()
|
||||
|> Enum.reduce(tree, &put_dir_size/2)
|
||||
end
|
||||
|
||||
defp put_dir_size(path, tree) do
|
||||
{:dir, nil, paths} = tree[path]
|
||||
size = tree |> Map.take(paths) |> Enum.map(fn {_, {_, size, _}} -> size end) |> Enum.sum()
|
||||
Map.put(tree, path, {:dir, size, paths})
|
||||
end
|
||||
|
||||
defp dirs_fewest_descendents_first(tree, to_check \\ [@root], found_dirs \\ [])
|
||||
defp dirs_fewest_descendents_first(_tree, [], found_dirs), do: found_dirs
|
||||
|
||||
defp dirs_fewest_descendents_first(tree, [node | rest], dirs) do
|
||||
case tree[node] do
|
||||
{:file, _size, nil} -> dirs_fewest_descendents_first(tree, rest, dirs)
|
||||
{:dir, _size, paths} -> dirs_fewest_descendents_first(tree, paths ++ rest, [node | dirs])
|
||||
end
|
||||
end
|
||||
|
||||
defp build_tree(input, path \\ [], tree \\ %{})
|
||||
defp build_tree([], _path, tree), do: tree
|
||||
defp build_tree([{:cd, "/"} | rest], _path, tree), do: build_tree(rest, @root, tree)
|
||||
defp build_tree([{:cd, ".."} | rest], [_here | path], tree), do: build_tree(rest, path, tree)
|
||||
defp build_tree([{:cd, dir} | rest], path, tree), do: build_tree(rest, [dir | path], tree)
|
||||
|
||||
defp build_tree([{:ls, items} | rest], path, tree) do
|
||||
{paths, tree} =
|
||||
Enum.map_reduce(items, tree, fn
|
||||
# Create an empty directory for each dir listed
|
||||
# (needed if we we forget to ls inside a dir)
|
||||
{:dir, nil, name}, tree ->
|
||||
item_path = [name | path]
|
||||
{item_path, Map.put(tree, item_path, {:dir, nil, []})}
|
||||
|
||||
# Create a file for each file listed
|
||||
{:file, size, name}, tree ->
|
||||
item_path = [name | path]
|
||||
{item_path, Map.put(tree, item_path, {:file, size, nil})}
|
||||
end)
|
||||
|
||||
# Create current directory with listed children
|
||||
tree = Map.put(tree, path, {:dir, nil, paths})
|
||||
|
||||
build_tree(rest, path, tree)
|
||||
end
|
||||
|
||||
@total_size 70_000_000
|
||||
@space_needed 30_000_000
|
||||
|
||||
def part2(input) do
|
||||
tree = filetree_with_dir_sizes(input)
|
||||
|
||||
{:dir, space_used, _} = tree[@root]
|
||||
unused_space = @total_size - space_used
|
||||
space_to_free = @space_needed - unused_space
|
||||
|
||||
tree
|
||||
|> Enum.filter(&match?({_path, {:dir, _size, _paths}}, &1))
|
||||
|> Enum.map(fn {_path, {:dir, size, _paths}} -> size end)
|
||||
|> Enum.sort()
|
||||
|> Enum.find(fn size -> size >= space_to_free end)
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
input
|
||||
|> String.trim()
|
||||
|> String.split("\n")
|
||||
|> parse_input()
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_input([]), do: []
|
||||
defp parse_input(["$ cd " <> dir | rest]), do: [{:cd, dir} | parse_input(rest)]
|
||||
|
||||
defp parse_input(["$ ls" | rest]) do
|
||||
{listings, rest} = Enum.split_while(rest, &(!match?("$" <> _, &1)))
|
||||
|
||||
listings =
|
||||
Enum.map(listings, fn
|
||||
"dir " <> dir ->
|
||||
{:dir, nil, dir}
|
||||
|
||||
file ->
|
||||
[size, name] = String.split(file)
|
||||
{:file, String.to_integer(size), name}
|
||||
end)
|
||||
|
||||
[{:ls, listings} | parse_input(rest)]
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{result}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day7.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day7.run()
|
130
2022/day8.exs
130
2022/day8.exs
|
@ -1,130 +0,0 @@
|
|||
defmodule Day8 do
|
||||
defmodule Tree do
|
||||
defstruct height: nil, visible: false, score: []
|
||||
end
|
||||
|
||||
def part1({{max_x, max_y}, trees}) do
|
||||
trees
|
||||
|> look_in_direction(0..max_y, 0..max_x, false)
|
||||
|> look_in_direction(0..max_x, 0..max_y, true)
|
||||
|> look_in_direction(0..max_y, max_x..0, false)
|
||||
|> look_in_direction(0..max_x, max_y..0, true)
|
||||
|> Enum.count(&match?({_coord, %Tree{visible: true}}, &1))
|
||||
end
|
||||
|
||||
defp look_in_direction(trees, outer_range, inner_range, flip_xy?) do
|
||||
Enum.reduce(outer_range, trees, fn y, trees ->
|
||||
{_height, trees} =
|
||||
Enum.reduce_while(inner_range, {_height = -1, trees}, fn
|
||||
_x, {9, trees} ->
|
||||
{:halt, {9, trees}}
|
||||
|
||||
x, {height, trees} ->
|
||||
coord = if flip_xy?, do: {y, x}, else: {x, y}
|
||||
tree = trees[coord]
|
||||
|
||||
if tree.height > height do
|
||||
{:cont, {tree.height, Map.replace(trees, coord, %Tree{tree | visible: true})}}
|
||||
else
|
||||
{:cont, {height, trees}}
|
||||
end
|
||||
end)
|
||||
|
||||
trees
|
||||
end)
|
||||
end
|
||||
|
||||
def part2({{max_x, max_y}, trees}) do
|
||||
trees
|
||||
|> count_viewing_distance(0..max_y, 0..max_x, false)
|
||||
|> count_viewing_distance(0..max_x, 0..max_y, true)
|
||||
|> count_viewing_distance(0..max_y, max_x..0, false)
|
||||
|> count_viewing_distance(0..max_x, max_y..0, true)
|
||||
|> Enum.map(fn {_coord, %Tree{score: score}} -> Enum.reduce(score, &Kernel.*/2) end)
|
||||
|> Enum.max()
|
||||
end
|
||||
|
||||
defp count_viewing_distance(trees, outer_range, inner_range, flip_xy?) do
|
||||
Enum.reduce(outer_range, trees, fn y, trees ->
|
||||
{_scoring, trees} =
|
||||
Enum.reduce(inner_range, {_scoring = [], trees}, fn
|
||||
x, {scoring, trees} ->
|
||||
coord = if flip_xy?, do: {y, x}, else: {x, y}
|
||||
tree = trees[coord]
|
||||
|
||||
{scoring, trees} =
|
||||
Enum.reduce(scoring, {[], trees}, fn {coord, scoring_tree}, {scoring, trees} ->
|
||||
%Tree{score: [score | scores]} = scoring_tree
|
||||
scoring_tree = %Tree{scoring_tree | score: [score + 1 | scores]}
|
||||
trees = Map.replace(trees, coord, scoring_tree)
|
||||
|
||||
if scoring_tree.height <= tree.height do
|
||||
{scoring, trees}
|
||||
else
|
||||
{[{coord, scoring_tree} | scoring], trees}
|
||||
end
|
||||
end)
|
||||
|
||||
tree = %Tree{tree | score: [0 | tree.score]}
|
||||
{[{coord, tree} | scoring], Map.replace(trees, coord, tree)}
|
||||
end)
|
||||
|
||||
trees
|
||||
end)
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
grid =
|
||||
input
|
||||
|> String.trim()
|
||||
|> String.split("\n")
|
||||
|> Enum.map(&String.graphemes/1)
|
||||
|
||||
max_x = length(hd(grid)) - 1
|
||||
max_y = length(grid) - 1
|
||||
|
||||
trees =
|
||||
for {row, y} <- Enum.with_index(grid), {height, x} <- Enum.with_index(row), into: %{} do
|
||||
{{x, y}, %Tree{height: String.to_integer(height)}}
|
||||
end
|
||||
|
||||
{{max_x, max_y}, trees}
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{result}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day8.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day8.run()
|
109
2022/day9.exs
109
2022/day9.exs
|
@ -1,109 +0,0 @@
|
|||
defmodule Day9 do
|
||||
defmodule Rope do
|
||||
defstruct head: {0, 0},
|
||||
tail: {0, 0},
|
||||
knots: for(_ <- 1..8, do: {0, 0}),
|
||||
visited: MapSet.new([{0, 0}])
|
||||
end
|
||||
|
||||
def part1(input) do
|
||||
input
|
||||
|> Enum.reduce(%Rope{}, &motion/2)
|
||||
|> then(fn %Rope{visited: visited} -> MapSet.size(visited) end)
|
||||
end
|
||||
|
||||
defp motion({_dir, 0}, rope), do: rope
|
||||
|
||||
defp motion({dir, amount}, rope) do
|
||||
head = step_head(rope.head, dir)
|
||||
tail = step_tail(head, rope.tail)
|
||||
visited = MapSet.put(rope.visited, tail)
|
||||
motion({dir, amount - 1}, %Rope{head: head, tail: tail, visited: visited})
|
||||
end
|
||||
|
||||
defp step_head({x, y}, "R"), do: {x + 1, y}
|
||||
defp step_head({x, y}, "L"), do: {x - 1, y}
|
||||
defp step_head({x, y}, "U"), do: {x, y + 1}
|
||||
defp step_head({x, y}, "D"), do: {x, y - 1}
|
||||
|
||||
defp step_tail({hx, hy}, {tx, ty}) when abs(hx - tx) <= 1 and abs(hy - ty) <= 1, do: {tx, ty}
|
||||
defp step_tail({tx, hy}, {tx, ty}) when hy - ty > 0, do: {tx, ty + 1}
|
||||
defp step_tail({tx, hy}, {tx, ty}) when hy - ty < 0, do: {tx, ty - 1}
|
||||
defp step_tail({hx, ty}, {tx, ty}) when hx - tx > 0, do: {tx + 1, ty}
|
||||
defp step_tail({hx, ty}, {tx, ty}) when hx - tx < 0, do: {tx - 1, ty}
|
||||
|
||||
defp step_tail({hx, hy}, {tx, ty}) do
|
||||
dx = if hx - tx > 0, do: 1, else: -1
|
||||
dy = if hy - ty > 0, do: 1, else: -1
|
||||
{tx + dx, ty + dy}
|
||||
end
|
||||
|
||||
def part2(input) do
|
||||
input
|
||||
|> Enum.reduce(%Rope{}, &motion2/2)
|
||||
|> then(fn %Rope{visited: visited} -> MapSet.size(visited) end)
|
||||
end
|
||||
|
||||
defp motion2({_dir, 0}, rope), do: rope
|
||||
|
||||
defp motion2({dir, amount}, rope) do
|
||||
head = step_head(rope.head, dir)
|
||||
|
||||
{knots, leader} =
|
||||
Enum.map_reduce(rope.knots, head, fn knot, leader ->
|
||||
new_knot = step_tail(leader, knot)
|
||||
{new_knot, new_knot}
|
||||
end)
|
||||
|
||||
tail = step_tail(leader, rope.tail)
|
||||
|
||||
visited = MapSet.put(rope.visited, tail)
|
||||
motion2({dir, amount - 1}, %Rope{head: head, tail: tail, knots: knots, visited: visited})
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
input
|
||||
|> String.trim()
|
||||
|> String.split("\n")
|
||||
|> Enum.map(fn <<dir::binary-1, " ", amount::binary>> ->
|
||||
{dir, String.to_integer(amount)}
|
||||
end)
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{result}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day9.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day9.run()
|
|
@ -1,77 +0,0 @@
|
|||
defmodule Day1 do
|
||||
def part1(input) do
|
||||
input
|
||||
|> Enum.map(fn line ->
|
||||
numbers = filter_digits(line)
|
||||
String.to_integer(String.first(numbers) <> String.last(numbers))
|
||||
end)
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
def filter_digits(<<>>), do: <<>>
|
||||
def filter_digits(<<x, rest::binary>>) when x in ?1..?9, do: <<x>> <> filter_digits(rest)
|
||||
def filter_digits(<<_, rest::binary>>), do: filter_digits(rest)
|
||||
|
||||
def part2(input) do
|
||||
input
|
||||
|> Enum.map(fn line ->
|
||||
numbers = filter_digits2(line)
|
||||
String.to_integer(String.first(numbers) <> String.last(numbers))
|
||||
end)
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
def filter_digits2(<<>>), do: <<>>
|
||||
def filter_digits2(<<x, rest::binary>>) when x in ?1..?9, do: <<x>> <> filter_digits2(rest)
|
||||
def filter_digits2(<<"one", rest::binary>>), do: "1" <> filter_digits2("e" <> rest)
|
||||
def filter_digits2(<<"two", rest::binary>>), do: "2" <> filter_digits2("o" <> rest)
|
||||
def filter_digits2(<<"three", rest::binary>>), do: "3" <> filter_digits2("e" <> rest)
|
||||
def filter_digits2(<<"four", rest::binary>>), do: "4" <> filter_digits2(rest)
|
||||
def filter_digits2(<<"five", rest::binary>>), do: "5" <> filter_digits2("e" <> rest)
|
||||
def filter_digits2(<<"six", rest::binary>>), do: "6" <> filter_digits2(rest)
|
||||
def filter_digits2(<<"seven", rest::binary>>), do: "7" <> filter_digits2("n" <> rest)
|
||||
def filter_digits2(<<"eight", rest::binary>>), do: "8" <> filter_digits2("t" <> rest)
|
||||
def filter_digits2(<<"nine", rest::binary>>), do: "9" <> filter_digits2("e" <> rest)
|
||||
def filter_digits2(<<_, rest::binary>>), do: filter_digits2(rest)
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
String.split(input, "\n", trim: true)
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{result}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day1.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day1.run()
|
|
@ -1,79 +0,0 @@
|
|||
#!/usr/bin/env elixir
|
||||
defmodule Day2 do
|
||||
def part1(input) do
|
||||
input
|
||||
|> Map.filter(fn {_id, cubes} ->
|
||||
cubes["red"] <= 12 and cubes["green"] <= 13 and cubes["blue"] <= 14
|
||||
end)
|
||||
|> Map.keys()
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
def part2(input) do
|
||||
input
|
||||
|> Enum.map(fn {_id, cubes} -> cubes |> Map.values() |> Enum.product() end)
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
input
|
||||
|> String.split("\n", trim: true)
|
||||
|> Map.new(fn line ->
|
||||
["Game " <> id | rounds] = String.split(line, [": ", "; "])
|
||||
|
||||
rounds =
|
||||
rounds
|
||||
|> Enum.map(&parse_round/1)
|
||||
|> Enum.reduce(
|
||||
%{"red" => 0, "green" => 0, "blue" => 0},
|
||||
&Map.merge(&1, &2, fn _colour, count1, count2 -> max(count1, count2) end)
|
||||
)
|
||||
|
||||
{String.to_integer(id), rounds}
|
||||
end)
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
def parse_round(round) do
|
||||
round
|
||||
|> String.split([", ", " "])
|
||||
|> Enum.chunk_every(2)
|
||||
|> Map.new(fn [number, colour] -> {colour, String.to_integer(number)} end)
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{inspect(result)}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day2.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day2.run()
|
|
@ -1,90 +0,0 @@
|
|||
#!/usr/bin/env elixir
|
||||
defmodule Day3 do
|
||||
def part1(_input) do
|
||||
# for each number, check around its edges to see if there's a symbol
|
||||
fn {{x, y}, number, length}, acc ->
|
||||
top = for x <- (x - 1)..(x + length), do: {x, y - 1}
|
||||
bottom = for x <- (x - 1)..(x + length), do: {x, y + 1}
|
||||
box = [{x - 1, y}, {x + length, y}] ++ top ++ bottom
|
||||
if part_number?(box), do: [number | acc], else: acc
|
||||
end
|
||||
|> :ets.foldl(_acc = [], :numbers)
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
def part_number?([]), do: false
|
||||
|
||||
def part_number?([coord | rest]) do
|
||||
case :ets.lookup(:symbols, coord) do
|
||||
[_symbol] -> true
|
||||
[] -> part_number?(rest)
|
||||
end
|
||||
end
|
||||
|
||||
def part2(_input) do
|
||||
:ok
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
:ets.new(:numbers, [:named_table])
|
||||
:ets.new(:symbols, [:named_table])
|
||||
parse_schematic(input, _x = 0, _y = 0)
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
def parse_schematic("", _x, _y), do: :ok
|
||||
def parse_schematic("." <> rest, x, y), do: parse_schematic(rest, x + 1, y)
|
||||
def parse_schematic("\n" <> rest, _x, y), do: parse_schematic(rest, 0, y + 1)
|
||||
|
||||
def parse_schematic(<<digit::utf8>> <> rest, x, y) when digit in ?0..?9 do
|
||||
{number, length, rest} = take_digits(<<digit::utf8>> <> rest, "", 0)
|
||||
:ets.insert(:numbers, {{x, y}, number, length})
|
||||
parse_schematic(rest, x + length, y)
|
||||
end
|
||||
|
||||
def parse_schematic(<<symbol::utf8>> <> rest, x, y) do
|
||||
:ets.insert(:symbols, {{x, y}, <<symbol::utf8>>})
|
||||
parse_schematic(rest, x + 1, y)
|
||||
end
|
||||
|
||||
def take_digits(<<digit::utf8>> <> rest, digits, count) when digit in ?0..?9,
|
||||
do: take_digits(rest, digits <> <<digit::utf8>>, count + 1)
|
||||
|
||||
def take_digits(rest, digits, count), do: {String.to_integer(digits), count, rest}
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{inspect(result)}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day3.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
# Day3.run()
|
|
@ -1,78 +0,0 @@
|
|||
#!/usr/bin/env elixir
|
||||
defmodule Day4 do
|
||||
def part1(input) do
|
||||
input
|
||||
|> Enum.map(fn 0 -> 0; num_winners -> 2 ** (num_winners - 1) end)
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
def part2(input) do
|
||||
input
|
||||
|> Enum.map(fn num_winners -> {_num_copies = 1, num_winners} end)
|
||||
|> copy()
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
def copy([]), do: []
|
||||
|
||||
def copy([{num_copies, num_winners} | rest]) do
|
||||
{to_copy, left_alone} = Enum.split(rest, num_winners)
|
||||
|
||||
copied =
|
||||
Enum.map(to_copy, fn {child_num_copies, num_winners} ->
|
||||
{num_copies + child_num_copies, num_winners}
|
||||
end)
|
||||
|
||||
[num_copies | copy(copied ++ left_alone)]
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
input
|
||||
|> String.split("\n", trim: true)
|
||||
|> Enum.map(fn line ->
|
||||
[_card_id, winning, have] = String.split(line, [": ", " | "])
|
||||
|
||||
winning = winning |> String.split(" ", trim: true) |> MapSet.new()
|
||||
have = have |> String.split(" ", trim: true) |> MapSet.new()
|
||||
|
||||
MapSet.intersection(winning, have) |> MapSet.size()
|
||||
end)
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{inspect(result)}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day4.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day4.run()
|
110
2023/day5.exs
110
2023/day5.exs
|
@ -1,110 +0,0 @@
|
|||
#!/usr/bin/env elixir
|
||||
defmodule Day5 do
|
||||
def part1({seeds, maps}) do
|
||||
seeds
|
||||
|> Enum.map(fn seed -> Enum.reduce(maps, seed, &lookup/2) end)
|
||||
|> Enum.min()
|
||||
end
|
||||
|
||||
def lookup([], item), do: item
|
||||
|
||||
def lookup([{source, offset} | rest], item) do
|
||||
if item in source do
|
||||
item + offset
|
||||
else
|
||||
lookup(rest, item)
|
||||
end
|
||||
end
|
||||
|
||||
def part2({seeds, maps}) do
|
||||
seed_ranges =
|
||||
seeds
|
||||
|> Enum.chunk_every(2)
|
||||
|> Enum.map(fn [start, len] -> start..(start + len - 1) end)
|
||||
|
||||
maps
|
||||
|> Enum.reduce(seed_ranges, &process_ranges/2)
|
||||
|> Enum.map(& &1.first)
|
||||
|> Enum.min()
|
||||
end
|
||||
|
||||
def process_ranges(_map_ranges, []), do: []
|
||||
|
||||
def process_ranges(map_ranges, [item_range | rest]) do
|
||||
overlap =
|
||||
Enum.find(map_ranges, fn {source, _offset} -> not Range.disjoint?(source, item_range) end)
|
||||
|
||||
case overlap do
|
||||
{source, offset} ->
|
||||
to_shift = max(source.first, item_range.first)..min(source.last, item_range.last)
|
||||
|
||||
remainders =
|
||||
[
|
||||
if(item_range.first < source.first, do: item_range.first..(source.first - 1)),
|
||||
if(item_range.last > source.last, do: (source.last + 1)..item_range.last)
|
||||
]
|
||||
|> Enum.reject(&is_nil/1)
|
||||
|
||||
[Range.shift(to_shift, offset) | process_ranges(map_ranges, remainders ++ rest)]
|
||||
|
||||
nil ->
|
||||
[item_range | process_ranges(map_ranges, rest)]
|
||||
end
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
["seeds: " <> seeds | maps] = String.split(input, "\n\n")
|
||||
seeds = seeds |> String.split() |> Enum.map(&String.to_integer/1)
|
||||
|
||||
maps =
|
||||
Enum.map(maps, fn line ->
|
||||
[_header, _map | items] = String.split(line)
|
||||
|
||||
items
|
||||
|> Enum.map(&String.to_integer/1)
|
||||
|> Enum.chunk_every(3)
|
||||
|> Enum.map(fn [dest, source, offset] ->
|
||||
{source..(source + offset), dest - source}
|
||||
end)
|
||||
end)
|
||||
|
||||
{seeds, maps}
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{inspect(result, charlists: :as_lists)}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day5.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day5.run()
|
|
@ -1,69 +0,0 @@
|
|||
#!/usr/bin/env elixir
|
||||
defmodule Day6 do
|
||||
def part1(input) do
|
||||
input
|
||||
|> Enum.map(fn line -> Enum.map(line, &String.to_integer/1) end)
|
||||
|> Enum.zip()
|
||||
|> Enum.map(fn {race, record} -> count_winning(race, record) end)
|
||||
|> Enum.product()
|
||||
end
|
||||
|
||||
def count_winning(race, record) do
|
||||
scores = for n <- 1..div(race, 2), do: n * (race - n)
|
||||
[_middle | rev_no_middle] = reversed = Enum.reverse(scores)
|
||||
scores = scores ++ if rem(race, 2) == 1, do: reversed, else: rev_no_middle
|
||||
Enum.count(scores, fn score -> score > record end)
|
||||
end
|
||||
|
||||
def part2(input) do
|
||||
[race, record] = Enum.map(input, fn line -> line |> Enum.join() |> String.to_integer() end)
|
||||
count_winning(race, record)
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
input
|
||||
|> String.split("\n", trim: true)
|
||||
|> Enum.map(fn line ->
|
||||
line
|
||||
|> String.split()
|
||||
|> Enum.drop(1)
|
||||
end)
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{inspect(result)}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day6.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day6.run()
|
123
2023/day7.exs
123
2023/day7.exs
|
@ -1,123 +0,0 @@
|
|||
#!/usr/bin/env elixir
|
||||
defmodule Day7 do
|
||||
def part1(input) do
|
||||
input
|
||||
|> Enum.sort_by(&detect_type/1, &rank/2)
|
||||
|> Enum.with_index()
|
||||
|> Enum.map(fn {{_hand, bid}, rank} -> bid * (rank + 1) end)
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
def detect_type({hand, _bid}) do
|
||||
frequencies =
|
||||
hand
|
||||
|> Enum.frequencies()
|
||||
|> Enum.group_by(fn {_card, freq} -> freq end)
|
||||
|> Map.new(fn {freq, cards} -> {freq, length(cards)} end)
|
||||
|
||||
type =
|
||||
cond do
|
||||
frequencies[5] -> :five_kind
|
||||
frequencies[4] -> :four_kind
|
||||
frequencies[3] && frequencies[2] -> :full_house
|
||||
frequencies[3] -> :three_kind
|
||||
frequencies[2] == 2 -> :two_pair
|
||||
frequencies[2] -> :one_pair
|
||||
true -> :high_card
|
||||
end
|
||||
|
||||
{type, hand}
|
||||
end
|
||||
|
||||
def rank({same_type, hand1}, {same_type, hand2}), do: tiebreak(hand1, hand2)
|
||||
def rank({:high_card, _}, _), do: true
|
||||
def rank({:one_pair, _}, {:high_card, _}), do: false
|
||||
def rank({:one_pair, _}, _), do: true
|
||||
def rank({:two_pair, _}, {b, _}) when b in [:high_card, :one_pair], do: false
|
||||
def rank({:two_pair, _}, _), do: true
|
||||
def rank({:three_kind, _}, {b, _}) when b in [:high_card, :one_pair, :two_pair], do: false
|
||||
def rank({:three_kind, _}, _), do: true
|
||||
def rank({:full_house, _}, {b, _}) when b in [:five_kind, :four_kind], do: true
|
||||
def rank({:full_house, _}, _), do: false
|
||||
def rank({:four_kind, _}, {:five_kind, _}), do: true
|
||||
def rank({:four_kind, _}, _), do: false
|
||||
def rank({:five_kind, _}, _), do: false
|
||||
|
||||
def tiebreak([first | resta], [first | restb]), do: tiebreak(resta, restb)
|
||||
def tiebreak(["1" | _], _), do: true
|
||||
def tiebreak(["2" | _], ["1" | _]), do: false
|
||||
def tiebreak(["2" | _], _), do: true
|
||||
def tiebreak(["3" | _], [b | _]) when b in ["1", "2"], do: false
|
||||
def tiebreak(["3" | _], _), do: true
|
||||
def tiebreak(["4" | _], [b | _]) when b in ["1", "2", "3"], do: false
|
||||
def tiebreak(["4" | _], _), do: true
|
||||
def tiebreak(["5" | _], [b | _]) when b in ["1", "2", "3", "4"], do: false
|
||||
def tiebreak(["5" | _], _), do: true
|
||||
def tiebreak(["6" | _], [b | _]) when b in ["1", "2", "3", "4", "5"], do: false
|
||||
def tiebreak(["6" | _], _), do: true
|
||||
def tiebreak(["7" | _], [b | _]) when b in ["1", "2", "3", "4", "5", "6"], do: false
|
||||
def tiebreak(["7" | _], _), do: true
|
||||
def tiebreak(["8" | _], [b | _]) when b in ["A", "K", "Q", "J", "T", "9"], do: true
|
||||
def tiebreak(["8" | _], _), do: false
|
||||
def tiebreak(["9" | _], [b | _]) when b in ["A", "K", "Q", "J", "T"], do: true
|
||||
def tiebreak(["9" | _], _), do: false
|
||||
def tiebreak(["T" | _], [b | _]) when b in ["A", "K", "Q", "J"], do: true
|
||||
def tiebreak(["T" | _], _), do: false
|
||||
def tiebreak(["J" | _], [b | _]) when b in ["A", "K", "Q"], do: true
|
||||
def tiebreak(["J" | _], _), do: false
|
||||
def tiebreak(["Q" | _], [b | _]) when b in ["A", "K"], do: true
|
||||
def tiebreak(["Q" | _], _), do: false
|
||||
def tiebreak(["K" | _], ["A" | _]), do: true
|
||||
def tiebreak(["K" | _], _), do: false
|
||||
def tiebreak(["A" | _], _), do: false
|
||||
|
||||
def part2(_input) do
|
||||
:ok
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
input
|
||||
|> String.split("\n", trim: true)
|
||||
|> Enum.map(fn line ->
|
||||
[hands, bid] = String.split(line)
|
||||
{String.graphemes(hands), String.to_integer(bid)}
|
||||
end)
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{inspect(result)}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day7.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
# Day7.run()
|
|
@ -1,87 +0,0 @@
|
|||
#!/usr/bin/env elixir
|
||||
defmodule Day8 do
|
||||
def part1({instructions, network}) do
|
||||
instructions
|
||||
|> Stream.cycle()
|
||||
|> Enum.reduce_while({"AAA", 0}, fn
|
||||
_side, {"ZZZ", count} -> {:halt, count}
|
||||
side, {node, count} -> {:cont, {elem(network[node], side), count + 1}}
|
||||
end)
|
||||
end
|
||||
|
||||
def part2({instructions, network}) do
|
||||
# Following the algorithm from the question naively seems to take too long, so we need to
|
||||
# find a shortcut.
|
||||
# It seems that each ghost runs in a cycle, so we can solve the number of steps for each
|
||||
# ghost's path, and then find the lowest common multiple of those lengths, which will
|
||||
# be the first time they arrive at the final spaces together.
|
||||
network
|
||||
|> Map.keys()
|
||||
|> Enum.filter(&match?(<<_::binary-2, "A">>, &1))
|
||||
|> Enum.map(fn start ->
|
||||
instructions
|
||||
|> Stream.cycle()
|
||||
|> Enum.reduce_while({start, 0}, fn
|
||||
_side, {<<_::binary-2, "Z">>, count} -> {:halt, count}
|
||||
side, {node, count} -> {:cont, {elem(network[node], side), count + 1}}
|
||||
end)
|
||||
end)
|
||||
|> Enum.reduce(fn a, b -> div(a * b, Integer.gcd(a, b)) end)
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
[instructions, nodes] = String.split(input, "\n\n")
|
||||
|
||||
instructions =
|
||||
instructions
|
||||
|> String.graphemes()
|
||||
|> Enum.map(fn
|
||||
"L" -> 0
|
||||
"R" -> 1
|
||||
end)
|
||||
|
||||
network =
|
||||
for <<key::binary-3, " = (", left::binary-3, ", ", right::binary-3, ")\n" <- nodes>>,
|
||||
into: %{},
|
||||
do: {key, {left, right}}
|
||||
|
||||
{instructions, network}
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{result}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day8.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day8.run()
|
|
@ -1,78 +0,0 @@
|
|||
#!/usr/bin/env elixir
|
||||
defmodule Day9 do
|
||||
def part1(histories) do
|
||||
histories
|
||||
|> Enum.map(&walk/1)
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
def walk(history) do
|
||||
case Enum.uniq(history) do
|
||||
[last] -> last
|
||||
_ -> List.last(history) + walk(diff(history))
|
||||
end
|
||||
end
|
||||
|
||||
def diff([_]), do: []
|
||||
def diff([a, b | rest]), do: [b - a | diff([b | rest])]
|
||||
|
||||
def part2(histories) do
|
||||
histories
|
||||
|> Enum.map(&walk2/1)
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
def walk2(history) do
|
||||
case Enum.uniq(history) do
|
||||
[first] -> first
|
||||
[first | _] -> first - walk2(diff(history))
|
||||
end
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
input
|
||||
|> String.split("\n", trim: true)
|
||||
|> Enum.map(fn line ->
|
||||
line
|
||||
|> String.split(" ")
|
||||
|> Enum.map(&String.to_integer/1)
|
||||
end)
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{inspect(result)}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day9.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day9.run()
|
|
@ -1,63 +0,0 @@
|
|||
#!/usr/bin/env elixir
|
||||
defmodule Day1 do
|
||||
def part1({list1, list2}) do
|
||||
list1 = Enum.sort(list1)
|
||||
list2 = Enum.sort(list2)
|
||||
|
||||
Enum.zip_with(list1, list2, &abs(&1 - &2)) |> Enum.sum()
|
||||
end
|
||||
|
||||
def part2({list1, list2}) do
|
||||
frequencies = Enum.frequencies(list2)
|
||||
Enum.reduce(list1, 0, fn item, count -> count + item * Map.get(frequencies, item, 0) end)
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
integers =
|
||||
input
|
||||
|> String.split([" ", "\n"], trim: true)
|
||||
|> Enum.map(&String.to_integer/1)
|
||||
|
||||
list1 = Enum.take_every(integers, 2)
|
||||
list2 = Enum.take_every(tl(integers), 2)
|
||||
|
||||
{list1, list2}
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{inspect(result)}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day1.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day1.run()
|
|
@ -1,78 +0,0 @@
|
|||
#!/usr/bin/env elixir
|
||||
defmodule Day10 do
|
||||
def part1({grid, zeros}) do
|
||||
zeros
|
||||
|> Enum.map(fn point -> point |> find_trails(grid, 0) |> Enum.uniq() |> Enum.count() end)
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
def find_trails(point, _grid, 9), do: [point]
|
||||
|
||||
def find_trails({x, y}, grid, height) do
|
||||
find_neighbours(x, y, height + 1, grid)
|
||||
|> Enum.flat_map(fn {point, _} -> find_trails(point, grid, height + 1) end)
|
||||
end
|
||||
|
||||
def find_neighbours(x, y, height, grid) do
|
||||
grid
|
||||
|> Map.take([{x - 1, y}, {x + 1, y}, {x, y - 1}, {x, y + 1}])
|
||||
|> Enum.filter(&match?({_, ^height}, &1))
|
||||
end
|
||||
|
||||
def part2({grid, zeros}) do
|
||||
zeros
|
||||
|> Enum.map(fn point -> point |> find_trails(grid, 0) |> Enum.count() end)
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
{grid, zeros, _, _} =
|
||||
for <<char::binary-1 <- input>>, reduce: {%{}, [], 0, 0} do
|
||||
{grid, zeros, x, y} ->
|
||||
case char do
|
||||
"\n" -> {grid, zeros, 0, y + 1}
|
||||
"0" -> {Map.put(grid, {x, y}, 0), [{x, y} | zeros], x + 1, y}
|
||||
char -> {Map.put(grid, {x, y}, String.to_integer(char)), zeros, x + 1, y}
|
||||
end
|
||||
end
|
||||
|
||||
{grid, zeros}
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{inspect(result)}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day10.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day10.run()
|
|
@ -1,79 +0,0 @@
|
|||
#!/usr/bin/env elixir
|
||||
defmodule Day11 do
|
||||
def part1(stones) do
|
||||
blink(stones, 25)
|
||||
end
|
||||
|
||||
def part2(stones) do
|
||||
blink(stones, 75)
|
||||
end
|
||||
|
||||
def blink(stones, 0), do: stones |> Map.values() |> Enum.sum()
|
||||
|
||||
def blink(stones, times) do
|
||||
stones
|
||||
|> Enum.reduce(%{}, fn
|
||||
{0, count}, next ->
|
||||
Map.update(next, 1, count, &(&1 + count))
|
||||
|
||||
{stone, count}, next ->
|
||||
string = Integer.to_string(stone)
|
||||
size = byte_size(string)
|
||||
|
||||
if rem(size, 2) == 0 do
|
||||
{a, b} = String.split_at(string, div(size, 2))
|
||||
|
||||
next
|
||||
|> Map.update(String.to_integer(a), count, &(&1 + count))
|
||||
|> Map.update(String.to_integer(b), count, &(&1 + count))
|
||||
else
|
||||
Map.update(next, stone * 2024, count, &(&1 + count))
|
||||
end
|
||||
end)
|
||||
|> blink(times - 1)
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
input
|
||||
|> String.split([" ", "\n"], trim: true)
|
||||
|> Enum.map(&String.to_integer/1)
|
||||
|> Enum.frequencies()
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{inspect(result)}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day11.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day11.run()
|
176
2024/day12.exs
176
2024/day12.exs
|
@ -1,176 +0,0 @@
|
|||
#!/usr/bin/env elixir
|
||||
defmodule Day12 do
|
||||
defmodule Region do
|
||||
defstruct label: nil, plots: MapSet.new(), perimeter: 0
|
||||
end
|
||||
|
||||
def part1({grid, size_x, size_y}) do
|
||||
find_regions(grid, size_x, size_y)
|
||||
|> Enum.map(fn region -> MapSet.size(region.plots) * region.perimeter end)
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
def find_regions(grid, size_x, size_y) do
|
||||
for x <- 0..(size_x - 1), y <- 0..(size_y - 1), reduce: {[], MapSet.new()} do
|
||||
{regions, seen} ->
|
||||
case find_region(x, y, grid, seen) do
|
||||
{:already_found, _seen} -> {regions, seen}
|
||||
{region, seen} -> {[region | regions], seen}
|
||||
end
|
||||
end
|
||||
|> elem(0)
|
||||
end
|
||||
|
||||
def find_region(x, y, grid, seen, region \\ nil) do
|
||||
if MapSet.member?(seen, {x, y}) do
|
||||
{region || :already_found, seen}
|
||||
else
|
||||
region = region || %Region{label: Map.fetch!(grid, {x, y})}
|
||||
|
||||
seen = MapSet.put(seen, {x, y})
|
||||
region_neighbours = get_region_neighbours(x, y, region.label, grid)
|
||||
|
||||
region = %Region{
|
||||
region
|
||||
| plots: MapSet.put(region.plots, {x, y}),
|
||||
perimeter: region.perimeter + (4 - length(region_neighbours))
|
||||
}
|
||||
|
||||
Enum.reduce(region_neighbours, {region, seen}, fn {x, y}, {region, seen} ->
|
||||
find_region(x, y, grid, seen, region)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
def get_region_neighbours(x, y, label, grid) do
|
||||
grid
|
||||
|> Map.take([{x - 1, y}, {x + 1, y}, {x, y - 1}, {x, y + 1}])
|
||||
|> Map.filter(&match?({_coord, ^label}, &1))
|
||||
|> Map.keys()
|
||||
end
|
||||
|
||||
def part2({grid, size_x, size_y}) do
|
||||
find_regions(grid, size_x, size_y)
|
||||
|> Enum.map(fn region -> MapSet.size(region.plots) * count_sides(region) end)
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
def count_sides(region) do
|
||||
{min_x, _y} = Enum.min_by(region.plots, fn {x, _y} -> x end)
|
||||
{_x, min_y} = Enum.min_by(region.plots, fn {_x, y} -> y end)
|
||||
{max_x, _y} = Enum.max_by(region.plots, fn {x, _y} -> x end)
|
||||
{_x, max_y} = Enum.max_by(region.plots, fn {_x, y} -> y end)
|
||||
|
||||
left =
|
||||
Enum.reduce(min_x..max_x, 0, fn x, sides ->
|
||||
Enum.reduce(min_y..max_y, {sides, false}, fn y, {sides, sequence?} ->
|
||||
plot? = MapSet.member?(region.plots, {x, y})
|
||||
open? = MapSet.member?(region.plots, {x - 1, y})
|
||||
|
||||
cond do
|
||||
plot? and not open? and not sequence? -> {sides + 1, true}
|
||||
plot? and not open? and sequence? -> {sides, true}
|
||||
true -> {sides, false}
|
||||
end
|
||||
end)
|
||||
|> elem(0)
|
||||
end)
|
||||
|
||||
right =
|
||||
Enum.reduce(min_x..max_x, 0, fn x, sides ->
|
||||
Enum.reduce(min_y..max_y, {sides, false}, fn y, {sides, sequence?} ->
|
||||
plot? = MapSet.member?(region.plots, {x, y})
|
||||
open? = MapSet.member?(region.plots, {x + 1, y})
|
||||
|
||||
cond do
|
||||
plot? and not open? and not sequence? -> {sides + 1, true}
|
||||
plot? and not open? and sequence? -> {sides, true}
|
||||
true -> {sides, false}
|
||||
end
|
||||
end)
|
||||
|> elem(0)
|
||||
end)
|
||||
|
||||
top =
|
||||
Enum.reduce(min_y..max_y, 0, fn y, sides ->
|
||||
Enum.reduce(min_x..max_x, {sides, false}, fn x, {sides, sequence?} ->
|
||||
plot? = MapSet.member?(region.plots, {x, y})
|
||||
open? = MapSet.member?(region.plots, {x, y - 1})
|
||||
|
||||
cond do
|
||||
plot? and not open? and not sequence? -> {sides + 1, true}
|
||||
plot? and not open? and sequence? -> {sides, true}
|
||||
true -> {sides, false}
|
||||
end
|
||||
end)
|
||||
|> elem(0)
|
||||
end)
|
||||
|
||||
bottom =
|
||||
Enum.reduce(min_y..max_y, 0, fn y, sides ->
|
||||
Enum.reduce(min_x..max_x, {sides, false}, fn x, {sides, sequence?} ->
|
||||
plot? = MapSet.member?(region.plots, {x, y})
|
||||
open? = MapSet.member?(region.plots, {x, y + 1})
|
||||
|
||||
cond do
|
||||
plot? and not open? and not sequence? -> {sides + 1, true}
|
||||
plot? and not open? and sequence? -> {sides, true}
|
||||
true -> {sides, false}
|
||||
end
|
||||
end)
|
||||
|> elem(0)
|
||||
end)
|
||||
|
||||
left + right + top + bottom
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
{grid, _, x, y} =
|
||||
for <<char::binary-1 <- input>>, reduce: {%{}, 0, 0, 0} do
|
||||
{grid, x, max_x, y} ->
|
||||
case char do
|
||||
"\n" -> {grid, 0, x, y + 1}
|
||||
char -> {Map.put(grid, {x, y}, char), x + 1, max_x, y}
|
||||
end
|
||||
end
|
||||
|
||||
{grid, x, y}
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{inspect(result)}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir dayDay12.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day12.run()
|
|
@ -1,81 +0,0 @@
|
|||
#!/usr/bin/env elixir
|
||||
defmodule Day13 do
|
||||
defmodule Machine do
|
||||
defstruct [:a, :b, :prize]
|
||||
end
|
||||
|
||||
def part1(input) do
|
||||
input
|
||||
# these are equivalent linear equations
|
||||
# know how to do it algebraically, but not algorithmically
|
||||
# e.g. for first example
|
||||
#
|
||||
# 94a + 22b - 8400 = 34a + 67b - 5400
|
||||
# 94a + 22b - 3000 = 34a + 67b
|
||||
# 60a + 22b - 3000 = 67b
|
||||
# 60a - 3000 = 45b
|
||||
# 4a - 200 = 3b
|
||||
# a = 3/4b + 50
|
||||
# 34(3/4b + 50) + 67b - 5400 = 0
|
||||
# 25.5b + 1700 + 67b - 5400 = 0
|
||||
# 92.5b = 3700
|
||||
# b = 40
|
||||
# 34a + 67(40) = 5400
|
||||
# 34a + 2680 = 5400
|
||||
# 34a = 2720
|
||||
# a = 80
|
||||
end
|
||||
|
||||
def part2(_input) do
|
||||
:ok
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
input
|
||||
|> String.split("\n\n")
|
||||
|> Enum.map(fn machine ->
|
||||
machine
|
||||
|> String.split(["+", ",", "=", "\n"], trim: true)
|
||||
|> tl()
|
||||
|> Enum.take_every(2)
|
||||
|> Enum.map(&String.to_integer/1)
|
||||
end)
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{inspect(result)}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day13.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
# Day13.run()
|
144
2024/day14.exs
144
2024/day14.exs
|
@ -1,144 +0,0 @@
|
|||
#!/usr/bin/env elixir
|
||||
defmodule Day14 do
|
||||
def part1({robots, size_x, size_y}) do
|
||||
mid_x = div(size_x, 2)
|
||||
mid_y = div(size_y, 2)
|
||||
|
||||
robots
|
||||
|> Enum.reduce({0, 0, 0, 0}, fn [px, py, vx, vy], {a, b, c, d} ->
|
||||
x = Integer.mod(px + vx * 100, size_x)
|
||||
y = Integer.mod(py + vy * 100, size_y)
|
||||
|
||||
case {x, y} do
|
||||
{x, y} when x < mid_x and y < mid_y -> {a + 1, b, c, d}
|
||||
{x, y} when x < mid_x and y > mid_y -> {a, b + 1, c, d}
|
||||
{x, y} when x > mid_x and y < mid_y -> {a, b, c + 1, d}
|
||||
{x, y} when x > mid_x and y > mid_y -> {a, b, c, d + 1}
|
||||
{x, y} when x == mid_x or y == mid_y -> {a, b, c, d}
|
||||
end
|
||||
end)
|
||||
|> Tuple.product()
|
||||
end
|
||||
|
||||
def part2({robots, size_x, size_y}) do
|
||||
# The tree looks like this
|
||||
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||
# X X
|
||||
# X X
|
||||
# X X
|
||||
# X X
|
||||
# X X X
|
||||
# X XXX X
|
||||
# X XXXXX X
|
||||
# X XXXXXXX X
|
||||
# X XXXXXXXXX X
|
||||
# X XXXXX X
|
||||
# X XXXXXXX X
|
||||
# X XXXXXXXXX X
|
||||
# X XXXXXXXXXXX X
|
||||
# X XXXXXXXXXXXXX X
|
||||
# X XXXXXXXXX X
|
||||
# X XXXXXXXXXXX X
|
||||
# X XXXXXXXXXXXXX X
|
||||
# X XXXXXXXXXXXXXXX X
|
||||
# X XXXXXXXXXXXXXXXXX X
|
||||
# X XXXXXXXXXXXXX X
|
||||
# X XXXXXXXXXXXXXXX X
|
||||
# X XXXXXXXXXXXXXXXXX X
|
||||
# X XXXXXXXXXXXXXXXXXXX X
|
||||
# X XXXXXXXXXXXXXXXXXXXXX X
|
||||
# X XXX X
|
||||
# X XXX X
|
||||
# X XXX X
|
||||
# X X
|
||||
# X X
|
||||
# X X
|
||||
# X X
|
||||
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||
# Could improve this by searching for that pattern explicitly. In this case I just searched
|
||||
# manually: noticed things looked suspicious after 27 seconds
|
||||
# also noticed that repeated every 101 seconds
|
||||
# Checked each 101 second jump until found the above tree
|
||||
find_tree(robots, size_x, size_y)
|
||||
end
|
||||
|
||||
def find_tree(robots, size_x, size_y, count \\ 27, acc \\ 27) do
|
||||
robots = tick(robots, size_x, size_y, count)
|
||||
robomap = MapSet.new(robots, fn [x, y, _, _] -> {x, y} end)
|
||||
|
||||
for y <- 0..(size_y - 1) do
|
||||
for x <- 0..(size_x - 1) do
|
||||
if MapSet.member?(robomap, {x, y}) do
|
||||
IO.write("X")
|
||||
else
|
||||
IO.write(" ")
|
||||
end
|
||||
end
|
||||
|
||||
IO.puts("|")
|
||||
end
|
||||
|
||||
line = IO.gets("#{acc} tree [y/N]?")
|
||||
|
||||
if line |> String.trim() |> String.downcase() == "y" do
|
||||
acc
|
||||
else
|
||||
find_tree(robots, size_x, size_y, 101, acc + 101)
|
||||
end
|
||||
end
|
||||
|
||||
def tick(robots, size_x, size_y, times) do
|
||||
Enum.map(robots, fn [px, py, vx, vy] ->
|
||||
[Integer.mod(px + vx * times, size_x), Integer.mod(py + vy * times, size_y), vx, vy]
|
||||
end)
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename, size_x, size_y] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename),
|
||||
{size_x, ""} <- Integer.parse(size_x),
|
||||
{size_y, ""} <- Integer.parse(size_y) do
|
||||
robots =
|
||||
input
|
||||
|> String.split(["p=", ",", " ", "v=", "\n"], trim: true)
|
||||
|> Enum.map(&String.to_integer/1)
|
||||
|> Enum.chunk_every(4)
|
||||
|
||||
{robots, size_x, size_y}
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{inspect(result)}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day14.exs input_filename size_x size_y")
|
||||
end
|
||||
end
|
||||
|
||||
Day14.run()
|
244
2024/day15.exs
244
2024/day15.exs
|
@ -1,244 +0,0 @@
|
|||
#!/usr/bin/env elixir
|
||||
defmodule Day15 do
|
||||
def part1({warehouse, robot, moves}) do
|
||||
attempt_moves(moves, robot, warehouse)
|
||||
|> Enum.filter(&match?({_, :box}, &1))
|
||||
|> Enum.map(fn {{x, y}, :box} -> x + y * 100 end)
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
def attempt_moves([], _robot, warehouse), do: warehouse
|
||||
|
||||
def attempt_moves([move | next_moves], robot, warehouse) do
|
||||
next = move.(robot)
|
||||
|
||||
case Map.fetch(warehouse, next) do
|
||||
:error ->
|
||||
attempt_moves(next_moves, next, warehouse)
|
||||
|
||||
{:ok, :wall} ->
|
||||
attempt_moves(next_moves, robot, warehouse)
|
||||
|
||||
{:ok, :box} ->
|
||||
case get_pushable_boxes(next, move, warehouse) do
|
||||
:wall ->
|
||||
attempt_moves(next_moves, robot, warehouse)
|
||||
|
||||
boxes ->
|
||||
warehouse =
|
||||
Enum.reduce(boxes, warehouse, fn box, warehouse ->
|
||||
warehouse |> Map.delete(box) |> Map.put(move.(box), :box)
|
||||
end)
|
||||
|
||||
attempt_moves(next_moves, next, warehouse)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def get_pushable_boxes(box, move, warehouse, pushable_boxes \\ []) do
|
||||
next = move.(box)
|
||||
pushable_boxes = [box | pushable_boxes]
|
||||
|
||||
case Map.fetch(warehouse, next) do
|
||||
:error -> pushable_boxes
|
||||
{:ok, :wall} -> :wall
|
||||
{:ok, :box} -> get_pushable_boxes(next, move, warehouse, pushable_boxes)
|
||||
end
|
||||
end
|
||||
|
||||
def part2({warehouse, {x, y}, moves}) do
|
||||
bigger_warehouse =
|
||||
Enum.reduce(warehouse, %{}, fn {{x, y}, v}, bigger ->
|
||||
{left, right} =
|
||||
case v do
|
||||
:box -> {{:box, :left}, {:box, :right}}
|
||||
:wall -> {:wall, :wall}
|
||||
end
|
||||
|
||||
bigger |> Map.put({x * 2, y}, left) |> Map.put({x * 2 + 1, y}, right)
|
||||
end)
|
||||
|
||||
robot = {x * 2, y}
|
||||
|
||||
attempt_moves2(moves, robot, bigger_warehouse)
|
||||
|> Enum.filter(&match?({_, {:box, :left}}, &1))
|
||||
|> Enum.map(fn {{x, y}, {:box, :left}} -> x + y * 100 end)
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
def attempt_moves2([], _robot, warehouse), do: warehouse
|
||||
|
||||
def attempt_moves2([move | next_moves], robot, warehouse) do
|
||||
next = move.(robot)
|
||||
|
||||
case Map.fetch(warehouse, next) do
|
||||
:error ->
|
||||
attempt_moves2(next_moves, next, warehouse)
|
||||
|
||||
{:ok, :wall} ->
|
||||
attempt_moves2(next_moves, robot, warehouse)
|
||||
|
||||
{:ok, {:box, side}} ->
|
||||
case get_pushable_boxes2(next, side, move, warehouse) do
|
||||
:wall ->
|
||||
attempt_moves2(next_moves, robot, warehouse)
|
||||
|
||||
boxes ->
|
||||
new_boxes = Map.new(boxes, fn {box, side} -> {move.(box), {:box, side}} end)
|
||||
|
||||
warehouse =
|
||||
warehouse
|
||||
|> Map.drop(Enum.map(boxes, &elem(&1, 0)))
|
||||
|> Map.merge(new_boxes)
|
||||
|
||||
attempt_moves2(next_moves, next, warehouse)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# need to account for 2-width boxes when pushing vertically
|
||||
# @<--pushing down moves down all RHS of stack
|
||||
# [][][][]
|
||||
# [][][] @
|
||||
# [][] []<--make sure RHS moves too
|
||||
# []
|
||||
# @<- pushing up pushes all
|
||||
def get_pushable_boxes2(box, side, move, warehouse, pushable_boxes \\ []) do
|
||||
next = move.(box)
|
||||
pushable_boxes = [{box, side} | pushable_boxes]
|
||||
|
||||
case abs(elem(next, 1) - elem(box, 1)) do
|
||||
# horizontal
|
||||
0 ->
|
||||
case Map.fetch(warehouse, next) do
|
||||
:error ->
|
||||
pushable_boxes
|
||||
|
||||
{:ok, :wall} ->
|
||||
:wall
|
||||
|
||||
{:ok, {:box, next_side}} ->
|
||||
get_pushable_boxes2(next, next_side, move, warehouse, pushable_boxes)
|
||||
end
|
||||
|
||||
# vertical
|
||||
1 ->
|
||||
{x, y} = box
|
||||
|
||||
{other_box, other_side} =
|
||||
case side do
|
||||
:left -> {{x + 1, y}, :right}
|
||||
:right -> {{x - 1, y}, :left}
|
||||
end
|
||||
|
||||
other_next = move.(other_box)
|
||||
|
||||
pushable_boxes = [{other_box, other_side} | pushable_boxes]
|
||||
|
||||
side
|
||||
|> case do
|
||||
:left ->
|
||||
{{Map.fetch(warehouse, next), Map.fetch(warehouse, other_next)}, next, other_next}
|
||||
|
||||
:right ->
|
||||
{{Map.fetch(warehouse, other_next), Map.fetch(warehouse, next)}, other_next, next}
|
||||
end
|
||||
|> case do
|
||||
{{:error, :error}, _, _} ->
|
||||
pushable_boxes
|
||||
|
||||
{{{:ok, :wall}, _}, _, _} ->
|
||||
:wall
|
||||
|
||||
{{_, {:ok, :wall}}, _, _} ->
|
||||
:wall
|
||||
|
||||
{{{:ok, {:box, side}}, :error}, left, _right} ->
|
||||
get_pushable_boxes2(left, side, move, warehouse, pushable_boxes)
|
||||
|
||||
{{:error, {:ok, {:box, side}}}, _left, right} ->
|
||||
get_pushable_boxes2(right, side, move, warehouse, pushable_boxes)
|
||||
|
||||
{{{:ok, {:box, :left}}, {:ok, {:box, :right}}}, left, _} ->
|
||||
# if left-right, just one box, only need to check one side
|
||||
# []
|
||||
# []
|
||||
get_pushable_boxes2(left, :left, move, warehouse, pushable_boxes)
|
||||
|
||||
{{{:ok, {:box, :right}}, {:ok, {:box, :left}}}, left, right} ->
|
||||
# if right-left, two boxes, so need to check both...
|
||||
# [][]
|
||||
# []
|
||||
with left when left != :wall <- get_pushable_boxes2(left, :right, move, warehouse, []),
|
||||
right when right != :wall <- get_pushable_boxes2(right, :left, move, warehouse, []) do
|
||||
pushable_boxes ++ left ++ right
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
[raw_map, raw_moves] = String.split(input, "\n\n")
|
||||
|
||||
{_, _y, robot, map} =
|
||||
for <<char::binary-1 <- raw_map <> "\n">>, reduce: {0, 0, nil, %{}} do
|
||||
{x, y, robot, map} ->
|
||||
case char do
|
||||
"." -> {x + 1, y, robot, map}
|
||||
"#" -> {x + 1, y, robot, Map.put(map, {x, y}, :wall)}
|
||||
"O" -> {x + 1, y, robot, Map.put(map, {x, y}, :box)}
|
||||
"@" -> {x + 1, y, {x, y}, map}
|
||||
"\n" -> {0, y + 1, robot, map}
|
||||
end
|
||||
end
|
||||
|
||||
moves =
|
||||
for <<char::binary-1 <- String.replace(raw_moves, "\n", "")>> do
|
||||
case char do
|
||||
"^" -> fn {x, y} -> {x, y - 1} end
|
||||
"v" -> fn {x, y} -> {x, y + 1} end
|
||||
"<" -> fn {x, y} -> {x - 1, y} end
|
||||
">" -> fn {x, y} -> {x + 1, y} end
|
||||
end
|
||||
end
|
||||
|
||||
{map, robot, moves}
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{inspect(result)}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day15.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day15.run()
|
145
2024/day17.exs
145
2024/day17.exs
|
@ -1,145 +0,0 @@
|
|||
#!/usr/bin/env elixir
|
||||
defmodule Day17 do
|
||||
defmodule Computer do
|
||||
defstruct a: 0, b: 0, c: 0, output: [], program: []
|
||||
end
|
||||
|
||||
def part1(computer) do
|
||||
process(computer.program, computer)
|
||||
|> Map.fetch!(:output)
|
||||
|> Enum.reverse()
|
||||
|> Enum.join(",")
|
||||
end
|
||||
|
||||
def process([], computer), do: computer
|
||||
|
||||
# adv div(A, 2^ combo operand) -> A
|
||||
def process([0, operand | program], computer) do
|
||||
value = div(computer.a, 2 ** combo(operand, computer))
|
||||
computer = %Computer{computer | a: value}
|
||||
process(program, computer)
|
||||
end
|
||||
|
||||
# bxl B XOR literal operand -> B
|
||||
def process([1, operand | program], computer) do
|
||||
value = Bitwise.bxor(computer.b, operand)
|
||||
computer = %Computer{computer | b: value}
|
||||
process(program, computer)
|
||||
end
|
||||
|
||||
# bst combo operand mod 8 -> B (only 3 lowest bits)
|
||||
def process([2, operand | program], computer) do
|
||||
value = rem(combo(operand, computer), 8)
|
||||
computer = %Computer{computer | b: value}
|
||||
process(program, computer)
|
||||
end
|
||||
|
||||
# jnz noting if A is 0, otherwise set instruction to literal operand - don't inc by 2 if jump
|
||||
def process([3, operand | program], computer) do
|
||||
if computer.a == 0 do
|
||||
process(program, computer)
|
||||
else
|
||||
program = Enum.drop(computer.program, operand)
|
||||
process(program, computer)
|
||||
end
|
||||
end
|
||||
|
||||
# bxc B XOR C -> B (ignore operand)
|
||||
def process([4, _operand | program], computer) do
|
||||
value = Bitwise.bxor(computer.b, computer.c)
|
||||
computer = %Computer{computer | b: value}
|
||||
process(program, computer)
|
||||
end
|
||||
|
||||
# out combo operand mod 8 + output (CSV)
|
||||
def process([5, operand | program], computer) do
|
||||
value = rem(combo(operand, computer), 8)
|
||||
computer = %Computer{computer | output: [value | computer.output]}
|
||||
process(program, computer)
|
||||
end
|
||||
|
||||
# bdv same as adv but store in B
|
||||
def process([6, operand | program], computer) do
|
||||
value = div(computer.a, 2 ** combo(operand, computer))
|
||||
computer = %Computer{computer | b: value}
|
||||
process(program, computer)
|
||||
end
|
||||
|
||||
# cdv same as adv but store in C
|
||||
def process([7, operand | program], computer) do
|
||||
value = div(computer.a, 2 ** combo(operand, computer))
|
||||
computer = %Computer{computer | c: value}
|
||||
process(program, computer)
|
||||
end
|
||||
|
||||
def combo(literal, _computer) when literal in [0, 1, 2, 3], do: literal
|
||||
def combo(4, computer), do: computer.a
|
||||
def combo(5, computer), do: computer.b
|
||||
def combo(6, computer), do: computer.c
|
||||
|
||||
def part2(computer) do
|
||||
# TODO runs forever. Probably need to detect loops?
|
||||
search_for_self(computer)
|
||||
end
|
||||
|
||||
def search_for_self(computer, a \\ 0) do
|
||||
computer = %Computer{computer | a: a}
|
||||
output = process(computer.program, computer).output |> Enum.reverse()
|
||||
|
||||
if output == computer.program do
|
||||
a
|
||||
else
|
||||
search_for_self(computer, a + 1)
|
||||
end
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
"Register A: " <> input = input
|
||||
{a, input} = Integer.parse(input)
|
||||
"\nRegister B: " <> input = input
|
||||
{b, input} = Integer.parse(input)
|
||||
"\nRegister C: " <> input = input
|
||||
{c, input} = Integer.parse(input)
|
||||
"\n\nProgram: " <> input = input
|
||||
program = input |> String.split([",", "\n"], trim: true) |> Enum.map(&String.to_integer/1)
|
||||
|
||||
%Computer{a: a, b: b, c: c, program: program}
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{inspect(result)}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day17.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day17.run()
|
|
@ -1,71 +0,0 @@
|
|||
#!/usr/bin/env elixir
|
||||
defmodule Day2 do
|
||||
def part1(reports) do
|
||||
Enum.count(reports, &check_report/1)
|
||||
end
|
||||
|
||||
def check_report([a, b | _]) when abs(b - a) not in [1, 2, 3], do: false
|
||||
def check_report([a, b | rest]), do: check_report([b | rest], b - a)
|
||||
def check_report([_], _dir), do: true
|
||||
|
||||
def check_report([a, b | rest], dir) do
|
||||
new_dir = b - a
|
||||
|
||||
if ((dir < 0 and new_dir < 0) or (dir > 0 and new_dir > 0)) and abs(new_dir) <= 3 do
|
||||
check_report([b | rest], new_dir)
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def part2(reports) do
|
||||
Enum.count(reports, fn report ->
|
||||
Enum.any?(0..(length(report) - 1), fn dampen ->
|
||||
report |> List.delete_at(dampen) |> check_report()
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
input
|
||||
|> String.split("\n", trim: true)
|
||||
|> Enum.map(fn line -> line |> String.split() |> Enum.map(&String.to_integer/1) end)
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{inspect(result)}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day2.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day2.run()
|
|
@ -1,133 +0,0 @@
|
|||
#!/usr/bin/env elixir
|
||||
defmodule Day21 do
|
||||
@numpad %{
|
||||
"7" => {0, 0},
|
||||
"8" => {1, 0},
|
||||
"9" => {2, 0},
|
||||
"4" => {0, 1},
|
||||
"5" => {1, 1},
|
||||
"6" => {2, 1},
|
||||
"1" => {0, 2},
|
||||
"2" => {1, 2},
|
||||
"3" => {2, 2},
|
||||
"0" => {1, 3},
|
||||
"A" => {2, 3}
|
||||
}
|
||||
|
||||
@keypad %{
|
||||
"^" => {1, 0},
|
||||
"A" => {2, 0},
|
||||
"<" => {0, 1},
|
||||
"v" => {1, 1},
|
||||
">" => {2, 1}
|
||||
}
|
||||
|
||||
def part1(input) do
|
||||
Enum.map(input, &complexity/1)
|
||||
end
|
||||
|
||||
def complexity(code) do
|
||||
length =
|
||||
code
|
||||
|> derive_keys(@numpad)
|
||||
# asdfsadfsdafdfs for the robot, some derived combinations are shorter, why?
|
||||
# Maybe going left non-sequentially is expensive because of round trips to the A buttom
|
||||
# so sequentially is cheaper?
|
||||
# Need to figure out what the situation that causes a longer sequence is
|
||||
|> derive_keys(@keypad)
|
||||
|> derive_keys(@keypad)
|
||||
|> byte_size()
|
||||
|> dbg()
|
||||
|
||||
{numeric, "A"} = Integer.parse(code)
|
||||
dbg(numeric)
|
||||
|
||||
numeric * length
|
||||
end
|
||||
|
||||
def derive_keys(seq, pad) do
|
||||
String.graphemes("A" <> seq)
|
||||
|> Enum.chunk_every(2, 1, :discard)
|
||||
|> Enum.map_join("", fn [a, b] -> keys(a, b, pad) <> "A" end)
|
||||
end
|
||||
|
||||
def numpad, do: @numpad
|
||||
|
||||
def keys(a, b, pad) do
|
||||
{x1, y1} = Map.fetch!(pad, a)
|
||||
{x2, y2} = Map.fetch!(pad, b)
|
||||
dx = x2 - x1
|
||||
dy = y2 - y1
|
||||
|
||||
keys_x = keys_x(dx)
|
||||
keys_y = keys_y(dy)
|
||||
|
||||
# avoid the gap
|
||||
if dx < 0 do
|
||||
keys_y <> keys_x
|
||||
else
|
||||
keys_x <> keys_y
|
||||
end
|
||||
end
|
||||
|
||||
def keys_x(-2), do: "<<"
|
||||
def keys_x(-1), do: "<"
|
||||
def keys_x(0), do: ""
|
||||
def keys_x(1), do: ">"
|
||||
def keys_x(2), do: ">>"
|
||||
|
||||
def keys_y(-3), do: "^^^"
|
||||
def keys_y(-2), do: "^^"
|
||||
def keys_y(-1), do: "^"
|
||||
def keys_y(0), do: ""
|
||||
def keys_y(1), do: "v"
|
||||
def keys_y(2), do: "vv"
|
||||
def keys_y(3), do: "vvv"
|
||||
|
||||
def keypad, do: @keypad
|
||||
|
||||
def part2(_input) do
|
||||
:ok
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
String.split(input, "\n", trim: true)
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{inspect(result)}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day21.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
# Day21.run()
|
|
@ -1,76 +0,0 @@
|
|||
#!/usr/bin/env elixir
|
||||
defmodule Day22 do
|
||||
import Bitwise
|
||||
|
||||
def part1(input) do
|
||||
Enum.sum_by(input, fn secret ->
|
||||
Enum.reduce(1..2000, secret, fn _, secret -> next(secret) end)
|
||||
end)
|
||||
end
|
||||
|
||||
def next(secret) do
|
||||
secret
|
||||
|> then(fn secret -> bxor(secret <<< 6, secret) &&& 16_777_21 end)
|
||||
|> then(fn secret -> bxor(secret >>> 5, secret) &&& 16_777_21 end)
|
||||
|> then(fn secret -> bxor(secret <<< 11, secret) &&& 16_777_21 end)
|
||||
end
|
||||
|
||||
def part2(input) do
|
||||
input
|
||||
|> Enum.map(fn secret ->
|
||||
1..2000
|
||||
|> Enum.map_reduce(secret, fn _, secret -> {rem(secret, 10), next(secret)} end)
|
||||
|> elem(0)
|
||||
|> Enum.chunk_every(5, 1, :discard)
|
||||
|> Enum.reduce(%{}, fn [a, b, c, d, bananas], changes ->
|
||||
Map.put_new(changes, {b - a, c - b, d - c, bananas - d}, bananas)
|
||||
end)
|
||||
end)
|
||||
|> Enum.reduce(&Map.merge(&1, &2, fn _k, v1, v2 -> v1 + v2 end))
|
||||
|> Enum.max_by(fn {_sequence, bananas} -> bananas end)
|
||||
|> elem(1)
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
input
|
||||
|> String.split("\n", trim: true)
|
||||
|> Enum.map(&String.to_integer/1)
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{inspect(result)}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day22.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day22.run()
|
|
@ -1,60 +0,0 @@
|
|||
#!/usr/bin/env elixir
|
||||
defmodule Day3 do
|
||||
def part1(input) do
|
||||
Regex.scan(~r/mul\((\d+),(\d+)\)/, input, capture: :all_but_first)
|
||||
|> Enum.map(fn [a, b] -> String.to_integer(a) * String.to_integer(b) end)
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
def part2(input) do
|
||||
Regex.scan(~r/mul\((\d+),(\d+)\)|do\(\)|don't\(\)/, input)
|
||||
|> Enum.reduce({0, :process}, fn
|
||||
["do()"], {sum, _} -> {sum, :process}
|
||||
["don't()"], {sum, _} -> {sum, :skip}
|
||||
_, {sum, :skip} -> {sum, :skip}
|
||||
[_, a, b], {sum, :process} -> {sum + String.to_integer(a) * String.to_integer(b), :process}
|
||||
end)
|
||||
|> elem(0)
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
input
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{inspect(result)}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day3.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day3.run()
|
125
2024/day4.exs
125
2024/day4.exs
|
@ -1,125 +0,0 @@
|
|||
#!/usr/bin/env elixir
|
||||
defmodule Day4 do
|
||||
def part1(grid) do
|
||||
n = :array.size(grid)
|
||||
|
||||
horizontal =
|
||||
for y <- 0..(n - 1), x <- 0..(n - 4), reduce: 0 do
|
||||
count ->
|
||||
for x_offset <- 0..(4 - 1), into: "" do
|
||||
:array.get(x + x_offset, :array.get(y, grid))
|
||||
end
|
||||
|> consider(count)
|
||||
end
|
||||
|
||||
vertical =
|
||||
for y <- 0..(n - 4), x <- 0..(n - 1), reduce: 0 do
|
||||
count ->
|
||||
for y_offset <- 0..(4 - 1), into: "" do
|
||||
:array.get(x, :array.get(y + y_offset, grid))
|
||||
end
|
||||
|> consider(count)
|
||||
end
|
||||
|
||||
diagonal1 =
|
||||
for y <- 0..(n - 4), x <- 0..(n - 4), reduce: 0 do
|
||||
count ->
|
||||
for offset <- 0..(4 - 1), into: "" do
|
||||
:array.get(x + offset, :array.get(y + offset, grid))
|
||||
end
|
||||
|> consider(count)
|
||||
end
|
||||
|
||||
diagonal2 =
|
||||
for y <- 0..(n - 4), x <- (n - 1)..(4 - 1), reduce: 0 do
|
||||
count ->
|
||||
for offset <- 0..(4 - 1), into: "" do
|
||||
:array.get(x - offset, :array.get(y + offset, grid))
|
||||
end
|
||||
|> consider(count)
|
||||
end
|
||||
|
||||
horizontal + vertical + diagonal1 + diagonal2
|
||||
end
|
||||
|
||||
def consider("XMAS", count), do: count + 1
|
||||
def consider("SAMX", count), do: count + 1
|
||||
def consider(_, count), do: count
|
||||
|
||||
def part2(grid) do
|
||||
n = :array.size(grid)
|
||||
|
||||
for y <- 0..(n - 3), x <- 0..(n - 3), reduce: 0 do
|
||||
count ->
|
||||
diagonal1 =
|
||||
for offset <- 0..2, into: "" do
|
||||
:array.get(x + offset, :array.get(y + offset, grid))
|
||||
end
|
||||
|
||||
diagonal2 =
|
||||
for offset <- 0..2, into: "" do
|
||||
:array.get(x + 2 - offset, :array.get(y + offset, grid))
|
||||
end
|
||||
|
||||
if diagonal1 in ["MAS", "SAM"] and diagonal2 in ["MAS", "SAM"] do
|
||||
count + 1
|
||||
else
|
||||
count
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
for <<char::binary-1 <- input>>, reduce: {:array.new(), :array.new(), 0, 0} do
|
||||
{rows, cols, x, y} ->
|
||||
case char do
|
||||
"\n" ->
|
||||
rows = :array.set(y, cols, rows)
|
||||
cols = :array.new()
|
||||
{rows, cols, 0, y + 1}
|
||||
|
||||
char ->
|
||||
cols = :array.set(x, char, cols)
|
||||
{rows, cols, x + 1, y}
|
||||
end
|
||||
end
|
||||
|> elem(0)
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{inspect(result)}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day4.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day4.run()
|
|
@ -1,78 +0,0 @@
|
|||
#!/usr/bin/env elixir
|
||||
defmodule Day5 do
|
||||
def part1({updates, _greater?, sorted?}) do
|
||||
updates
|
||||
|> Enum.filter(sorted?)
|
||||
|> Enum.map(&Enum.at(&1, div(length(&1), 2)))
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
def part2({updates, greater?, sorted?}) do
|
||||
updates
|
||||
|> Enum.reject(sorted?)
|
||||
|> Enum.map(&(Enum.sort(&1, greater?) |> Enum.at(div(length(&1), 2))))
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
[rules_raw, updates_raw] = String.split(input, "\n\n")
|
||||
|
||||
less_than =
|
||||
String.split(rules_raw, ["|", "\n"])
|
||||
|> Enum.map(&String.to_integer/1)
|
||||
|> Enum.chunk_every(2)
|
||||
|> Enum.group_by(fn [a, _] -> a end, fn [_, b] -> b end)
|
||||
|
||||
greater? = fn greater, lesser -> lesser in Map.get(less_than, greater, []) end
|
||||
|
||||
sorted? = fn report ->
|
||||
report
|
||||
|> Enum.chunk_every(2, 1, :discard)
|
||||
|> Enum.all?(fn [greater, lesser] -> greater?.(greater, lesser) end)
|
||||
end
|
||||
|
||||
updates =
|
||||
updates_raw
|
||||
|> String.split("\n", trim: true)
|
||||
|> Enum.map(fn row -> String.split(row, ",") |> Enum.map(&String.to_integer/1) end)
|
||||
|
||||
{updates, greater?, sorted?}
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{inspect(result)}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day5.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day5.run()
|
116
2024/day6.exs
116
2024/day6.exs
|
@ -1,116 +0,0 @@
|
|||
#!/usr/bin/env elixir
|
||||
defmodule Patrol do
|
||||
defstruct obstacles: MapSet.new(),
|
||||
visited: MapSet.new(),
|
||||
path: MapSet.new(),
|
||||
x: 0,
|
||||
y: 0,
|
||||
max_x: nil,
|
||||
max_y: nil,
|
||||
dir_x: 0,
|
||||
dir_y: -1
|
||||
end
|
||||
|
||||
defmodule Day6 do
|
||||
def part1(patrol), do: MapSet.size(patrol(patrol).visited)
|
||||
|
||||
def patrol(%Patrol{} = patrol) when patrol.x in [-1, patrol.max_x] or patrol.y in [-1, patrol.max_y] do
|
||||
patrol
|
||||
end
|
||||
|
||||
def patrol(%Patrol{} = patrol) do
|
||||
next_x = patrol.x + patrol.dir_x
|
||||
next_y = patrol.y + patrol.dir_y
|
||||
|
||||
patrol =
|
||||
if MapSet.member?(patrol.obstacles, {next_x, next_y}) do
|
||||
{dir_x, dir_y} = turn_right(patrol.dir_x, patrol.dir_y)
|
||||
%Patrol{patrol | dir_x: dir_x, dir_y: dir_y}
|
||||
else
|
||||
visited = MapSet.put(patrol.visited, {patrol.x, patrol.y})
|
||||
%Patrol{patrol | x: next_x, y: next_y, visited: visited}
|
||||
end
|
||||
|
||||
next_path = {patrol.x, patrol.y, patrol.dir_x, patrol.dir_y}
|
||||
|
||||
if MapSet.member?(patrol.path, next_path) do
|
||||
:loop
|
||||
else
|
||||
patrol = %Patrol{patrol | path: MapSet.put(patrol.path, next_path)}
|
||||
patrol(patrol)
|
||||
end
|
||||
end
|
||||
|
||||
def turn_right(0, -1), do: {1, 0}
|
||||
def turn_right(1, 0), do: {0, 1}
|
||||
def turn_right(0, 1), do: {-1, 0}
|
||||
def turn_right(-1, 0), do: {0, -1}
|
||||
|
||||
def part2(patrol) do
|
||||
completed_patrol = patrol(patrol)
|
||||
|
||||
candidate_obstructions =
|
||||
Enum.reduce(completed_patrol.visited, MapSet.new(), fn {x, y}, candidate_obstructions ->
|
||||
Enum.into([{x - 1, y}, {x + 1, y}, {x, y - 1}, {x, y + 1}], candidate_obstructions)
|
||||
end)
|
||||
|
||||
Enum.count(candidate_obstructions, fn {x, y} ->
|
||||
new_obstacles = MapSet.put(patrol.obstacles, {x, y})
|
||||
new_patrol = %Patrol{patrol | obstacles: new_obstacles}
|
||||
|
||||
patrol(new_patrol) == :loop
|
||||
end)
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
{_x, max_x, max_y, {x, y}, obstacles} =
|
||||
for <<char::binary-1 <- input>>, reduce: {0, 0, 0, nil, MapSet.new()} do
|
||||
{x, max_x, y, guard, obstacles} ->
|
||||
case char do
|
||||
"." -> {x + 1, max_x, y, guard, obstacles}
|
||||
"#" -> {x + 1, max_x, y, guard, MapSet.put(obstacles, {x, y})}
|
||||
"^" -> {x + 1, max_x, y, {x, y}, obstacles}
|
||||
"\n" -> {0, x, y + 1, guard, obstacles}
|
||||
end
|
||||
end
|
||||
|
||||
%Patrol{x: x, y: y, max_x: max_x, max_y: max_y, obstacles: obstacles}
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{inspect(result)}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day6.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day6.run()
|
|
@ -1,77 +0,0 @@
|
|||
#!/usr/bin/env elixir
|
||||
defmodule Day7 do
|
||||
def part1(input) do
|
||||
Enum.sum_by(input, fn {answer, values} ->
|
||||
if solvable?(answer, values), do: answer, else: 0
|
||||
end)
|
||||
end
|
||||
|
||||
def solvable?(answer, [result]), do: answer == result
|
||||
|
||||
def solvable?(answer, [a, b | rest]) do
|
||||
solvable?(answer, [a * b | rest]) or solvable?(answer, [a + b | rest])
|
||||
end
|
||||
|
||||
def part2(input) do
|
||||
Enum.sum_by(input, fn {answer, values} ->
|
||||
if solvable2?(answer, values), do: answer, else: 0
|
||||
end)
|
||||
end
|
||||
|
||||
def solvable2?(answer, [result]), do: answer == result
|
||||
|
||||
def solvable2?(answer, [a, b | rest]) do
|
||||
solvable2?(answer, [a * b | rest]) or solvable2?(answer, [a + b | rest]) or
|
||||
solvable2?(answer, [String.to_integer("#{a}#{b}") | rest])
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
input
|
||||
|> String.split("\n", trim: true)
|
||||
|> Enum.map(fn line ->
|
||||
[result | values] =
|
||||
line
|
||||
|> String.split([":", " "], trim: true)
|
||||
|> Enum.map(&String.to_integer/1)
|
||||
|
||||
{result, values}
|
||||
end)
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{inspect(result)}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day7.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day7.run()
|
107
2024/day8.exs
107
2024/day8.exs
|
@ -1,107 +0,0 @@
|
|||
#!/usr/bin/env elixir
|
||||
defmodule Day8 do
|
||||
def part1({x, y, frequencies}) do
|
||||
Enum.reduce(frequencies, MapSet.new(), fn antennas, antinodes ->
|
||||
antennas
|
||||
|> find_pairs()
|
||||
|> Enum.flat_map(fn pair -> find_antinodes(pair, x, y) end)
|
||||
|> Enum.into(antinodes)
|
||||
end)
|
||||
|> MapSet.size()
|
||||
end
|
||||
|
||||
def find_pairs([_]), do: []
|
||||
def find_pairs([a | rest]), do: for(b <- rest, do: {a, b}) ++ find_pairs(rest)
|
||||
|
||||
def find_antinodes({{x_a, y_a}, {x_b, y_b}}, max_x, max_y) do
|
||||
dx = x_a - x_b
|
||||
dy = y_a - y_b
|
||||
antinode_1 = {x_a + dx, y_a + dy}
|
||||
antinode_2 = {x_b - dx, y_b - dy}
|
||||
|
||||
Enum.filter([antinode_1, antinode_2], fn
|
||||
{x, y} -> 0 <= x and x < max_x and 0 <= y and y < max_y
|
||||
end)
|
||||
end
|
||||
|
||||
def part2({x, y, frequencies}) do
|
||||
Enum.reduce(frequencies, MapSet.new(), fn antennas, antinodes ->
|
||||
antennas
|
||||
|> find_pairs()
|
||||
|> Enum.flat_map(fn pair -> find_resonant_harmonics(pair, x, y) end)
|
||||
|> Enum.into(antinodes)
|
||||
end)
|
||||
|> MapSet.size()
|
||||
end
|
||||
|
||||
def find_resonant_harmonics({{x_a, y_a}, {x_b, y_b}}, max_x, max_y) do
|
||||
dx = x_a - x_b
|
||||
dy = y_a - y_b
|
||||
|
||||
[{x_a, y_a}, {x_b, y_b}] ++
|
||||
resonate(x_a, y_a, dx, dy, max_x, max_y) ++
|
||||
resonate(x_b, y_b, dx * -1, dy * -1, max_x, max_y)
|
||||
end
|
||||
|
||||
def resonate(x, y, dx, dy, max_x, max_y, multiplier \\ 1) do
|
||||
next_x = x + dx * multiplier
|
||||
next_y = y + dy * multiplier
|
||||
|
||||
if next_x < 0 or next_x >= max_x or next_y < 0 or next_y >= max_y do
|
||||
[]
|
||||
else
|
||||
[{next_x, next_y} | resonate(x, y, dx, dy, max_x, max_y, multiplier + 1)]
|
||||
end
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
{_, x, y, antennas} =
|
||||
for <<char::binary-1 <- input>>, reduce: {0, 0, 0, []} do
|
||||
{x, max_x, y, antennas} ->
|
||||
case char do
|
||||
"." -> {x + 1, max_x, y, antennas}
|
||||
"\n" -> {0, x, y + 1, antennas}
|
||||
freq -> {x + 1, max_x, y, [{freq, {x, y}} | antennas]}
|
||||
end
|
||||
end
|
||||
|
||||
{x, y, antennas |> Enum.group_by(&elem(&1, 0), &elem(&1, 1)) |> Map.values()}
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{inspect(result)}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day8.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day8.run()
|
116
2024/day9.exs
116
2024/day9.exs
|
@ -1,116 +0,0 @@
|
|||
#!/usr/bin/env elixir
|
||||
defmodule Day9 do
|
||||
def part1(input) do
|
||||
{size, _, spaces, _, disk, files} =
|
||||
Enum.reduce(input, {0, 0, 0, true, %{}, []}, fn len,
|
||||
{idx, id, spaces, file?, disk, files} ->
|
||||
if file? do
|
||||
disk = Enum.into(idx..(idx + len - 1), disk, fn i -> {i, id} end)
|
||||
files = if file?, do: for(_ <- 1..len, do: id) ++ files, else: files
|
||||
{idx + len, id + 1, spaces, not file?, disk, files}
|
||||
else
|
||||
{idx + len, id, spaces + len, not file?, disk, files}
|
||||
end
|
||||
end)
|
||||
|
||||
for i <- 0..(size - spaces - 1), reduce: {0, files} do
|
||||
{checksum, files} ->
|
||||
case Map.fetch(disk, i) do
|
||||
{:ok, block} ->
|
||||
{checksum + block * i, files}
|
||||
|
||||
:error ->
|
||||
[block | files] = files
|
||||
{checksum + block * i, files}
|
||||
end
|
||||
end
|
||||
|> elem(0)
|
||||
end
|
||||
|
||||
# Super inefficient, but this is the 2nd attempt after trying to be more efficient
|
||||
# worked on the sample but not the actual input
|
||||
def part2(input) do
|
||||
{_, _, _, disk, files} =
|
||||
Enum.reduce(input, {0, 0, true, %{}, []}, fn len, {idx, id, file?, disk, files} ->
|
||||
if file? do
|
||||
disk = Map.put(disk, idx, {:file, id, len})
|
||||
files = [{idx, id, len} | files]
|
||||
{idx + len, id + 1, not file?, disk, files}
|
||||
else
|
||||
disk = Map.put(disk, idx, {:space, len})
|
||||
{idx + len, id, not file?, disk, files}
|
||||
end
|
||||
end)
|
||||
|
||||
Enum.reduce(files, disk, fn {idx, id, len}, disk ->
|
||||
# This scans the disk every time. Could possibly optimise by storing the known
|
||||
# eligible spaces separately
|
||||
case Enum.find(0..(idx - 1), &match?({:space, gap} when gap >= len, disk[&1])) do
|
||||
nil ->
|
||||
disk
|
||||
|
||||
space_idx ->
|
||||
{:space, spaces} = Map.fetch!(disk, space_idx)
|
||||
disk = disk |> Map.delete(idx) |> Map.put(space_idx, {:file, id, len})
|
||||
|
||||
remainder_spaces = spaces - len
|
||||
|
||||
if remainder_spaces > 0 do
|
||||
Map.put(disk, space_idx + len, {:space, remainder_spaces})
|
||||
else
|
||||
disk
|
||||
end
|
||||
end
|
||||
end)
|
||||
|> Enum.reduce(0, fn
|
||||
{_idx, {:space, _}}, checksum ->
|
||||
checksum
|
||||
|
||||
{idx, {:file, id, len}}, checksum ->
|
||||
for idx <- idx..(idx + len - 1), reduce: checksum do
|
||||
checksum -> checksum + idx * id
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
for <<c::binary-1 <- String.trim_trailing(input, "\n")>>, do: String.to_integer(c)
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{inspect(result)}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir day9.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day9.run()
|
|
@ -1,3 +1,7 @@
|
|||
# AdventOfCode
|
||||
|
||||
My (attempted) solutions to [Advent of Code](https://adventofcode.com/).
|
||||
|
||||
- [2021: Zig](https://github.com/adamu/AdventOfCode/tree/main/2021)
|
||||
- [2020: Elixir](https://github.com/adamu/AdventOfCode/tree/main/2020)
|
||||
- [2018: Elixir](https://github.com/adamu/AdventOfCode/tree/main/2018)
|
||||
|
|
51
dayN.exs
51
dayN.exs
|
@ -1,51 +0,0 @@
|
|||
#!/usr/bin/env elixir
|
||||
defmodule DayREPLACE_ME do
|
||||
def part1(input) do
|
||||
input
|
||||
end
|
||||
|
||||
def part2(_input) do
|
||||
:not_implemented
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
input
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{inspect(result)}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir dayREPLACE_ME.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
# DayREPLACE_ME.run()
|
25
template/README
Normal file
25
template/README
Normal file
|
@ -0,0 +1,25 @@
|
|||
Day REPLACE_ME Notes
|
||||
|
||||
+--------+
|
||||
| Part 1 |
|
||||
+--------+
|
||||
|
||||
$ elixir dayREPLACE_MEpart1.exs
|
||||
|
||||
Thoughts:
|
||||
|
||||
|
||||
+--------+
|
||||
| Part 2 |
|
||||
+--------+
|
||||
|
||||
$ elixir dayREPLACE_MEpart2.exs
|
||||
|
||||
Thoughts:
|
||||
|
||||
|
||||
+------------------+
|
||||
| Overall Thoughts |
|
||||
+------------------+
|
||||
|
||||
|
9
template/dayNpart1.exs
Normal file
9
template/dayNpart1.exs
Normal file
|
@ -0,0 +1,9 @@
|
|||
defmodule DayREPLACE_MEPart1 do
|
||||
def run do
|
||||
File.read!("input")
|
||||
|> String.split("\n", trim: true)
|
||||
|> IO.inspect()
|
||||
end
|
||||
end
|
||||
|
||||
DayREPLACE_MEPart1.run()
|
Loading…
Add table
Reference in a new issue