Powered by AppSignal & Oban Pro

Day 16: Proboscidea Volcanium

Day 16: Proboscidea Volcanium.livemd

Day 16: Proboscidea Volcanium

Mix.install([
  {:kino, "~> 0.8.0"},
  {:kino_aoc, git: "https://github.com/ljgago/kino_aoc"}
])

Section

{:ok, puzzle_input} = KinoAOC.download_puzzle("2022", "16", System.fetch_env!("LB_WEIZHLIU"))
sample_input = """
Valve AA has flow rate=0; tunnels lead to valves DD, II, BB
Valve BB has flow rate=13; tunnels lead to valves CC, AA
Valve CC has flow rate=2; tunnels lead to valves DD, BB
Valve DD has flow rate=20; tunnels lead to valves CC, AA, EE
Valve EE has flow rate=3; tunnels lead to valves FF, DD
Valve FF has flow rate=0; tunnels lead to valves EE, GG
Valve GG has flow rate=0; tunnels lead to valves FF, HH
Valve HH has flow rate=22; tunnel leads to valve GG
Valve II has flow rate=0; tunnels lead to valves AA, JJ
Valve JJ has flow rate=21; tunnel leads to valve II
"""
defmodule D16 do
  def parse_input_to_valves(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.map(&parse_line/1)
  end

  def parse_line(raw_line) do
    [valve_head, string_tunnels] = String.split(raw_line, ~r"; tunnels? leads? to valves? ")
    ["Valve " <> valve, string_flow_rate] = valve_head |> String.split(" has flow rate=")

    %{
      name: valve,
      tunnels: String.split(string_tunnels, ", "),
      flow_rate: String.to_integer(string_flow_rate)
    }
  end

  def to_digraph(valves) do
    graph = :digraph.new()

    Enum.each(valves, fn valve ->
      :digraph.add_vertex(graph, valve.name)
    end)

    Enum.each(valves, fn valve ->
      Enum.each(valve.tunnels, fn lead_to ->
        :digraph.add_edge(graph, valve.name, lead_to)
      end)
    end)

    graph
  end

  def expected_pressure(current_valve, target_valve, graph, remain_minutes) do
    distance = distance(graph, current_valve.name, target_valve.name)
    (remain_minutes - distance - 1) * target_valve.flow_rate
  end

  def distance(_, from, from), do: 0

  def distance(graph, from, to) do
    graph
    |> :digraph.get_short_path(from, to)
    |> length()
    |> Kernel.-(1)
  end

  def expected_valve_pressures(state, current, valves, graph) do
    valves
    |> Enum.map(&amp;{&amp;1.name, expected_pressure(current, &amp;1, graph, state.remain)})
  end

  def next(state, from, valves, graph) do
    expected_valve_pressures(state, from, valves, graph)
    |> Enum.sort_by(&amp;elem(&amp;1, 1), :desc)
    |> Enum.take(3)
    |> Enum.map(fn {current, value} ->
      elapsed = distance(graph, from.name, current) + 1

      if state.remain - elapsed <= 0 do
        state
      else
        next(
          state
          |> Map.update!(:pressure, &amp;Kernel.+(&amp;1, value))
          |> Map.update!(:remain, &amp;(&amp;1 - elapsed)),
          %{name: current, flow_rate: 0},
          valves |> Enum.reject(&amp;(&amp;1.name == current)),
          graph
        )
      end
    end)
  end

  def next2(state, from1, from2, valves, graph) do
    {to1, value1} =
      to_valve1 =
      expected_valve_pressures(state, from1, valves, graph)
      |> Enum.sort_by(&amp;elem(&amp;1, 1), :desc)
      |> hd()

    elapsed = distance(graph, from1.name, to1) + 1

    if state.remain - elapsed <= 0 do
      state
    else
      state =
        state
        |> Map.update!(:pressure, &amp;Kernel.+(&amp;1, value1))
        |> Map.update!(:remain, &amp;(&amp;1 - (elapsed - 1)))

      valves = valves |> Enum.reject(&amp;(&amp;1.name == to1))

      {to2, value2} =
        to_valve2 =
        expected_valve_pressures(state, from2, valves, graph)
        |> Enum.sort_by(&amp;elem(&amp;1, 1), :desc)
        |> hd()

      elapsed = distance(graph, from2.name, to2) + 1

      if state.remain - elapsed <= 0 do
        state
      else
        state =
          state
          |> Map.update!(:pressure, &amp;Kernel.+(&amp;1, value2))
          |> Map.update!(:remain, &amp;(&amp;1 - elapsed))

        valves = valves |> Enum.reject(&amp;(&amp;1.name == to2))

        next2(state, %{name: to1}, %{name: to2}, valves, graph)
      end
    end
  end

  def part1(valves) do
    graph = to_digraph(valves)
    state = %{remain: 30, opend_valves: [], pressure: 0}

    next(state, %{name: "AA", flow_rate: 0}, valves, graph)
    |> List.flatten()
    |> Enum.sort_by(&amp; &amp;1.pressure, :desc)
  end

  def part2(valves) do
    graph = to_digraph(valves)
    state = %{remain: 26, opend_valves: [], pressure: 0}

    next2(state, %{name: "AA", flow_rate: 0}, %{name: "AA", flow_rate: 0}, valves, graph)
    # |> List.flatten()
    # |> Enum.sort_by(& &1.pressure, :desc)
  end
end

puzzle_input
|> D16.parse_input_to_valves()
|> D16.part1()

# puzzle_input
# |> D16.parse_input_to_valves()
# |> D16.part2()