From 6617f5365e865532351d716a02372acce148c9e4 Mon Sep 17 00:00:00 2001 From: Kharec Date: Tue, 3 Feb 2026 16:30:08 +0100 Subject: [PATCH] refactor+feat: use bitmask approach and cyclic/acyclic dfs for resolution --- internal/2025/DayEleven/code.go | 122 ++++++++++++++++++++++++++++++-- 1 file changed, 115 insertions(+), 7 deletions(-) diff --git a/internal/2025/DayEleven/code.go b/internal/2025/DayEleven/code.go index 029c1da..5301691 100644 --- a/internal/2025/DayEleven/code.go +++ b/internal/2025/DayEleven/code.go @@ -2,6 +2,7 @@ package dayeleven import ( "os" + "slices" "strings" "advent-of-code/internal/registry" @@ -16,7 +17,7 @@ func ParseInput(filepath string) []string { return strings.Split(string(content), "\n") } -func PartOne(data []string) int { +func buildGraph(data []string) map[string][]string { graph := make(map[string][]string) for _, line := range data { @@ -24,22 +25,129 @@ func PartOne(data []string) int { graph[device] = strings.Fields(outputsStr) } - var countPaths func(string) int - countPaths = func(node string) int { - if node == "out" { + return graph +} + +func hasCycle(graph map[string][]string) bool { + const ( + unvisited = iota + visiting + visited + ) + + state := make(map[string]uint8) + + var dfs func(node string) bool + dfs = func(node string) bool { + switch state[node] { + case visiting: + return true + case visited: + return false + } + + state[node] = visiting + if slices.ContainsFunc(graph[node], dfs) { + return true + } + state[node] = visited + return false + } + + for node := range graph { + if state[node] == unvisited { + if dfs(node) { + return true + } + } + } + + return false +} + +func countPaths(graph map[string][]string, start, end string, requiredNodes map[string]bool) int { + requiredIndex := make(map[string]uint) + var targetMask uint + for node := range requiredNodes { + requiredIndex[node] = uint(len(requiredIndex)) + targetMask |= 1 << requiredIndex[node] + } + + if hasCycle(graph) { + var dfs func(node string, visited map[string]bool, seenMask uint) int + dfs = func(node string, visited map[string]bool, seenMask uint) int { + if node == end { + if seenMask != targetMask { + return 0 + } + return 1 + } + + if idx, ok := requiredIndex[node]; ok { + seenMask |= 1 << idx + } + + visited[node] = true + defer delete(visited, node) + + count := 0 + for _, neighbor := range graph[node] { + if !visited[neighbor] { + count += dfs(neighbor, visited, seenMask) + } + } + return count + } + + return dfs(start, make(map[string]bool), 0) + } + + type key struct { + node string + mask uint + } + memo := make(map[key]int) + + var dfs func(node string, seenMask uint) int + dfs = func(node string, seenMask uint) int { + if node == end { + if seenMask != targetMask { + return 0 + } return 1 } + if idx, ok := requiredIndex[node]; ok { + seenMask |= 1 << idx + } + + state := key{node: node, mask: seenMask} + if cached, ok := memo[state]; ok { + return cached + } + count := 0 for _, neighbor := range graph[node] { - count += countPaths(neighbor) + count += dfs(neighbor, seenMask) } + + memo[state] = count return count } - return countPaths("you") + return dfs(start, 0) +} + +func PartOne(data []string) int { + graph := buildGraph(data) + return countPaths(graph, "you", "out", nil) } func PartTwo(data []string) int { - return 0 + graph := buildGraph(data) + requiredNodes := map[string]bool{ + "dac": true, + "fft": true, + } + return countPaths(graph, "svr", "out", requiredNodes) }