Advent of Code - Day 10
Part 1
raw_sample = """
0123
1234
8765
9876
"""
big_raw_sample = """
89010123
78121874
87430965
96549874
45678903
32019012
01329801
10456732
"""
A good hiking trail is as long as possible and has an even, gradual, uphill slope.
A hiking trail is any path that starts at height 0, ends at height 9, and always increases by a height of exactly 1 at each step. Hiking trails never include diagonal steps - only up, down, left, or right (from the perspective of the map).
A trailhead is any position that starts one or more hiking trails - here, these positions will always have height 0.
A trailhead’s score is the number of 9-height positions reachable from that trailhead via a hiking trail. In the above example, the single trailhead in the top left corner has a score of 1 because it can reach a single 9 (the one in the bottom left).
This trailhead has a score of 2:
...0...
...1...
...2...
6543456
7.....7
8.....8
9.....9
(The positions marked . are impassable tiles to simplify these examples; they do not appear on your actual topographic map.)
This trailhead has a score of 4 because every 9 is reachable via a hiking trail except the one immediately to the left of the trailhead:
..90..9
...1.98
...2..7
6543456
765.987
876....
987....
Here’s a larger example:
89010123
78121874
87430965
96549874
45678903
32019012
01329801
10456732
This larger example has 9 trailheads. Considering the trailheads in reading order, they have scores of 5, 6, 5, 3, 1, 3, 5, 3, and 5. Adding these scores together, the sum of the scores of all trailheads is 36.
The reindeer gleefully carries over a protractor and adds it to the pile. What is the sum of the scores of all trailheads on your topographic map?
defmodule Part1 do
def parse_input(input) do
input
|> String.split("\n", trim: true)
|> Enum.with_index()
|> Enum.reduce({%{}, MapSet.new()}, fn {line, row}, {trail_map, trailhead_set} ->
line
|> String.graphemes()
|> Enum.with_index()
|> Enum.reduce({trail_map, trailhead_set}, fn {height, col}, {trail_map, trailhead_set} ->
trailhead_set =
if height == "0" do
MapSet.put(trailhead_set, {row, col})
else
trailhead_set
end
trail_map = Map.put(trail_map, {row, col}, String.to_integer(height))
{trail_map, trailhead_set}
end)
end)
end
def rows_and_cols(input) do
temp =
input
|> String.split("\n", trim: true)
rows = temp |> length()
cols = temp |> Enum.at(0) |> String.graphemes() |> length()
{rows, cols}
end
def recurse({current_x, current_y}, _trail_map, rows, cols, _last_val, acc)
when current_x < 0 or current_y < 0 or current_x >= rows or current_y >= cols do
acc
end
def recurse({current_x, current_y} = cur_pos, trail_map, rows, cols, last_val, acc) do
cur_val = trail_map |> Map.fetch!({current_x, current_y})
if cur_val - last_val == 1 or last_val == -1 do
if cur_val == 9 do
MapSet.put(acc, cur_pos)
else
acc1 = recurse({current_x - 1, current_y}, trail_map, rows, cols, cur_val, acc)
acc2 = recurse({current_x, current_y - 1}, trail_map, rows, cols, cur_val, acc)
acc3 = recurse({current_x + 1, current_y}, trail_map, rows, cols, cur_val, acc)
acc4 = recurse({current_x, current_y + 1}, trail_map, rows, cols, cur_val, acc)
acc
|> MapSet.union(acc1)
|> MapSet.union(acc2)
|> MapSet.union(acc3)
|> MapSet.union(acc4)
end
else
acc
end
end
def solve(input) do
{rows, cols} = Part1.rows_and_cols(input)
{trail_map, trailhead_set} = Part1.parse_input(input)
trailhead_set
|> Enum.reduce(0, fn trailhead, acc ->
recurse(trailhead, trail_map, rows, cols, -1, MapSet.new())
|> MapSet.size()
|> Kernel.+(acc)
end)
end
end
Part1.solve(raw_sample)
Part1.solve(big_raw_sample)
File.read!("/Users/errantsky/elixir-projects/phoenix-projects/advent_of_code_2024/inputs/day10.txt")
|> Part1.solve()
Part 2
defmodule Part2 do
def parse_input(input) do
input
|> String.split("\n", trim: true)
|> Enum.with_index()
|> Enum.reduce({%{}, MapSet.new()}, fn {line, row}, {trail_map, trailhead_set} ->
line
|> String.graphemes()
|> Enum.with_index()
|> Enum.reduce({trail_map, trailhead_set}, fn {height, col}, {trail_map, trailhead_set} ->
trailhead_set =
if height == "0" do
MapSet.put(trailhead_set, {row, col})
else
trailhead_set
end
trail_map = Map.put(trail_map, {row, col}, String.to_integer(height))
{trail_map, trailhead_set}
end)
end)
end
def rows_and_cols(input) do
temp =
input
|> String.split("\n", trim: true)
rows = temp |> length()
cols = temp |> Enum.at(0) |> String.graphemes() |> length()
{rows, cols}
end
def recurse({current_x, current_y}, _trail_map, rows, cols, _last_val, _acc)
when current_x < 0 or current_y < 0 or current_x >= rows or current_y >= cols do
0
end
def recurse({current_x, current_y}, trail_map, rows, cols, last_val, acc) do
cur_val = trail_map |> Map.fetch!({current_x, current_y})
if cur_val - last_val == 1 or last_val == -1 do
if cur_val == 9 do
acc + 1
else
acc1 = recurse({current_x - 1, current_y}, trail_map, rows, cols, cur_val, acc)
acc2 = recurse({current_x, current_y - 1}, trail_map, rows, cols, cur_val, acc)
acc3 = recurse({current_x + 1, current_y}, trail_map, rows, cols, cur_val, acc)
acc4 = recurse({current_x, current_y + 1}, trail_map, rows, cols, cur_val, acc)
acc + acc1 + acc2 + acc3 + acc4
end
else
0
end
end
def solve(input) do
{rows, cols} = Part1.rows_and_cols(input)
{trail_map, trailhead_set} = Part1.parse_input(input)
trailhead_set
|> Enum.reduce(0, fn trailhead, acc ->
recurse(trailhead, trail_map, rows, cols, -1, 0)
|> Kernel.+(acc)
end)
end
end
p2_sample = """
012345
123456
234567
345678
416789
567891
"""
p2_sample
|> Part2.solve() == 227
File.read!("/Users/errantsky/elixir-projects/phoenix-projects/advent_of_code_2024/inputs/day10.txt")
|> Part2.solve()