export type Graph = Map<string, Array<string>>
export const PART1_START = 'you'
export const PART1_TARGET = 'out'
export const PART2_START = 'svr'
export const PART2_TARGET = 'out'
export const PART2_REQUIRED_DEVICES = ['dac', 'fft']
export function parseGraph(rawInput: string): Graph {
const graph: Graph = new Map()
rawInput
.split('\n')
.map((line) => line.trim())
.filter((line) => line.length > 0)
.forEach((line) => {
const separatorIndex = line.indexOf(':')
if (separatorIndex === -1) {
throw new Error(`Invalid device definition: "${line}"`)
}
const device = line.slice(0, separatorIndex).trim()
if (device.length === 0) {
throw new Error(`Missing device name in line: "${line}"`)
}
if (graph.has(device)) {
throw new Error(`Duplicate definition for device "${device}"`)
}
const outputsSection = line.slice(separatorIndex + 1).trim()
const outputs =
outputsSection.length === 0 ? [] : outputsSection.split(/\s+/g)
graph.set(device, outputs)
})
return graph
}
export function countPathsThroughDevices(
graph: Graph,
start: string,
target: string,
requiredDevices: Array<string>,
): bigint {
if (!graph.has(start) && start !== target) {
return 0n
}
const requiredMaskByDevice = new Map<string, number>()
requiredDevices.forEach((device, index) => {
requiredMaskByDevice.set(device, 1 << index)
})
const fullMask =
requiredDevices.length === 0 ? 0 : (1 << requiredDevices.length) - 1
const memo = new Map<string, Map<number, bigint>>()
const visiting = new Set<string>()
function dfs(node: string, mask: number): bigint {
const deviceMask = requiredMaskByDevice.get(node) ?? 0
const updatedMask = mask | deviceMask
if (node === target) {
return updatedMask === fullMask ? 1n : 0n
}
let nodeMemo = memo.get(node)
if (!nodeMemo) {
nodeMemo = new Map()
memo.set(node, nodeMemo)
}
if (nodeMemo.has(updatedMask)) {
return nodeMemo.get(updatedMask)!
}
if (visiting.has(node)) {
throw new Error(
`Cycle detected involving device "${node}" - cannot count paths`,
)
}
visiting.add(node)
let total = 0n
const outputs = graph.get(node) ?? []
for (const next of outputs) {
total += dfs(next, updatedMask)
}
visiting.delete(node)
nodeMemo.set(updatedMask, total)
return total
}
return dfs(start, 0)
}
function toJsonSafeNumber(value: bigint): string | number {
const maxSafe = BigInt(Number.MAX_SAFE_INTEGER)
if (value <= maxSafe) {
return Number(value)
}
return value.toString()
}
export function solve(input: string): Promise<string | number | object> {
const graph = parseGraph(input)
const part1Paths = countPathsThroughDevices(graph, PART1_START, PART1_TARGET, [])
const part2Paths = countPathsThroughDevices(
graph,
PART2_START,
PART2_TARGET,
PART2_REQUIRED_DEVICES,
)
return Promise.resolve({
part1: toJsonSafeNumber(part1Paths),
part2: toJsonSafeNumber(part2Paths),
details: {
devices: graph.size,
part1: { start: PART1_START, target: PART1_TARGET },
part2: {
start: PART2_START,
target: PART2_TARGET,
required: PART2_REQUIRED_DEVICES,
},
},
})
}
Create a file at src/solvers/day11.ts with the following structure:
export async function solve(input: string): Promise<string | number | object> {
// Your solution here
// The input parameter contains the puzzle input as a string
// Example:
const lines = input.trim().split('\n');
// Process and return your answer
return 'Your answer here';
}Then, import it in src/solvers/index.ts and add it to the solvers object:
import * as day11 from './day11'
export const solvers = {
// ... existing solvers
'11': day11,
}The solver function will receive the puzzle input as a string and should return the solution (string, number, or object).