Compare commits
92 commits
cd77059be3
...
main
Author | SHA1 | Date | |
---|---|---|---|
2169d4dc40 | |||
1916aa94a7 | |||
6503a02d1e | |||
4d3293cd3e | |||
f180c206ac | |||
684438a5a1 | |||
0efa69721f | |||
8b2b0cce65 | |||
e5efb08102 | |||
dce598de72 | |||
1e74b21dc5 | |||
44a5d8eb27 | |||
17e72ebf4c | |||
3d68fa4048 | |||
27babd9ed7 | |||
ad1826489e | |||
67ef614680 | |||
550b652b45 | |||
f1762d886a | |||
6eee6cace0 | |||
b788fa1096 | |||
522bbf2dee | |||
7f9e61c347 | |||
f80444d913 | |||
316c93d06a | |||
91a503a487 | |||
00237e6e93 | |||
7e34282fb5 | |||
b3992f8d52 | |||
7bde581e50 | |||
d8c7478ad6 | |||
87331c5a5a | |||
88a64017fc | |||
c5b88598d8 | |||
9dd9b8f2e2 | |||
58df8b822b | |||
a6911216fa | |||
9e6a872461 | |||
2bcc94c802 | |||
79b64b911d | |||
afaacf77a1 | |||
2b7d50ebdd | |||
e183e04f23 | |||
7ff40275c7 | |||
6b2cbd6058 | |||
b0b4a3f040 | |||
748e0a2311 | |||
53feb06507 | |||
2ab4d1c439 | |||
13e154a0c2 | |||
84d40cce99 | |||
0585418e3c | |||
53fe13031e | |||
0a27136e95 | |||
32679a526a | |||
fb87183a6f | |||
615f7cef3c | |||
1d20417d1d | |||
3960278955 | |||
0e1f1bcaa0 | |||
3b1d14580a | |||
07820fe7dd | |||
1a314c7238 | |||
8d54cefbdd | |||
1b87681fda | |||
01747af081 | |||
8cd6e34a29 | |||
92334ee1a6 | |||
f4f999d078 | |||
1f452e961f | |||
187cf58cb3 | |||
cb850a5b5e | |||
4955687b53 | |||
1f7c32b758 | |||
fc0eb4a63c | |||
2ebd8fe609 | |||
280e6fcf2b | |||
9a81f47677 | |||
6bf0d44e09 | |||
8861ce0b8e | |||
c02b8af2aa | |||
59157c5a11 | |||
462ab21364 | |||
be7c251662 | |||
6d677300ee | |||
8a7dab06d2 | |||
ba09492857 | |||
26f694c449 | |||
9c22642be4 | |||
46582ed853 | |||
39c28b5897 | |||
2c69ef4b78 |
39 changed files with 3075 additions and 42 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
input
|
||||
sample
|
||||
arena
|
52
2015/day1.exs
Normal file
52
2015/day1.exs
Normal file
|
@ -0,0 +1,52 @@
|
|||
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()
|
66
2015/day2.exs
Normal file
66
2015/day2.exs
Normal file
|
@ -0,0 +1,66 @@
|
|||
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()
|
67
2015/day3.exs
Normal file
67
2015/day3.exs
Normal file
|
@ -0,0 +1,67 @@
|
|||
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()
|
50
2015/day4.exs
Normal file
50
2015/day4.exs
Normal file
|
@ -0,0 +1,50 @@
|
|||
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()
|
86
2015/day5.exs
Normal file
86
2015/day5.exs
Normal file
|
@ -0,0 +1,86 @@
|
|||
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()
|
|
@ -1,5 +0,0 @@
|
|||
# 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">
|
95
2021/day10.exs
Normal file
95
2021/day10.exs
Normal file
|
@ -0,0 +1,95 @@
|
|||
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
Normal file
106
2021/day13.exs
Normal file
|
@ -0,0 +1,106 @@
|
|||
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
Normal file
140
2021/day16.exs
Normal file
|
@ -0,0 +1,140 @@
|
|||
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()
|
98
2021/day3.exs
Normal file
98
2021/day3.exs
Normal file
|
@ -0,0 +1,98 @@
|
|||
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
Normal file
133
2021/day4.exs
Normal file
|
@ -0,0 +1,133 @@
|
|||
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()
|
83
2021/day5.exs
Normal file
83
2021/day5.exs
Normal file
|
@ -0,0 +1,83 @@
|
|||
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()
|
84
2021/day6.exs
Normal file
84
2021/day6.exs
Normal file
|
@ -0,0 +1,84 @@
|
|||
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()
|
81
2021/day7.exs
Normal file
81
2021/day7.exs
Normal file
|
@ -0,0 +1,81 @@
|
|||
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
Normal file
111
2021/day9.exs
Normal file
|
@ -0,0 +1,111 @@
|
|||
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()
|
11
2022/README.md
Normal file
11
2022/README.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# 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
|
||||
```
|
58
2022/day1.exs
Normal file
58
2022/day1.exs
Normal file
|
@ -0,0 +1,58 @@
|
|||
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
Normal file
109
2022/day10.exs
Normal file
|
@ -0,0 +1,109 @@
|
|||
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
Normal file
188
2022/day11.exs
Normal file
|
@ -0,0 +1,188 @@
|
|||
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
Normal file
100
2022/day2.exs
Normal file
|
@ -0,0 +1,100 @@
|
|||
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()
|
71
2022/day3.exs
Normal file
71
2022/day3.exs
Normal file
|
@ -0,0 +1,71 @@
|
|||
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()
|
55
2022/day4.exs
Normal file
55
2022/day4.exs
Normal file
|
@ -0,0 +1,55 @@
|
|||
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()
|
92
2022/day5.exs
Normal file
92
2022/day5.exs
Normal file
|
@ -0,0 +1,92 @@
|
|||
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()
|
72
2022/day6.exs
Normal file
72
2022/day6.exs
Normal file
|
@ -0,0 +1,72 @@
|
|||
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
Normal file
145
2022/day7.exs
Normal file
|
@ -0,0 +1,145 @@
|
|||
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
Normal file
130
2022/day8.exs
Normal file
|
@ -0,0 +1,130 @@
|
|||
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
Normal file
109
2022/day9.exs
Normal file
|
@ -0,0 +1,109 @@
|
|||
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()
|
77
2023/day1.exs
Normal file
77
2023/day1.exs
Normal file
|
@ -0,0 +1,77 @@
|
|||
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()
|
79
2023/day2.exs
Executable file
79
2023/day2.exs
Executable file
|
@ -0,0 +1,79 @@
|
|||
#!/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()
|
90
2023/day3.exs
Executable file
90
2023/day3.exs
Executable file
|
@ -0,0 +1,90 @@
|
|||
#!/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()
|
78
2023/day4.exs
Executable file
78
2023/day4.exs
Executable file
|
@ -0,0 +1,78 @@
|
|||
#!/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
Executable file
110
2023/day5.exs
Executable file
|
@ -0,0 +1,110 @@
|
|||
#!/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()
|
69
2023/day6.exs
Executable file
69
2023/day6.exs
Executable file
|
@ -0,0 +1,69 @@
|
|||
#!/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
Normal file
123
2023/day7.exs
Normal file
|
@ -0,0 +1,123 @@
|
|||
#!/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()
|
|
@ -2,6 +2,6 @@
|
|||
|
||||
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)
|
||||
- [2022: Elixir](https://git.adamu.jp/adam/AdventOfCode/src/branch/main/2022)
|
||||
- [2020: Elixir](https://git.adamu.jp/adam/AdventOfCode/src/branch/main/2020)
|
||||
- [2018: Elixir](https://git.adamu.jp/adam/AdventOfCode/src/branch/main/2018)
|
||||
|
|
51
dayN.exs
Executable file
51
dayN.exs
Executable file
|
@ -0,0 +1,51 @@
|
|||
#!/usr/bin/env elixir
|
||||
defmodule DayREPLACE_ME do
|
||||
def part1(input) do
|
||||
input
|
||||
end
|
||||
|
||||
def part2(_input) do
|
||||
:ok
|
||||
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()
|
|
@ -1,25 +0,0 @@
|
|||
Day REPLACE_ME Notes
|
||||
|
||||
+--------+
|
||||
| Part 1 |
|
||||
+--------+
|
||||
|
||||
$ elixir dayREPLACE_MEpart1.exs
|
||||
|
||||
Thoughts:
|
||||
|
||||
|
||||
+--------+
|
||||
| Part 2 |
|
||||
+--------+
|
||||
|
||||
$ elixir dayREPLACE_MEpart2.exs
|
||||
|
||||
Thoughts:
|
||||
|
||||
|
||||
+------------------+
|
||||
| Overall Thoughts |
|
||||
+------------------+
|
||||
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
defmodule DayREPLACE_MEPart1 do
|
||||
def run do
|
||||
File.read!("input")
|
||||
|> String.split("\n", trim: true)
|
||||
|> IO.inspect()
|
||||
end
|
||||
end
|
||||
|
||||
DayREPLACE_MEPart1.run()
|
Loading…
Reference in a new issue