package main import ( "aoc/utils" "fmt" "strings" ) func main() { g := NewGraph(utils.ReadLines()) fmt.Println("part1: ", g.Part1()) fmt.Println("part2: ", g.Part2()) } type Graph struct { edges [][]uint16 bigCaves uint16 } func (g *Graph) Part1() int { initCache() return g.countPathsPart1(startCave, 0) } func (g *Graph) Part2() int { initCache() return g.countPathsPart2(startCave, 0) } const ( startCave = 1 endCave = 0 ) func caveMask(cave uint16) uint16 { return 1 << cave } func (g *Graph) countPathsPart1(from uint16, visited uint16) int { var count int if count, ok := getCachedResult(from, visited); ok { return count } for _, to := range g.edges[from] { if to == endCave { count++ continue } toMask := caveMask(to) &^ g.bigCaves if visited&toMask == 0 { count += g.countPathsPart1(to, visited|toMask) } } putToCache(from, visited, count) return count } const doubleMask = 1 << 15 func (g *Graph) countPathsPart2(from uint16, visited uint16) int { var count int if count, ok := getCachedResult(from, visited); ok { return count } for _, to := range g.edges[from] { if to == endCave { count++ continue } toMask := caveMask(to) &^ g.bigCaves if visited&toMask == 0 { count += g.countPathsPart2(to, visited|toMask) } else if visited&doubleMask == 0 { count += g.countPathsPart2(to, visited|doubleMask) } } putToCache(from, visited, count) return count } // Memoïzation type key struct { cave uint16 visited uint16 } type cache map[key]int var memo cache func initCache() { memo = make(cache) } func getCachedResult(from uint16, visited uint16) (int, bool) { count, ok := memo[key{from, visited}] return count, ok } func putToCache(from uint16, visited uint16, count int) { memo[key{from, visited}] = count } // Input parsing func NewGraph(input []string) *Graph { g := newBuilder() g.parse(input) return &g.Graph } func newBuilder() *graphBuilder { return &graphBuilder{ Graph: Graph{edges: make([][]uint16, 2, 16)}, caves: map[string]uint16{ "start": startCave, "end": endCave, }, } } type graphBuilder struct { Graph caves map[string]uint16 } func (g *graphBuilder) parse(input []string) { for _, line := range input { edge := strings.Split(line, "-") c1 := g.getCave(edge[0]) c2 := g.getCave(edge[1]) g.addEdge(c1, c2) } } func (g *graphBuilder) addEdge(c1, c2 uint16) { if c1 != endCave && c2 != startCave { g.edges[c1] = append(g.edges[c1], c2) } if c1 != startCave && c2 != endCave { g.edges[c2] = append(g.edges[c2], c1) } } func (g *graphBuilder) getCave(name string) uint16 { if cave, ok := g.caves[name]; ok { return cave } cave := uint16(len(g.caves)) g.caves[name] = cave if 'A' <= name[0] && name[0] < 'Z' { g.bigCaves |= caveMask(cave) } g.edges = append(g.edges, nil) return cave }