220 lines
5.0 KiB
Go
220 lines
5.0 KiB
Go
package day8
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"regexp"
|
|
"slices"
|
|
"strings"
|
|
)
|
|
|
|
func Run() int {
|
|
fmt.Print("hello day 8")
|
|
filename := "day8/example3"
|
|
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
|
|
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
|
|
}
|
|
}
|
|
log.Printf("done one cycle iteration, results : %+v", 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)
|
|
}
|