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
10456732This 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
endPart1.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
endp2_sample = """
012345
123456
234567
345678
416789
567891
"""
p2_sample
|> Part2.solve() == 227File.read!("/Users/errantsky/elixir-projects/phoenix-projects/advent_of_code_2024/inputs/day10.txt")
|> Part2.solve()