Powered by AppSignal & Oban Pro

Advent of Code 2023 Day 17

2023/day-17.livemd

Advent of Code 2023 Day 17

Mix.install([
  {:kino_aoc, "~> 0.1.5"}
])

Section

{:ok, real_input} =
  KinoAOC.download_puzzle("2023", "17", System.fetch_env!("LB_AOC_SESSION"))
test_input =
  """
  2413432311323
  3215453535623
  3255245654254
  3446585845452
  4546657867536
  1438598798454
  4457876987766
  3637877979653
  4654967986887
  4564679986453
  1224686865563
  2546548887735
  4322674655533
  """
  |> String.trim()
puzzle_input = real_input
grid =
  for {row, i} <- puzzle_input |> String.split("\n") |> Stream.with_index(),
      {val, j} <- row |> String.to_charlist() |> Stream.with_index(),
      into: %{},
      do: {{i, j}, val - ?0}
defmodule AoC2023.Day17 do
  @spec part1(%{coord => loss}) :: total_loss
        when coord: {i :: non_neg_integer(), j :: non_neg_integer()},
             loss: pos_integer(),
             total_loss: pos_integer()
  def part1(grid) do
    {dest, _} = Enum.max(grid)

    :gb_sets.empty()
    |> enqueue(grid[{0, 1}] + manhattan({0, 1}, dest), {grid[{0, 1}], {{0, 1}, {0, 1, 1}}})
    |> enqueue(grid[{1, 0}] + manhattan({1, 0}, dest), {grid[{1, 0}], {{1, 0}, {1, 0, 1}}})
    |> total_loss_p1(
      grid,
      dest,
      MapSet.new([
        {{0, 1}, {0, 1, 1}},
        {{1, 0}, {1, 0, 1}}
      ])
    )
  end

  @spec part2(%{coord => loss}) :: total_loss
        when coord: {i :: non_neg_integer(), j :: non_neg_integer()},
             loss: pos_integer(),
             total_loss: pos_integer()
  def part2(grid) do
    {dest, _} = Enum.max(grid)

    :gb_sets.empty()
    |> enqueue(grid[{0, 1}] + manhattan({0, 1}, dest), {grid[{0, 1}], {{0, 1}, {0, 1, 1}}})
    |> enqueue(grid[{1, 0}] + manhattan({1, 0}, dest), {grid[{1, 0}], {{1, 0}, {1, 0, 1}}})
    |> total_loss_p2(
      grid,
      dest,
      MapSet.new([
        {{0, 1}, {0, 1, 1}},
        {{1, 0}, {1, 0, 1}}
      ])
    )
  end

  defp total_loss_p1(pq, grid, {max_i, max_j} = dest, seen) do
    case dequeue(pq) do
      {{loss, {^dest, _}}, _pq} ->
        loss

      {{loss, {{i, j}, {di, dj, steps}}}, pq} ->
        {i2, j2} = {i + dj, j - di}
        pds = {{i2, j2}, {dj, -di, 1}}

        {pq, seen} =
          if i2 in 0..max_i and j2 in 0..max_j and pds not in seen do
            {enqueue(
               pq,
               grid[{i2, j2}] + loss + manhattan({i2, j2}, dest),
               {grid[{i2, j2}] + loss, pds}
             ), MapSet.put(seen, pds)}
          else
            {pq, seen}
          end

        {i2, j2} = {i - dj, j + di}
        pds = {{i2, j2}, {-dj, di, 1}}

        {pq, seen} =
          if i2 in 0..max_i and j2 in 0..max_j and pds not in seen do
            {enqueue(
               pq,
               grid[{i2, j2}] + loss + manhattan({i2, j2}, dest),
               {grid[{i2, j2}] + loss, pds}
             ), MapSet.put(seen, pds)}
          else
            {pq, seen}
          end

        {i2, j2} = {i + di, j + dj}
        pds = {{i2, j2}, {di, dj, steps + 1}}

        {pq, seen} =
          if steps < 3 and i2 in 0..max_i and j2 in 0..max_j and pds not in seen do
            {enqueue(
               pq,
               grid[{i2, j2}] + loss + manhattan({i2, j2}, dest),
               {grid[{i2, j2}] + loss, pds}
             ), MapSet.put(seen, pds)}
          else
            {pq, seen}
          end

        total_loss_p1(pq, grid, dest, seen)
    end
  end

  defp total_loss_p2(pq, grid, {max_i, max_j} = dest, seen) do
    case dequeue(pq) do
      {{loss, {^dest, _}}, _pq} ->
        loss

      {{loss, {{i, j}, {di, dj, steps}}}, pq} ->
        {i2, j2} = {i + dj, j - di}
        pds = {{i2, j2}, {dj, -di, 1}}

        {pq, seen} =
          if steps >= 4 and i2 in 0..max_i and j2 in 0..max_j and pds not in seen do
            {enqueue(
               pq,
               grid[{i2, j2}] + loss + manhattan({i2, j2}, dest),
               {grid[{i2, j2}] + loss, pds}
             ), MapSet.put(seen, pds)}
          else
            {pq, seen}
          end

        {i2, j2} = {i - dj, j + di}
        pds = {{i2, j2}, {-dj, di, 1}}

        {pq, seen} =
          if steps >= 4 and i2 in 0..max_i and j2 in 0..max_j and pds not in seen do
            {enqueue(
               pq,
               grid[{i2, j2}] + loss + manhattan({i2, j2}, dest),
               {grid[{i2, j2}] + loss, pds}
             ), MapSet.put(seen, pds)}
          else
            {pq, seen}
          end

        {i2, j2} = {i + di, j + dj}
        pds = {{i2, j2}, {di, dj, steps + 1}}

        {pq, seen} =
          if steps < 10 and i2 in 0..max_i and j2 in 0..max_j and pds not in seen do
            {enqueue(
               pq,
               grid[{i2, j2}] + loss + manhattan({i2, j2}, dest),
               {grid[{i2, j2}] + loss, pds}
             ), MapSet.put(seen, pds)}
          else
            {pq, seen}
          end

        total_loss_p2(pq, grid, dest, seen)
    end
  end

  defp enqueue(pq, priority, item) do
    :gb_sets.insert({priority, item}, pq)
  end

  defp dequeue(pq) do
    {{_priority, item}, pq} = :gb_sets.take_smallest(pq)
    {item, pq}
  end

  defp manhattan({i1, j1}, {i2, j2}) do
    abs(i1 - i2) + abs(j1 - j2)
  end
end
AoC2023.Day17.part1(grid)
AoC2023.Day17.part2(grid)