Powered by AppSignal & Oban Pro

Day 12

2022/day12.livemd

Day 12

Mix.install([
  {:kino_aoc, git: "https://github.com/ljgago/kino_aoc"},
  {:libgraph, ">= 0.0.0"}
])
:ok

Section

{:ok, puzzle_input} =
  KinoAOC.download_puzzle("2022", "12", System.fetch_env!("LB_ADVENT_OF_CODE_SESSION"))
{:ok,
 "abccccaaacaccccaaaaacccccccaaccccccccaaaaaaccccccaaaaaccccccccccaaaaaaaaacccccccaaaaaaaaaaaaaaccaaaaaccccccccccccaccacccccccccccccccccccccccccccccccccccccccaaaaaa\nabccaacaaaaaccaaaaacccccaaaaaccccccccaaaaaaccccccaaaaaacccccccccaaaaaaaaaaaaacccaaaaaaaaaaaaaaaaaaaaaccccccccccccaaaacccccccccccccccccccccccccccccccccccccccaaaaaa\nabccaaaaaaaaccaaaaaacccccaaaaaccccccaaaaaaaacccccaaaaaaccccccccccaaaaaaaaaaaacccaaaaaacaaaaaacaaaaaaaaccccccccccaaaaacccccaccccccccccccccccccaaacccccccccccccaaaaa\nabcccaaaaaccccccaaaacccccaaaaacccccaaaaaaaaaaccccaaaaaacccccccccaaaaaaaaaaaaaacaaaaaaaaaaaaaacaaaaaaaaccccccccccaaaaaacccaaacccccccccccccccccaaaccccccccccccccaaaa\nabaaacaaaaacccccacccccccaaaaaccccccaaaaaaaaaaccccccaaaccccccccccaaaaaaaaacaaaaaaaaaaaaaaaaaaacccaaacaccaaaccccccaaaaaaaacaaacccccccccccaaccccaaacccccccccccccccaac\nabaaacaacaaaaccccccccccccccaaccccccacaaaaacccccaacccccccccccccccaaaacaaaaaaaaaacccaacccaaacaacccaaccccaaaaccccccccaacaaaaaaaaaaccccccccaaaaccaaaccccccccccccccaaac\nabaaccaaccaaacacccccccccccccccccccccccaaaacccaaaaaaccaaaccccccccccaacaaaaaaaaaacccaaccccccccccccccccccaaaaccccccccccccaaaaaaaaaccccccciiiiiaaaaacccccccccccccccccc\nabaaccccaaaaaaaacccccccccccccccccccccccaaccccaaaaaaccaaaaaccccacccaaccaaacaaaaacccccccccccccccaacccccccaaaccccccccccccccaaaaacccccccciiiiiiiiaaaaaccccccaaaccccccc\nabaaacccaaaaaaaacccccccccccccccccccccccccccccaaaaaacaaaaaccccaaaaaaaccaaccaaacccccccaaaaacacccaaccccccccccaacccccccccccaaaaaaccccccciiiiiiiiijjaaaaaccccaaacaccccc\nabaaaccccaaaaaaccccccccccccccccccccaaccccccccaaaaaccaaaaacccccaaaaaaaaccccccccccccccaaaaaaaaaaaaccccccccccaaacaaccccccaaaaaaaccccccciiinnnnoijjjjjjjjjjaaaaaaacccc\nabccccccccaaaaacccccaacccccccccccaaaacccccccccaaaacccaaaaaccccaaaaaaaaacccccccccccccaaaaaaaaaaaaaaccccccccaaaaaacccaacaaacaaacccccchhinnnnnoojjjjjjjjjkkaaaaaacccc\nabcccccccaaaaaacaaacaacccccccccccaaaaaaccccccccccccccaacccccccaaaaaaaaacaaccccccccccaaaaaaaaaaaaaaacccccaaaaaaacccaaaaccccccacaaccchhinnnnnoooojjjjjjkkkkaaaaccccc\nabaacccaccaaaccccaaaaaccccccccccccaaaaccccccccccccccccccccccccaaaaaaaacaaaaaaaccccccaaaaaaaaaaaaaaacccccaaaaaaacccaaaaccccaaacaaachhhnnntttuooooooooppkkkaaaaccccc\nabaacccaaaaaaaccccaaaaaacccccccccaaaaaccccccccccccccccccccccccaaaaaaacccaaaaacccccccccaaacaaaaaaaaccccccccaaaaacccaaaacccccaaaaacchhhnnnttttuooooooppppkkkaaaccccc\nabaacccaaaaaaccccaaaaaaacccccccccaacaaccccccccccccccccccccccaaaccaaaccaaaaaaacccccccccccccaaaaaaaccccccaacaacaaacccccccccccaaaaaahhhhnntttttuuouuuuupppkkkcccccccc\nabaaaacaaaaaaaaaaaaaaacccccccccccccccccccccccccccccccccccccaaaacccaaacaaaaaaaaccccccccccccaccaaaccccccaaacaaccccccccccccccaaaaaahhhhnnntttxxxuuuuuuupppkkkcccccccc\nabaaaacaaaaaaaaaaacaaacccaaacccccccccccccccccccccacccccccccaaaacccccccaaaaaaaaccccccccccccccccaaacccccaaacaaacccccccccccccaaaaaahhhhmnnttxxxxuuyuuuuuppkkkcccccccc\nabaaaccaaaaaaaaccccaaaccccaaaccacccccccccccaaaaaaaaccccccccaaaacccccccccaaacaacccccccccccccccccccccaaaaaaaaaacccccacccccccaacaghhhmmmmtttxxxxxxyyyuupppkkccccccccc\nabaaccaaaaaaaccccccccccccaaaaaaaacccccccccccaaaaaaccccccccccccccccccccccaaccccccaacccccccccccccccccaaaaaaaaacccccaaccccccccccagggmmmmttttxxxxxyyyyvvppkkkccccccccc\nabaacccaccaaacccccccccccaaaaaaaaccccccccccccaaaaaaccccccccccccccccccccccccccaaacaaaccccccccccccccccccaaaaaccccaaaaacaacccccccgggmmmmttttxxxxxyyyyvvpppiiiccccccccc\nSbaaaaaccccaaccccccccccaaaaaaaaacacccccccccaaaaaaaacccccccccccccccaacccccccccaaaaaccccccccccaaaacccccaaaaaacccaaaaaaaaccaaaccgggmmmsssxxxEzzzzyyvvvpppiiiccccccccc\nabaaaaaccccccccccccccccaaaaaaaaaaaaaaaaaccaaaaaaaaaacccccccccccaaaaacccccccccaaaaaaaccccccccaaaaaccccaaaaaaaccccaaaaacccaaaaagggmmmsssxxxxxyyyyyyvvqqqiiiccccccccc\nabaaaaacccccccccccccccccaaaaaaaacaaaaaacccaaaaaaaaaaccccccccccccaaaaacccccccaaaaaaaacccccccaaaaaaccccaaacaaacccaaaaacccaaaaaagggmmmssswwwwwyyyyyyyvvqqqiiicccccccc\nabaaaaccccccccccccccccccccaaaaaaaaaaaaacccacacaaacccccccccccccccaaaaacccccccaaaaaaaacccccccaaaaaaccccacccccccccaacaaaccaaaaaagggmmmsssswwwwyyyyyyyvvvqqiiicccccccc\nabaaaaacccccccccccccccccccaacccaaaaaaaaaccccccaaaccccccccccccccaaaaaccccccccaacaaacccccccccaaaaaacccccccccccccccccaaccccaaaaagggmmmmssssswwyywwvvvvvvqqiiicccccccc\nabaaaaacccccccccccccc" <> ...}
# puzzle_input = """
# Sabqponm
# abcryxxl
# accszExk
# acctuvwj
# abdefghi
# """
nil
min = ?a - ?a
max = ?z - ?a

{map, start, stop} =
  puzzle_input
  |> String.split("\n", trim: true)
  |> Enum.map(&to_charlist/1)
  |> Enum.with_index()
  |> Enum.flat_map(fn {line, y} ->
    line
    |> Enum.with_index()
    |> Enum.map(fn {c, x} -> {{x, y}, c} end)
  end)
  |> Enum.reduce({%{}, nil, nil}, fn {p, v}, {map, s, e} ->
    case v do
      ?S -> {Map.put(map, p, min), p, e}
      ?E -> {Map.put(map, p, max), s, p}
      _ -> {Map.put(map, p, v - ?a), s, e}
    end
  end)

graph =
  map
  |> Enum.reduce(Graph.new(), fn {p, v}, graph ->
    {x, y} = p

    graph =
      if map[{x - 1, y}] && v + 1 >= map[{x - 1, y}],
        do: Graph.add_edge(graph, {x, y}, {x - 1, y}, label: [v, map[{x - 1, y}]]),
        else: graph

    graph =
      if map[{x + 1, y}] && v + 1 >= map[{x + 1, y}],
        do: Graph.add_edge(graph, {x, y}, {x + 1, y}, label: [v, map[{x + 1, y}]]),
        else: graph

    graph =
      if map[{x, y - 1}] && v + 1 >= map[{x, y - 1}],
        do: Graph.add_edge(graph, {x, y}, {x, y - 1}, label: [v, map[{x, y - 1}]]),
        else: graph

    graph =
      if map[{x, y + 1}] && v + 1 >= map[{x, y + 1}],
        do: Graph.add_edge(graph, {x, y}, {x, y + 1}, label: [v, map[{x, y + 1}]]),
        else: graph
  end)
warning: variable "graph" is unused (there is a variable with the same name in the context, use the pin operator (^) to match on it or prefix this variable with underscore if it is not meant to be used)
  2022/day12.livemd#cell:6nz6sdhe57lxfvshc57dctj7qaalwwz3:42
#Graph<type: directed, num_vertices: 6642, num_edges: 24117>

Task 1

length(Graph.get_shortest_path(graph, start, stop)) - 1
437
graph.vertices
%{
  1148931603 => {118, 31},
  15476201 => {158, 33},
  3480980581 => {19, 26},
  513279067 => {86, 0},
  1642831488 => {111, 2},
  669567979 => {38, 18},
  1898334127 => {77, 28},
  2414760132 => {41, 27},
  1433989350 => {134, 33},
  4010762779 => {100, 37},
  1619384061 => {142, 27},
  2736818014 => {105, 29},
  3132807327 => {56, 13},
  1603662291 => {94, 23},
  183280155 => {35, 27},
  2518352043 => {127, 27},
  824501714 => {15, 33},
  1813555375 => {70, 18},
  733666720 => {72, 5},
  4293490381 => {13, 24},
  4037128590 => {87, 10},
  1287605231 => {99, 3},
  2697150767 => {19, 29},
  3345146674 => {78, 5},
  811252706 => {132, 14},
  3035418479 => {156, 5},
  2090318173 => {149, 1},
  1306366779 => {71, 39},
  113692275 => {158, 28},
  1809958846 => {4, 0},
  2979249361 => {95, 23},
  712034521 => {2, 34},
  838646858 => {127, 2},
  2636390019 => {97, 34},
  200998860 => {73, 23},
  2440897975 => {91, 7},
  2757930521 => {103, 38},
  2626031906 => {92, 21},
  4000959801 => {44, 20},
  2762901234 => {69, 16},
  3967362516 => {72, 9},
  1858208296 => {81, 16},
  3063825234 => {140, 14},
  1669140133 => {46, 21},
  684221776 => {146, 38},
  623366404 => {63, 40},
  3623360588 => {149, 6},
  2885933829 => {137, 14},
  3219344086 => {136, ...},
  3451324297 => {...},
  ...
}

Task 2

tgraph = Graph.transpose(graph)
#Graph<type: directed, num_vertices: 6642, num_edges: 24117>
lowest =
  Enum.flat_map(map, fn
    {p, 0} -> [p]
    _ -> []
  end)
[
  {59, 38},
  {38, 5},
  {76, 17},
  {66, 36},
  {54, 26},
  {10, 18},
  {3, 21},
  {42, 40},
  {73, 32},
  {43, 36},
  {2, 9},
  {80, 31},
  {20, 12},
  {63, 11},
  {5, 15},
  {76, 4},
  {136, 4},
  {3, 9},
  {90, 11},
  {85, 39},
  {24, 29},
  {91, 28},
  {55, 27},
  {108, 10},
  {84, 4},
  {9, 9},
  {161, 38},
  {3, 33},
  {68, 22},
  {143, 3},
  {49, 8},
  {70, 26},
  {39, 25},
  {18, 13},
  {88, 0},
  {32, 21},
  {96, 3},
  {84, 40},
  {53, 1},
  {88, 30},
  {120, 10},
  {36, 4},
  {115, 13},
  {112, 3},
  {42, 0},
  {4, 17},
  {35, 12},
  {114, 19},
  {20, ...},
  {...},
  ...
]
# Copied BF implementation from `libgraph` to optimise it 

defmodule BellmanFord do
  @moduledoc """
  The Bellman–Ford algorithm is an algorithm that computes shortest paths from a single
  source vertex to all of the other vertices in a weighted digraph.
  It is capable of handling graphs in which some of the edge weights are negative numbers
  Time complexity: O(VLogV)
  """

  @type distance :: %{Graph.vertex_id() => integer}

  @doc """
  Returns nil when graph has negative cycle.
  """
  @spec call(Graph.t(), Graph.vertex()) :: [Graph.vertex()] | nil
  def call(%Graph{vertices: vs, edges: meta}, source) do
    distances = source |> Graph.Utils.vertex_id() |> init_distances(vs)

    weights = Enum.map(meta, &edge_weight/1)

    distances =
      for _ <- 1..map_size(vs),
          {{u, v}, weight} <- weights,
          reduce: distances do
        distances ->
          case distances do
            %{^u => :infinity} ->
              distances

            %{^u => du, ^v => dv} when du + weight < dv ->
              %{distances | v => du + weight}

            _ ->
              distances
          end
      end

    if has_negative_cycle?(distances, weights) do
      nil
    else
      Map.new(distances, fn {k, v} -> {Map.fetch!(vs, k), v} end)
    end
  end

  @spec init_distances(Graph.vertex(), Graph.vertices()) :: distance
  defp init_distances(vertex_id, vertices) do
    Map.new(vertices, fn
      {id, _vertex} when id == vertex_id -> {id, 0}
      {id, _} -> {id, :infinity}
    end)
  end

  @spec edge_weight(term) :: float
  defp edge_weight({e, edge_value}), do: {e, edge_value |> Map.values() |> List.first()}

  defp has_negative_cycle?(%{} = distances, meta) do
    Enum.any?(meta, fn {{u, v}, weight} ->
      %{^u => du, ^v => dv} = distances

      du != :infinity and du + weight < dv
    end)
  end
end
{:module, BellmanFord, <<70, 79, 82, 49, 0, 0, 18, ...>>, {:has_negative_cycle?, 2}}
paths =
  tgraph
  # |> Graph.bellman_ford(stop)
  |> BellmanFord.call(stop)
%{
  {76, 13} => :infinity,
  {38, 2} => :infinity,
  {1, 26} => 430,
  {140, 11} => 100,
  {32, 15} => 388,
  {89, 14} => 328,
  {35, 30} => 392,
  {156, 9} => :infinity,
  {4, 5} => 454,
  {74, 12} => :infinity,
  {11, 39} => 425,
  {131, 5} => 323,
  {22, 38} => 413,
  {29, 25} => 403,
  {86, 10} => :infinity,
  {83, 36} => 366,
  {29, 26} => 402,
  {47, 27} => :infinity,
  {9, 34} => 426,
  {137, 16} => 12,
  {90, 0} => :infinity,
  {103, 39} => 289,
  {126, 13} => :infinity,
  {47, 38} => :infinity,
  {128, 35} => 268,
  {20, 3} => 412,
  {145, 20} => 24,
  {143, 39} => 247,
  {79, 17} => 335,
  {75, 0} => :infinity,
  {150, 18} => 167,
  {147, 10} => 178,
  {148, 26} => 75,
  {76, 2} => :infinity,
  {138, 9} => 104,
  {58, 33} => 372,
  {150, 5} => 296,
  {54, 31} => 374,
  {22, 37} => 412,
  {75, 36} => :infinity,
  {91, 35} => :infinity,
  {143, 4} => :infinity,
  {121, 27} => 283,
  {91, 38} => :infinity,
  {159, 26} => 268,
  {131, 24} => 124,
  {58, 10} => 367,
  {111, 14} => :infinity,
  {75, ...} => 345,
  {...} => 411,
  ...
}
paths
|> Map.take(lowest)
|> Map.values()
|> Enum.min()
430