Advent-of-Code-2023/day8/dayEight.go

224 lines
5.1 KiB
Go

package day8
import (
"fmt"
"log"
"os"
"regexp"
"slices"
"strings"
)
func Run() int {
fmt.Print("hello day 8")
filename := "day8/input"
net, path := Read(filename)
fmt.Printf("got %+v and %+v\n", net, path)
result := GhostTraverse(net, path)
return result
}
func getGhostStartNodes(net Network) []Node {
simultaneousPaths := make([]Node, 0)
for nodeName, node := range net.Nodes {
if strings.HasSuffix(nodeName, "A") {
simultaneousPaths = append(simultaneousPaths, node)
}
}
log.Printf("collected start points: %+v\n", simultaneousPaths)
return simultaneousPaths
}
type GhostPathInfo struct {
InitialSteps, CycleLen int
}
func getGhostPathInfo(startNode Node, net Network, pathString string) GhostPathInfo {
path := Path {Instruction: pathString}
initialSteps, cycleLen := 0, 1
runnerNode := startNode
for !strings.HasSuffix(runnerNode.Name, "Z") {
direction := path.next()
runnerNode = getNextNode(net, runnerNode, direction)
initialSteps += 1
}
// one more step for starting to check for cycle
oneDirection := path.next()
runnerNode = getNextNode(net, runnerNode, oneDirection)
for !strings.HasSuffix(runnerNode.Name, "Z") {
direction := path.next()
runnerNode = getNextNode(net, runnerNode, direction)
cycleLen += 1
}
return GhostPathInfo{
InitialSteps: initialSteps,
CycleLen: cycleLen,
}
}
func GhostTraverse(net Network, path Path) int {
simultaneousPaths := getGhostStartNodes(net)
pathInfos := make([]GhostPathInfo, 0, len(simultaneousPaths))
for _, pathStart := range simultaneousPaths {
pathInfos = append(pathInfos, getGhostPathInfo(pathStart, net, path.Instruction))
}
log.Printf("path infos are %+v\n", pathInfos)
// and now the algo.
// if initial paths equal - that's answer
// if not - seed round, +1 cycle to each
// if equal - that the answer
// if not - here's the main loop
// find biggest.
// then iterate all but that one.
// calculate diff % curCycle. if == 0, just add to where they stand together
// if not - add cycles to this with one overhopping
// and check whether they are all equeal
stepCounts := make([]int, len(pathInfos))
for i, pathInfo := range pathInfos {
stepCounts[i] = pathInfo.InitialSteps
}
// oh, i don't need to seed the cycle, they are already not equal
cycle := 0
for !allEqual(stepCounts) {
max := slices.Max(stepCounts)
for i, pathInfo := range pathInfos {
steps := stepCounts[i]
if steps == max {
continue
}
diff := max - steps
isCatchingUp := diff % pathInfo.CycleLen == 0
if isCatchingUp {
stepCounts[i] = max
} else {
overJump := (diff / pathInfo.CycleLen + 1) * pathInfo.CycleLen
stepCounts[i] += overJump
}
}
cycle += 1
if cycle % 10000000 == 0 {
log.Printf("done %d cycle iteration, results : %+v", cycle, stepCounts)
}
}
return stepCounts[0]
}
func allEqual(nums []int) bool {
head := nums[0]
for _, num := range nums {
if num != head {
return false
}
}
return true
}
func getNextNode(net Network, curNode Node, direction rune) Node {
var nextNodeName string
switch direction {
case 'L':
nextNodeName = curNode.Left
case 'R':
nextNodeName = curNode.Right
}
nextNode, found := net.Nodes[nextNodeName]
if !found {
panic(fmt.Sprintf("instruction %s from node %+v results in not found next node %s",
string(direction), curNode, nextNodeName))
}
return nextNode
}
func TraverseNetwork(net Network, path Path) int {
stepsNum := 0
curNode := net.Nodes["AAA"]
for curNode.Name != "ZZZ" {
direction := path.next()
var nextNodeName string
switch direction {
case 'L':
nextNodeName = curNode.Left
case 'R':
nextNodeName = curNode.Right
}
nextNode, found := net.Nodes[nextNodeName]
if !found {
panic(fmt.Sprintf("at step %d instruction %s from node %+v results in not found next node %s",
stepsNum, string(direction), curNode, nextNodeName))
}
curNode = nextNode
stepsNum += 1
}
return stepsNum
}
func Read(filename string) (Network, Path) {
net := Network{
Nodes: make(map[string]Node),
}
bytes, err := os.ReadFile(filename)
if err != nil {
panic(fmt.Sprintln("error reading file ", filename))
}
lines := strings.Split(string(bytes), "\n")
path := Path{
Instruction: strings.TrimSpace(lines[0]),
}
netDescriptions := lines[2:]
re := regexp.MustCompile(`(?P<NAME>\D{3}) = \((?P<LEFT>\D{3}), (?P<RIGHT>\D{3})\)`)
nameIndex := re.SubexpIndex("NAME")
leftIndex := re.SubexpIndex("LEFT")
rightIndex := re.SubexpIndex("RIGHT")
for _, line := range netDescriptions {
if line == "" {
continue
}
match := re.FindStringSubmatch(line)
if match == nil {
panic(fmt.Sprintln("error finding match in string : ", line))
}
node := Node{
Name: match[nameIndex],
Left: match[leftIndex],
Right: match[rightIndex],
}
net.Nodes[node.Name] = node
}
return net, path
}
type Node struct {
Name string
Left, Right string
}
type Network struct {
Nodes map[string]Node
}
type Path struct {
Instruction string
curStep int
}
func (p *Path) next() rune {
curInstruction := p.Instruction[p.curStep]
p.curStep += 1
if p.curStep == len(p.Instruction) {
p.curStep = 0
}
return rune(curInstruction)
}