Compare commits

..

No commits in common. "main" and "cd77059be38e3cbf32d17d44819af544a668815d" have entirely different histories.

59 changed files with 43 additions and 5206 deletions

3
.gitignore vendored
View file

@ -1,3 +0,0 @@
input
sample
arena

View file

@ -1,52 +0,0 @@
defmodule Day1 do
def part1(<<>>), do: 0
def part1(<<?(, rest::binary>>), do: 1 + part1(rest)
def part1(<<?), rest::binary>>), do: -1 + part1(rest)
def part2(input), do: find_basement(input, _floor = 0, _pos = 1)
def find_basement(<<?), _::binary>>, 0, pos), do: pos
def find_basement(<<?), rest::binary>>, floor, pos), do: find_basement(rest, floor - 1, pos + 1)
def find_basement(<<?(, rest::binary>>, floor, pos), do: find_basement(rest, floor + 1, pos + 1)
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
input
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{result}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day1.exs input_filename")
end
end
Day1.run()

View file

@ -1,66 +0,0 @@
defmodule Day2 do
def part1(input) do
input
|> Enum.map(fn [x, y, z] ->
a = x * y
b = x * z
c = y * z
min = Enum.min([a, b, c])
2 * (a + b + c) + min
end)
|> Enum.sum()
end
def part2(input) do
input
|> Enum.map(fn box ->
[x, y, z] = Enum.sort(box)
2 * (x + y) + x * y * z
end)
|> Enum.sum()
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
input
|> String.split(["\n", "x"], trim: true)
|> Enum.map(&String.to_integer/1)
|> Enum.chunk_every(3)
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{result}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day2.exs input_filename")
end
end
Day2.run()

View file

@ -1,67 +0,0 @@
defmodule Day3 do
def part1(input), do: move(input, MapSet.new([{0, 0}]), {0, 0})
def move(<<?<, rest::binary>>, houses, {x, y}), do: deliver(rest, houses, {x - 1, y})
def move(<<?>, rest::binary>>, houses, {x, y}), do: deliver(rest, houses, {x + 1, y})
def move(<<?^, rest::binary>>, houses, {x, y}), do: deliver(rest, houses, {x, y - 1})
def move(<<?v, rest::binary>>, houses, {x, y}), do: deliver(rest, houses, {x, y + 1})
def move(<<>>, houses, _addr), do: MapSet.size(houses)
def deliver(moves, houses, addr), do: move(moves, MapSet.put(houses, addr), addr)
def part2(input), do: move2(input, MapSet.new([{0, 0}]), {0, 0}, {0, 0})
def move2(<<ms, mb, rest::binary>>, houses, santa, bot) do
santa = next(ms, santa)
bot = next(mb, bot)
move2(rest, MapSet.union(houses, MapSet.new([santa, bot])), santa, bot)
end
def move2(<<>>, houses, _santa, _bot), do: MapSet.size(houses)
def next(?<, {x, y}), do: {x - 1, y}
def next(?>, {x, y}), do: {x + 1, y}
def next(?^, {x, y}), do: {x, y - 1}
def next(?v, {x, y}), do: {x, y + 1}
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
input
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{result}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day3.exs input_filename")
end
end
Day3.run()

View file

@ -1,50 +0,0 @@
defmodule Day4 do
def part1(input), do: mine(input, 1, "00000")
def mine(input, nonce, prefix) do
hash = :crypto.hash(:md5, input <> Integer.to_string(nonce)) |> Base.encode16()
if String.starts_with?(hash, prefix), do: nonce, else: mine(input, nonce + 1, prefix)
end
def part2(input), do: mine(input, 1, "000000")
def input do
with [input] <- System.argv() do
input
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{result}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day4.exs input")
end
end
Day4.run()

View file

@ -1,86 +0,0 @@
defmodule Day5 do
def part1(input) do
Enum.count(input, &nice?/1)
end
def nice?(str) do
at_least_three_vowels(str) and
twice_in_a_row(str) and
not String.contains?(str, ["ab", "cd", "pq", "xy"])
end
def at_least_three_vowels(str) do
str
|> String.graphemes()
|> Enum.frequencies()
|> Map.take(["a", "e", "i", "o", "u"])
|> Map.values()
|> Enum.sum()
|> Kernel.>=(3)
end
def twice_in_a_row(<<x, x, _::binary>>), do: true
def twice_in_a_row(<<_x, rest::binary>>), do: twice_in_a_row(rest)
def twice_in_a_row(<<>>), do: false
def part2(input) do
Enum.count(input, &nice2?/1)
end
def nice2?(str) do
pairs = pairs(str)
uniq = MapSet.new(pairs)
length(pairs) > MapSet.size(uniq) and sandwich(str)
end
def pairs(str, prev \\ nil)
def pairs(<<x, x, rest::binary>>, <<x, x>>), do: pairs(<<x, rest::binary>>)
def pairs(<<x, y, rest::binary>>, _prev), do: [<<x, y>> | pairs(<<y, rest::binary>>, <<x, y>>)]
def pairs(<<_>>, _prev), do: []
def sandwich(<<x, y, x, _rest::binary>>), do: true
def sandwich(<<_x, y, z, rest::binary>>), do: sandwich(<<y, z, rest::binary>>)
def sandwich(_), do: false
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
input
|> String.split("\n", trim: true)
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{result}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day5.exs input_filename")
end
end
Day5.run()

5
2021/README.md Normal file
View file

@ -0,0 +1,5 @@
# Advent of Code 2021 in Zig!
I just started learning Zig on Christmas Day 2021, so after running through a few tutorials I'm planning on running through a few of the 2021 puzzles as a way to get to know the language, probably starting sometime in January 2022.
<img width="943" alt="image" src="https://user-images.githubusercontent.com/498229/147409293-6a855c9c-7d89-4440-9192-240eb47b3d85.png">

View file

@ -1,95 +0,0 @@
defmodule Day10 do
def part1(input) do
input
|> Enum.map(&parse_line(&1, []))
|> Enum.filter(&match?({:corrupted, _}, &1))
|> Enum.map(fn {:corrupted, char} -> score(char) end)
|> Enum.sum()
end
defp parse_line(_, {:corrupted, char}), do: {:corrupted, char}
defp parse_line("", stack), do: {:incomplete, stack}
defp parse_line(<<char::utf8, rest::binary>>, []), do: parse_line(rest, [char])
defp parse_line(<<char::utf8, next::binary>>, [prev | popped] = stack) do
stack =
case char do
char when char in '([{<' -> [char | stack]
?) when prev == ?( -> popped
?] when prev == ?[ -> popped
?} when prev == ?{ -> popped
?> when prev == ?< -> popped
char -> {:corrupted, char}
end
parse_line(next, stack)
end
defp score(?)), do: 3
defp score(?]), do: 57
defp score(?}), do: 1197
defp score(?>), do: 25137
def part2(input) do
scores =
input
|> Enum.map(&parse_line(&1, []))
|> Enum.filter(&match?({:incomplete, _}, &1))
|> Enum.map(fn {_, chars} -> score2(chars) end)
|> Enum.sort()
Enum.at(scores, trunc(length(scores) / 2))
end
defp score2(chars) do
for char <- chars, reduce: 0 do
score -> score * 5 + points(char)
end
end
defp points(?(), do: 1
defp points(?[), do: 2
defp points(?{), do: 3
defp points(?<), do: 4
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
String.split(input, "\n", trim: true)
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{result}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day10.exs input_filename")
end
end
Day10.run()

View file

@ -1,106 +0,0 @@
defmodule Day13 do
defmodule Paper do
defstruct dots: MapSet.new(), instructions: []
end
def part1(paper) do
folded = fold(paper)
MapSet.size(folded.dots)
end
defp fold(%Paper{dots: dots, instructions: [{axis, pos} | rest]}) do
dots =
MapSet.new(dots, fn {x, y} ->
cond do
axis == "x" and x > pos -> {pos - (x - pos), y}
axis == "y" and y > pos -> {x, pos - (y - pos)}
true -> {x, y}
end
end)
%Paper{dots: dots, instructions: rest}
end
def part2(paper) do
paper
|> fold_all
|> draw
end
defp fold_all(%Paper{instructions: []} = paper), do: paper
defp fold_all(%Paper{} = paper), do: paper |> fold |> fold_all
defp draw(paper) do
{max_x, max_y} =
for {x, y} <- paper.dots, reduce: {0, 0} do
{max_x, max_y} -> {max(x, max_x), max(y, max_y)}
end
for y <- 0..max_y do
row =
for x <- 0..max_x do
if MapSet.member?(paper.dots, {x, y}), do: ?#, else: ?\s
end
# could Enum.join("\n") here, but I want to play with iolists
[row | "\n"]
end
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
[dots, instructions] = String.split(input, "\n\n")
dots =
dots
|> String.split([",", "\n"])
|> Enum.map(&String.to_integer/1)
|> Enum.chunk_every(2)
|> MapSet.new(fn [x, y] -> {x, y} end)
instructions =
instructions
|> String.split("\n", trim: true)
|> Enum.map(fn <<"fold along ", axis::binary-1, "=", pos::binary>> ->
{axis, String.to_integer(pos)}
end)
%Paper{dots: dots, instructions: instructions}
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{result}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day13.exs input_filename")
end
end
Day13.run()

View file

@ -1,140 +0,0 @@
defmodule Day16 do
def part1(input) do
{_type, _rest, _count, version_sum} = decode_packet(input, 0, 0)
version_sum
end
@literal_type 4
@literal_part 1
@literal_end 0
@total_length_type 0
@sub_packets_length_type 1
defp decode_packet(<<version::3, @literal_type::3, rest::bits>>, count, version_sum) do
decode_literal(rest, <<>>, count + 6, version_sum + version)
end
defp decode_packet(<<version::3, operator_type::3, rest::bits>>, count, version_sum) do
{sub_packets, rest, count, version_sum} =
case rest do
<<@total_length_type::1, length::15, rest::bits>> ->
decode_sub_packets_by(
:length,
length,
[],
rest,
count + 3 + 3 + 1 + 15,
version_sum + version
)
<<@sub_packets_length_type::1, num_sub_packets::11, rest::bits>> ->
decode_sub_packets_by(
:quantity,
num_sub_packets,
[],
rest,
count + 3 + 3 + 1 + 11,
version_sum + version
)
end
{{:operator, operator_type, sub_packets}, rest, count, version_sum}
end
defp decode_literal(<<@literal_part::1, group::bits-4, rest::bits>>, acc, count, version_sum) do
decode_literal(rest, <<acc::bits, group::bits>>, count + 5, version_sum)
end
defp decode_literal(<<@literal_end::1, group::bits-4, rest::bits>>, acc, count, version_sum) do
literal_binary = <<acc::bits, group::bits>>
<<literal::size(bit_size(literal_binary))>> = literal_binary
{{:literal, literal}, rest, count + 5, version_sum}
end
defp decode_sub_packets_by(_method, 0, decoded_packets, bits, count, version_sum) do
{Enum.reverse(decoded_packets), bits, count, version_sum}
end
defp decode_sub_packets_by(method, remaining, decoded_packets, bits, count, version_sum) do
{decoded_packet, rest, packet_size, version_sum} = decode_packet(bits, 0, version_sum)
remaining =
case method do
:length -> remaining - packet_size
:quantity -> remaining - 1
end
decode_sub_packets_by(
method,
remaining,
[decoded_packet | decoded_packets],
rest,
count + packet_size,
version_sum
)
end
def part2(input) do
{packet, _rest, _count, _version_sum} = decode_packet(input, 0, 0)
evaluate(packet)
end
defp evaluate({:literal, literal}), do: literal
defp evaluate({:operator, op, args}) do
args
|> Enum.map(&evaluate/1)
|> calculate(op)
end
defp calculate(args, 0), do: Enum.sum(args)
defp calculate(args, 1), do: Enum.reduce(args, &Kernel.*/2)
defp calculate(args, 2), do: Enum.min(args)
defp calculate(args, 3), do: Enum.max(args)
defp calculate([a, b], 5), do: if(a > b, do: 1, else: 0)
defp calculate([a, b], 6), do: if(a < b, do: 1, else: 0)
defp calculate([a, b], 7), do: if(a == b, do: 1, else: 0)
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
input
|> String.trim()
|> Base.decode16!()
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def(run) do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{result}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day16.exs input_filename")
end
end
Day16.run()

View file

@ -1,98 +0,0 @@
defmodule Day3 do
def part1(input) do
gamma_rating = find_rating(input, 0, :most, <<>>)
epsillon_rating = find_rating(input, 0, :least, <<>>)
gamma_rating * epsillon_rating
end
defp find_rating([value | _], _, _, bits) when bit_size(value) == bit_size(bits) do
<<rating::size(bit_size(bits))>> = bits
rating
end
defp find_rating(values, bits_seen, mode, acc) do
bit = find_common(values, bits_seen, mode)
find_rating(values, bits_seen + 1, mode, <<acc::bits-size(bits_seen), bit::1>>)
end
def part2(input) do
oxygen_generator_rating = find_rating2(input, 0, :most)
co2_scrubber_rating = find_rating2(input, 0, :least)
oxygen_generator_rating * co2_scrubber_rating
end
defp find_rating2([bits], _bits_seen, _mode) do
<<value::size(bit_size(bits))>> = bits
value
end
defp find_rating2(values, bits_seen, mode) do
bit = find_common(values, bits_seen, mode)
values
|> filter_by_bit(bits_seen, bit)
|> find_rating2(bits_seen + 1, mode)
end
defp find_common(values, bits_seen, mode) do
{zeros, ones} =
values
|> Enum.map(fn <<_::size(bits_seen), bit::1, _rest::bits>> -> bit end)
|> Enum.reduce({_zeros = 0, _ones = 0}, fn
0, {zeros, ones} -> {zeros + 1, ones}
1, {zeros, ones} -> {zeros, ones + 1}
end)
case mode do
:most -> if zeros <= ones, do: 1, else: 0
:least -> if zeros <= ones, do: 0, else: 1
end
end
defp filter_by_bit(values, num_prev_bits, bit) do
Enum.filter(values, &match?(<<_::size(num_prev_bits), ^bit::1, _rest::bits>>, &1))
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
strings = String.split(input, "\n", trim: true)
length = byte_size(hd(strings))
Enum.map(strings, &<<String.to_integer(&1, 2)::size(length)>>)
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{result}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir dayREPLACE_ME.exs input_filename")
end
end
Day3.run()

View file

@ -1,133 +0,0 @@
defmodule Day4 do
@rows for y <- 0..4, do: for(x <- 0..4, do: {x, y})
@cols for x <- 0..4, do: for(y <- 0..4, do: {x, y})
def part1(input) do
{current_number, winning_board} = play(input)
sum_unmarked(winning_board) * current_number
end
def part2(input) do
{current_number, last_board} = play_until_last(input)
sum_unmarked(last_board) * current_number
end
def play({[current_number | next_numbers], boards}) do
boards = for board <- boards, do: mark(board, current_number)
case check_for_winner(boards) do
nil -> play({next_numbers, boards})
board -> {current_number, board}
end
end
def play_until_last({_numbers, [_last_board]} = input), do: play(input)
def play_until_last({numbers, boards} = input) do
{_current_number, current_winner} = play(input)
current_winner_no_markers = Map.new(current_winner, fn {k, {v, _marker}} -> {k, v} end)
remaining_boards =
Enum.reject(boards, fn board ->
current_winner_no_markers == Map.new(board, fn {k, {v, _marker}} -> {k, v} end)
end)
play_until_last({numbers, remaining_boards})
end
def sum_unmarked(board) do
board
|> Enum.filter(&match?({_key, {_number, false}}, &1))
|> Enum.map(fn {_key, {number, _marked}} -> number end)
|> Enum.sum()
end
def mark(board, current_number) do
case Enum.find(board, &match?({_key, {^current_number, false}}, &1)) do
nil -> board
{key, _value} -> Map.put(board, key, {current_number, true})
end
end
def check_for_winner([]), do: nil
def check_for_winner([board | boards]) do
row_complete? =
Enum.any?(@rows, fn row ->
values = for key <- row, do: board[key]
Enum.all?(values, &match?({_number, true}, &1))
end)
col_complete? =
Enum.any?(@cols, fn col ->
values = for key <- col, do: board[key]
Enum.all?(values, &match?({_number, true}, &1))
end)
if row_complete? or col_complete? do
board
else
check_for_winner(boards)
end
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
[numbers | boards] = String.split(input, "\n", trim: true)
numbers = numbers |> String.split(",") |> Enum.map(&String.to_integer/1)
boards = parse_boards(boards)
{numbers, boards}
else
_ -> :error
end
end
defp parse_boards([]), do: []
defp parse_boards(boards) do
{board, boards} = Enum.split(boards, 5)
board = Enum.map(board, &String.split/1)
board =
for {row, y} <- Enum.with_index(board), {value, x} <- Enum.with_index(row), into: %{} do
{{x, y}, {String.to_integer(value), false}}
end
[board | parse_boards(boards)]
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{result}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day4.exs input_filename")
end
end
Day4.run()

View file

@ -1,83 +0,0 @@
defmodule Day5 do
def part1(input) do
input
|> Enum.reduce(_points = %{}, fn
{{x, y1}, {x, y2}}, points ->
for y <- y1..y2, reduce: points, do: (points -> plot(points, {x, y}))
{{x1, y}, {x2, y}}, points ->
for x <- x1..x2, reduce: points, do: (points -> plot(points, {x, y}))
_point, points ->
points
end)
|> Enum.count(fn {_point, count} -> count >= 2 end)
end
def part2(input) do
input
|> Enum.reduce(_points = %{}, fn
{{x, y1}, {x, y2}}, points ->
for y <- y1..y2, reduce: points, do: (points -> plot(points, {x, y}))
{{x1, y}, {x2, y}}, points ->
for x <- x1..x2, reduce: points, do: (points -> plot(points, {x, y}))
{{x1, x1}, {x2, x2}}, points ->
for x <- x1..x2, reduce: points, do: (points -> plot(points, {x, x}))
{{x1, y1}, {x2, y2}}, points ->
for point <- Enum.zip(x1..x2, y1..y2), reduce: points, do: (points -> plot(points, point))
end)
|> Enum.count(fn {_point, count} -> count >= 2 end)
end
defp plot(points, point) do
Map.put(points, point, (points[point] || 0) + 1)
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
input
|> String.split(["\n", " -> ", ","], trim: true)
|> Enum.map(&String.to_integer/1)
|> Enum.chunk_every(4)
|> Enum.map(fn [x1, y1, x2, y2] -> {{x1, y1}, {x2, y2}} end)
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{result}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day5.exs input_filename")
end
end
Day5.run()

View file

@ -1,84 +0,0 @@
defmodule Day6 do
def part1(input) do
input
|> run_days(80)
|> length()
end
defp run_days(school, 0), do: school
defp run_days(school, days), do: run_days(day(school), days - 1)
defp day(school), do: Enum.flat_map(school, &fish/1)
defp fish(0), do: [6, 8]
defp fish(age), do: [age - 1]
# Implementing part 2 completely differently to avoid exponential growth
def part2(input) do
counts = Map.new(0..8, fn age -> {age, 0} end)
input
|> Enum.frequencies()
|> Enum.into(counts)
|> run_days2(256)
|> Map.values()
|> Enum.sum()
end
defp run_days2(counts, 0), do: counts
defp run_days2(counts, days), do: run_days2(day2(counts), days - 1)
defp day2(counts) do
{breeders, counts} = Map.pop(counts, 0)
counts
|> Map.new(fn {age, count} -> {age - 1, count} end)
|> Map.update(6, 0, &(&1 + breeders))
|> Map.put(8, breeders)
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
input
|> String.trim()
|> String.split(",")
|> Enum.map(&String.to_integer/1)
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{result}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day6.exs input_filename")
end
end
Day6.run()

View file

@ -1,81 +0,0 @@
defmodule Day7 do
def part1(input) do
input
|> Enum.frequencies()
|> Enum.into(Map.new(Enum.min(input)..Enum.max(input), &{&1, 0}))
|> calculate_costs()
|> Enum.min()
end
defp calculate_costs(crabs) do
for here <- Map.keys(crabs) do
for {there, crabs_there} <- Map.delete(crabs, here), reduce: 0 do
cost -> cost + abs(here - there) * crabs_there
end
end
end
def part2(input) do
input
|> Enum.frequencies()
|> Enum.into(Map.new(Enum.min(input)..Enum.max(input), &{&1, 0}))
|> calculate_sum_costs()
|> Enum.min()
end
defp calculate_sum_costs(crabs) do
for here <- Map.keys(crabs) do
for {there, crabs_there} <- Map.delete(crabs, here), reduce: 0 do
cost ->
distance = abs(here - there)
# Mmm maths. Can get the sum of 1..n by doing (n * n+1)/2. Guess you have to know it.
sum = div(distance * (distance + 1), 2)
cost + sum * crabs_there
end
end
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
input
|> String.trim()
|> String.split(",")
|> Enum.map(&String.to_integer/1)
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{result}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day7.exs input_filename")
end
end
Day7.run()

View file

@ -1,111 +0,0 @@
defmodule Day9 do
defmodule Cave do
defstruct floor: %{}, max_x: 0, max_y: 0
end
def part1(cave) do
for x <- 0..cave.max_x, y <- 0..cave.max_y, lowest_neighbour?(cave, x, y), reduce: 0 do
risk_level -> risk_level + cave.floor[{x, y}] + 1
end
end
defp neighbours(cave, x, y) do
[{x - 1, y}, {x + 1, y}, {x, y - 1}, {x, y + 1}]
|> Enum.reject(fn {x, y} -> x < 0 or x > cave.max_x or y < 0 or y > cave.max_y end)
end
defp lowest_neighbour?(cave, x, y) do
here = cave.floor[{x, y}]
Enum.all?(neighbours(cave, x, y), fn {xn, yn} -> here < cave.floor[{xn, yn}] end)
end
def part2(cave) do
cave
|> remove_nines()
|> find_basins()
|> Enum.sort(:desc)
|> Enum.take(3)
|> Enum.reduce(&Kernel.*/2)
end
defp find_basins(%Cave{floor: floor}) when floor == %{}, do: []
defp find_basins(cave) do
{cave, location} = get_start_location(cave)
{cave, basin} = find_basin(cave, [location], [location])
[basin | find_basins(cave)]
end
defp find_basin(cave, [], basin), do: {cave, length(basin)}
defp find_basin(cave, [{x, y} | locations], basin) do
neighbours = cave.floor |> Map.take(neighbours(cave, x, y)) |> Map.keys()
cave = %Cave{cave | floor: Map.drop(cave.floor, neighbours)}
find_basin(cave, locations ++ neighbours, basin ++ neighbours)
end
defp remove_nines(cave) do
floor = Map.reject(cave.floor, &match?({_k, 9}, &1))
%Cave{cave | floor: floor}
end
defp get_start_location(cave) do
[{location, _v}] = Enum.take(cave.floor, 1)
cave = %Cave{cave | floor: Map.delete(cave.floor, location)}
{cave, location}
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
rows =
input
|> String.split("\n", trim: true)
|> Enum.map(&String.split(&1, "", trim: true))
floor =
for {row, y} <- Enum.with_index(rows), {height, x} <- Enum.with_index(row), into: %{} do
{{x, y}, String.to_integer(height)}
end
max_x = length(hd(rows)) - 1
max_y = length(rows) - 1
%Cave{floor: floor, max_x: max_x, max_y: max_y}
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{result}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day9.exs input_filename")
end
end
Day9.run()

View file

@ -1,11 +0,0 @@
# AdventOfCode2022
My (attempted) solutions to [Advent of Code 2022](https://adventofcode.com/2022) in Elixir.
<img width="869" alt="image" src="https://user-images.githubusercontent.com/498229/207063420-9e176055-d40c-4b33-a826-368c04a7b75d.png">
## Running:
```sh
elixir dayN.exs input_filename
```

View file

@ -1,58 +0,0 @@
defmodule Day1 do
def part1(input) do
input
|> Enum.map(&Enum.sum/1)
|> Enum.max()
end
def part2(input) do
input
|> Enum.map(&Enum.sum/1)
|> Enum.sort(:desc)
|> Enum.take(3)
|> Enum.sum()
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
input
|> String.split("\n\n")
|> Enum.map(&for elf <- String.split(&1, "\n", trim: true), do: String.to_integer(elf))
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{result}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day1.exs input_filename")
end
end
Day1.run()

View file

@ -1,109 +0,0 @@
defmodule Day10 do
defmodule Instr do
defstruct cmd: nil, cycles: 0, amt: nil
end
def part1(input) do
process(input, _cycle = 1, _x = 1, _sample_at = 20, _sig = 0)
end
defp process([], _cycle, _x, _sample_at, sig), do: sig
defp process(instrs, cycle, x, sample_at, sig) when cycle == sample_at do
process(instrs, cycle, x, sample_at + 40, sig + cycle * x)
end
defp process([%Instr{cycles: 0} | rest], cycle, x, sample_at, sig) do
process(rest, cycle + 1, x, sample_at, sig)
end
defp process([instr = %Instr{cycles: 1, cmd: :noop} | rest], cycle, x, sample_at, sig) do
process([%Instr{instr | cycles: 0} | rest], cycle, x, sample_at, sig)
end
defp process([instr = %Instr{cycles: 1, cmd: :addx, amt: amt} | rest], cycle, x, sample_at, sig) do
process([%Instr{instr | cycles: 0} | rest], cycle, x + amt, sample_at, sig)
end
defp process([instr = %Instr{cycles: cycles} | rest], cycle, x, sample_at, sig) do
process([%Instr{instr | cycles: cycles - 1} | rest], cycle + 1, x, sample_at, sig)
end
def part2(input) do
input
|> scan(_x = 1, _pixel = 0, _pixels = [[]])
|> Enum.map(&Enum.reverse/1)
|> Enum.reverse()
|> Enum.join("\n")
end
defp scan([], _x, _pixel, pixels), do: pixels
defp scan(instrs, x, _pixel = 40, pixels) do
scan(instrs, x, _pixel = 0, [[] | pixels])
end
defp scan([%Instr{cmd: :noop} | rest], x, pixel, [row | pixels]) do
scan(rest, x, pixel + 1, [draw_pixel(pixel, x, row) | pixels])
end
defp scan([%Instr{cycles: 1, cmd: :addx, amt: amt} | rest], x, pixel, [row | pixels]) do
scan(rest, x + amt, pixel + 1, [draw_pixel(pixel, x, row) | pixels])
end
defp scan([instr = %Instr{cycles: cycles} | rest], x, pixel, [row | pixels]) do
scan([%Instr{instr | cycles: cycles - 1} | rest], x, pixel + 1, [
draw_pixel(pixel, x, row) | pixels
])
end
defp draw_pixel(pixel, x, row) when pixel in (x - 1)..(x + 1), do: ["#" | row]
defp draw_pixel(_pixel, _x, row), do: [" " | row]
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
input
|> String.trim()
|> String.split("\n")
|> Enum.map(fn
"noop" -> %Instr{cmd: :noop, cycles: 1}
"addx " <> x -> %Instr{cmd: :addx, cycles: 2, amt: String.to_integer(x)}
end)
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{result}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day10.exs input_filename")
end
end
Day10.run()

View file

@ -1,188 +0,0 @@
defmodule Monkey do
use GenServer
defstruct [
:num,
:items_caught,
:operation,
:divisor,
:true_monkey,
:false_monkey,
:inspected_count
]
# Server
#########
def init(%Monkey{} = monkey) do
{:ok, {monkey, _monkeys = %{}}}
end
def handle_cast({:catch, item}, {monkey, monkies}) do
monkey = catch_item(monkey, item)
{:noreply, {monkey, monkies}}
end
def handle_call({:register, monkies}, _from, {monkey, _monkies}) do
{:reply, :ok, {monkey, monkies}}
end
def handle_call(:turn, _from, {monkey, monkies}) do
monkey = do_business(monkey, monkies)
{:reply, :done, {%Monkey{monkey | items_caught: []}, monkies}}
end
def handle_call(:get_count, _from, {monkey, monkies}) do
{:reply, monkey.inspected_count, {monkey, monkies}}
end
# Client
#########
def throw(monkey, item), do: GenServer.cast(monkey, {:catch, item})
def register(monkey, monkeys), do: GenServer.call(monkey, {:register, monkeys})
def take_turn(monkey), do: GenServer.call(monkey, :turn)
def report_count(monkey), do: GenServer.call(monkey, :get_count)
# Monkey business
##################
defp catch_item(monkey, item), do: %Monkey{monkey | items_caught: [item | monkey.items_caught]}
defp do_business(monkey, monkies) do
count =
for item <- Enum.reverse(monkey.items_caught), reduce: 0 do
count ->
item = monkey.operation.(item) |> div(3)
to_monkey =
if rem(item, monkey.divisor) == 0 do
monkey.true_monkey
else
monkey.false_monkey
end
throw(monkies[to_monkey], item)
count + 1
end
%Monkey{monkey | items_caught: [], inspected_count: monkey.inspected_count + count}
end
end
defmodule Day11 do
def part1(input) do
monkies = setup_monkies(input)
order = monkies |> Map.keys() |> Enum.sort()
run_rounds(20, monkies, order)
order
|> Enum.map(&Monkey.report_count(monkies[&1]))
|> Enum.sort(:desc)
|> Enum.take(2)
|> Enum.reduce(&Kernel.*/2)
end
def setup_monkies(input) do
monkies =
input
|> Enum.with_index()
|> Map.new(fn {monkey, idx} ->
{:ok, pid} = GenServer.start_link(Monkey, monkey)
{idx, pid}
end)
Enum.each(monkies, fn {_k, monkey} -> Monkey.register(monkey, monkies) end)
monkies
end
defp run_rounds(0, _monkies, _order), do: :ok
defp run_rounds(rounds, monkies, order) do
Enum.each(order, &Monkey.take_turn(monkies[&1]))
run_rounds(rounds - 1, monkies, order)
end
def part2(_input) do
"not implemented"
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
input
|> String.split("\n\n")
|> Enum.map(fn monkey ->
[
<<"Monkey ", monkey_num::binary-1, ":">>,
"Starting items: " <> starting_items,
<<"Operation: new = old ", op::binary-1, " ", operand::binary>>,
"Test: divisible by " <> divisor,
"If true: throw to monkey " <> true_monkey,
"If false: throw to monkey " <> false_monkey
] = monkey |> String.trim() |> String.split("\n") |> Enum.map(&String.trim/1)
starting_items = String.split(starting_items, ", ")
[monkey_num, divisor, true_monkey, false_monkey | starting_items] =
Enum.map(
[monkey_num, divisor, true_monkey, false_monkey] ++ starting_items,
&String.to_integer/1
)
operation =
case {op, operand} do
{"+", "old"} -> fn old -> old + old end
{"*", "old"} -> fn old -> old * old end
{"+", operand} -> fn old -> old + String.to_integer(operand) end
{"*", operand} -> fn old -> old * String.to_integer(operand) end
end
%Monkey{
num: monkey_num,
items_caught: Enum.reverse(starting_items),
operation: operation,
divisor: divisor,
true_monkey: true_monkey,
false_monkey: false_monkey,
inspected_count: 0
}
end)
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{result}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day11.exs input_filename")
end
end
Day11.run()

View file

@ -1,100 +0,0 @@
defmodule Day2 do
def part1(input) do
input
|> Enum.map(fn
{opponent, "X"} -> {opponent, :rock}
{opponent, "Y"} -> {opponent, :paper}
{opponent, "Z"} -> {opponent, :scissors}
end)
|> Enum.map(&play/1)
|> Enum.sum()
end
@rock 1
@paper 2
@scissors 3
@lose 0
@draw 3
@win 6
defp play({:rock, :rock}), do: @rock + @draw
defp play({:paper, :rock}), do: @rock + @lose
defp play({:scissors, :rock}), do: @rock + @win
defp play({:rock, :paper}), do: @paper + @win
defp play({:paper, :paper}), do: @paper + @draw
defp play({:scissors, :paper}), do: @paper + @lose
defp play({:rock, :scissors}), do: @scissors + @lose
defp play({:paper, :scissors}), do: @scissors + @win
defp play({:scissors, :scissors}), do: @scissors + @draw
def part2(input) do
input
|> Enum.map(fn
{opponent, "X"} -> {opponent, :lose}
{opponent, "Y"} -> {opponent, :draw}
{opponent, "Z"} -> {opponent, :win}
end)
|> Enum.map(&play2/1)
|> Enum.sum()
end
defp play2({:rock, :win}), do: @win + @paper
defp play2({:paper, :win}), do: @win + @scissors
defp play2({:scissors, :win}), do: @win + @rock
defp play2({:rock, :draw}), do: @draw + @rock
defp play2({:paper, :draw}), do: @draw + @paper
defp play2({:scissors, :draw}), do: @draw + @scissors
defp play2({:rock, :lose}), do: @lose + @scissors
defp play2({:paper, :lose}), do: @lose + @rock
defp play2({:scissors, :lose}), do: @lose + @paper
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
input
|> String.split([" ", "\n"], trim: true)
|> Enum.map(fn
"A" -> :rock
"B" -> :paper
"C" -> :scissors
me -> me
end)
|> Enum.chunk_every(2)
|> Enum.map(&List.to_tuple/1)
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{result}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day2.exs input_filename")
end
end
Day2.run()

View file

@ -1,71 +0,0 @@
defmodule Day3 do
def part1(input) do
input
|> Enum.map(fn str ->
size = byte_size(str)
str
|> String.split("", trim: true)
|> Enum.split(div(size, 2))
end)
|> Enum.map(fn {a, b} -> hd(a -- a -- b) end)
|> Enum.map(&score/1)
|> Enum.sum()
end
defp score(<<lower::utf8>>) when lower in ?a..?z, do: lower - ?a + 1
defp score(<<upper::utf8>>) when upper in ?A..?Z, do: upper - ?A + 27
def part2(input) do
input
|> Enum.map(&String.split(&1, "", trim: true))
|> Enum.chunk_every(3)
|> Enum.map(fn [a, b, c] ->
d = a -- a -- b
hd(d -- d -- c)
end)
|> Enum.map(&score/1)
|> Enum.sum()
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
String.split(input, "\n", trim: true)
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{result}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day3.exs input_filename")
end
end
Day3.run()

View file

@ -1,55 +0,0 @@
defmodule Day4 do
def part1(input) do
Enum.count(input, fn [a, b] -> MapSet.subset?(a, b) or MapSet.subset?(b, a) end)
end
def part2(input) do
Enum.count(input, fn [a, b] -> not MapSet.disjoint?(a, b) end)
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
input
|> String.split(["-", ",", "\n"], trim: true)
|> Enum.map(&String.to_integer/1)
|> Enum.chunk_every(2)
|> Enum.map(fn [a, b] -> MapSet.new(a..b) end)
|> Enum.chunk_every(2)
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{result}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day4.exs input_filename")
end
end
Day4.run()

View file

@ -1,92 +0,0 @@
defmodule Day5 do
def part1(input), do: input |> move_with(&move_9000/3) |> top_crates()
def part2(input), do: input |> move_with(&move_9001/3) |> top_crates()
defp move_with({stacks, procedure}, move) do
for [number, from_index, to_index] <- procedure, reduce: stacks do
stacks ->
{moved_from, moved_to} = move.(number, stacks[from_index], stacks[to_index])
%{stacks | from_index => moved_from, to_index => moved_to}
end
end
defp move_9000(0, from, to), do: {from, to}
defp move_9000(number, [crate | from], to), do: move_9000(number - 1, from, [crate | to])
defp move_9001(number, from, to) do
{moving, moved_from} = Enum.split(from, number)
{moved_from, moving ++ to}
end
defp top_crates(stacks) do
stacks
|> Enum.sort_by(fn {label, _stack} -> label end)
|> Enum.reduce("", fn {_label, [top | _stack]}, acc -> acc <> top end)
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
[raw_stacks, raw_procedure] = String.split(input, "\n\n")
stacks =
raw_stacks
|> String.split("\n")
|> parse_stacks()
|> Enum.zip()
|> Enum.map(fn stack -> stack |> Tuple.to_list() |> Enum.drop_while(&is_nil/1) end)
|> Enum.with_index()
|> Map.new(fn {stack, index} -> {index + 1, stack} end)
procedure =
raw_procedure
|> String.split(["move ", " from ", " to ", "\n"], trim: true)
|> Enum.map(&String.to_integer/1)
|> Enum.chunk_every(3)
{stacks, procedure}
else
_ -> :error
end
end
defp parse_stacks([_labels_]), do: []
defp parse_stacks([line | rest]), do: [parse_line(line) | parse_stacks(rest)]
defp parse_line(<<"">>), do: []
defp parse_line(<<" ", rest::binary>>), do: [nil | parse_line(rest)]
defp parse_line(<<"[", crate::binary-1, "]", rest::binary>>), do: [crate | parse_line(rest)]
defp parse_line(<<" ", rest::binary>>), do: parse_line(rest)
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{result}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day5.exs input_filename")
end
end
Day5.run()

View file

@ -1,72 +0,0 @@
defmodule Day6 do
@packet_marker_size 4
@message_marker_size 14
def part1(input) do
find_packet_marker(input)
end
defguardp all_different(a, b, c, d)
when a != b and a != c and a != d and b != c and b != d and c != d
defp find_packet_marker(buffer, seen \\ @packet_marker_size)
defp find_packet_marker([a, b, c, d | _rest], seen) when all_different(a, b, c, d), do: seen
defp find_packet_marker([_skip | rest], seen), do: find_packet_marker(rest, seen + 1)
def part2(input) do
find_message_marker(input)
end
defp find_message_marker([_skip | rest] = buffer, seen \\ @message_marker_size) do
window = Enum.take(buffer, @message_marker_size)
if Enum.uniq(window) == window do
seen
else
find_message_marker(rest, seen + 1)
end
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
input
|> String.trim()
|> String.codepoints()
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{result}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day6.exs input_filename")
end
end
Day6.run()

View file

@ -1,145 +0,0 @@
defmodule Day7 do
@root ["/"]
def part1(input) do
input
|> filetree_with_dir_sizes()
|> Enum.filter(fn
{_k, {:dir, size, _paths}} -> size <= 100_000
{_k, {:file, _size, _paths}} -> false
end)
|> Enum.map(fn {_k, {_type, size, _paths}} -> size end)
|> Enum.sum()
end
defp filetree_with_dir_sizes(input) do
tree = build_tree(input)
tree
|> dirs_fewest_descendents_first()
|> Enum.reduce(tree, &put_dir_size/2)
end
defp put_dir_size(path, tree) do
{:dir, nil, paths} = tree[path]
size = tree |> Map.take(paths) |> Enum.map(fn {_, {_, size, _}} -> size end) |> Enum.sum()
Map.put(tree, path, {:dir, size, paths})
end
defp dirs_fewest_descendents_first(tree, to_check \\ [@root], found_dirs \\ [])
defp dirs_fewest_descendents_first(_tree, [], found_dirs), do: found_dirs
defp dirs_fewest_descendents_first(tree, [node | rest], dirs) do
case tree[node] do
{:file, _size, nil} -> dirs_fewest_descendents_first(tree, rest, dirs)
{:dir, _size, paths} -> dirs_fewest_descendents_first(tree, paths ++ rest, [node | dirs])
end
end
defp build_tree(input, path \\ [], tree \\ %{})
defp build_tree([], _path, tree), do: tree
defp build_tree([{:cd, "/"} | rest], _path, tree), do: build_tree(rest, @root, tree)
defp build_tree([{:cd, ".."} | rest], [_here | path], tree), do: build_tree(rest, path, tree)
defp build_tree([{:cd, dir} | rest], path, tree), do: build_tree(rest, [dir | path], tree)
defp build_tree([{:ls, items} | rest], path, tree) do
{paths, tree} =
Enum.map_reduce(items, tree, fn
# Create an empty directory for each dir listed
# (needed if we we forget to ls inside a dir)
{:dir, nil, name}, tree ->
item_path = [name | path]
{item_path, Map.put(tree, item_path, {:dir, nil, []})}
# Create a file for each file listed
{:file, size, name}, tree ->
item_path = [name | path]
{item_path, Map.put(tree, item_path, {:file, size, nil})}
end)
# Create current directory with listed children
tree = Map.put(tree, path, {:dir, nil, paths})
build_tree(rest, path, tree)
end
@total_size 70_000_000
@space_needed 30_000_000
def part2(input) do
tree = filetree_with_dir_sizes(input)
{:dir, space_used, _} = tree[@root]
unused_space = @total_size - space_used
space_to_free = @space_needed - unused_space
tree
|> Enum.filter(&match?({_path, {:dir, _size, _paths}}, &1))
|> Enum.map(fn {_path, {:dir, size, _paths}} -> size end)
|> Enum.sort()
|> Enum.find(fn size -> size >= space_to_free end)
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
input
|> String.trim()
|> String.split("\n")
|> parse_input()
else
_ -> :error
end
end
defp parse_input([]), do: []
defp parse_input(["$ cd " <> dir | rest]), do: [{:cd, dir} | parse_input(rest)]
defp parse_input(["$ ls" | rest]) do
{listings, rest} = Enum.split_while(rest, &(!match?("$" <> _, &1)))
listings =
Enum.map(listings, fn
"dir " <> dir ->
{:dir, nil, dir}
file ->
[size, name] = String.split(file)
{:file, String.to_integer(size), name}
end)
[{:ls, listings} | parse_input(rest)]
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{result}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day7.exs input_filename")
end
end
Day7.run()

View file

@ -1,130 +0,0 @@
defmodule Day8 do
defmodule Tree do
defstruct height: nil, visible: false, score: []
end
def part1({{max_x, max_y}, trees}) do
trees
|> look_in_direction(0..max_y, 0..max_x, false)
|> look_in_direction(0..max_x, 0..max_y, true)
|> look_in_direction(0..max_y, max_x..0, false)
|> look_in_direction(0..max_x, max_y..0, true)
|> Enum.count(&match?({_coord, %Tree{visible: true}}, &1))
end
defp look_in_direction(trees, outer_range, inner_range, flip_xy?) do
Enum.reduce(outer_range, trees, fn y, trees ->
{_height, trees} =
Enum.reduce_while(inner_range, {_height = -1, trees}, fn
_x, {9, trees} ->
{:halt, {9, trees}}
x, {height, trees} ->
coord = if flip_xy?, do: {y, x}, else: {x, y}
tree = trees[coord]
if tree.height > height do
{:cont, {tree.height, Map.replace(trees, coord, %Tree{tree | visible: true})}}
else
{:cont, {height, trees}}
end
end)
trees
end)
end
def part2({{max_x, max_y}, trees}) do
trees
|> count_viewing_distance(0..max_y, 0..max_x, false)
|> count_viewing_distance(0..max_x, 0..max_y, true)
|> count_viewing_distance(0..max_y, max_x..0, false)
|> count_viewing_distance(0..max_x, max_y..0, true)
|> Enum.map(fn {_coord, %Tree{score: score}} -> Enum.reduce(score, &Kernel.*/2) end)
|> Enum.max()
end
defp count_viewing_distance(trees, outer_range, inner_range, flip_xy?) do
Enum.reduce(outer_range, trees, fn y, trees ->
{_scoring, trees} =
Enum.reduce(inner_range, {_scoring = [], trees}, fn
x, {scoring, trees} ->
coord = if flip_xy?, do: {y, x}, else: {x, y}
tree = trees[coord]
{scoring, trees} =
Enum.reduce(scoring, {[], trees}, fn {coord, scoring_tree}, {scoring, trees} ->
%Tree{score: [score | scores]} = scoring_tree
scoring_tree = %Tree{scoring_tree | score: [score + 1 | scores]}
trees = Map.replace(trees, coord, scoring_tree)
if scoring_tree.height <= tree.height do
{scoring, trees}
else
{[{coord, scoring_tree} | scoring], trees}
end
end)
tree = %Tree{tree | score: [0 | tree.score]}
{[{coord, tree} | scoring], Map.replace(trees, coord, tree)}
end)
trees
end)
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
grid =
input
|> String.trim()
|> String.split("\n")
|> Enum.map(&String.graphemes/1)
max_x = length(hd(grid)) - 1
max_y = length(grid) - 1
trees =
for {row, y} <- Enum.with_index(grid), {height, x} <- Enum.with_index(row), into: %{} do
{{x, y}, %Tree{height: String.to_integer(height)}}
end
{{max_x, max_y}, trees}
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{result}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day8.exs input_filename")
end
end
Day8.run()

View file

@ -1,109 +0,0 @@
defmodule Day9 do
defmodule Rope do
defstruct head: {0, 0},
tail: {0, 0},
knots: for(_ <- 1..8, do: {0, 0}),
visited: MapSet.new([{0, 0}])
end
def part1(input) do
input
|> Enum.reduce(%Rope{}, &motion/2)
|> then(fn %Rope{visited: visited} -> MapSet.size(visited) end)
end
defp motion({_dir, 0}, rope), do: rope
defp motion({dir, amount}, rope) do
head = step_head(rope.head, dir)
tail = step_tail(head, rope.tail)
visited = MapSet.put(rope.visited, tail)
motion({dir, amount - 1}, %Rope{head: head, tail: tail, visited: visited})
end
defp step_head({x, y}, "R"), do: {x + 1, y}
defp step_head({x, y}, "L"), do: {x - 1, y}
defp step_head({x, y}, "U"), do: {x, y + 1}
defp step_head({x, y}, "D"), do: {x, y - 1}
defp step_tail({hx, hy}, {tx, ty}) when abs(hx - tx) <= 1 and abs(hy - ty) <= 1, do: {tx, ty}
defp step_tail({tx, hy}, {tx, ty}) when hy - ty > 0, do: {tx, ty + 1}
defp step_tail({tx, hy}, {tx, ty}) when hy - ty < 0, do: {tx, ty - 1}
defp step_tail({hx, ty}, {tx, ty}) when hx - tx > 0, do: {tx + 1, ty}
defp step_tail({hx, ty}, {tx, ty}) when hx - tx < 0, do: {tx - 1, ty}
defp step_tail({hx, hy}, {tx, ty}) do
dx = if hx - tx > 0, do: 1, else: -1
dy = if hy - ty > 0, do: 1, else: -1
{tx + dx, ty + dy}
end
def part2(input) do
input
|> Enum.reduce(%Rope{}, &motion2/2)
|> then(fn %Rope{visited: visited} -> MapSet.size(visited) end)
end
defp motion2({_dir, 0}, rope), do: rope
defp motion2({dir, amount}, rope) do
head = step_head(rope.head, dir)
{knots, leader} =
Enum.map_reduce(rope.knots, head, fn knot, leader ->
new_knot = step_tail(leader, knot)
{new_knot, new_knot}
end)
tail = step_tail(leader, rope.tail)
visited = MapSet.put(rope.visited, tail)
motion2({dir, amount - 1}, %Rope{head: head, tail: tail, knots: knots, visited: visited})
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
input
|> String.trim()
|> String.split("\n")
|> Enum.map(fn <<dir::binary-1, " ", amount::binary>> ->
{dir, String.to_integer(amount)}
end)
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{result}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day9.exs input_filename")
end
end
Day9.run()

View file

@ -1,77 +0,0 @@
defmodule Day1 do
def part1(input) do
input
|> Enum.map(fn line ->
numbers = filter_digits(line)
String.to_integer(String.first(numbers) <> String.last(numbers))
end)
|> Enum.sum()
end
def filter_digits(<<>>), do: <<>>
def filter_digits(<<x, rest::binary>>) when x in ?1..?9, do: <<x>> <> filter_digits(rest)
def filter_digits(<<_, rest::binary>>), do: filter_digits(rest)
def part2(input) do
input
|> Enum.map(fn line ->
numbers = filter_digits2(line)
String.to_integer(String.first(numbers) <> String.last(numbers))
end)
|> Enum.sum()
end
def filter_digits2(<<>>), do: <<>>
def filter_digits2(<<x, rest::binary>>) when x in ?1..?9, do: <<x>> <> filter_digits2(rest)
def filter_digits2(<<"one", rest::binary>>), do: "1" <> filter_digits2("e" <> rest)
def filter_digits2(<<"two", rest::binary>>), do: "2" <> filter_digits2("o" <> rest)
def filter_digits2(<<"three", rest::binary>>), do: "3" <> filter_digits2("e" <> rest)
def filter_digits2(<<"four", rest::binary>>), do: "4" <> filter_digits2(rest)
def filter_digits2(<<"five", rest::binary>>), do: "5" <> filter_digits2("e" <> rest)
def filter_digits2(<<"six", rest::binary>>), do: "6" <> filter_digits2(rest)
def filter_digits2(<<"seven", rest::binary>>), do: "7" <> filter_digits2("n" <> rest)
def filter_digits2(<<"eight", rest::binary>>), do: "8" <> filter_digits2("t" <> rest)
def filter_digits2(<<"nine", rest::binary>>), do: "9" <> filter_digits2("e" <> rest)
def filter_digits2(<<_, rest::binary>>), do: filter_digits2(rest)
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
String.split(input, "\n", trim: true)
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{result}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day1.exs input_filename")
end
end
Day1.run()

View file

@ -1,79 +0,0 @@
#!/usr/bin/env elixir
defmodule Day2 do
def part1(input) do
input
|> Map.filter(fn {_id, cubes} ->
cubes["red"] <= 12 and cubes["green"] <= 13 and cubes["blue"] <= 14
end)
|> Map.keys()
|> Enum.sum()
end
def part2(input) do
input
|> Enum.map(fn {_id, cubes} -> cubes |> Map.values() |> Enum.product() end)
|> Enum.sum()
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
input
|> String.split("\n", trim: true)
|> Map.new(fn line ->
["Game " <> id | rounds] = String.split(line, [": ", "; "])
rounds =
rounds
|> Enum.map(&parse_round/1)
|> Enum.reduce(
%{"red" => 0, "green" => 0, "blue" => 0},
&Map.merge(&1, &2, fn _colour, count1, count2 -> max(count1, count2) end)
)
{String.to_integer(id), rounds}
end)
else
_ -> :error
end
end
def parse_round(round) do
round
|> String.split([", ", " "])
|> Enum.chunk_every(2)
|> Map.new(fn [number, colour] -> {colour, String.to_integer(number)} end)
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{inspect(result)}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day2.exs input_filename")
end
end
Day2.run()

View file

@ -1,90 +0,0 @@
#!/usr/bin/env elixir
defmodule Day3 do
def part1(_input) do
# for each number, check around its edges to see if there's a symbol
fn {{x, y}, number, length}, acc ->
top = for x <- (x - 1)..(x + length), do: {x, y - 1}
bottom = for x <- (x - 1)..(x + length), do: {x, y + 1}
box = [{x - 1, y}, {x + length, y}] ++ top ++ bottom
if part_number?(box), do: [number | acc], else: acc
end
|> :ets.foldl(_acc = [], :numbers)
|> Enum.sum()
end
def part_number?([]), do: false
def part_number?([coord | rest]) do
case :ets.lookup(:symbols, coord) do
[_symbol] -> true
[] -> part_number?(rest)
end
end
def part2(_input) do
:ok
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
:ets.new(:numbers, [:named_table])
:ets.new(:symbols, [:named_table])
parse_schematic(input, _x = 0, _y = 0)
else
_ -> :error
end
end
def parse_schematic("", _x, _y), do: :ok
def parse_schematic("." <> rest, x, y), do: parse_schematic(rest, x + 1, y)
def parse_schematic("\n" <> rest, _x, y), do: parse_schematic(rest, 0, y + 1)
def parse_schematic(<<digit::utf8>> <> rest, x, y) when digit in ?0..?9 do
{number, length, rest} = take_digits(<<digit::utf8>> <> rest, "", 0)
:ets.insert(:numbers, {{x, y}, number, length})
parse_schematic(rest, x + length, y)
end
def parse_schematic(<<symbol::utf8>> <> rest, x, y) do
:ets.insert(:symbols, {{x, y}, <<symbol::utf8>>})
parse_schematic(rest, x + 1, y)
end
def take_digits(<<digit::utf8>> <> rest, digits, count) when digit in ?0..?9,
do: take_digits(rest, digits <> <<digit::utf8>>, count + 1)
def take_digits(rest, digits, count), do: {String.to_integer(digits), count, rest}
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{inspect(result)}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day3.exs input_filename")
end
end
# Day3.run()

View file

@ -1,78 +0,0 @@
#!/usr/bin/env elixir
defmodule Day4 do
def part1(input) do
input
|> Enum.map(fn 0 -> 0; num_winners -> 2 ** (num_winners - 1) end)
|> Enum.sum()
end
def part2(input) do
input
|> Enum.map(fn num_winners -> {_num_copies = 1, num_winners} end)
|> copy()
|> Enum.sum()
end
def copy([]), do: []
def copy([{num_copies, num_winners} | rest]) do
{to_copy, left_alone} = Enum.split(rest, num_winners)
copied =
Enum.map(to_copy, fn {child_num_copies, num_winners} ->
{num_copies + child_num_copies, num_winners}
end)
[num_copies | copy(copied ++ left_alone)]
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
input
|> String.split("\n", trim: true)
|> Enum.map(fn line ->
[_card_id, winning, have] = String.split(line, [": ", " | "])
winning = winning |> String.split(" ", trim: true) |> MapSet.new()
have = have |> String.split(" ", trim: true) |> MapSet.new()
MapSet.intersection(winning, have) |> MapSet.size()
end)
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{inspect(result)}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day4.exs input_filename")
end
end
Day4.run()

View file

@ -1,110 +0,0 @@
#!/usr/bin/env elixir
defmodule Day5 do
def part1({seeds, maps}) do
seeds
|> Enum.map(fn seed -> Enum.reduce(maps, seed, &lookup/2) end)
|> Enum.min()
end
def lookup([], item), do: item
def lookup([{source, offset} | rest], item) do
if item in source do
item + offset
else
lookup(rest, item)
end
end
def part2({seeds, maps}) do
seed_ranges =
seeds
|> Enum.chunk_every(2)
|> Enum.map(fn [start, len] -> start..(start + len - 1) end)
maps
|> Enum.reduce(seed_ranges, &process_ranges/2)
|> Enum.map(& &1.first)
|> Enum.min()
end
def process_ranges(_map_ranges, []), do: []
def process_ranges(map_ranges, [item_range | rest]) do
overlap =
Enum.find(map_ranges, fn {source, _offset} -> not Range.disjoint?(source, item_range) end)
case overlap do
{source, offset} ->
to_shift = max(source.first, item_range.first)..min(source.last, item_range.last)
remainders =
[
if(item_range.first < source.first, do: item_range.first..(source.first - 1)),
if(item_range.last > source.last, do: (source.last + 1)..item_range.last)
]
|> Enum.reject(&is_nil/1)
[Range.shift(to_shift, offset) | process_ranges(map_ranges, remainders ++ rest)]
nil ->
[item_range | process_ranges(map_ranges, rest)]
end
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
["seeds: " <> seeds | maps] = String.split(input, "\n\n")
seeds = seeds |> String.split() |> Enum.map(&String.to_integer/1)
maps =
Enum.map(maps, fn line ->
[_header, _map | items] = String.split(line)
items
|> Enum.map(&String.to_integer/1)
|> Enum.chunk_every(3)
|> Enum.map(fn [dest, source, offset] ->
{source..(source + offset), dest - source}
end)
end)
{seeds, maps}
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{inspect(result, charlists: :as_lists)}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day5.exs input_filename")
end
end
Day5.run()

View file

@ -1,69 +0,0 @@
#!/usr/bin/env elixir
defmodule Day6 do
def part1(input) do
input
|> Enum.map(fn line -> Enum.map(line, &String.to_integer/1) end)
|> Enum.zip()
|> Enum.map(fn {race, record} -> count_winning(race, record) end)
|> Enum.product()
end
def count_winning(race, record) do
scores = for n <- 1..div(race, 2), do: n * (race - n)
[_middle | rev_no_middle] = reversed = Enum.reverse(scores)
scores = scores ++ if rem(race, 2) == 1, do: reversed, else: rev_no_middle
Enum.count(scores, fn score -> score > record end)
end
def part2(input) do
[race, record] = Enum.map(input, fn line -> line |> Enum.join() |> String.to_integer() end)
count_winning(race, record)
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
input
|> String.split("\n", trim: true)
|> Enum.map(fn line ->
line
|> String.split()
|> Enum.drop(1)
end)
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{inspect(result)}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day6.exs input_filename")
end
end
Day6.run()

View file

@ -1,123 +0,0 @@
#!/usr/bin/env elixir
defmodule Day7 do
def part1(input) do
input
|> Enum.sort_by(&detect_type/1, &rank/2)
|> Enum.with_index()
|> Enum.map(fn {{_hand, bid}, rank} -> bid * (rank + 1) end)
|> Enum.sum()
end
def detect_type({hand, _bid}) do
frequencies =
hand
|> Enum.frequencies()
|> Enum.group_by(fn {_card, freq} -> freq end)
|> Map.new(fn {freq, cards} -> {freq, length(cards)} end)
type =
cond do
frequencies[5] -> :five_kind
frequencies[4] -> :four_kind
frequencies[3] && frequencies[2] -> :full_house
frequencies[3] -> :three_kind
frequencies[2] == 2 -> :two_pair
frequencies[2] -> :one_pair
true -> :high_card
end
{type, hand}
end
def rank({same_type, hand1}, {same_type, hand2}), do: tiebreak(hand1, hand2)
def rank({:high_card, _}, _), do: true
def rank({:one_pair, _}, {:high_card, _}), do: false
def rank({:one_pair, _}, _), do: true
def rank({:two_pair, _}, {b, _}) when b in [:high_card, :one_pair], do: false
def rank({:two_pair, _}, _), do: true
def rank({:three_kind, _}, {b, _}) when b in [:high_card, :one_pair, :two_pair], do: false
def rank({:three_kind, _}, _), do: true
def rank({:full_house, _}, {b, _}) when b in [:five_kind, :four_kind], do: true
def rank({:full_house, _}, _), do: false
def rank({:four_kind, _}, {:five_kind, _}), do: true
def rank({:four_kind, _}, _), do: false
def rank({:five_kind, _}, _), do: false
def tiebreak([first | resta], [first | restb]), do: tiebreak(resta, restb)
def tiebreak(["1" | _], _), do: true
def tiebreak(["2" | _], ["1" | _]), do: false
def tiebreak(["2" | _], _), do: true
def tiebreak(["3" | _], [b | _]) when b in ["1", "2"], do: false
def tiebreak(["3" | _], _), do: true
def tiebreak(["4" | _], [b | _]) when b in ["1", "2", "3"], do: false
def tiebreak(["4" | _], _), do: true
def tiebreak(["5" | _], [b | _]) when b in ["1", "2", "3", "4"], do: false
def tiebreak(["5" | _], _), do: true
def tiebreak(["6" | _], [b | _]) when b in ["1", "2", "3", "4", "5"], do: false
def tiebreak(["6" | _], _), do: true
def tiebreak(["7" | _], [b | _]) when b in ["1", "2", "3", "4", "5", "6"], do: false
def tiebreak(["7" | _], _), do: true
def tiebreak(["8" | _], [b | _]) when b in ["A", "K", "Q", "J", "T", "9"], do: true
def tiebreak(["8" | _], _), do: false
def tiebreak(["9" | _], [b | _]) when b in ["A", "K", "Q", "J", "T"], do: true
def tiebreak(["9" | _], _), do: false
def tiebreak(["T" | _], [b | _]) when b in ["A", "K", "Q", "J"], do: true
def tiebreak(["T" | _], _), do: false
def tiebreak(["J" | _], [b | _]) when b in ["A", "K", "Q"], do: true
def tiebreak(["J" | _], _), do: false
def tiebreak(["Q" | _], [b | _]) when b in ["A", "K"], do: true
def tiebreak(["Q" | _], _), do: false
def tiebreak(["K" | _], ["A" | _]), do: true
def tiebreak(["K" | _], _), do: false
def tiebreak(["A" | _], _), do: false
def part2(_input) do
:ok
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
input
|> String.split("\n", trim: true)
|> Enum.map(fn line ->
[hands, bid] = String.split(line)
{String.graphemes(hands), String.to_integer(bid)}
end)
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{inspect(result)}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day7.exs input_filename")
end
end
# Day7.run()

View file

@ -1,87 +0,0 @@
#!/usr/bin/env elixir
defmodule Day8 do
def part1({instructions, network}) do
instructions
|> Stream.cycle()
|> Enum.reduce_while({"AAA", 0}, fn
_side, {"ZZZ", count} -> {:halt, count}
side, {node, count} -> {:cont, {elem(network[node], side), count + 1}}
end)
end
def part2({instructions, network}) do
# Following the algorithm from the question naively seems to take too long, so we need to
# find a shortcut.
# It seems that each ghost runs in a cycle, so we can solve the number of steps for each
# ghost's path, and then find the lowest common multiple of those lengths, which will
# be the first time they arrive at the final spaces together.
network
|> Map.keys()
|> Enum.filter(&match?(<<_::binary-2, "A">>, &1))
|> Enum.map(fn start ->
instructions
|> Stream.cycle()
|> Enum.reduce_while({start, 0}, fn
_side, {<<_::binary-2, "Z">>, count} -> {:halt, count}
side, {node, count} -> {:cont, {elem(network[node], side), count + 1}}
end)
end)
|> Enum.reduce(fn a, b -> div(a * b, Integer.gcd(a, b)) end)
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
[instructions, nodes] = String.split(input, "\n\n")
instructions =
instructions
|> String.graphemes()
|> Enum.map(fn
"L" -> 0
"R" -> 1
end)
network =
for <<key::binary-3, " = (", left::binary-3, ", ", right::binary-3, ")\n" <- nodes>>,
into: %{},
do: {key, {left, right}}
{instructions, network}
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{result}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day8.exs input_filename")
end
end
Day8.run()

View file

@ -1,78 +0,0 @@
#!/usr/bin/env elixir
defmodule Day9 do
def part1(histories) do
histories
|> Enum.map(&walk/1)
|> Enum.sum()
end
def walk(history) do
case Enum.uniq(history) do
[last] -> last
_ -> List.last(history) + walk(diff(history))
end
end
def diff([_]), do: []
def diff([a, b | rest]), do: [b - a | diff([b | rest])]
def part2(histories) do
histories
|> Enum.map(&walk2/1)
|> Enum.sum()
end
def walk2(history) do
case Enum.uniq(history) do
[first] -> first
[first | _] -> first - walk2(diff(history))
end
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
input
|> String.split("\n", trim: true)
|> Enum.map(fn line ->
line
|> String.split(" ")
|> Enum.map(&String.to_integer/1)
end)
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{inspect(result)}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day9.exs input_filename")
end
end
Day9.run()

View file

@ -1,63 +0,0 @@
#!/usr/bin/env elixir
defmodule Day1 do
def part1({list1, list2}) do
list1 = Enum.sort(list1)
list2 = Enum.sort(list2)
Enum.zip_with(list1, list2, &abs(&1 - &2)) |> Enum.sum()
end
def part2({list1, list2}) do
frequencies = Enum.frequencies(list2)
Enum.reduce(list1, 0, fn item, count -> count + item * Map.get(frequencies, item, 0) end)
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
integers =
input
|> String.split([" ", "\n"], trim: true)
|> Enum.map(&String.to_integer/1)
list1 = Enum.take_every(integers, 2)
list2 = Enum.take_every(tl(integers), 2)
{list1, list2}
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{inspect(result)}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day1.exs input_filename")
end
end
Day1.run()

View file

@ -1,78 +0,0 @@
#!/usr/bin/env elixir
defmodule Day10 do
def part1({grid, zeros}) do
zeros
|> Enum.map(fn point -> point |> find_trails(grid, 0) |> Enum.uniq() |> Enum.count() end)
|> Enum.sum()
end
def find_trails(point, _grid, 9), do: [point]
def find_trails({x, y}, grid, height) do
find_neighbours(x, y, height + 1, grid)
|> Enum.flat_map(fn {point, _} -> find_trails(point, grid, height + 1) end)
end
def find_neighbours(x, y, height, grid) do
grid
|> Map.take([{x - 1, y}, {x + 1, y}, {x, y - 1}, {x, y + 1}])
|> Enum.filter(&match?({_, ^height}, &1))
end
def part2({grid, zeros}) do
zeros
|> Enum.map(fn point -> point |> find_trails(grid, 0) |> Enum.count() end)
|> Enum.sum()
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
{grid, zeros, _, _} =
for <<char::binary-1 <- input>>, reduce: {%{}, [], 0, 0} do
{grid, zeros, x, y} ->
case char do
"\n" -> {grid, zeros, 0, y + 1}
"0" -> {Map.put(grid, {x, y}, 0), [{x, y} | zeros], x + 1, y}
char -> {Map.put(grid, {x, y}, String.to_integer(char)), zeros, x + 1, y}
end
end
{grid, zeros}
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{inspect(result)}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day10.exs input_filename")
end
end
Day10.run()

View file

@ -1,79 +0,0 @@
#!/usr/bin/env elixir
defmodule Day11 do
def part1(stones) do
blink(stones, 25)
end
def part2(stones) do
blink(stones, 75)
end
def blink(stones, 0), do: stones |> Map.values() |> Enum.sum()
def blink(stones, times) do
stones
|> Enum.reduce(%{}, fn
{0, count}, next ->
Map.update(next, 1, count, &(&1 + count))
{stone, count}, next ->
string = Integer.to_string(stone)
size = byte_size(string)
if rem(size, 2) == 0 do
{a, b} = String.split_at(string, div(size, 2))
next
|> Map.update(String.to_integer(a), count, &(&1 + count))
|> Map.update(String.to_integer(b), count, &(&1 + count))
else
Map.update(next, stone * 2024, count, &(&1 + count))
end
end)
|> blink(times - 1)
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
input
|> String.split([" ", "\n"], trim: true)
|> Enum.map(&String.to_integer/1)
|> Enum.frequencies()
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{inspect(result)}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day11.exs input_filename")
end
end
Day11.run()

View file

@ -1,176 +0,0 @@
#!/usr/bin/env elixir
defmodule Day12 do
defmodule Region do
defstruct label: nil, plots: MapSet.new(), perimeter: 0
end
def part1({grid, size_x, size_y}) do
find_regions(grid, size_x, size_y)
|> Enum.map(fn region -> MapSet.size(region.plots) * region.perimeter end)
|> Enum.sum()
end
def find_regions(grid, size_x, size_y) do
for x <- 0..(size_x - 1), y <- 0..(size_y - 1), reduce: {[], MapSet.new()} do
{regions, seen} ->
case find_region(x, y, grid, seen) do
{:already_found, _seen} -> {regions, seen}
{region, seen} -> {[region | regions], seen}
end
end
|> elem(0)
end
def find_region(x, y, grid, seen, region \\ nil) do
if MapSet.member?(seen, {x, y}) do
{region || :already_found, seen}
else
region = region || %Region{label: Map.fetch!(grid, {x, y})}
seen = MapSet.put(seen, {x, y})
region_neighbours = get_region_neighbours(x, y, region.label, grid)
region = %Region{
region
| plots: MapSet.put(region.plots, {x, y}),
perimeter: region.perimeter + (4 - length(region_neighbours))
}
Enum.reduce(region_neighbours, {region, seen}, fn {x, y}, {region, seen} ->
find_region(x, y, grid, seen, region)
end)
end
end
def get_region_neighbours(x, y, label, grid) do
grid
|> Map.take([{x - 1, y}, {x + 1, y}, {x, y - 1}, {x, y + 1}])
|> Map.filter(&match?({_coord, ^label}, &1))
|> Map.keys()
end
def part2({grid, size_x, size_y}) do
find_regions(grid, size_x, size_y)
|> Enum.map(fn region -> MapSet.size(region.plots) * count_sides(region) end)
|> Enum.sum()
end
def count_sides(region) do
{min_x, _y} = Enum.min_by(region.plots, fn {x, _y} -> x end)
{_x, min_y} = Enum.min_by(region.plots, fn {_x, y} -> y end)
{max_x, _y} = Enum.max_by(region.plots, fn {x, _y} -> x end)
{_x, max_y} = Enum.max_by(region.plots, fn {_x, y} -> y end)
left =
Enum.reduce(min_x..max_x, 0, fn x, sides ->
Enum.reduce(min_y..max_y, {sides, false}, fn y, {sides, sequence?} ->
plot? = MapSet.member?(region.plots, {x, y})
open? = MapSet.member?(region.plots, {x - 1, y})
cond do
plot? and not open? and not sequence? -> {sides + 1, true}
plot? and not open? and sequence? -> {sides, true}
true -> {sides, false}
end
end)
|> elem(0)
end)
right =
Enum.reduce(min_x..max_x, 0, fn x, sides ->
Enum.reduce(min_y..max_y, {sides, false}, fn y, {sides, sequence?} ->
plot? = MapSet.member?(region.plots, {x, y})
open? = MapSet.member?(region.plots, {x + 1, y})
cond do
plot? and not open? and not sequence? -> {sides + 1, true}
plot? and not open? and sequence? -> {sides, true}
true -> {sides, false}
end
end)
|> elem(0)
end)
top =
Enum.reduce(min_y..max_y, 0, fn y, sides ->
Enum.reduce(min_x..max_x, {sides, false}, fn x, {sides, sequence?} ->
plot? = MapSet.member?(region.plots, {x, y})
open? = MapSet.member?(region.plots, {x, y - 1})
cond do
plot? and not open? and not sequence? -> {sides + 1, true}
plot? and not open? and sequence? -> {sides, true}
true -> {sides, false}
end
end)
|> elem(0)
end)
bottom =
Enum.reduce(min_y..max_y, 0, fn y, sides ->
Enum.reduce(min_x..max_x, {sides, false}, fn x, {sides, sequence?} ->
plot? = MapSet.member?(region.plots, {x, y})
open? = MapSet.member?(region.plots, {x, y + 1})
cond do
plot? and not open? and not sequence? -> {sides + 1, true}
plot? and not open? and sequence? -> {sides, true}
true -> {sides, false}
end
end)
|> elem(0)
end)
left + right + top + bottom
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
{grid, _, x, y} =
for <<char::binary-1 <- input>>, reduce: {%{}, 0, 0, 0} do
{grid, x, max_x, y} ->
case char do
"\n" -> {grid, 0, x, y + 1}
char -> {Map.put(grid, {x, y}, char), x + 1, max_x, y}
end
end
{grid, x, y}
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{inspect(result)}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir dayDay12.exs input_filename")
end
end
Day12.run()

View file

@ -1,81 +0,0 @@
#!/usr/bin/env elixir
defmodule Day13 do
defmodule Machine do
defstruct [:a, :b, :prize]
end
def part1(input) do
input
# these are equivalent linear equations
# know how to do it algebraically, but not algorithmically
# e.g. for first example
#
# 94a + 22b - 8400 = 34a + 67b - 5400
# 94a + 22b - 3000 = 34a + 67b
# 60a + 22b - 3000 = 67b
# 60a - 3000 = 45b
# 4a - 200 = 3b
# a = 3/4b + 50
# 34(3/4b + 50) + 67b - 5400 = 0
# 25.5b + 1700 + 67b - 5400 = 0
# 92.5b = 3700
# b = 40
# 34a + 67(40) = 5400
# 34a + 2680 = 5400
# 34a = 2720
# a = 80
end
def part2(_input) do
:ok
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
input
|> String.split("\n\n")
|> Enum.map(fn machine ->
machine
|> String.split(["+", ",", "=", "\n"], trim: true)
|> tl()
|> Enum.take_every(2)
|> Enum.map(&String.to_integer/1)
end)
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{inspect(result)}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day13.exs input_filename")
end
end
# Day13.run()

View file

@ -1,144 +0,0 @@
#!/usr/bin/env elixir
defmodule Day14 do
def part1({robots, size_x, size_y}) do
mid_x = div(size_x, 2)
mid_y = div(size_y, 2)
robots
|> Enum.reduce({0, 0, 0, 0}, fn [px, py, vx, vy], {a, b, c, d} ->
x = Integer.mod(px + vx * 100, size_x)
y = Integer.mod(py + vy * 100, size_y)
case {x, y} do
{x, y} when x < mid_x and y < mid_y -> {a + 1, b, c, d}
{x, y} when x < mid_x and y > mid_y -> {a, b + 1, c, d}
{x, y} when x > mid_x and y < mid_y -> {a, b, c + 1, d}
{x, y} when x > mid_x and y > mid_y -> {a, b, c, d + 1}
{x, y} when x == mid_x or y == mid_y -> {a, b, c, d}
end
end)
|> Tuple.product()
end
def part2({robots, size_x, size_y}) do
# The tree looks like this
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# X X
# X X
# X X
# X X
# X X X
# X XXX X
# X XXXXX X
# X XXXXXXX X
# X XXXXXXXXX X
# X XXXXX X
# X XXXXXXX X
# X XXXXXXXXX X
# X XXXXXXXXXXX X
# X XXXXXXXXXXXXX X
# X XXXXXXXXX X
# X XXXXXXXXXXX X
# X XXXXXXXXXXXXX X
# X XXXXXXXXXXXXXXX X
# X XXXXXXXXXXXXXXXXX X
# X XXXXXXXXXXXXX X
# X XXXXXXXXXXXXXXX X
# X XXXXXXXXXXXXXXXXX X
# X XXXXXXXXXXXXXXXXXXX X
# X XXXXXXXXXXXXXXXXXXXXX X
# X XXX X
# X XXX X
# X XXX X
# X X
# X X
# X X
# X X
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# Could improve this by searching for that pattern explicitly. In this case I just searched
# manually: noticed things looked suspicious after 27 seconds
# also noticed that repeated every 101 seconds
# Checked each 101 second jump until found the above tree
find_tree(robots, size_x, size_y)
end
def find_tree(robots, size_x, size_y, count \\ 27, acc \\ 27) do
robots = tick(robots, size_x, size_y, count)
robomap = MapSet.new(robots, fn [x, y, _, _] -> {x, y} end)
for y <- 0..(size_y - 1) do
for x <- 0..(size_x - 1) do
if MapSet.member?(robomap, {x, y}) do
IO.write("X")
else
IO.write(" ")
end
end
IO.puts("|")
end
line = IO.gets("#{acc} tree [y/N]?")
if line |> String.trim() |> String.downcase() == "y" do
acc
else
find_tree(robots, size_x, size_y, 101, acc + 101)
end
end
def tick(robots, size_x, size_y, times) do
Enum.map(robots, fn [px, py, vx, vy] ->
[Integer.mod(px + vx * times, size_x), Integer.mod(py + vy * times, size_y), vx, vy]
end)
end
def input do
with [input_filename, size_x, size_y] <- System.argv(),
{:ok, input} <- File.read(input_filename),
{size_x, ""} <- Integer.parse(size_x),
{size_y, ""} <- Integer.parse(size_y) do
robots =
input
|> String.split(["p=", ",", " ", "v=", "\n"], trim: true)
|> Enum.map(&String.to_integer/1)
|> Enum.chunk_every(4)
{robots, size_x, size_y}
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{inspect(result)}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day14.exs input_filename size_x size_y")
end
end
Day14.run()

View file

@ -1,244 +0,0 @@
#!/usr/bin/env elixir
defmodule Day15 do
def part1({warehouse, robot, moves}) do
attempt_moves(moves, robot, warehouse)
|> Enum.filter(&match?({_, :box}, &1))
|> Enum.map(fn {{x, y}, :box} -> x + y * 100 end)
|> Enum.sum()
end
def attempt_moves([], _robot, warehouse), do: warehouse
def attempt_moves([move | next_moves], robot, warehouse) do
next = move.(robot)
case Map.fetch(warehouse, next) do
:error ->
attempt_moves(next_moves, next, warehouse)
{:ok, :wall} ->
attempt_moves(next_moves, robot, warehouse)
{:ok, :box} ->
case get_pushable_boxes(next, move, warehouse) do
:wall ->
attempt_moves(next_moves, robot, warehouse)
boxes ->
warehouse =
Enum.reduce(boxes, warehouse, fn box, warehouse ->
warehouse |> Map.delete(box) |> Map.put(move.(box), :box)
end)
attempt_moves(next_moves, next, warehouse)
end
end
end
def get_pushable_boxes(box, move, warehouse, pushable_boxes \\ []) do
next = move.(box)
pushable_boxes = [box | pushable_boxes]
case Map.fetch(warehouse, next) do
:error -> pushable_boxes
{:ok, :wall} -> :wall
{:ok, :box} -> get_pushable_boxes(next, move, warehouse, pushable_boxes)
end
end
def part2({warehouse, {x, y}, moves}) do
bigger_warehouse =
Enum.reduce(warehouse, %{}, fn {{x, y}, v}, bigger ->
{left, right} =
case v do
:box -> {{:box, :left}, {:box, :right}}
:wall -> {:wall, :wall}
end
bigger |> Map.put({x * 2, y}, left) |> Map.put({x * 2 + 1, y}, right)
end)
robot = {x * 2, y}
attempt_moves2(moves, robot, bigger_warehouse)
|> Enum.filter(&match?({_, {:box, :left}}, &1))
|> Enum.map(fn {{x, y}, {:box, :left}} -> x + y * 100 end)
|> Enum.sum()
end
def attempt_moves2([], _robot, warehouse), do: warehouse
def attempt_moves2([move | next_moves], robot, warehouse) do
next = move.(robot)
case Map.fetch(warehouse, next) do
:error ->
attempt_moves2(next_moves, next, warehouse)
{:ok, :wall} ->
attempt_moves2(next_moves, robot, warehouse)
{:ok, {:box, side}} ->
case get_pushable_boxes2(next, side, move, warehouse) do
:wall ->
attempt_moves2(next_moves, robot, warehouse)
boxes ->
new_boxes = Map.new(boxes, fn {box, side} -> {move.(box), {:box, side}} end)
warehouse =
warehouse
|> Map.drop(Enum.map(boxes, &elem(&1, 0)))
|> Map.merge(new_boxes)
attempt_moves2(next_moves, next, warehouse)
end
end
end
# need to account for 2-width boxes when pushing vertically
# @<--pushing down moves down all RHS of stack
# [][][][]
# [][][] @
# [][] []<--make sure RHS moves too
# []
# @<- pushing up pushes all
def get_pushable_boxes2(box, side, move, warehouse, pushable_boxes \\ []) do
next = move.(box)
pushable_boxes = [{box, side} | pushable_boxes]
case abs(elem(next, 1) - elem(box, 1)) do
# horizontal
0 ->
case Map.fetch(warehouse, next) do
:error ->
pushable_boxes
{:ok, :wall} ->
:wall
{:ok, {:box, next_side}} ->
get_pushable_boxes2(next, next_side, move, warehouse, pushable_boxes)
end
# vertical
1 ->
{x, y} = box
{other_box, other_side} =
case side do
:left -> {{x + 1, y}, :right}
:right -> {{x - 1, y}, :left}
end
other_next = move.(other_box)
pushable_boxes = [{other_box, other_side} | pushable_boxes]
side
|> case do
:left ->
{{Map.fetch(warehouse, next), Map.fetch(warehouse, other_next)}, next, other_next}
:right ->
{{Map.fetch(warehouse, other_next), Map.fetch(warehouse, next)}, other_next, next}
end
|> case do
{{:error, :error}, _, _} ->
pushable_boxes
{{{:ok, :wall}, _}, _, _} ->
:wall
{{_, {:ok, :wall}}, _, _} ->
:wall
{{{:ok, {:box, side}}, :error}, left, _right} ->
get_pushable_boxes2(left, side, move, warehouse, pushable_boxes)
{{:error, {:ok, {:box, side}}}, _left, right} ->
get_pushable_boxes2(right, side, move, warehouse, pushable_boxes)
{{{:ok, {:box, :left}}, {:ok, {:box, :right}}}, left, _} ->
# if left-right, just one box, only need to check one side
# []
# []
get_pushable_boxes2(left, :left, move, warehouse, pushable_boxes)
{{{:ok, {:box, :right}}, {:ok, {:box, :left}}}, left, right} ->
# if right-left, two boxes, so need to check both...
# [][]
# []
with left when left != :wall <- get_pushable_boxes2(left, :right, move, warehouse, []),
right when right != :wall <- get_pushable_boxes2(right, :left, move, warehouse, []) do
pushable_boxes ++ left ++ right
end
end
end
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
[raw_map, raw_moves] = String.split(input, "\n\n")
{_, _y, robot, map} =
for <<char::binary-1 <- raw_map <> "\n">>, reduce: {0, 0, nil, %{}} do
{x, y, robot, map} ->
case char do
"." -> {x + 1, y, robot, map}
"#" -> {x + 1, y, robot, Map.put(map, {x, y}, :wall)}
"O" -> {x + 1, y, robot, Map.put(map, {x, y}, :box)}
"@" -> {x + 1, y, {x, y}, map}
"\n" -> {0, y + 1, robot, map}
end
end
moves =
for <<char::binary-1 <- String.replace(raw_moves, "\n", "")>> do
case char do
"^" -> fn {x, y} -> {x, y - 1} end
"v" -> fn {x, y} -> {x, y + 1} end
"<" -> fn {x, y} -> {x - 1, y} end
">" -> fn {x, y} -> {x + 1, y} end
end
end
{map, robot, moves}
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{inspect(result)}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day15.exs input_filename")
end
end
Day15.run()

View file

@ -1,145 +0,0 @@
#!/usr/bin/env elixir
defmodule Day17 do
defmodule Computer do
defstruct a: 0, b: 0, c: 0, output: [], program: []
end
def part1(computer) do
process(computer.program, computer)
|> Map.fetch!(:output)
|> Enum.reverse()
|> Enum.join(",")
end
def process([], computer), do: computer
# adv div(A, 2^ combo operand) -> A
def process([0, operand | program], computer) do
value = div(computer.a, 2 ** combo(operand, computer))
computer = %Computer{computer | a: value}
process(program, computer)
end
# bxl B XOR literal operand -> B
def process([1, operand | program], computer) do
value = Bitwise.bxor(computer.b, operand)
computer = %Computer{computer | b: value}
process(program, computer)
end
# bst combo operand mod 8 -> B (only 3 lowest bits)
def process([2, operand | program], computer) do
value = rem(combo(operand, computer), 8)
computer = %Computer{computer | b: value}
process(program, computer)
end
# jnz noting if A is 0, otherwise set instruction to literal operand - don't inc by 2 if jump
def process([3, operand | program], computer) do
if computer.a == 0 do
process(program, computer)
else
program = Enum.drop(computer.program, operand)
process(program, computer)
end
end
# bxc B XOR C -> B (ignore operand)
def process([4, _operand | program], computer) do
value = Bitwise.bxor(computer.b, computer.c)
computer = %Computer{computer | b: value}
process(program, computer)
end
# out combo operand mod 8 + output (CSV)
def process([5, operand | program], computer) do
value = rem(combo(operand, computer), 8)
computer = %Computer{computer | output: [value | computer.output]}
process(program, computer)
end
# bdv same as adv but store in B
def process([6, operand | program], computer) do
value = div(computer.a, 2 ** combo(operand, computer))
computer = %Computer{computer | b: value}
process(program, computer)
end
# cdv same as adv but store in C
def process([7, operand | program], computer) do
value = div(computer.a, 2 ** combo(operand, computer))
computer = %Computer{computer | c: value}
process(program, computer)
end
def combo(literal, _computer) when literal in [0, 1, 2, 3], do: literal
def combo(4, computer), do: computer.a
def combo(5, computer), do: computer.b
def combo(6, computer), do: computer.c
def part2(computer) do
# TODO runs forever. Probably need to detect loops?
search_for_self(computer)
end
def search_for_self(computer, a \\ 0) do
computer = %Computer{computer | a: a}
output = process(computer.program, computer).output |> Enum.reverse()
if output == computer.program do
a
else
search_for_self(computer, a + 1)
end
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
"Register A: " <> input = input
{a, input} = Integer.parse(input)
"\nRegister B: " <> input = input
{b, input} = Integer.parse(input)
"\nRegister C: " <> input = input
{c, input} = Integer.parse(input)
"\n\nProgram: " <> input = input
program = input |> String.split([",", "\n"], trim: true) |> Enum.map(&String.to_integer/1)
%Computer{a: a, b: b, c: c, program: program}
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{inspect(result)}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day17.exs input_filename")
end
end
Day17.run()

View file

@ -1,71 +0,0 @@
#!/usr/bin/env elixir
defmodule Day2 do
def part1(reports) do
Enum.count(reports, &check_report/1)
end
def check_report([a, b | _]) when abs(b - a) not in [1, 2, 3], do: false
def check_report([a, b | rest]), do: check_report([b | rest], b - a)
def check_report([_], _dir), do: true
def check_report([a, b | rest], dir) do
new_dir = b - a
if ((dir < 0 and new_dir < 0) or (dir > 0 and new_dir > 0)) and abs(new_dir) <= 3 do
check_report([b | rest], new_dir)
else
false
end
end
def part2(reports) do
Enum.count(reports, fn report ->
Enum.any?(0..(length(report) - 1), fn dampen ->
report |> List.delete_at(dampen) |> check_report()
end)
end)
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
input
|> String.split("\n", trim: true)
|> Enum.map(fn line -> line |> String.split() |> Enum.map(&String.to_integer/1) end)
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{inspect(result)}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day2.exs input_filename")
end
end
Day2.run()

View file

@ -1,133 +0,0 @@
#!/usr/bin/env elixir
defmodule Day21 do
@numpad %{
"7" => {0, 0},
"8" => {1, 0},
"9" => {2, 0},
"4" => {0, 1},
"5" => {1, 1},
"6" => {2, 1},
"1" => {0, 2},
"2" => {1, 2},
"3" => {2, 2},
"0" => {1, 3},
"A" => {2, 3}
}
@keypad %{
"^" => {1, 0},
"A" => {2, 0},
"<" => {0, 1},
"v" => {1, 1},
">" => {2, 1}
}
def part1(input) do
Enum.map(input, &complexity/1)
end
def complexity(code) do
length =
code
|> derive_keys(@numpad)
# asdfsadfsdafdfs for the robot, some derived combinations are shorter, why?
# Maybe going left non-sequentially is expensive because of round trips to the A buttom
# so sequentially is cheaper?
# Need to figure out what the situation that causes a longer sequence is
|> derive_keys(@keypad)
|> derive_keys(@keypad)
|> byte_size()
|> dbg()
{numeric, "A"} = Integer.parse(code)
dbg(numeric)
numeric * length
end
def derive_keys(seq, pad) do
String.graphemes("A" <> seq)
|> Enum.chunk_every(2, 1, :discard)
|> Enum.map_join("", fn [a, b] -> keys(a, b, pad) <> "A" end)
end
def numpad, do: @numpad
def keys(a, b, pad) do
{x1, y1} = Map.fetch!(pad, a)
{x2, y2} = Map.fetch!(pad, b)
dx = x2 - x1
dy = y2 - y1
keys_x = keys_x(dx)
keys_y = keys_y(dy)
# avoid the gap
if dx < 0 do
keys_y <> keys_x
else
keys_x <> keys_y
end
end
def keys_x(-2), do: "<<"
def keys_x(-1), do: "<"
def keys_x(0), do: ""
def keys_x(1), do: ">"
def keys_x(2), do: ">>"
def keys_y(-3), do: "^^^"
def keys_y(-2), do: "^^"
def keys_y(-1), do: "^"
def keys_y(0), do: ""
def keys_y(1), do: "v"
def keys_y(2), do: "vv"
def keys_y(3), do: "vvv"
def keypad, do: @keypad
def part2(_input) do
:ok
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
String.split(input, "\n", trim: true)
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{inspect(result)}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day21.exs input_filename")
end
end
# Day21.run()

View file

@ -1,76 +0,0 @@
#!/usr/bin/env elixir
defmodule Day22 do
import Bitwise
def part1(input) do
Enum.sum_by(input, fn secret ->
Enum.reduce(1..2000, secret, fn _, secret -> next(secret) end)
end)
end
def next(secret) do
secret
|> then(fn secret -> bxor(secret <<< 6, secret) &&& 16_777_21 end)
|> then(fn secret -> bxor(secret >>> 5, secret) &&& 16_777_21 end)
|> then(fn secret -> bxor(secret <<< 11, secret) &&& 16_777_21 end)
end
def part2(input) do
input
|> Enum.map(fn secret ->
1..2000
|> Enum.map_reduce(secret, fn _, secret -> {rem(secret, 10), next(secret)} end)
|> elem(0)
|> Enum.chunk_every(5, 1, :discard)
|> Enum.reduce(%{}, fn [a, b, c, d, bananas], changes ->
Map.put_new(changes, {b - a, c - b, d - c, bananas - d}, bananas)
end)
end)
|> Enum.reduce(&Map.merge(&1, &2, fn _k, v1, v2 -> v1 + v2 end))
|> Enum.max_by(fn {_sequence, bananas} -> bananas end)
|> elem(1)
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
input
|> String.split("\n", trim: true)
|> Enum.map(&String.to_integer/1)
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{inspect(result)}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day22.exs input_filename")
end
end
Day22.run()

View file

@ -1,60 +0,0 @@
#!/usr/bin/env elixir
defmodule Day3 do
def part1(input) do
Regex.scan(~r/mul\((\d+),(\d+)\)/, input, capture: :all_but_first)
|> Enum.map(fn [a, b] -> String.to_integer(a) * String.to_integer(b) end)
|> Enum.sum()
end
def part2(input) do
Regex.scan(~r/mul\((\d+),(\d+)\)|do\(\)|don't\(\)/, input)
|> Enum.reduce({0, :process}, fn
["do()"], {sum, _} -> {sum, :process}
["don't()"], {sum, _} -> {sum, :skip}
_, {sum, :skip} -> {sum, :skip}
[_, a, b], {sum, :process} -> {sum + String.to_integer(a) * String.to_integer(b), :process}
end)
|> elem(0)
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
input
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{inspect(result)}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day3.exs input_filename")
end
end
Day3.run()

View file

@ -1,125 +0,0 @@
#!/usr/bin/env elixir
defmodule Day4 do
def part1(grid) do
n = :array.size(grid)
horizontal =
for y <- 0..(n - 1), x <- 0..(n - 4), reduce: 0 do
count ->
for x_offset <- 0..(4 - 1), into: "" do
:array.get(x + x_offset, :array.get(y, grid))
end
|> consider(count)
end
vertical =
for y <- 0..(n - 4), x <- 0..(n - 1), reduce: 0 do
count ->
for y_offset <- 0..(4 - 1), into: "" do
:array.get(x, :array.get(y + y_offset, grid))
end
|> consider(count)
end
diagonal1 =
for y <- 0..(n - 4), x <- 0..(n - 4), reduce: 0 do
count ->
for offset <- 0..(4 - 1), into: "" do
:array.get(x + offset, :array.get(y + offset, grid))
end
|> consider(count)
end
diagonal2 =
for y <- 0..(n - 4), x <- (n - 1)..(4 - 1), reduce: 0 do
count ->
for offset <- 0..(4 - 1), into: "" do
:array.get(x - offset, :array.get(y + offset, grid))
end
|> consider(count)
end
horizontal + vertical + diagonal1 + diagonal2
end
def consider("XMAS", count), do: count + 1
def consider("SAMX", count), do: count + 1
def consider(_, count), do: count
def part2(grid) do
n = :array.size(grid)
for y <- 0..(n - 3), x <- 0..(n - 3), reduce: 0 do
count ->
diagonal1 =
for offset <- 0..2, into: "" do
:array.get(x + offset, :array.get(y + offset, grid))
end
diagonal2 =
for offset <- 0..2, into: "" do
:array.get(x + 2 - offset, :array.get(y + offset, grid))
end
if diagonal1 in ["MAS", "SAM"] and diagonal2 in ["MAS", "SAM"] do
count + 1
else
count
end
end
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
for <<char::binary-1 <- input>>, reduce: {:array.new(), :array.new(), 0, 0} do
{rows, cols, x, y} ->
case char do
"\n" ->
rows = :array.set(y, cols, rows)
cols = :array.new()
{rows, cols, 0, y + 1}
char ->
cols = :array.set(x, char, cols)
{rows, cols, x + 1, y}
end
end
|> elem(0)
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{inspect(result)}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day4.exs input_filename")
end
end
Day4.run()

View file

@ -1,78 +0,0 @@
#!/usr/bin/env elixir
defmodule Day5 do
def part1({updates, _greater?, sorted?}) do
updates
|> Enum.filter(sorted?)
|> Enum.map(&Enum.at(&1, div(length(&1), 2)))
|> Enum.sum()
end
def part2({updates, greater?, sorted?}) do
updates
|> Enum.reject(sorted?)
|> Enum.map(&(Enum.sort(&1, greater?) |> Enum.at(div(length(&1), 2))))
|> Enum.sum()
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
[rules_raw, updates_raw] = String.split(input, "\n\n")
less_than =
String.split(rules_raw, ["|", "\n"])
|> Enum.map(&String.to_integer/1)
|> Enum.chunk_every(2)
|> Enum.group_by(fn [a, _] -> a end, fn [_, b] -> b end)
greater? = fn greater, lesser -> lesser in Map.get(less_than, greater, []) end
sorted? = fn report ->
report
|> Enum.chunk_every(2, 1, :discard)
|> Enum.all?(fn [greater, lesser] -> greater?.(greater, lesser) end)
end
updates =
updates_raw
|> String.split("\n", trim: true)
|> Enum.map(fn row -> String.split(row, ",") |> Enum.map(&String.to_integer/1) end)
{updates, greater?, sorted?}
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{inspect(result)}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day5.exs input_filename")
end
end
Day5.run()

View file

@ -1,116 +0,0 @@
#!/usr/bin/env elixir
defmodule Patrol do
defstruct obstacles: MapSet.new(),
visited: MapSet.new(),
path: MapSet.new(),
x: 0,
y: 0,
max_x: nil,
max_y: nil,
dir_x: 0,
dir_y: -1
end
defmodule Day6 do
def part1(patrol), do: MapSet.size(patrol(patrol).visited)
def patrol(%Patrol{} = patrol) when patrol.x in [-1, patrol.max_x] or patrol.y in [-1, patrol.max_y] do
patrol
end
def patrol(%Patrol{} = patrol) do
next_x = patrol.x + patrol.dir_x
next_y = patrol.y + patrol.dir_y
patrol =
if MapSet.member?(patrol.obstacles, {next_x, next_y}) do
{dir_x, dir_y} = turn_right(patrol.dir_x, patrol.dir_y)
%Patrol{patrol | dir_x: dir_x, dir_y: dir_y}
else
visited = MapSet.put(patrol.visited, {patrol.x, patrol.y})
%Patrol{patrol | x: next_x, y: next_y, visited: visited}
end
next_path = {patrol.x, patrol.y, patrol.dir_x, patrol.dir_y}
if MapSet.member?(patrol.path, next_path) do
:loop
else
patrol = %Patrol{patrol | path: MapSet.put(patrol.path, next_path)}
patrol(patrol)
end
end
def turn_right(0, -1), do: {1, 0}
def turn_right(1, 0), do: {0, 1}
def turn_right(0, 1), do: {-1, 0}
def turn_right(-1, 0), do: {0, -1}
def part2(patrol) do
completed_patrol = patrol(patrol)
candidate_obstructions =
Enum.reduce(completed_patrol.visited, MapSet.new(), fn {x, y}, candidate_obstructions ->
Enum.into([{x - 1, y}, {x + 1, y}, {x, y - 1}, {x, y + 1}], candidate_obstructions)
end)
Enum.count(candidate_obstructions, fn {x, y} ->
new_obstacles = MapSet.put(patrol.obstacles, {x, y})
new_patrol = %Patrol{patrol | obstacles: new_obstacles}
patrol(new_patrol) == :loop
end)
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
{_x, max_x, max_y, {x, y}, obstacles} =
for <<char::binary-1 <- input>>, reduce: {0, 0, 0, nil, MapSet.new()} do
{x, max_x, y, guard, obstacles} ->
case char do
"." -> {x + 1, max_x, y, guard, obstacles}
"#" -> {x + 1, max_x, y, guard, MapSet.put(obstacles, {x, y})}
"^" -> {x + 1, max_x, y, {x, y}, obstacles}
"\n" -> {0, x, y + 1, guard, obstacles}
end
end
%Patrol{x: x, y: y, max_x: max_x, max_y: max_y, obstacles: obstacles}
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{inspect(result)}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day6.exs input_filename")
end
end
Day6.run()

View file

@ -1,77 +0,0 @@
#!/usr/bin/env elixir
defmodule Day7 do
def part1(input) do
Enum.sum_by(input, fn {answer, values} ->
if solvable?(answer, values), do: answer, else: 0
end)
end
def solvable?(answer, [result]), do: answer == result
def solvable?(answer, [a, b | rest]) do
solvable?(answer, [a * b | rest]) or solvable?(answer, [a + b | rest])
end
def part2(input) do
Enum.sum_by(input, fn {answer, values} ->
if solvable2?(answer, values), do: answer, else: 0
end)
end
def solvable2?(answer, [result]), do: answer == result
def solvable2?(answer, [a, b | rest]) do
solvable2?(answer, [a * b | rest]) or solvable2?(answer, [a + b | rest]) or
solvable2?(answer, [String.to_integer("#{a}#{b}") | rest])
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
input
|> String.split("\n", trim: true)
|> Enum.map(fn line ->
[result | values] =
line
|> String.split([":", " "], trim: true)
|> Enum.map(&String.to_integer/1)
{result, values}
end)
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{inspect(result)}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day7.exs input_filename")
end
end
Day7.run()

View file

@ -1,107 +0,0 @@
#!/usr/bin/env elixir
defmodule Day8 do
def part1({x, y, frequencies}) do
Enum.reduce(frequencies, MapSet.new(), fn antennas, antinodes ->
antennas
|> find_pairs()
|> Enum.flat_map(fn pair -> find_antinodes(pair, x, y) end)
|> Enum.into(antinodes)
end)
|> MapSet.size()
end
def find_pairs([_]), do: []
def find_pairs([a | rest]), do: for(b <- rest, do: {a, b}) ++ find_pairs(rest)
def find_antinodes({{x_a, y_a}, {x_b, y_b}}, max_x, max_y) do
dx = x_a - x_b
dy = y_a - y_b
antinode_1 = {x_a + dx, y_a + dy}
antinode_2 = {x_b - dx, y_b - dy}
Enum.filter([antinode_1, antinode_2], fn
{x, y} -> 0 <= x and x < max_x and 0 <= y and y < max_y
end)
end
def part2({x, y, frequencies}) do
Enum.reduce(frequencies, MapSet.new(), fn antennas, antinodes ->
antennas
|> find_pairs()
|> Enum.flat_map(fn pair -> find_resonant_harmonics(pair, x, y) end)
|> Enum.into(antinodes)
end)
|> MapSet.size()
end
def find_resonant_harmonics({{x_a, y_a}, {x_b, y_b}}, max_x, max_y) do
dx = x_a - x_b
dy = y_a - y_b
[{x_a, y_a}, {x_b, y_b}] ++
resonate(x_a, y_a, dx, dy, max_x, max_y) ++
resonate(x_b, y_b, dx * -1, dy * -1, max_x, max_y)
end
def resonate(x, y, dx, dy, max_x, max_y, multiplier \\ 1) do
next_x = x + dx * multiplier
next_y = y + dy * multiplier
if next_x < 0 or next_x >= max_x or next_y < 0 or next_y >= max_y do
[]
else
[{next_x, next_y} | resonate(x, y, dx, dy, max_x, max_y, multiplier + 1)]
end
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
{_, x, y, antennas} =
for <<char::binary-1 <- input>>, reduce: {0, 0, 0, []} do
{x, max_x, y, antennas} ->
case char do
"." -> {x + 1, max_x, y, antennas}
"\n" -> {0, x, y + 1, antennas}
freq -> {x + 1, max_x, y, [{freq, {x, y}} | antennas]}
end
end
{x, y, antennas |> Enum.group_by(&elem(&1, 0), &elem(&1, 1)) |> Map.values()}
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{inspect(result)}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day8.exs input_filename")
end
end
Day8.run()

View file

@ -1,116 +0,0 @@
#!/usr/bin/env elixir
defmodule Day9 do
def part1(input) do
{size, _, spaces, _, disk, files} =
Enum.reduce(input, {0, 0, 0, true, %{}, []}, fn len,
{idx, id, spaces, file?, disk, files} ->
if file? do
disk = Enum.into(idx..(idx + len - 1), disk, fn i -> {i, id} end)
files = if file?, do: for(_ <- 1..len, do: id) ++ files, else: files
{idx + len, id + 1, spaces, not file?, disk, files}
else
{idx + len, id, spaces + len, not file?, disk, files}
end
end)
for i <- 0..(size - spaces - 1), reduce: {0, files} do
{checksum, files} ->
case Map.fetch(disk, i) do
{:ok, block} ->
{checksum + block * i, files}
:error ->
[block | files] = files
{checksum + block * i, files}
end
end
|> elem(0)
end
# Super inefficient, but this is the 2nd attempt after trying to be more efficient
# worked on the sample but not the actual input
def part2(input) do
{_, _, _, disk, files} =
Enum.reduce(input, {0, 0, true, %{}, []}, fn len, {idx, id, file?, disk, files} ->
if file? do
disk = Map.put(disk, idx, {:file, id, len})
files = [{idx, id, len} | files]
{idx + len, id + 1, not file?, disk, files}
else
disk = Map.put(disk, idx, {:space, len})
{idx + len, id, not file?, disk, files}
end
end)
Enum.reduce(files, disk, fn {idx, id, len}, disk ->
# This scans the disk every time. Could possibly optimise by storing the known
# eligible spaces separately
case Enum.find(0..(idx - 1), &match?({:space, gap} when gap >= len, disk[&1])) do
nil ->
disk
space_idx ->
{:space, spaces} = Map.fetch!(disk, space_idx)
disk = disk |> Map.delete(idx) |> Map.put(space_idx, {:file, id, len})
remainder_spaces = spaces - len
if remainder_spaces > 0 do
Map.put(disk, space_idx + len, {:space, remainder_spaces})
else
disk
end
end
end)
|> Enum.reduce(0, fn
{_idx, {:space, _}}, checksum ->
checksum
{idx, {:file, id, len}}, checksum ->
for idx <- idx..(idx + len - 1), reduce: checksum do
checksum -> checksum + idx * id
end
end)
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
for <<c::binary-1 <- String.trim_trailing(input, "\n")>>, do: String.to_integer(c)
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{inspect(result)}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir day9.exs input_filename")
end
end
Day9.run()

View file

@ -1,3 +1,7 @@
# AdventOfCode
My (attempted) solutions to [Advent of Code](https://adventofcode.com/).
- [2021: Zig](https://github.com/adamu/AdventOfCode/tree/main/2021)
- [2020: Elixir](https://github.com/adamu/AdventOfCode/tree/main/2020)
- [2018: Elixir](https://github.com/adamu/AdventOfCode/tree/main/2018)

View file

@ -1,51 +0,0 @@
#!/usr/bin/env elixir
defmodule DayREPLACE_ME do
def part1(input) do
input
end
def part2(_input) do
:not_implemented
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
input
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{inspect(result)}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir dayREPLACE_ME.exs input_filename")
end
end
# DayREPLACE_ME.run()

25
template/README Normal file
View file

@ -0,0 +1,25 @@
Day REPLACE_ME Notes
+--------+
| Part 1 |
+--------+
$ elixir dayREPLACE_MEpart1.exs
Thoughts:
+--------+
| Part 2 |
+--------+
$ elixir dayREPLACE_MEpart2.exs
Thoughts:
+------------------+
| Overall Thoughts |
+------------------+

9
template/dayNpart1.exs Normal file
View file

@ -0,0 +1,9 @@
defmodule DayREPLACE_MEPart1 do
def run do
File.read!("input")
|> String.split("\n", trim: true)
|> IO.inspect()
end
end
DayREPLACE_MEPart1.run()