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

aoc 2021 day 15

2021/elixir/day-15.livemd

aoc 2021 day 15

Setup

Mix.install([{:kino, "~> 0.4.1"}, {:heap, "~> 2.0"}])
input = Kino.Input.textarea("input")

Part 1

defmodule PQ do
  def new do
    []
  end

  def add([{w, _v} = h | tail], {new_weight, pos}) when new_weight > w do
    [h | add(tail, {new_weight, pos})]
  end

  def add(pq, pos), do: [pos | pq]
end

defmodule PathFinder do
  @directions [{0, 1}, {1, 0}, {-1, 0}, {0, -1}]

  def shortest(graph, start_coord, end_coord) do
    queue = PQ.new()
    distance_map = %{start_coord => 0}
    queue = PQ.add(queue, {0, {0, 0}})

    find_path(queue, end_coord, distance_map, graph)
  end

  def find_path([{_, end_coord} | _rest_queue], end_coord, distance_map, _graph) do
    distance_map[end_coord]
  end

  def find_path([{cur_distance, {cur_x, cur_y}} | rest_queue], end_coord, distance_map, graph) do
    {next_distance_map, next_queue} =
      for {dx, dy} <- @directions,
          {nx, ny} = {cur_x + dx, cur_y + dy},
          is_map_key(graph, {nx, ny}),
          next_distance = cur_distance + graph[{nx, ny}],
          next_distance < Map.get(distance_map, {nx, ny}, :infinity),
          reduce: {distance_map, rest_queue} do
        {map, nq} ->
          next_map = Map.put(map, {nx, ny}, next_distance)
          next_queue = PQ.add(nq, {next_distance, {nx, ny}})
          {next_map, next_queue}
      end

    find_path(next_queue, end_coord, next_distance_map, graph)
  end
end
coord_map =
  input
  |> Kino.Input.read()
  |> String.split("\n", trim: true)
  |> Enum.with_index()
  |> Enum.flat_map(fn {line, y} ->
    line
    |> String.to_charlist()
    |> Enum.with_index(0)
    |> Enum.map(fn {v, x} -> {{x, y}, v - ?0} end)
  end)
  |> Enum.into(%{})

start_point = {0, 0}
end_point = Enum.max(coord_map) |> elem(0)

shortest_path_map = %{start_point => 0}

queue = [start_point]

###

# coord_map
PathFinder.shortest(coord_map, start_point, end_point)

Part 2

defmodule PathFinder2.Heap do
  @directions [{0, 1}, {1, 0}, {-1, 0}, {0, -1}]

  def find_path(heap, end_coord, shortest_path_map, coord_finder) do
    case Heap.empty?(heap) do
      false ->
        {cur_risk, {cur_x, cur_y}} = Heap.root(heap)
        heap = Heap.pop(heap)

        unless {cur_x, cur_y} == end_coord do
          {next_shortest_path_map, next_heap} =
            for {dx, dy} <- @directions,
                risk = coord_finder.({cur_x + dx, cur_y + dy}),
                reduce: {shortest_path_map, heap} do
              {map, nq} ->
                {nx, ny} = {cur_x + dx, cur_y + dy}

                if map[{nx, ny}] > cur_risk + risk do
                  {map |> Map.put({nx, ny}, cur_risk + risk),
                   nq |> Heap.push({cur_risk + risk, {nx, ny}})}
                else
                  {map, nq}
                end
            end

          find_path(next_heap, end_coord, next_shortest_path_map, coord_finder)
        else
          find_path(heap, end_coord, shortest_path_map, coord_finder)
        end

      true ->
        shortest_path_map[end_coord]
    end

    # IO.inspect(target, label: "#{cur_x}, #{cur_y}")
    # find_path(rest_queue ++ List.wrap(target), end_coord, next_shortest_path_map, coord_finder)
  end
end
defmodule PathFinder2.PQ do
  @directions [{0, 1}, {1, 0}, {-1, 0}, {0, -1}]

  def shortest(coord_finder, start_coord, end_coord) do
    queue = PQ.new()
    distance_map = %{start_coord => 0}
    queue = PQ.add(queue, {0, {0, 0}})

    find_path(queue, end_coord, distance_map, coord_finder)
  end

  def find_path([{_, end_coord} | _rest_queue], end_coord, distance_map, _graph) do
    distance_map[end_coord]
  end

  def find_path(
        [{cur_distance, {cur_x, cur_y}} | rest_queue],
        end_coord,
        distance_map,
        coord_finder
      ) do
    {next_distance_map, next_queue} =
      for {dx, dy} <- @directions,
          {nx, ny} = {cur_x + dx, cur_y + dy},
          next_risk = coord_finder.({nx, ny}),
          not is_nil(next_risk),
          next_distance = cur_distance + next_risk,
          next_distance < Map.get(distance_map, {nx, ny}, :infinity),
          reduce: {distance_map, rest_queue} do
        {map, nq} ->
          next_map = Map.put(map, {nx, ny}, next_distance)
          next_queue = PQ.add(nq, {next_distance, {nx, ny}})
          {next_map, next_queue}
      end

    find_path(next_queue, end_coord, next_distance_map, coord_finder)
  end
end
coord_map =
  input
  |> Kino.Input.read()
  |> String.split("\n", trim: true)
  |> Enum.with_index()
  |> Enum.flat_map(fn {line, y} ->
    line
    |> String.to_charlist()
    |> Enum.with_index(0)
    |> Enum.map(&amp;{{elem(&amp;1, 1), y}, elem(&amp;1, 0) - ?0})
  end)
  |> Enum.into(%{})

start_point = {0, 0}
{max_x, max_y} = Enum.max(coord_map) |> elem(0)
length_x = max_x + 1
length_y = max_y + 1

coord_finder = fn {x, y} ->
  if x >= 0 and y >= 0 and x < length_x * 5 and y < length_y * 5 do
    {ox, xp} = {rem(x, length_x), div(x, length_x)}

    {oy, yp} = {rem(y, length_y), div(y, length_y)}

    rem(coord_map[{ox, oy}] + xp + yp - 1, 9) + 1
  else
    nil
  end
end

PathFinder2.PQ.shortest(coord_finder, {0, 0}, {length_x * 5 - 1, length_y * 5 - 1})

# {np, nq} = PathFinder2.find_path(nq, end_point, np, coord_finder)