mirror of
https://github.com/RussellChamp/advent-of-code-2021.git
synced 2026-10-27 21:52:32 +00:00
day 12
This commit is contained in:
1 parent
44923a1ed4
commit
c1786c95ad
2 files changed
+202
No files matched your search
+191
@@ -0,0 +1,191 @@
|
||||
/* Day 12: Passage Pathing */
|
||||
package main
|
||||
|
||||
import (
|
||||
"AoC2021/utils/log"
|
||||
"AoC2021/utils/timer"
|
||||
"bufio"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
func check(e error) {
|
||||
if e != nil {
|
||||
log.Println(log.DEBUG, "Oh snap!")
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
|
||||
// Assumptions: The input data won't ever put us in an infinite loop of large caverns
|
||||
// This might be something to protect against if this was production code
|
||||
|
||||
func main() {
|
||||
log.Println(log.NORMAL, "--- Day 12: Passage Pathing ---")
|
||||
timer.Start()
|
||||
part1()
|
||||
timer.Tick()
|
||||
log.Println(log.NORMAL)
|
||||
|
||||
part2()
|
||||
timer.Tick()
|
||||
log.Println(log.NORMAL)
|
||||
}
|
||||
|
||||
func part1() {
|
||||
log.Println(log.NORMAL, "* Part 1 *")
|
||||
log.Println(log.NORMAL, " Goal: Find all valid path to navigate")
|
||||
log.Println(log.NORMAL, " Answer: How many paths through this cave system are there that visit small caves at most once?")
|
||||
|
||||
nodeMap := loadNodeMap()
|
||||
log.Println(log.DIAGNOSTIC, nodeMap)
|
||||
|
||||
log.Println(log.DEBUG, "The start node is", nodeMap["start"], "with connections", nodeMap["start"].connections)
|
||||
|
||||
paths := getPaths(nodeMap, 0)
|
||||
|
||||
log.Printf(log.NORMAL, "Found %d paths from 'start' to 'end'!\n", len(paths))
|
||||
}
|
||||
|
||||
func part2() {
|
||||
log.Println(log.NORMAL, "* Part 2 *")
|
||||
log.Println(log.NORMAL, " Goal: Find all valid path to navigate")
|
||||
log.Println(log.NORMAL, " Answer: Given these new rules, how many paths through this cave system are there?")
|
||||
|
||||
nodeMap := loadNodeMap()
|
||||
log.Println(log.DIAGNOSTIC, nodeMap)
|
||||
|
||||
log.Println(log.DEBUG, "The start node is", nodeMap["start"], "with connections", nodeMap["start"].connections)
|
||||
|
||||
paths := getPaths(nodeMap, 1)
|
||||
|
||||
// for idx, p := range paths {
|
||||
// if p[0] != "start" || p[len(p)-1] != "end" {
|
||||
// panic(fmt.Sprintf("Error: path %d is invalid: %s\n", idx, p))
|
||||
// }
|
||||
// }
|
||||
|
||||
log.Printf(log.NORMAL, "Found %d paths from 'start' to 'end'!\n", len(paths))
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
name string
|
||||
isLarge bool
|
||||
connections *[]string
|
||||
}
|
||||
|
||||
type NodeMap = map[string]Node
|
||||
|
||||
func loadNodeMap() NodeMap {
|
||||
|
||||
input, err := os.Open("./input.txt")
|
||||
check(err)
|
||||
defer input.Close()
|
||||
|
||||
scanner := bufio.NewScanner(input)
|
||||
lines := 0
|
||||
nodeMap := make(NodeMap)
|
||||
connections := 0
|
||||
|
||||
for scanner.Scan() {
|
||||
nodeStrs := strings.Split(scanner.Text(), "-")
|
||||
lines++
|
||||
if len(nodeStrs) != 2 {
|
||||
panic("Did not read in expected 2 nodes from input line")
|
||||
}
|
||||
// Check if either node already exists, add them to the slice, and add them to each other's connections
|
||||
node1Str, node2Str := nodeStrs[0], nodeStrs[1]
|
||||
log.Printf(log.DIAGNOSTIC, "Processing nodes %s & %s: ", node1Str, node2Str)
|
||||
|
||||
node1, node1exists := nodeMap[node1Str]
|
||||
if !node1exists {
|
||||
isLarge := unicode.IsUpper(rune(node1Str[0]))
|
||||
nodeMap[node1Str] = Node{node1Str, isLarge, &[]string{}}
|
||||
node1 = nodeMap[node1Str]
|
||||
}
|
||||
|
||||
node2, node2exists := nodeMap[node2Str]
|
||||
if !node2exists {
|
||||
isLarge := unicode.IsUpper(rune(node2Str[0]))
|
||||
nodeMap[node2Str] = Node{node2Str, isLarge, &[]string{}}
|
||||
node2 = nodeMap[node2Str]
|
||||
}
|
||||
|
||||
*node1.connections = append(*node1.connections, node2Str)
|
||||
*node2.connections = append(*node2.connections, node1Str)
|
||||
log.Println(log.DIAGNOSTIC, node1, node2)
|
||||
connections += 2
|
||||
}
|
||||
|
||||
log.Printf(log.NORMAL, "Read %d nodes with %d connections from %d lines from input\n", len(nodeMap), connections, lines)
|
||||
|
||||
return nodeMap
|
||||
}
|
||||
|
||||
func getPaths(nodeMap NodeMap, revisitsAllowed int) [][]string {
|
||||
visitCountMap := initVisitCountMap(nodeMap)
|
||||
|
||||
// start at 'start' and work our way to 'end'
|
||||
path := []string{"start"}
|
||||
log.Printf(log.DEBUG, "Starting iteration of nodes from 'start'\n")
|
||||
|
||||
paths := getValidPathsFrom("start", path, nodeMap, visitCountMap, revisitsAllowed)
|
||||
|
||||
return paths
|
||||
}
|
||||
|
||||
type VisitMap = map[string]int
|
||||
|
||||
func initVisitCountMap(nodeMap NodeMap) VisitMap {
|
||||
visitCountMap := make(VisitMap, len(nodeMap))
|
||||
for n := range nodeMap {
|
||||
visitCountMap[n] = 0
|
||||
}
|
||||
|
||||
return visitCountMap
|
||||
}
|
||||
|
||||
func getValidPathsFrom(node string, path []string, nodeMap NodeMap, visitMap VisitMap, revisitsAllowed int) [][]string {
|
||||
var paths [][]string
|
||||
// just stop once we reach the end
|
||||
if node == "end" {
|
||||
log.Printf(log.DIAGNOSTIC, "The final visit count looks like %s\n", visitMap)
|
||||
return [][]string{path}
|
||||
}
|
||||
|
||||
n, exists := nodeMap[node]
|
||||
if exists {
|
||||
visitMap[node] += 1
|
||||
for _, cStr := range *n.connections {
|
||||
// do not return to the 'start' node
|
||||
// allow traveling to large nodes
|
||||
// allow traveling to small nodes that we have not visited
|
||||
// allow traveling to small nodes when we still have some 'revisitAllowed' points left
|
||||
if cStr != "start" && (nodeMap[cStr].isLarge || visitMap[cStr] == 0 || revisitsAllowed > 0) {
|
||||
// if we triggered due to 'revisitAllowed' points, decrease the point pool for the next time
|
||||
nextRevisitsAllowed := revisitsAllowed
|
||||
if !nodeMap[cStr].isLarge && visitMap[cStr] > 0 && revisitsAllowed > 0 {
|
||||
nextRevisitsAllowed -= 1
|
||||
}
|
||||
newBasePath := append(path, cStr)
|
||||
log.Printf(log.DIAGNOSTIC, "Processed path %s\n", newBasePath)
|
||||
newPaths := getValidPathsFrom(cStr, newBasePath, nodeMap, copyMap(visitMap), nextRevisitsAllowed)
|
||||
if len(newPaths) > 0 {
|
||||
paths = append(paths, newPaths...)
|
||||
}
|
||||
} else {
|
||||
log.Printf(log.DIAGNOSTIC, "Did NOT follow %s %s (visit count: %d) and pruned this path\n", path, cStr, visitMap[cStr])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return paths
|
||||
}
|
||||
|
||||
func copyMap(v1 VisitMap) VisitMap {
|
||||
v2 := VisitMap{}
|
||||
for v := range v1 {
|
||||
v2[v] = v1[v]
|
||||
}
|
||||
return v2
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"AoC2021/utils/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDay12(t *testing.T) {
|
||||
assert.IsTrue(t, true, "cool")
|
||||
assert.EqualsStr(t, "foo", "bar", "baz")
|
||||
}
|
||||
Reference in new issue
Block a user