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

Advent of Code 2024 - Day 10

2024/10.livemd

Advent of Code 2024 - Day 10

Mix.install([
  {:req, "~> 0.5"},
  {:benchee, "~> 1.3"}
])

Input

opts = [headers: [{"cookie", "session=#{System.fetch_env!("LB_AOC_SESSION")}"}]]
puzzle_input = Req.get!("https://adventofcode.com/2024/day/10/input", opts).body
defmodule Grid do
  def parse(puzzle_input) do
    String.split(puzzle_input, "\n", trim: true)
    |> Enum.reduce({[], 0, 0}, fn row, {list, _width, height} ->
      {list ++ String.graphemes(row), String.length(row), height + 1}
    end)
  end

  def at(grid, width, height, {x, y} = pos) do
    if inside?(width, height, pos), do: Enum.at(grid, y * width + x), else: nil
  end

  def inside?(width, height, {x, y}) do
    x >= 0 and x < width and y >= 0 and y < height
  end

  def find_valid_paths(grid, w, h, {x, y} = pos, last_num, acc) do
    cell = at(grid, w, h, pos) || ""
    
    case Integer.parse(cell) do
      {9, ""} when last_num == 8 ->
        [pos | acc]

      {num, ""} when num == last_num + 1 ->
        [{0, -1}, {1, 0}, {0, 1}, {-1, 0}]
        |> Enum.map(fn {dir_x, dir_y} -> {dir_x + x, dir_y + y} end)
        |> Enum.flat_map(fn new_pos ->
          find_valid_paths(grid, w, h, new_pos, num, acc)
        end)

      _other ->
        acc
    end
  end
end

Puzzle 1

puzzle_1 = fn ->
  {grid, w, h} = Grid.parse(puzzle_input)
  
  coords =
    for x <- 0..(w - 1), y <- 0..(h - 1) do
      {x, y}
    end
  
  Enum.flat_map(coords, fn pos ->
    case Grid.at(grid, w, h, pos) do
      "0" -> Grid.find_valid_paths(grid, w, h, pos, -1, []) |> Enum.uniq()
      _other -> []
    end
  end)
  |> Enum.count()
end

puzzle_1.()

Puzzle 2

puzzle_2 = fn ->
  {grid, w, h} = Grid.parse(puzzle_input)

  coords =
    for x <- 0..(w - 1), y <- 0..(h - 1) do
      {x, y}
    end

  Enum.flat_map(coords, fn pos ->
    case Grid.at(grid, w, h, pos) do
      "0" -> Grid.find_valid_paths(grid, w, h, pos, -1, [])
      _other -> []
    end
  end)
  |> Enum.count()
end

puzzle_2.()

Benchmarks

Benchee.run(
  %{
    "puzzle_1" => fn -> puzzle_1.() end,
    "puzzle_2" => fn -> puzzle_2.() end
  })
Name ips average deviation median 99th %
puzzle_2 29.75 33.62 ms ±8.61% 33.33 ms 53.41 ms
puzzle_1 29.69 33.68 ms ±6.49% 33.36 ms 50.11 ms