diff --git a/days/12.go b/days/12.go new file mode 100644 index 0000000..4a546f0 --- /dev/null +++ b/days/12.go @@ -0,0 +1,156 @@ +package days + +import ( + "fmt" + "math" + "strconv" + "strings" + + u "parnic.com/aoc2019/utilities" +) + +type moonData struct { + pos u.Vec3[int] + vel u.Vec3[int] +} + +func (m *moonData) applyGravity(other *moonData) { + applyGravityAxis := func(pos1, pos2, vel1, vel2 *int) { + if *pos1 < *pos2 { + *vel1++ + *vel2-- + } else if *pos1 > *pos2 { + *vel1-- + *vel2++ + } + } + + applyGravityAxis(&m.pos.X, &other.pos.X, &m.vel.X, &other.vel.X) + applyGravityAxis(&m.pos.Y, &other.pos.Y, &m.vel.Y, &other.vel.Y) + applyGravityAxis(&m.pos.Z, &other.pos.Z, &m.vel.Z, &other.vel.Z) +} + +func (m *moonData) applyVelocity() { + m.pos.Add(m.vel) +} + +func (m moonData) getPotentialEnergy() int { + return int(math.Abs(float64(m.pos.X))) + + int(math.Abs(float64(m.pos.Y))) + + int(math.Abs(float64(m.pos.Z))) +} + +func (m moonData) getKineticEnergy() int { + return int(math.Abs(float64(m.vel.X))) + + int(math.Abs(float64(m.vel.Y))) + + int(math.Abs(float64(m.vel.Z))) +} + +func (m moonData) getTotalEnergy() int { + return m.getPotentialEnergy() * m.getKineticEnergy() +} + +type Day12 struct { + moons []*moonData +} + +func (d *Day12) Parse() { + lines := u.GetStringLines("12p") + d.moons = make([]*moonData, len(lines)) + for i, line := range lines { + trimmed := line[1 : len(line)-1] + vals := strings.Split(trimmed, ", ") + x, _ := strconv.Atoi(vals[0][2:]) + y, _ := strconv.Atoi(vals[1][2:]) + z, _ := strconv.Atoi(vals[2][2:]) + d.moons[i] = &moonData{ + pos: u.Vec3[int]{X: x, Y: y, Z: z}, + } + } +} + +func (d Day12) Num() int { + return 12 +} + +func (d Day12) copyMoons() []*moonData { + moons := make([]*moonData, len(d.moons)) + for i, moon := range d.moons { + moonCopy := *moon + moons[i] = &moonCopy + } + + return moons +} + +func getAllEnergy(moons ...*moonData) int { + energy := 0 + for _, moon := range moons { + energy += moon.getTotalEnergy() + } + return energy +} + +func (d *Day12) Part1() string { + moons := d.copyMoons() + + numSteps := 1000 + + for i := 0; i < numSteps; i++ { + for i, moon1 := range moons { + for _, moon2 := range moons[i+1:] { + moon1.applyGravity(moon2) + } + + moon1.applyVelocity() + } + } + + return fmt.Sprintf("Total energy after %d steps: %s%d%s", numSteps, u.TextBold, getAllEnergy(moons...), u.TextReset) +} + +func (d *Day12) Part2() string { + moons := d.copyMoons() + + orig := make([]u.Vec3[int], len(moons)) + for i, moon := range moons { + orig[i] = moon.pos + } + period := u.Vec3[int]{} + + for loops := 0; period.X == 0 || period.Y == 0 || period.Z == 0; loops++ { + for i, moon1 := range moons { + for _, moon2 := range moons[i+1:] { + moon1.applyGravity(moon2) + } + moon1.applyVelocity() + } + + foundX := true + foundY := true + foundZ := true + for i, moon := range moons { + if moon.pos.X != orig[i].X || moon.vel.X != 0 { + foundX = false + } + if moon.pos.Y != orig[i].Y || moon.vel.Y != 0 { + foundY = false + } + if moon.pos.Z != orig[i].Z || moon.vel.Z != 0 { + foundZ = false + } + } + if foundX && period.X == 0 { + period.X = loops + 1 + } + if foundY && period.Y == 0 { + period.Y = loops + 1 + } + if foundZ && period.Z == 0 { + period.Z = loops + 1 + } + } + + stepsRequired := u.LCM(period.X, period.Y, period.Z) + return fmt.Sprintf("Iterations to reach a previous state: %s%d%s", u.TextBold, stepsRequired, u.TextReset) +} diff --git a/inputs/12p.txt b/inputs/12p.txt new file mode 100644 index 0000000..503dc0b --- /dev/null +++ b/inputs/12p.txt @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/inputs/12s1.txt b/inputs/12s1.txt new file mode 100644 index 0000000..4e4b09c --- /dev/null +++ b/inputs/12s1.txt @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/inputs/12s2.txt b/inputs/12s2.txt new file mode 100644 index 0000000..c0edb40 --- /dev/null +++ b/inputs/12s2.txt @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/main.go b/main.go index ee0ff88..5b62ab3 100644 --- a/main.go +++ b/main.go @@ -42,6 +42,7 @@ var dayMap = []day{ &days.Day09{}, &days.Day10{}, &days.Day11{}, + &days.Day12{}, } func main() { diff --git a/utilities/math.go b/utilities/math.go new file mode 100644 index 0000000..de6c372 --- /dev/null +++ b/utilities/math.go @@ -0,0 +1,27 @@ +package utilities + +func GCD[T Integer](a, b T) T { + if b == 0 { + return a + } + return GCD(b, a%b) +} + +func LCM[T Integer](nums ...T) uint64 { + num := len(nums) + if num == 0 { + return 0 + } else if num == 1 { + return uint64(nums[0]) + } + + ret := lcm(nums[0], nums[1]) + for i := 2; i < len(nums); i++ { + ret = lcm(uint64(nums[i]), ret) + } + return ret +} + +func lcm[T Integer](a, b T) uint64 { + return uint64(a*b) / uint64(GCD(a, b)) +} diff --git a/utilities/vector.go b/utilities/vector.go index fbfc345..34ea4d0 100644 --- a/utilities/vector.go +++ b/utilities/vector.go @@ -7,6 +7,12 @@ type Vec2[T Number] struct { Y T } +type Vec3[T Number] struct { + X T + Y T + Z T +} + func (v Vec2[T]) Dot(other Vec2[T]) T { return (v.X * other.X) + (v.Y * other.Y) } @@ -37,3 +43,27 @@ func VecBetween[T Number](a, b Vec2[T]) Vec2[T] { Y: a.Y - b.Y, } } + +func (v Vec3[T]) Dot(other Vec3[T]) T { + return (v.X * other.X) + (v.Y * other.Y) + (v.Z * other.Z) +} + +func (v Vec3[T]) Len() T { + return T(math.Sqrt(float64(v.LenSquared()))) +} + +func (v Vec3[T]) LenSquared() T { + return (v.X * v.X) + (v.Y * v.Y) + (v.Z * v.Z) +} + +func (v *Vec3[T]) Add(other Vec3[T]) { + v.X += other.X + v.Y += other.Y + v.Z += other.Z +} + +func (v Vec3[T]) Equals(other Vec3[T]) bool { + return v.X == other.X && + v.Y == other.Y && + v.Z == other.Z +}