Skip to content

Commit

Permalink
finished day 8
Browse files Browse the repository at this point in the history
  • Loading branch information
devries committed Dec 8, 2023
1 parent 77db65e commit e9d2f95
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 2 deletions.
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Advent of Code 2023

[![Tests](https://github.com/devries/advent_of_code_2023/actions/workflows/main.yml/badge.svg)](https://github.com/devries/advent_of_code_2023/actions/workflows/main.yml)
[![Stars: 14](https://img.shields.io/badge/⭐_Stars-14-yellow)](https://adventofcode.com/2023)
[![Stars: 16](https://img.shields.io/badge/⭐_Stars-16-yellow)](https://adventofcode.com/2023)

## Plan for This Year

Expand Down Expand Up @@ -126,3 +126,16 @@ the third run of my solution after compilation on my Raspberry Pi.
two 3s, one J, and two other cards, I would add the J to the 3s making three
of a kind. I got a bit hung up on the golang sorting pattern, maybe because I
hadn't had my coffee?

- [Day 8: Haunted Wasteland](https://adventofcode.com/2023/day/8) - [part 1](day08p1/solution.go), [part 2](day08p2/solution.go)

This is a series where you calculate the least common multiple of a set of
cycling states in order to find the interval over which all of the states
fully run through their cycles. I initially printed out the step at which the
cycle started and how many steps until it repeated. I noticed that the problem
is contrived such that the number of steps it initially took to reach the
desired end state was equal to the interval between times it hit that end
state, which makes the problem a straighforward least common multiples problem.
If the problem had not been contrived in that way, it is possible that there
would not have been a period over which all starting states eventually
synchronize so all ending states are reached at the same time.
50 changes: 50 additions & 0 deletions day08p1/solution.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package day08p1

import (
"io"
"strings"

"aoc/utils"
)

func Solve(r io.Reader) any {
lines := utils.ReadLines(r)

// Get all LR moves
moves := []rune(lines[0])

neighbors := make(map[string][]string)
for i := 2; i < len(lines); i++ {
parts := strings.Split(lines[i], " = ")

destinations := strings.Trim(parts[1], "()")

leftright := strings.Split(destinations, ", ")

neighbors[parts[0]] = leftright
}

next := neighbors["AAA"]
steps := 0

outer:
for {
for _, d := range moves {
steps++
switch d {
case 'L':
if next[0] == "ZZZ" {
break outer
}
next = neighbors[next[0]]
case 'R':
if next[1] == "ZZZ" {
break outer
}
next = neighbors[next[1]]
}
}
}

return steps
}
48 changes: 48 additions & 0 deletions day08p1/solution_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package day08p1

import (
"strings"
"testing"

"aoc/utils"
)

var testInput = `RL
AAA = (BBB, CCC)
BBB = (DDD, EEE)
CCC = (ZZZ, GGG)
DDD = (DDD, DDD)
EEE = (EEE, EEE)
GGG = (GGG, GGG)
ZZZ = (ZZZ, ZZZ)`

var testInput2 = `LLR
AAA = (BBB, BBB)
BBB = (AAA, ZZZ)
ZZZ = (ZZZ, ZZZ)`

func TestSolve(t *testing.T) {
tests := []struct {
input string
answer int
}{
{testInput, 2},
{testInput2, 6},
}

if testing.Verbose() {
utils.Verbose = true
}

for _, test := range tests {
r := strings.NewReader(test.input)

result := Solve(r).(int)

if result != test.answer {
t.Errorf("Expected %d, got %d", test.answer, result)
}
}
}
75 changes: 75 additions & 0 deletions day08p2/solution.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package day08p2

import (
"io"
"strings"

"aoc/utils"
)

func Solve(r io.Reader) any {
lines := utils.ReadLines(r)

// Get all LR moves
moves := []rune(lines[0])

neighbors := make(map[string][]string)
for i := 2; i < len(lines); i++ {
parts := strings.Split(lines[i], " = ")

destinations := strings.Trim(parts[1], "()")

leftright := strings.Split(destinations, ", ")

neighbors[parts[0]] = leftright
}

cyclesteps := int64(1)

// Let's find the ones ending in A
for pos := range neighbors {
if !strings.HasSuffix(pos, "A") {
continue
}
var steps int64

hits := []Hit{}

outer:
for {
for i, d := range moves {
next := neighbors[pos]
steps++
switch d {
case 'L':
pos = next[0]
case 'R':
pos = next[1]
}

if strings.HasSuffix(pos, "Z") {
h := Hit{pos, i, steps}
for _, ph := range hits {
if h.Candidate == ph.Candidate {
// An examination shows that the interval between hits appears to be the same as the
// time from start to first hit. This is slightly contrived, but makes the solution easier.
// This is not a general solution, but a solution that works for this and I suspect all
// advent of code inputs for this day.
cyclesteps = utils.Lcm(cyclesteps, h.Steps-ph.Steps)
break outer
}
}
hits = append(hits, h)
}
}
}
}
return cyclesteps
}

// State when destination candidate is hit
type Hit struct {
Candidate string // Ends with Z
Cycle int // i value when hit
Steps int64 // step at which it was hit
}
42 changes: 42 additions & 0 deletions day08p2/solution_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package day08p2

import (
"strings"
"testing"

"aoc/utils"
)

var testInput = `LR
11A = (11B, XXX)
11B = (XXX, 11Z)
11Z = (11B, XXX)
22A = (22B, XXX)
22B = (22C, 22C)
22C = (22Z, 22Z)
22Z = (22B, 22B)
XXX = (XXX, XXX)`

func TestSolve(t *testing.T) {
tests := []struct {
input string
answer int64
}{
{testInput, 6},
}

if testing.Verbose() {
utils.Verbose = true
}

for _, test := range tests {
r := strings.NewReader(test.input)

result := Solve(r).(int64)

if result != test.answer {
t.Errorf("Expected %d, got %d", test.answer, result)
}
}
}
2 changes: 1 addition & 1 deletion inputs

0 comments on commit e9d2f95

Please sign in to comment.