Advent of Code 2024 - Day 08
Mix.install([
{:kino_aoc, "~> 0.1.7"}
])
Introduction
2024 - Day 08
Puzzle
{:ok, puzzle_input} =
KinoAOC.download_puzzle("2024", "8", System.fetch_env!("LB_AOC_SESSION"))
Parser
Code - Parser
defmodule Parser do
def parse(input) do
lines =
[head | _] =
input
|> String.split("\n", trim: true)
lines
|> Enum.with_index()
|> Enum.flat_map(fn {line, y} ->
line
|> String.codepoints()
|> Enum.with_index()
|> Enum.map(fn {char, x} -> {{x, y}, char} end)
end)
|> Enum.group_by(fn {_, char} -> char end, fn {pos, _} -> pos end)
|> Map.filter(fn {k, _} -> k != "." end)
|> then(fn map ->
{map, String.length(head), Enum.count(lines)}
end)
end
end
Tests - Parser
ExUnit.start(autorun: false)
defmodule ParserTest do
use ExUnit.Case, async: true
import Parser
@input """
...A
4.f.
z...
"""
@expected {%{"A" => [{3, 0}], "4" => [{0, 1}], "f" => [{2, 1}], "z" => [{0, 2}]}, 4, 3}
test "parse test" do
actual = parse(@input)
assert actual == @expected
end
end
ExUnit.run()
Shared
defmodule Shared do
def solve(input, case) do
{antennas, max_x, max_y} = Parser.parse(input)
antennas
|> Enum.reduce([], fn {_, points}, frequencies ->
frequencies(points, max_x, max_y, case, frequencies)
end)
|> List.flatten()
|> Enum.uniq()
|> Enum.count()
end
defp frequencies([_], _, _, _, freq), do: freq
defp frequencies([{x, y} | tail], max_x, max_y, case, freq) do
tail
|> Enum.map(fn {tx, ty} ->
dx = x - tx
dy = y - ty
[
jump(x, y, dx, dy, max_x, max_y, case),
jump(tx, ty, -dx, -dy, max_x, max_y, case)
]
end)
|> then(&frequencies(tail, max_x, max_y, case, [&1 | freq]))
end
defp in_range(x, y, max_x, max_y), do: x > -1 and x < max_x and y > -1 and y < max_y
defp jump(x, y, dx, dy, max_x, max_y, case) do
stream =
case case do
:part_one -> [1]
:part_two -> Stream.from_index()
end
stream
|> Stream.map(fn step ->
x = x + step * dx
y = y + step * dy
{x, y}
end)
|> Enum.take_while(fn {x, y} -> in_range(x, y, max_x, max_y) end)
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), do: Shared.solve(input, :part_one)
end
Tests - Part 1
ExUnit.start(autorun: false)
defmodule PartOneTest do
use ExUnit.Case, async: true
import PartOne
@input """
............
........0...
.....0......
.......0....
....0.......
......A.....
............
............
........A...
.........A..
............
............
"""
@expected 14
test "part one" do
actual = run(@input)
assert actual == @expected
end
end
ExUnit.run()
Solution - Part 1
PartOne.solve(puzzle_input)
Part Two
Code - Part 2
defmodule PartTwo do
def solve(input) do
IO.puts("--- Part Two ---")
IO.puts("Result: #{run(input)}")
end
def run(input), do: Shared.solve(input, :part_two)
end
Tests - Part 2
ExUnit.start(autorun: false)
defmodule PartTwoTest do
use ExUnit.Case, async: true
import PartTwo
@input """
............
........0...
.....0......
.......0....
....0.......
......A.....
............
............
........A...
.........A..
............
............
"""
@expected 34
test "part two" do
actual = run(@input)
assert actual == @expected
end
end
ExUnit.run()
Solution - Part 2
PartTwo.solve(puzzle_input)