refactor+feat: use bitmask approach and cyclic/acyclic dfs for resolution
This commit is contained in:
@@ -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] {
|
||||
count += countPaths(neighbor)
|
||||
if !visited[neighbor] {
|
||||
count += dfs(neighbor, visited, seenMask)
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
return countPaths("you")
|
||||
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 += dfs(neighbor, seenMask)
|
||||
}
|
||||
|
||||
memo[state] = count
|
||||
return count
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user