250 lines
4.4 KiB
Go
250 lines
4.4 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"bufio"
|
||
|
"fmt"
|
||
|
"os"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
func main() {
|
||
|
if len(os.Args) != 2 {
|
||
|
fmt.Fprintln(os.Stderr, "Usage: day08 FILE")
|
||
|
}
|
||
|
|
||
|
input, err := os.Open(os.Args[1])
|
||
|
if err != nil {
|
||
|
fmt.Fprintln(os.Stderr, err)
|
||
|
}
|
||
|
|
||
|
scan := bufio.NewScanner(input)
|
||
|
|
||
|
result, err := run(scan)
|
||
|
if err != nil {
|
||
|
fmt.Println("ERR", err)
|
||
|
os.Exit(1)
|
||
|
}
|
||
|
|
||
|
fmt.Println("result", result)
|
||
|
}
|
||
|
|
||
|
type result struct {
|
||
|
stepsPT1 int
|
||
|
stepsPT2 uint64
|
||
|
}
|
||
|
|
||
|
func run(scan *bufio.Scanner) (*result, error) {
|
||
|
var path []rune
|
||
|
m := make(nodeMap)
|
||
|
|
||
|
for scan.Scan() {
|
||
|
text := scan.Text()
|
||
|
if len(text) == 0 {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if len(path) == 0 {
|
||
|
fmt.Println("path", text)
|
||
|
path = []rune(strings.TrimSpace(text))
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
n := &node{}
|
||
|
i, err := fmt.Sscanf(text, "%s = (%s %s", &n.value, &n.lvalue, &n.rvalue)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
n.lvalue = strings.TrimRight(n.lvalue, ",)")
|
||
|
n.rvalue = strings.TrimRight(n.rvalue, ",)")
|
||
|
m[n.value] = n
|
||
|
|
||
|
fmt.Println("value", i, n.value, n.lvalue, n.rvalue)
|
||
|
}
|
||
|
if err := m.mapNodes(); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
steps1 := SolutionPT1(m, path)
|
||
|
steps2 := SolutionPT2(m, path)
|
||
|
|
||
|
return &result{steps1, steps2}, nil
|
||
|
}
|
||
|
|
||
|
type node struct {
|
||
|
value string
|
||
|
lvalue, rvalue string
|
||
|
left, right *node
|
||
|
}
|
||
|
|
||
|
type nodeMap map[string]*node
|
||
|
|
||
|
func (m nodeMap) mapNodes() error {
|
||
|
for k, v := range m {
|
||
|
if ptr, ok := m[v.lvalue]; ok {
|
||
|
v.left = ptr
|
||
|
} else {
|
||
|
return fmt.Errorf("%s L-> %s not found", k, v.lvalue)
|
||
|
}
|
||
|
if ptr, ok := m[v.rvalue]; ok {
|
||
|
v.right = ptr
|
||
|
} else {
|
||
|
return fmt.Errorf("%s R-> %s not found", k, v.rvalue)
|
||
|
}
|
||
|
|
||
|
m[k] = v
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func SolutionPT1(m nodeMap, path []rune) int {
|
||
|
fmt.Println("---- PART 1 BEGIN ----")
|
||
|
position, ok := m["AAA"]
|
||
|
if !ok {
|
||
|
return 0
|
||
|
}
|
||
|
var i int
|
||
|
var steps int
|
||
|
|
||
|
for steps < 100000 {
|
||
|
steps++
|
||
|
if path[i] == 'R' {
|
||
|
fmt.Println("step", steps, position.value, "R->", position.rvalue)
|
||
|
position = position.right
|
||
|
} else {
|
||
|
fmt.Println("step", steps, position.value, "L->", position.lvalue)
|
||
|
position = position.left
|
||
|
}
|
||
|
|
||
|
if position.value == "ZZZ" {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
i++
|
||
|
if i > len(path)-1 {
|
||
|
i = 0
|
||
|
}
|
||
|
}
|
||
|
fmt.Println("---- PART 1 END ----")
|
||
|
return steps
|
||
|
}
|
||
|
|
||
|
func SolutionPT2(m nodeMap, path []rune) uint64 {
|
||
|
fmt.Println("---- PART 2 BEGIN ----")
|
||
|
|
||
|
type loop struct {
|
||
|
start, position, end *node
|
||
|
steps uint64
|
||
|
}
|
||
|
loops := make(map[*node]loop)
|
||
|
|
||
|
endpoints := make(map[*node]struct{})
|
||
|
for k, n := range m {
|
||
|
if strings.HasSuffix(k, "A") {
|
||
|
fmt.Println("start", k)
|
||
|
loops[n] = loop{start: n, position: n}
|
||
|
}
|
||
|
|
||
|
if strings.HasSuffix(k, "Z") {
|
||
|
fmt.Println("stop", k)
|
||
|
endpoints[n] = struct{}{}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var i int
|
||
|
var steps uint64
|
||
|
var stops int
|
||
|
maxUint := ^uint64(0)
|
||
|
loopsFound := 0
|
||
|
|
||
|
for steps < maxUint {
|
||
|
steps++
|
||
|
if path[i] == 'R' {
|
||
|
for k, loop := range loops {
|
||
|
// fmt.Println("step", steps, position.value, "R->", position.rvalue)
|
||
|
loop.position = loop.position.right
|
||
|
loops[k] = loop
|
||
|
}
|
||
|
} else {
|
||
|
for k, loop := range loops {
|
||
|
// fmt.Println("step", steps, position.value, "L->", position.lvalue)
|
||
|
loop.position = loop.position.left
|
||
|
loops[k] = loop
|
||
|
}
|
||
|
}
|
||
|
|
||
|
done := true
|
||
|
s := 0
|
||
|
for k, loop := range loops {
|
||
|
if _, ok := endpoints[loop.position]; !ok {
|
||
|
// fmt.Println("no stop", i, position.value)
|
||
|
done = false
|
||
|
// break
|
||
|
} else {
|
||
|
// fmt.Println("stop", i, position.value)
|
||
|
if loop.end == nil {
|
||
|
loop.end = loop.position
|
||
|
loop.steps = steps
|
||
|
fmt.Println("loop found", loop.position.value, "steps", steps)
|
||
|
loops[k] = loop
|
||
|
loopsFound++
|
||
|
}
|
||
|
s++
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if loopsFound == len(loops) {
|
||
|
var values []uint64
|
||
|
for _, loop := range loops {
|
||
|
values = append(values, loop.steps)
|
||
|
}
|
||
|
return LCM(values...)
|
||
|
}
|
||
|
|
||
|
if s > stops {
|
||
|
stops = s
|
||
|
fmt.Println("stops", stops, "steps", steps)
|
||
|
}
|
||
|
|
||
|
if done {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
i++
|
||
|
if i > len(path)-1 {
|
||
|
i = 0
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fmt.Println("---- PART 2 END ----")
|
||
|
return steps
|
||
|
}
|
||
|
|
||
|
// greatest common divisor (GCD) via Euclidean algorithm
|
||
|
func GCD(a, b uint64) uint64 {
|
||
|
for b != 0 {
|
||
|
t := b
|
||
|
b = a % b
|
||
|
a = t
|
||
|
}
|
||
|
return a
|
||
|
}
|
||
|
|
||
|
// find Least Common Multiple (LCM) via GCD
|
||
|
func LCM(integers ...uint64) uint64 {
|
||
|
if len(integers) == 0 {
|
||
|
return 0
|
||
|
}
|
||
|
if len(integers) == 1 {
|
||
|
return integers[0]
|
||
|
}
|
||
|
|
||
|
a, b := integers[0], integers[1]
|
||
|
result := a * b / GCD(a, b)
|
||
|
|
||
|
for _, c := range integers[2:] {
|
||
|
result = LCM(result, c)
|
||
|
}
|
||
|
|
||
|
return result
|
||
|
}
|