179 lines
3 KiB
Go
179 lines
3 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/jdkaplan/advent-of-code/aoc"
|
|
)
|
|
|
|
func main() {
|
|
lines := aoc.Input().ReadLines("day12.txt")
|
|
fmt.Println(part1(lines))
|
|
fmt.Println(part2(lines))
|
|
}
|
|
|
|
func part1(lines []string) int {
|
|
g := NewGraph(lines)
|
|
return g.Paths()
|
|
}
|
|
func part2(lines []string) int {
|
|
g := NewGraph(lines)
|
|
return g.Paths2()
|
|
}
|
|
|
|
type Graph struct {
|
|
Edges map[string][]string
|
|
Big map[string]struct{}
|
|
Small map[string]struct{}
|
|
}
|
|
|
|
func NewGraph(lines []string) *Graph {
|
|
g := &Graph{
|
|
Edges: make(map[string][]string),
|
|
Big: make(map[string]struct{}),
|
|
Small: make(map[string]struct{}),
|
|
}
|
|
for _, l := range lines {
|
|
a, b := aoc.Cut(l, "-")
|
|
g.Edges[a] = append(g.Edges[a], b)
|
|
g.Edges[b] = append(g.Edges[b], a)
|
|
if isBig(a) {
|
|
g.Big[a] = struct{}{}
|
|
} else {
|
|
g.Small[a] = struct{}{}
|
|
}
|
|
if isBig(b) {
|
|
g.Big[b] = struct{}{}
|
|
} else {
|
|
g.Small[b] = struct{}{}
|
|
}
|
|
}
|
|
return g
|
|
}
|
|
|
|
func (g Graph) Neighbors(name string) []string {
|
|
return g.Edges[name]
|
|
}
|
|
|
|
type path []string
|
|
|
|
func (p path) String() string {
|
|
return strings.Join(p, ",")
|
|
}
|
|
|
|
func (p path) Contains(name string) bool {
|
|
for _, n := range p {
|
|
if n == name {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (p path) Tip() string {
|
|
return p[len(p)-1]
|
|
}
|
|
|
|
func (p path) Plus(name string) path {
|
|
// Copy memory to avoid overwriting the last element in the slice.
|
|
p2 := make(path, len(p))
|
|
copy(p2, p)
|
|
return append(p2, name)
|
|
}
|
|
|
|
func (g Graph) Paths() (count int) {
|
|
queue := []path{{"start"}}
|
|
for len(queue) > 0 {
|
|
p := queue[0]
|
|
queue = queue[1:]
|
|
tip := p.Tip()
|
|
for _, n := range g.Neighbors(tip) {
|
|
ext := p.Plus(n)
|
|
if n == "end" {
|
|
count++
|
|
continue
|
|
}
|
|
if !isBig(n) && p.Contains(n) {
|
|
// Don't include small caves twice
|
|
continue
|
|
}
|
|
queue = append(queue, ext)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func isBig(name string) bool {
|
|
return strings.ToUpper(name) == name
|
|
}
|
|
|
|
type path2 struct {
|
|
caves []string
|
|
doubled bool
|
|
}
|
|
|
|
func (p *path2) Plus(name string) (path2, bool) {
|
|
var caves []string
|
|
doubled := p.doubled
|
|
canDupe := isBig(name)
|
|
for _, c := range p.caves {
|
|
caves = append(caves, c)
|
|
if c != name {
|
|
// No dupe.
|
|
continue
|
|
}
|
|
if canDupe {
|
|
continue
|
|
}
|
|
// c == name for a small cave
|
|
if doubled {
|
|
// Already found a doubled cave, can't include this one.
|
|
return path2{}, false
|
|
}
|
|
// Contains two of c now.
|
|
doubled = true
|
|
}
|
|
return path2{
|
|
caves: append(caves, name),
|
|
doubled: doubled,
|
|
}, true
|
|
}
|
|
|
|
func (p path2) String() string {
|
|
return strings.Join(p.caves, ",")
|
|
}
|
|
|
|
func (p path2) Tip() string {
|
|
return p.caves[len(p.caves)-1]
|
|
}
|
|
|
|
func (g Graph) Paths2() (count int) {
|
|
queue := []path2{
|
|
{
|
|
caves: []string{"start"},
|
|
doubled: false,
|
|
},
|
|
}
|
|
for len(queue) > 0 {
|
|
p := queue[0]
|
|
queue = queue[1:]
|
|
tip := p.Tip()
|
|
for _, n := range g.Neighbors(tip) {
|
|
if n == "start" {
|
|
// No loops!
|
|
continue
|
|
}
|
|
ext, ok := p.Plus(n)
|
|
if n == "end" {
|
|
count++
|
|
continue
|
|
}
|
|
if !ok {
|
|
continue
|
|
}
|
|
queue = append(queue, ext)
|
|
}
|
|
}
|
|
return
|
|
}
|