144 lines
4.3 KiB
Elixir
Executable file
144 lines
4.3 KiB
Elixir
Executable file
#!/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()
|