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

Day 12: Passage Pathing

2021/elixir/day-12.livemd

Day 12: Passage Pathing

Setup

Mix.install([
  {:kino, "~> 0.4.1"}
])

Input

input = Kino.Input.textarea("Input")

Part 1

defmodule M do
  def parse(input) do
    for p <- String.split(input, "\n", trim: true) do
      [a, b] = String.split(p, "-", trim: true)
      {a, b} = {String.to_atom(a), String.to_atom(b)}
      [{a, b}, {b, a}]
    end
    |> List.flatten()
  end

  def traveled?(path, cave) do
    if String.downcase(Atom.to_string(cave)) == Atom.to_string(cave) do
      Enum.member?(path, cave)
    else
      false
    end
  end

  def travel(paths) do
    travel(paths, :start, [])
  end

  def travel(paths, cave, path) do
    path = path ++ [cave]
    adjacent = Keyword.get_values(paths, cave)

    if cave == :end do
      IO.inspect(path)
      path
    else
      for c <- adjacent do
        if !traveled?(path, c) do
          travel(paths, c, path)
        end
      end
    end
  end
end

Kino.Input.read(input)
|> M.parse()
|> M.travel()
|> List.flatten()
|> Enum.dedup()
|> Enum.count(fn c -> c == :start end)

Part 2

defmodule M do
  def parse(input) do
    for p <- String.split(input, "\n", trim: true) do
      [a, b] = String.split(p, "-", trim: true)
      {a, b} = {String.to_atom(a), String.to_atom(b)}
      [{a, b}, {b, a}]
    end
    |> List.flatten()
  end

  def traveled?(path, cave) do
    cond do
      cave in [:start, :end] ->
        Enum.member?(path, cave)

      String.upcase(Atom.to_string(cave)) == Atom.to_string(cave) ->
        false

      true ->
        Enum.frequencies(path)
        |> Enum.any?(fn {k, v} ->
          String.downcase(Atom.to_string(k)) == Atom.to_string(k) and
            v >= 2 and
            Enum.member?(path, cave)
        end)
    end
  end

  def travel(paths) do
    travel(paths, :start, [])
  end

  def travel(paths, cave, path) do
    path = path ++ [cave]
    adjacent = Keyword.get_values(paths, cave)

    if cave == :end do
      # IO.inspect(path)
      path
    else
      for c <- adjacent do
        if !traveled?(path, c) do
          travel(paths, c, path)
        end
      end
    end
  end
end

Kino.Input.read(input)
|> M.parse()
|> M.travel()
|> List.flatten()
|> Enum.dedup()
|> Enum.count(fn c -> c == :start end)