Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Advent of Code - Day 10

livebooks/day10.livemd

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()