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

Day 12

live/day12.livemd

Day 12

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

Input

input = Kino.Input.textarea("In put here:")
map =
  input
  |> Kino.Input.read()
  |> String.split("\n", strip: true)
  |> Enum.map(&(String.split(&1, "-") |> List.to_tuple()))

defmodule Cave do
  def neighbors(node, map) do
    Enum.flat_map(map, fn
      {^node, a} -> [a]
      {a, ^node} -> [a]
      {_, _} -> []
    end)
  end

  def split(list) do
    Enum.group_by(list, fn x ->
      cond do
        x == "start" -> :start
        x == "end" -> :end
        x == String.upcase(x) -> :big
        x == String.downcase(x) -> :small
      end
    end)
  end

  def can_add?(x, path, maxcnt) do
    caves = path |> split

    if Map.has_key?(caves, :small) do
      duplicate = Enum.frequencies([x | caves.small]) |> Enum.filter(&(elem(&1, 1) > 1))

      case duplicate do
        [] -> true
        [{_, cnt}] -> cnt <= maxcnt
        _ -> false
      end
    else
      true
    end
  end

  def search(map, maxcnt \\ 1), do: search(map, "start", ["start"], maxcnt)

  def search(map, node, path, maxcnt) do
    paths = neighbors(node, map) |> split

    small_paths =
      if Map.has_key?(paths, :small) do
        Enum.flat_map(
          paths.small
          |> Enum.filter(fn x ->
            can_add?(x, path, maxcnt)
          end),
          fn x ->
            search(map, x, [x | path], maxcnt)
          end
        )
      else
        []
      end

    large_paths =
      if Map.has_key?(paths, :big) do
        Enum.flat_map(
          paths.big,
          fn x ->
            search(map, x, [x | path], maxcnt)
          end
        )
      else
        []
      end

    if Map.has_key?(paths, :end) do
      Enum.concat([[["end" | path]], small_paths, large_paths])
    else
      small_paths ++ large_paths
    end
  end
end

Part 1

Cave.search(map) |> IO.inspect() |> Enum.count()

Part 2

Cave.search(map, 2) |> IO.inspect() |> Enum.count()