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\D{3}) = \((?P\D{3}), (?P\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) }