Advent of Code 2024 - Day 14
Mix.install([
:kino_aoc
])
Introduction
2024 - Day 14
Puzzle
{:ok, puzzle_input} =
KinoAOC.download_puzzle("2024", "14", System.fetch_env!("LB_AOC_SESSION"))
Parser
Code - Parser
defmodule Parser do
@reg ~r/(\d+),(\d+) v=(-?\d+),(-?\d+)/
def parse(input) do
input
|> String.split("\n", trim: true)
|> Enum.map(fn line ->
[[_, x, y, vx, vy]] = Regex.scan(@reg, line)
[x, y, vx, vy]
|> Enum.map(&String.to_integer/1)
end)
|> Enum.group_by(
fn [x, y | _] -> {x, y} end,
fn [_, _, vx, vy] -> {vx, vy} end
)
end
end
Tests - Parser
ExUnit.start(autorun: false)
defmodule ParserTest do
use ExUnit.Case, async: true
import Parser
@input """
p=0,4 v=3,-3
p=6,3 v=-1,-3
p=0,4 v=-1,2
"""
@expected %{{0, 4} => [{3, -3}, {-1, 2}], {6, 3} => [{-1, -3}]}
test "parse test" do
actual = parse(@input)
assert actual == @expected
end
end
ExUnit.run()
Shared
defmodule Shared do
def move(map, max_turn, max_x, max_y) do
map
|> Enum.reduce(%{}, fn {{x, y}, robots}, map ->
robots
|> Enum.reduce(map, fn robot = {vx, vy}, map ->
next = wrap(x + vx * max_turn, y + vy * max_turn, max_x, max_y)
Map.update(map, next, [robot], &[robot | &1])
end)
end)
end
defp wrap(x, y, max_x, max_y) do
new_x = case rem(x, max_x) do
x when x > -1 -> x
x -> x + max_x
end
new_y = case rem(y, max_y) do
y when y > -1 -> y
y -> y + max_y
end
{new_x, new_y}
end
end
Part One
Code - Part 1
defmodule PartOne do
def solve(input) do
IO.puts("--- Part One ---")
IO.puts("Result: #{run(input)}")
end
def run(input, max_x \\ 101, max_y \\ 103) do
map =
input
|> Parser.parse()
|> Shared.move(100, max_x, max_y)
half_x = div(max_x, 2)
half_y = div(max_y, 2)
[
{0, 0, half_x - 1, half_y - 1},
{half_x + 1, 0, max_x, half_y - 1},
{0, half_y + 1, half_x - 1, max_y},
{half_x + 1, half_y + 1, max_x, max_y}
]
|> Enum.map(fn quarter -> count_quarter(map, quarter) end)
|> Enum.reduce(&Kernel.*/2)
end
defp count_quarter(map, {min_x, min_y, max_x, max_y}) do
map
|> Enum.filter(fn {{x, y}, _} ->
x >= min_x and x <= max_x and
y >= min_y and y <= max_y
end)
|> Enum.map(fn {_, robots} -> Enum.count(robots) end)
|> Enum.sum()
end
end
Tests - Part 1
ExUnit.start(autorun: false)
defmodule PartOneTest do
use ExUnit.Case, async: true
import PartOne
@input """
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
"""
@expected 12
test "part one" do
actual = run(@input, 11, 7)
assert actual == @expected
end
end
ExUnit.run()
Solution - Part 1
PartOne.solve(puzzle_input)
Part Two
Code - Part 2
defmodule PartTwo do
def run(input, tries, max_x \\ 101, max_y \\ 103) do
map = Parser.parse(input)
1..tries
|> Enum.map(fn time ->
[
"---Time #{time}---"
| Shared.move(map, time, max_x, max_y)
|> to_string(max_x, max_y)
]
end)
end
defp to_string(map, max_x, max_y) do
0..(max_y - 1)
|> Enum.reduce([], fn y, bathroom ->
0..(max_x - 1)
|> Enum.reduce([], fn x, line ->
[if(Map.has_key?(map, {x, y}), do: "#", else: " ") | line]
end)
|> Enum.reverse()
|> Enum.join()
|> then(&[&1 | bathroom])
end)
|> Enum.reverse()
end
end
Solution - Part 2
content =
PartTwo.run(puzzle_input, 10000)
|> Enum.filter(fn state ->
state
|> Enum.any?(fn line -> String.contains?(line, "##########") end)
end)
|> List.flatten()
|> Enum.join("\n")
Kino.Download.new(
fn -> content end,
filename: "result.txt",
label: "Result"
)