Day 21 - Advent of Code 2023
Mix.install([:kino, :benchee])
Links
Input
input = Kino.Input.textarea("Please paste your input file:")
input = input |> Kino.Input.read()
"...................................................................................................................................\n...##......#....#...#.....#.#....#...#...#...#......#........................##......#..#..#.#........#....................##......\n...#.......#..........#..#....#................#...##.....##...................#..........###......#.....#.........#....#.#........\n........#..##.............#..#...##...#..#...##...............................#.........................#..........................\n..............#............#..#..........#.#.#....#...#......................#..#......#.##...#............................#.......\n..##...............#...#...#......#.......................................#..............##...............#.....#...........#...#..\n.##............#.................#...#..#......#...................................#.......#.#..#......##................##........\n......###...#...#........#..............#....#.......##.......................##.......#..#.......#.........#...................#..\n.................#...........................................#......................#.....#.....##...........#...............#.....\n..#...#....#.......##...........#..#.............#............................##........#.......#...#...#...#....#....##...........\n..............#..#.....#.....#.........#..#..#..#..#.......#.#..#....##........#.....#.........#..............#........#..#...#....\n...##...............#...#.#....#............##..#.......................................#.......#...........#..#...........##....#.\n.......................................#......#...........#..........#.............#..........##.............#....#..##....#.......\n..................................#.............#........#...#.##.....................#....................#.##....#.............#.\n.#......##...#......#..#...#...........#...#.............#.............#..#.........#..#.#........#.#...........#..................\n............#............#..........#........#..........#.....#.#...#.......#........#.#...........#....#.............#.........#..\n.....#.......##............#...#.#.#...#...#..............##..#....#......#......................#...#...#..........#......#.#.....\n........#....#....#......#..........#........................#.....#....................................#.....#....#..##.##....#...\n............#.................#.##.........#.........#.....#........#.........#........#....###...........#.............#..........\n....##....#..#............#...#.#..#.........................#.#....##.....................#........#.........#....#....#........#.\n...#...#...........#..#..................#.......#.#..#............................................##................#.............\n...........#......#....#........#.#..........................#.#......#......##.............#..#...#...#.#..................#..#...\n.............................#........#............#........#........#.#.......#...............##....#........#...#..#..#..........\n..................................#...........................#.#..#.........................#...#.#......#..............##.....##.\n........................#..........................#..................#...#............................#.......#...........#...##..\n.......#.#.....#....#.....#................................##...#.........##..#.....................#..............................\n......#........##..........#.................#..##......#..#.......#..........#....................................................\n....#..........#......................................................##.#..#.#.##..................#......#............##...#.....\n.............#..........#.#..............................................#.#........#....#...........#.......................#.....\n..#.............#....#........#.#..............#.......#......#.#..#......#.#...........................................##.........\n..#.....#.....#....#...#....#..........#......#..............................#...........#............#..#.....##..........#.......\n...." <> ...
Solution
defmodule Day21 do
defdelegate parse(input), to: __MODULE__.Input
def part1(input, num_steps) do
{grid, start} = parse(input)
make_step([start], grid, [num_steps], :part1) |> List.first()
end
def part2(input) do
{grid, start} = parse(input)
x = div(26_501_365 - 65, 131)
steps = [
65 + 0 * 131,
65 + 1 * 131,
65 + 2 * 131
]
{a, b, c} = make_step([start], grid, steps, :part2) |> quadratic_values()
a * x ** 2 + b * x + c
end
defp quadratic_values([c0, c1, c2]) do
a = div(-2 * (c1 - c0) + (c2 - c0), 2)
b = c1 - c0 - a
{a, b, c0}
end
defp make_step(locations, grid, max_steps, location_counts \\ [], count \\ 0, part)
defp make_step(_, _, [], location_counts, _, _), do: Enum.reverse(location_counts)
defp make_step(locations, grid, [next_max | max_steps], location_counts, count, part) do
locations
|> Enum.flat_map(&next_locations(&1, grid, part))
|> Enum.map(&elem(&1, 0))
|> Enum.uniq()
|> then(fn
locations ->
{lc, ms} =
if count + 1 == next_max do
{[length(locations) | location_counts], max_steps}
else
{location_counts, [next_max | max_steps]}
end
make_step(locations, grid, ms, lc, count + 1, part)
end)
end
defp next_locations({x, y}, grid, part) do
[{x, y - 1}, {x + 1, y}, {x, y + 1}, {x - 1, y}]
|> Enum.map(fn xy ->
grid_xy = converted_xy(xy, grid.max_x, grid.max_y, part)
if grid[grid_xy] == ?., do: {xy, grid_xy}
end)
|> Enum.reject(&is_nil/1)
end
defp converted_xy(xy, _max_x, _max_y, :part1), do: xy
defp converted_xy({x, y}, max_x, max_y, _) do
{
convert_value(x, max_x),
convert_value(y, max_y)
}
end
def convert_value(value, max) when value >= 0,
do: rem(value, max + 1)
def convert_value(value, max) do
case value |> abs() |> rem(max + 1) do
0 -> 0
offset -> max + 1 - offset
end
end
defmodule Input do
def parse(input) do
input
|> String.splitter("\n", trim: true)
|> Stream.with_index()
|> Enum.reduce({%{}, nil}, &parse_line/2)
|> add_max_xy()
end
defp parse_line({line, row}, {grid, start}) do
line
|> String.to_charlist()
|> Enum.with_index()
|> Enum.reduce({grid, start}, fn
{?S, col}, {grid, nil} ->
{Map.put(grid, {col, row}, ?.), {col, row}}
{char, col}, {grid, start} ->
{Map.put(grid, {col, row}, char), start}
end)
end
defp add_max_xy({grid, start}) do
x = Map.keys(grid) |> Enum.map(&elem(&1, 0)) |> Enum.max()
y = Map.keys(grid) |> Enum.map(&elem(&1, 1)) |> Enum.max()
{Map.merge(grid, %{max_x: x, max_y: y}), start}
end
end
end
{:module, Day21, <<70, 79, 82, 49, 0, 0, 23, ...>>,
{:module, Day21.Input, <<70, 79, 82, ...>>, {:add_max_xy, 1}}}
Starting from the garden plot marked S on your map, how many garden plots could the Elf reach in exactly 64 steps?
Your puzzle answer was 3795
.
Day21.part1(input, 64)
3795
Starting from the garden plot marked S on your infinite map, how many garden plots could the Elf reach in exactly 26501365 steps?
Your puzzle answer was 630129824772393
.
Day21.part2(input)
630129824772393
Both parts of this puzzle are complete! They provide two gold stars: **
At this point, you should return to your Advent calendar and try another puzzle.
If you still want to see it, you can get your puzzle input.
Tests
ExUnit.start(auto_run: false)
defmodule Day21Test do
use ExUnit.Case, async: false
setup_all do
[
input:
"...........\n.....###.#.\n.###.##..#.\n..#.#...#..\n....#.#....\n.##..S####.\n.##..#...#.\n.......##..\n.##.#.####.\n.##..##.##.\n..........."
]
end
describe "part1/1" do
test "returns expected value", %{input: input} do
assert Day21.part1(input, 6) == 16
end
end
end
ExUnit.run()
.
Finished in 0.00 seconds (0.00s async, 0.00s sync)
1 test, 0 failures
Randomized with seed 278444
%{total: 1, excluded: 0, failures: 0, skipped: 0}
Benchmarking
defmodule Benchmarking do
# https://github.com/bencheeorg/benchee
def run(input) do
Benchee.run(
%{
"Part 1" => fn -> Day21.part1(input, 64) end,
"Part 2" => fn -> Day21.part2(input) end
},
memory_time: 2,
reduction_time: 2
)
nil
end
end
{:module, Benchmarking, <<70, 79, 82, 49, 0, 0, 7, ...>>, {:run, 1}}
Benchmarking.run(input)
Operating System: macOS
CPU Information: Apple M1 Pro
Number of Available Cores: 10
Available memory: 32 GB
Elixir 1.15.7
Erlang 26.1
Benchmark suite executing with the following configuration:
warmup: 2 s
time: 5 s
memory time: 2 s
reduction time: 2 s
parallel: 1
inputs: none specified
Estimated total run time: 22 s
Benchmarking Part 1 ...
Benchmarking Part 2 ...
Name ips average deviation median 99th %
Part 1 18.14 0.0551 s ±2.30% 0.0551 s 0.0595 s
Part 2 0.0991 10.09 s ±0.00% 10.09 s 10.09 s
Comparison:
Part 1 18.14
Part 2 0.0991 - 183.03x slower +10.03 s
Memory usage statistics:
Name Memory usage
Part 1 0.0749 GB
Part 2 11.11 GB - 148.33x memory usage +11.04 GB
**All measurements for memory usage were the same**
Reduction count statistics:
Name average deviation median 99th %
Part 1 0.00748 B ±0.00% 0.00748 B 0.00749 B
Part 2 1.11 B ±0.00% 1.11 B 1.11 B
Comparison:
Part 1 0.00748 B
Part 2 1.11 B - 148.53x reduction count +1.10 B
nil