Advent of Code 2023 - Day 12
Mix.install([
{:kino, "~> 0.11.0"},
{:kino_aoc, github: "ljgago/kino_aoc"},
{:memoize, "~> 1.4"}
])
Input
{:ok, puzzle_input} =
KinoAOC.download_puzzle("2023", "12", System.fetch_env!("LB_AOC_SESSION"))
input =
puzzle_input
|> String.split("\n", trim: true)
|> Enum.map(fn row ->
[record, checksum] = String.split(row, " ")
record = String.graphemes(record)
checksum =
checksum
|> String.split(",")
|> Enum.map(&String.to_integer/1)
{record, checksum}
end)
Part 1
Inspired by HyperNeutrino.
defmodule HotSprings do
use Memoize
def sum_counts(rows) do
Memoize.invalidate(__MODULE__)
for {record, checksum} <- rows, reduce: 0 do
sum -> sum + HotSprings.count(record, checksum)
end
end
def count(record, []) do
if "#" in record, do: 0, else: 1
end
def count([], []), do: 1
def count([], _), do: 0
defmemo count([first | rest] = record, [firstsum | restsum] = checksum) do
Task.async(fn ->
if first in [".", "?"] do
count(rest, checksum)
|> increase(:result)
end
if first in ["#", "?"] do
if firstsum <= length(record) and
"." not in Enum.slice(record, 0..(firstsum - 1)) and
(firstsum == length(record) or Enum.at(record, firstsum) != "#") do
record
|> Enum.slice((firstsum + 1)..-1)
|> count(restsum)
|> increase(:result)
end
end
Process.get(:result, 0)
end)
|> Task.await()
end
defp increase(by, key) do
current = Process.get(key, 0)
Process.put(key, current + by)
end
end
input
|> HotSprings.sum_counts()
Part 2
input
|> Enum.map(fn {record, checksum} ->
record =
record
|> List.duplicate(5)
|> Enum.join("?")
|> String.graphemes()
checksum =
checksum
|> List.duplicate(5)
|> List.flatten()
{record, checksum}
end)
|> HotSprings.sum_counts()