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

Day 12

2021/day-12.livemd

Day 12

Setup

Mix.install([{:kino, "~> 0.4.1"}])
example_input =
  Kino.Input.textarea("example input:")
  |> Kino.render()

real_input = Kino.Input.textarea("real input:")

Part 1

defmodule Graph do
  def new(input) do
    input
    |> Enum.flat_map(fn [from, to] ->
      [
        {from, %{neighbors: [to] -- ["start"], revisitable?: revisitable?(from)}},
        {to, %{neighbors: [from] -- ["start"], revisitable?: revisitable?(to)}}
      ]
    end)
    |> Enum.reduce(%{}, fn {from, map}, graph ->
      Map.update(graph, from, map, fn node ->
        %{node | neighbors: node.neighbors ++ map.neighbors}
      end)
    end)
  end

  def paths(_graph, ["end" | _] = paths, _seen), do: [Enum.reverse(paths)]

  def paths(graph, [key | _] = paths, seen) do
    node = graph[key]
    seen = if node.revisitable?, do: seen, else: MapSet.put(seen, key)
    next = for neighbor <- node.neighbors, neighbor not in seen, do: neighbor

    Enum.flat_map(next, fn neighbor ->
      paths(graph, [neighbor | paths], seen)
    end)
  end

  defp revisitable?(<>) when c in ?A..?Z, do: true
  defp revisitable?(_), do: false
end
real_input
|> Kino.Input.read()
|> String.split(["\n", "-"])
|> Enum.chunk_every(2)
|> Graph.new()
|> Graph.paths(["start"], MapSet.new())
|> Enum.count()

Part 2

defmodule Graph2 do
  def new(input) do
    input
    |> Enum.flat_map(fn [from, to] ->
      [
        {from, %{neighbors: [to] -- ["start"], revisitable?: revisitable?(from)}},
        {to, %{neighbors: [from] -- ["start"], revisitable?: revisitable?(to)}}
      ]
    end)
    |> Enum.reduce(%{}, fn {from, map}, graph ->
      Map.update(graph, from, map, fn node ->
        %{node | neighbors: node.neighbors ++ map.neighbors}
      end)
    end)
  end

  def paths(_graph, ["end" | _] = paths, _seen, _twice), do: [Enum.reverse(paths)]

  def paths(graph, [key | _] = paths, seen, twice) do
    node = graph[key]

    next =
      for neighbor <- node.neighbors,
          visitable?(node.revisitable?, key in seen, is_nil(twice)),
          do: neighbor

    twice = if !node.revisitable? and key in seen and is_nil(twice), do: key, else: twice
    seen = if node.revisitable?, do: seen, else: MapSet.put(seen, key)

    Enum.flat_map(next, fn neighbor ->
      paths(graph, [neighbor | paths], seen, twice)
    end)
  end

  defp revisitable?(<>) when c in ?A..?Z, do: true
  defp revisitable?(_), do: false

  defp visitable?(false, true, false), do: false
  defp visitable?(_, _, _), do: true
end
real_input
|> Kino.Input.read()
|> String.split(["\n", "-"])
|> Enum.chunk_every(2)
|> Graph2.new()
|> Graph2.paths(["start"], MapSet.new(), nil)
|> Enum.count()