Read some comments here and on Reddit to get some ideas and eventually tried finding an horizontal line of at least 10 robots. Should have found it by myself. Anyway, it was a cool one.
#!/usr/bin/env elixir
# AoC 2024. day 14.
to_i = &String.to_integer/1
advance = fn robots, sec ->
Enum.map(robots, fn robot ->
%{robot |
x: Integer.mod(robot.x+sec*robot.vx,101),
y: Integer.mod(robot.y+sec*robot.vy,103) }
end)
# sorting is used in part 2
|> Enum.sort(fn o1, o2 -> o1.y < o2.y || o1.y == o2.y && o1.x <= o2.x end)
end
count = fn robots ->
Enum.reduce(robots, {0,0,0,0}, fn robot, {q1,q2,q3,q4} ->
cond do
robot.x < 50 && robot.y < 51 -> {q1+1,q2,q3,q4}
robot.x > 50 && robot.y < 51 -> {q1,q2+1,q3,q4}
robot.x < 50 && robot.y > 51 -> {q1,q2,q3+1,q4}
robot.x > 50 && robot.y > 51 -> {q1,q2,q3,q4+1}
true -> {q1,q2,q3,q4}
end
end)
end
###########################################################
# Setup
robots = File.stream!("../day14.txt")
|> Stream.map(fn s -> Regex.run(~r/^p=(\d+),(\d+) v=(-?\d+),(-?\d+)/, s, capture: :all_but_first) end)
|> Enum.reduce([], fn [x, y, vx, vy], lst -> [%{x: to_i.(x), y: to_i.(y), vx: to_i.(vx), vy: to_i.(vy)} | lst] end)
# sorting is used in part 2
|> Enum.sort(fn o1, o2 -> o1.y < o2.y || o1.y == o2.y && o1.x <= o2.x end)
###########################################################
# Part 1
start = System.monotonic_time(:microsecond)
robots
|> advance.(100)
|> count.()
|> then(fn {q1,q2,q3,q4} -> q1*q2*q3*q4 end)
|> IO.inspect(label: "Part 1")
elapsed = System.monotonic_time(:microsecond) - start
IO.puts "Job done in #{elapsed} µs"
###########################################################
# Part 2
defmodule M do
@min_aligned 10
def horizontal_line(robots), do: horizontal_line(robots, 0, nil)
defp horizontal_line([], aligned, _prev), do: aligned >= @min_aligned
defp horizontal_line([r | robots], 0, nil), do: horizontal_line(robots, 0, r)
defp horizontal_line([r | robots], aligned, prev) do
cond do
aligned >= @min_aligned -> true
r.y == prev.y && r.x == prev.x+1 -> horizontal_line(robots, aligned+1, r)
true -> horizontal_line(robots, 0, r)
end
end
end
display = fn robots, x0, y0, width, height ->
robots = Map.new(robots, fn r -> {{r.x,r.y},{r.vx,r.vy}} end)
for y <- y0..y0+height, x <- x0..x0+width, reduce: y0 do
prev_y -> if y != prev_y, do: IO.puts("")
if robots[{x,y}] do
IO.write "#"
else
IO.write "."
end
y
end
IO.puts ""
end
start = System.monotonic_time(:microsecond)
Stream.iterate({robots, 0}, fn {robots, time} ->
robots = advance.(robots, 1)
{robots, time+1}
end)
|> Stream.drop_while(fn {robots, _time} ->
!M.horizontal_line(robots)
end)
|> Enum.at(0)
|> then(fn {robots, time} -> display.(robots, 0, 0, 101, 103) ; time end)
|> IO.inspect(label: "Part 2")
elapsed = System.monotonic_time(:microsecond) - start
IO.puts "Job done in #{elapsed} µs"