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

Day12

2023/elixir/day12.livemd

Day12

Mix.install([
  {:kino_aoc, git: "https://github.com/ljgago/kino_aoc"},
  {:benchee, "~> 1.0"},
  {:nimble_parsec, "~> 1.0"},
  {:libgraph, "~> 0.16.0"},
  {:math, "~> 0.7.0"}
])

Get Input

{:ok, data} = KinoAOC.download_puzzle("2023", "12", System.fetch_env!("LB_AOC_SECRET"))

Solve

defmodule Day12 do
  def out(res, t), do: IO.puts("Res #{t}: #{res}")

  def run(data, :p1) do
    data
    |> String.split("\n", trim: true)
    |> parse()
    |> Enum.map(&count/1)
    |> Enum.sum()
  end

  def run(data, :p2) do
    data
    |> String.split("\n", trim: true)
    |> parse()
    |> Enum.map(&unfold/1)
    |> Enum.map(&count/1)
    |> Enum.sum()
  end

  def parse(rows) do
    rows
    |> Enum.map(fn row ->
      [s, d] = String.split(row)
      d = for sym <- String.split(d, ",", trim: true), do: String.to_integer(sym)
      {s, List.to_tuple(d), false}
    end)
  end

  def unfold({str, gr, false}) do
    str = str |> List.duplicate(5) |> Enum.join("?")
    gr = Tuple.to_list(gr) |> List.duplicate(5) |> List.flatten() |> List.to_tuple()
    {str, gr, false}
  end

  def count({"", {}, _}), do: 1
  def count({"", {0}, _}), do: 1
  def count({"", _rest, _}), do: 0
  def count({str, {}, _}), do: (in?("#", str) &amp;&amp; 0) || 1

  def count(key) do
    case :ets.lookup(:springs, key) do
      [] ->
        {str, strings, in_group} = unpack(key)
        [sym | rest] = str
        [gr_h | gr_rest] = strings

        case sym do
          "#" ->
            if gr_h <= 0 do
              0
            else
              new_gr = [gr_h - 1 | gr_rest]

              nkey = pack({rest, new_gr, true})
              res = count(nkey)
              :ets.insert(:springs, {nkey, res})
              res
            end

          "." ->
            if in_group do
              if gr_h == 0 do
                nkey = pack({rest, gr_rest, false})
                res = count(nkey)
                :ets.insert(:springs, {nkey, res})
                res
              else
                0
              end
            else
              nkey = pack({rest, strings, false})
              res = count(nkey)
              :ets.insert(:springs, {nkey, res})
              res
            end

          "?" ->
            key_a = pack({["#" | rest], strings, in_group})
            res_a = count(key_a)
            :ets.insert(:springs, {key_a, res_a})

            key_b = pack({["." | rest], strings, in_group})
            res_b = count(key_b)
            :ets.insert(:springs, {key_b, res_b})

            res_a + res_b
        end

      [{_key, res}] ->
        res
    end
  end

  def unpack({str, tp, flag}) do
    {String.graphemes(str), Tuple.to_list(tp), flag}
  end

  def pack({lstr, gr, flag}) do
    {Enum.join(lstr), List.to_tuple(gr), flag}
  end

  def in?(c, str), do: :binary.match(str, c) != :nomatch
end

td = """
???.### 1,1,3
.??..??...?##. 1,1,3
?#?#?#?#?#?#?#? 1,3,1,6
????.#...#... 4,1,1
????.######..#####. 1,6,5
?###???????? 3,2,1
"""

:ets.new(:springs, [:set, :protected, :named_table])

td |> Day12.run(:p1) |> Day12.out("p1-test")
td |> Day12.run(:p2) |> Day12.out("p2-test")

data |> Day12.run(:p1) |> Day12.out("p1")
data |> Day12.run(:p2) |> Day12.out("p2")

:ets.delete(:springs)