defmodule Day19 do
defmodule Onsen do
@derive Inspect
@enforce_keys [:patterns, :designs]
defstruct @enforce_keys
def from_data(data) do
[patterns, designs] = data
|> String.split("\n\n", trim: true)
patterns = patterns
|> String.split(", ", trim: true)
designs = designs
|> String.split("\n", trim: true)
%Onsen{patterns: patterns, designs: designs}
end
def count_feasible_designs(onsen) do
onsen.designs
|> Enum.filter(& feasible?(&1, onsen.patterns) |> elem(0))
|> Enum.count()
end
defp feasible?(design, patterns, vis \\ MapSet.new()) do
cond do
MapSet.member?(vis, design) ->
{false, vis}
design == "" ->
{true, vis}
true ->
for pat <- patterns, reduce: {false, MapSet.put(vis, design)} do
{acc, vis} ->
cond do
acc == true ->
{acc, vis}
String.starts_with?(design, pat) ->
design
|> String.split_at(String.length(pat))
|> elem(1)
|> feasible?(patterns, vis)
true ->
{acc, vis}
end
end
end
end
def count_feasible_ways(onsen) do
onsen.designs
|> Enum.map(& ways(&1, onsen.patterns) |> elem(0))
|> Enum.sum()
end
defp ways(design, patterns, vis \\ Map.new()) do
case Map.fetch(vis, design) do
{:ok, count} ->
{count, vis}
:error ->
if design == "" do
{1, vis}
else
for pat <- patterns, reduce: {0, vis} do
{acc, vis} ->
cond do
String.starts_with?(design, pat) ->
design
|> String.split_at(String.length(pat))
|> elem(1)
|> ways(patterns, vis)
|> then(fn {acc2, vis} -> {acc + acc2, vis} end)
true ->
{acc, vis}
end
end
|> then(fn {acc, vis} -> {acc, Map.put(vis, design, acc)} end)
end
end
end
end
def part1(data) do
data
|> Onsen.from_data()
|> Onsen.count_feasible_designs()
end
def part2(data) do
data
|> Onsen.from_data()
|> Onsen.count_feasible_ways()
end
end
data = IO.read(:stdio, :eof)
{time1, ans1} = :timer.tc(fn -> Day19.part1(data) end)
IO.puts("Time : #{time1 / 1000000}")
IO.puts("Answer: #{ans1}")
{time2, ans2} = :timer.tc(fn -> Day19.part2(data) end)
IO.puts("Time : #{time2 / 1000000}")
IO.puts("Answer: #{ans2}")