Advent of Code - Day 14
Mix.install([
{:kino, "~> 0.14.0"}
])
Part 1
ToDo
- Rewrite so the loop does not compute from zero 2.
raw_sample = """
p=0,4 v=3,-3
p=6,3 v=-1,-3
p=10,3 v=-1,2
p=2,0 v=2,-1
p=0,0 v=1,3
p=3,0 v=-2,-2
p=7,6 v=-1,-3
p=3,0 v=-1,-2
p=9,3 v=2,3
p=7,3 v=-1,2
p=2,4 v=2,-3
p=9,5 v=-3,-3
"""
You’ll need a way to predict where the robots will be in the future. Fortunately, they all seem to be moving on the tile floor in predictable straight lines.
You make a list (your puzzle input) of all of the robots’ current positions (p) and velocities (v), one robot per line.
Each robot’s velocity is given as v=x,y where x and y are given in tiles per second. Positive x means the robot is moving to the right, and positive y means the robot is moving down. So, a velocity of v=1,-2 means that each second, the robot moves 1 tile to the right and 2 tiles up.
When a robot would run into an edge of the space they’re in, they instead teleport to the other side,
The robots outside the actual bathroom are in a space which is 101 tiles wide and 103 tiles tall (when viewed from above). However, in this example, the robots are in a space which is only 11 tiles wide and 7 tiles tall.
To determine the safest area, count the number of robots in each quadrant after 100 seconds. Robots that are exactly in the middle (horizontally or vertically) don’t count as being in any quadrant, so the only relevant robots are:
..... 2..1.
..... .....
1.... .....
..... .....
...12 .....
.1... 1....
Predict the motion of the robots in your list within a space which is 101 tiles wide and 103 tiles tall. What will the safety factor be after exactly 100 seconds have elapsed?
defmodule Part1 do
def parse_input(input) do
pattern = ~r/p=(?-?\d+),(?-?\d+) v=(?-?\d+),(?-?\d+)/
Regex.scan(pattern, input)
|> Enum.map(fn match ->
parsed_list =
match
|> tl()
|> Enum.map(&String.to_integer(&1))
[px, py, vx, vy] = parsed_list
{{px, py}, {vx, vy}}
end)
end
def after_n(robot_list, times, rows, cols) do
1..times
|> Enum.reduce(robot_list, fn _, rlist ->
blink(rlist, rows, cols)
end)
end
def blink(robot_list, rows, cols) do
Enum.map(robot_list, fn robot ->
{{px, py}, {vx, vy}} = robot
{{
(px + vx) |> Integer.mod(rows),
(py + vy) |> Integer.mod(cols)
}, {vx, vy}}
end)
end
def find_result(robot_list, rows, cols) do
mid_x = div(rows, 2)
mid_y = div(cols, 2)
selected_robots =
robot_list
|> Enum.reject(fn {{px, py}, _} ->
px == mid_x or py == mid_y
end)
# quad 1
q1 =
selected_robots
|> Enum.filter(fn {{px, py}, _} ->
px < mid_x and py < mid_y
end)
|> Enum.count()
# quad 2
q2 =
selected_robots
|> Enum.filter(fn {{px, py}, _} ->
px < mid_x and py > mid_y
end)
|> Enum.count()
# quad 3
q3 =
selected_robots
|> Enum.filter(fn {{px, py}, _} ->
px > mid_x and py < mid_y
end)
|> Enum.count()
# quad 4
q4 =
selected_robots
|> Enum.filter(fn {{px, py}, _} ->
px > mid_x and py > mid_y
end)
|> Enum.count()
{q1, q2, q3, q4, q1 * q2 * q3 * q4}
end
def viz(robot_list, rows, cols, format \\ :readable) do
map =
for i <- 0..(rows - 1), j <- 0..(cols - 1), into: %{} do
{{i, j}, 0}
end
map =
robot_list
|> Enum.reduce(map, fn {p, _}, acc ->
{_, map} =
Map.get_and_update!(
acc,
p,
fn current_value ->
{current_value, current_value + 1}
end
)
map
end)
case format do
:readable ->
list =
for j <- 0..(cols - 1), into: [] do
for i <- 0..(rows - 1), into: [] do
case map[{i, j}] do
0 -> "."
_ -> "X"
end
end
end
list
|> Enum.map(fn line ->
line |> Enum.join(" ")
end)
|> Enum.join("\n")
:raw ->
list =
for j <- 0..(cols - 1), into: [] do
for i <- 0..(rows - 1), into: [] do
map[{i, j}]
end
end
end
end
def count_overlapping(raw_viz) do
raw_viz
|> Enum.flat_map(fn row ->
row |> Enum.filter(& &1 > 1)
end)
|> Enum.count()
end
end
robots = raw_sample
|> Part1.parse_input()
|> Part1.after_n(100, 11, 7)
|> Part1.viz(11, 7, :raw)
|> Part1.count_overlapping()
robots = raw_sample
|> Part1.parse_input()
|> Part1.after_n(100, 11, 7)
|> Part1.find_result(11, 7)
# |> Part1.viz(11, 7)
robots = raw_sample
|> Part1.parse_input()
|> Part1.after_n(100, 11, 7)
|> Part1.find_result(11, 7)
File.read!("/Users/errantsky/elixir-projects/phoenix-projects/advent_of_code_2024/inputs/day14.txt")
|> Part1.parse_input()
|> Part1.after_n(100, 101, 103)
|> Part1.find_result(101, 103)
import Kino.Shorts
78 181
103
12 113
111
src =
File.read!(
"/Users/errantsky/elixir-projects/phoenix-projects/advent_of_code_2024/inputs/day14.txt"
)
|> Part1.parse_input()
res = src
|> Part1.after_n(215, 101, 103)
|> Part1.viz(101, 103)
File.write!(
"/Users/errantsky/elixir-projects/phoenix-projects/advent_of_code_2024/inputs/day14-viz.txt",
res
)
1..5000
|> Enum.map(fn i ->
vz = src
|> Part1.after_n(i + 1, 101, 103)
|> Part1.viz(101, 103, :raw)
|> Part1.count_overlapping()
[vz, i]
end)
|> Enum.min_by(fn [c, _] -> c end)
501..510
|> Kino.animate(fn i ->
Process.sleep(500)
vz = src
|> Part1.after_n(i + 1, 101, 103)
|> Part1.viz(101, 103)
Kino.Markdown.new("**Iteration: #{i + 1}**\n\n" <> vz)
end)
next
# Initialize a counter to 0
counter = 0
# Create a button with the label "Next"
button = Kino.Control.button("Next")
# Create a Kino.Markdown widget to display the text
markdown = Kino.Markdown.new("Click the button to start counting!")
# Display the button and the markdown in the notebook
Kino.Layout.grid([button, markdown])
# Set up an event listener for the button
Kino.Control.on_click(button, fn _ ->
# Increment the counter
counter = counter + 1
# Generate the text based on the count
text = "Button clicked #{counter} time#{if counter == 1, do: "", else: "s"}!"
# Update the Markdown content
Kino.Markdown.update(markdown, text)
end)