Compare commits

..

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

39 changed files with 42 additions and 3075 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

@ -2,6 +2,6 @@
My (attempted) solutions to [Advent of Code](https://adventofcode.com/). My (attempted) solutions to [Advent of Code](https://adventofcode.com/).
- [2022: Elixir](https://git.adamu.jp/adam/AdventOfCode/src/branch/main/2022) - [2021: Zig](https://github.com/adamu/AdventOfCode/tree/main/2021)
- [2020: Elixir](https://git.adamu.jp/adam/AdventOfCode/src/branch/main/2020) - [2020: Elixir](https://github.com/adamu/AdventOfCode/tree/main/2020)
- [2018: Elixir](https://git.adamu.jp/adam/AdventOfCode/src/branch/main/2018) - [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
:ok
end
def input do
with [input_filename] <- System.argv(),
{:ok, input} <- File.read(input_filename) do
input
else
_ -> :error
end
end
#######################
# HERE BE BOILERPLATE #
#######################
def run do
case input() do
:error -> print_usage()
input -> run_parts_with_timer(input)
end
end
defp run_parts_with_timer(input) do
run_with_timer(1, fn -> part1(input) end)
run_with_timer(2, fn -> part2(input) end)
end
defp run_with_timer(part, fun) do
{time, result} = :timer.tc(fun)
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
IO.puts("#{inspect result}\n")
end
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
defp print_usage do
IO.puts("Usage: elixir dayREPLACE_ME.exs input_filename")
end
end
# DayREPLACE_ME.run()

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()