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

Day 12

2021/day_12.livemd

Day 12

Input

Mix.install([{:kino, github: "livebook-dev/kino"}])
textarea = Kino.Input.textarea("Input:")

Common

defmodule Common do
  def parse_input(raw_input) do
    raw_input
    |> String.split("\n", trim: true)
    |> Enum.map(fn line ->
      line
      |> String.split("-", trim: true)
    end)
    |> Enum.reduce(%{}, fn [from, to], acc ->
      acc
      |> Map.update(from, [to], &[to | &1])
      |> Map.update(to, [from], &[from | &1])
    end)
  end

  def paths(_input, ["end" | path], _decision_fun), do: [Enum.reverse(path)]

  def paths(input, [position | _tail] = path, decision_fun) do
    case input[position] do
      next_positions ->
        Enum.reduce(next_positions, [], fn
          next_position, paths ->
            paths ++
              if decision_fun.(path, next_position) do
                paths(input, [next_position | path], decision_fun)
              else
                []
              end
        end)
    end
  end

  def is_big_cave?(cave), do: cave =~ ~r/^[A-Z]+$/
  def is_small_cave?(cave), do: not is_big_cave?(cave)
end
raw_input = Kino.Input.read(textarea)
input = Common.parse_input(raw_input)

Part 1

defmodule Part1 do
  def run(input) do
    input
    |> Common.paths(["start"], &can_be_added?/2)
    |> Enum.count()
  end

  def can_be_added?(_path, "start"), do: false
  def can_be_added?(_path, "end"), do: true

  def can_be_added?(path, position) do
    Common.is_big_cave?(position) or not Enum.member?(path, position)
  end
end
Part1.run(input)

Part 2

defmodule Part2 do
  def run(input) do
    input
    |> Common.paths(["start"], &can_be_added?/2)
    |> Enum.count()
  end

  def can_be_added?(_path, "start"), do: false
  def can_be_added?(_path, "end"), do: true

  def can_be_added?(path, position) do
    if Common.is_big_cave?(position) do
      true
    else
      if already_has_two_small_caves?(path) do
        if already_has_cave?(path, position) do
          false
        else
          true
        end
      else
        true
      end
    end
  end

  def already_has_cave?(path, position) do
    Enum.member?(path, position)
  end

  def already_has_two_small_caves?(path) do
    path
    |> Enum.filter(&Common.is_small_cave?(&1))
    |> Enum.frequencies()
    |> Enum.any?(&(elem(&1, 1) == 2))
  end
end
Part2.run(input)