diff --git a/lib/common.ex b/lib/common.ex index b30c0ea..1fab35c 100644 --- a/lib/common.ex +++ b/lib/common.ex @@ -47,4 +47,14 @@ defmodule Aoc2023.Common do end def transpose(l), do: l |> Enum.zip |> Enum.map(&Tuple.to_list/1) + + def collapse(enum, v) do + enum + |> Stream.zip(enum |> Stream.drop(1) |> Stream.concat([nil])) + |> Stream.filter(fn {a, b} -> a != v || b != v end) + |> Stream.map(pipe(elem(0))) + end + + def repeat(lst, 0), do: [] + def repeat(lst, n), do: Enum.concat(lst, repeat(lst, n - 1)) end diff --git a/lib/day12.ex b/lib/day12.ex new file mode 100644 index 0000000..0d040a4 --- /dev/null +++ b/lib/day12.ex @@ -0,0 +1,93 @@ +defmodule Aoc2023.Day12 do + use Aoc2023 + + defmodule Cache do + use GenServer + + def start do + {:ok, pid} = GenServer.start_link(Cache, nil) + pid + end + + def memoize(cache, key, fun) do + got = GenServer.call(cache, {:get, key}) + if got == nil do + val = fun.(key) + GenServer.call(cache, {:set, key, val}) + val + else + got + end + end + + @impl true + def init(_) do + {:ok, %{}} + end + + @impl true + def handle_call({:get, key}, _, state) do + case state do + %{^key => value} -> + {:reply, value, state} + + _ -> + {:reply, nil, state} + end + end + + @impl true + def handle_call({:set, key, value}, _, state) do + {:reply, nil, Map.put_new(state, key, value)} + end + end + + def parse(input) do + input + |> lines + |> Enum.map( + pipe( + String.split() + |> then(fn [rec, blocks] -> + {rec |> String.to_charlist(), + blocks |> String.split(",") |> Enum.map(&String.to_integer/1)} + end) + ) + ) + end + + defp possibilities({rec, blocks}) do + loop = fn recur, arg -> + case arg do + {rec, []} -> if Enum.all?(rec, &(&1 in [??, ?.])) do 1 else 0 end + {rec, [block | blocks]} -> + # Expect rec to start with any number of ./?s up to the max allowing all remaining groups + # i.e. their lengths + an undamaged seperator + 0..(length(rec) - Enum.sum(blocks) - length(blocks) - block) + |> Enum.map(fn i -> repeat('.', i) ++ repeat('#', block) ++ '.' end) + |> Enum.filter(pipe(Enum.zip_with(rec, &(&1 == &2 || &2 == ??)) |> Enum.all?)) + |> Enum.map(&(recur.(recur, {Enum.drop(rec, length(&1)), blocks}))) + |> Enum.sum + end + end + cache = Cache.start() + memoized = fn recur, arg -> Cache.memoize(cache, arg, fn arg -> loop.(recur, arg) end) end + memoized.(memoized, {rec, blocks}) + end + + def part1(input) do + input + |> Enum.map(pipe(possibilities())) + |> Enum.sum() + end + + def part2(input) do + input + |> Enum.map(fn {rec, blocks} -> { + [rec] |> repeat(5) |> Enum.intersperse('?') |> Enum.flat_map(&id/1), + repeat(blocks, 5) + } end) + |> Enum.map(&possibilities/1) + |> Enum.sum() + end +end diff --git a/lib/mix_tasks.ex b/lib/mix_tasks.ex index acdc81b..133e15e 100644 --- a/lib/mix_tasks.ex +++ b/lib/mix_tasks.ex @@ -13,6 +13,7 @@ defmodule Mix.Tasks.Aoc do defp module(9), do: Aoc2023.Day9 defp module(10), do: Aoc2023.Day10 defp module(11), do: Aoc2023.Day11 + defp module(12), do: Aoc2023.Day12 # [MODULE INSERTION POINT] defp base_dir(), do: System.get_env("AOC_BASE") diff --git a/tests/day12/1 b/tests/day12/1 new file mode 100644 index 0000000..e925935 --- /dev/null +++ b/tests/day12/1 @@ -0,0 +1,6 @@ +???.### 1,1,3 +.??..??...?##. 1,1,3 +?#?#?#?#?#?#?#? 1,3,1,6 +????.#...#... 4,1,1 +????.######..#####. 1,6,5 +?###???????? 3,2,1 diff --git a/tests/day12/1.1 b/tests/day12/1.1 new file mode 100644 index 0000000..aabe6ec --- /dev/null +++ b/tests/day12/1.1 @@ -0,0 +1 @@ +21 diff --git a/tests/day12/1.2 b/tests/day12/1.2 new file mode 100644 index 0000000..aa2eea0 --- /dev/null +++ b/tests/day12/1.2 @@ -0,0 +1 @@ +525152