Advent of Code 2023 - Day 12

I tried to use combinatorial to solve today’s puzzles but failed (my brain burned out :exploding_head:). In the end I just used brute force with memoization.

Task.async_stream turned out to be very helpful. It both let me handle each line of input concurrently, and allows me to abuse process dictionaries :grin:

defmodule AoC2023.Day12 do

  # `input` for both parts are things like
  #
  # [
  #   {"???.###", [1,1,3]},
  #   {".??..??...###.", [1,1,3]},
  #   ...
  # ]

  @spec part1([{String.t(), [pos_integer()]}]) :: non_neg_integer()
  def part1(input) do
    input
    |> Task.async_stream(fn {springs, counts} ->
      aux(springs, ".", counts)
    end, ordered: false)
    |> Stream.map(&elem(&1, 1))
    |> Enum.sum()
  end

  @spec part2([{String.t(), [pos_integer()]}]) :: non_neg_integer()
  def part2(input) do
    input
    |> Enum.map(fn {springs, counts} ->
      {
        List.duplicate(springs, 5) |> Enum.join("?"),
        List.duplicate(counts, 5) |> List.flatten()
      }
    end)
    |> part1()
  end

  @spec aux(
    springs :: String.t(),
    previous_spring :: String.t(),
    counts :: [pos_integer()]
  ) :: non_neg_integer()
  defp aux("", _, []), do: 1

  defp aux("", _, [0]), do: 1

  defp aux("", _, _), do: 0

  defp aux("#" <> _, _, []), do: 0

  defp aux("#" <> _, _, [0 | _]), do: 0

  defp aux("#" <> rest, _, [h | t]), do: aux(rest, "#", [h - 1 | t])

  defp aux("." <> rest, _, []), do: aux(rest, ".", [])

  defp aux("." <> rest, "#", [0 | t]), do: aux(rest, ".", t)

  defp aux("." <> _, "#", [_ | _]), do: 0

  defp aux("." <> rest, ".", counts), do: aux(rest, ".", counts)

  defp aux("?" <> rest, "#", []), do: aux(rest, ".", [])

  defp aux("?" <> rest, "#", [0 | t]), do: aux(rest, ".", t)

  defp aux("?" <> rest, "#", [h | t]), do: aux(rest, "#", [h - 1 | t])

  defp aux("?" <> rest, ".", []), do: aux(rest, ".", [])

  defp aux("?" <> rest, ".", [0 | t]), do: aux(rest, ".", t)

  defp aux("?" <> rest, ".", [h | t]) do
    memoized({rest, [h | t]}, fn ->
      aux(rest, "#", [h - 1 | t]) + aux(rest, ".", [h | t])
    end)
  end

  defp memoized(key, fun) do
    with nil <- Process.get(key) do
      fun.() |> tap(&Process.put(key, &1))
    end
  end
end