Day 19
prelude
https://adventofcode.com/2024/day/19
import { inputDay } from "./lib/utilities.js"
const parser = inp => {
const [patterns, displays] = inp.trim().split("\n\n")
return [patterns.replaceAll(" ", "").split(",").sort(), displays.split("\n")]
}
const test = parser(`r, wr, b, g, bwu, rb, gb, br
brwrr
bggr
gbbr
rrbgbr
ubwu
bwurrg
brgr
bbrgwb
`)
display(test)
const input = await inputDay(19, { parser })
part 1
Let's start with a function that finds all the patterns that match a given display. Since we've sorted our pattern list, we can exit out if the pattern is lexically greater than the display we want to match.
We could use a prefix tree for greater efficiency if we wanted, but the pattern list is small enough that it's unlikely we'd get a big win out of it.
function matches(patterns, display) {
const matches = []
for (const pat of patterns) {
if (display.startsWith(pat)) matches.push(pat)
if (pat > display) return matches
}
return matches
}
display(matches(test[0], test[1][0]))
now let's write a function to search for a match
function search(patterns, display) {
let frontier = new Set([display])
let seen = new Set([])
while (frontier.size > 0) {
const newf = new Set()
for (const f of frontier) {
if (seen.has(f)) continue
seen.add(f)
for (const pat of matches(patterns, f)) {
if (f.length == pat.length) return true
newf.add(f.slice(pat.length))
}
}
frontier = newf
}
return false
}
display(search(test[0], "brwrr"))
display(test[1].map(d => search(test[0], d)).reduce((a, b) => a + b, 0))
display(input[1].map(d => search(input[0], d)).reduce((a, b) => a + b, 0))
part 2
To solve part 2, we're going to rub some dynamic programming on it.
First, a function that recursively searches the breaks and remembers results as it goes. This is important because there will be many duplicate branches of the tree.
function searchr(patterns, display, memo) {
if (memo.has(display)) return memo.get(display)
let n = matches(patterns, display)
.map(m =>
m.length === display.length
? 1
: searchr(patterns, display.slice(m.length), memo),
)
.reduce((a, b) => a + b, 0)
memo.set(display, n)
return n
}
display(searchr(test[0], "rrbgbr", new Map())) // 6
Then we can write a function that keeps the memo table between displays (because the sub-problem answers won't have changed), searches each display for the number of results it has, and sums them up
function search2(patterns, displays) {
const displaymemo = new Map()
return displays
.map(d => searchr(patterns, d, displaymemo))
.reduce((a, b) => a + b)
}
display(search2(...test)) // 16
const t = new Date()
display(search2(...input))
const t2 = new Date()
display(`part 2 took ${t2 - t}ms`)