diff --git a/day12.go b/day12.go new file mode 100644 index 0000000..c161e4e --- /dev/null +++ b/day12.go @@ -0,0 +1,207 @@ +package aoc2019 + +import ( + "bufio" + "fmt" + "io" + "math" + "os" + "regexp" + "strconv" + + "github.com/pkg/errors" +) + +type day12MoonSystem []*day12Moon + +func day12MoonSystemFromFile(inFile string) (day12MoonSystem, error) { + f, err := os.Open(inFile) + if err != nil { + return nil, errors.Wrap(err, "Unable to open inFile") + } + defer f.Close() + + return day12MoonSystemFromReader(f) +} + +func day12MoonSystemFromReader(r io.Reader) (day12MoonSystem, error) { + var moons day12MoonSystem + + scanner := bufio.NewScanner(r) + for scanner.Scan() { + m, err := day12MoonFromScan(scanner.Text()) + if err != nil { + return nil, errors.Wrap(err, "Unable to read input file") + } + moons = append(moons, m) + } + + return moons, errors.Wrap(scanner.Err(), "Unable to scan file") +} + +func (d day12MoonSystem) move(steps int) { + for i := 0; i < steps; i++ { + d.updateVelocity() + for _, m := range d { + m.move() + } + } +} + +func (d day12MoonSystem) totalEnergy() int64 { + var e int64 + for _, m := range d { + e += m.totalEnergy() + } + return e +} + +func (d day12MoonSystem) updateVelocity() { + for _, affected := range d { + for _, affector := range d { + if affector == affected { + // Moons does not affect themselves + continue + } + + if affector.PX > affected.PX { + affected.VX += 1 + } else if affector.PX < affected.PX { + affected.VX -= 1 + } + + if affector.PY > affected.PY { + affected.VY += 1 + } else if affector.PY < affected.PY { + affected.VY -= 1 + } + + if affector.PZ > affected.PZ { + affected.VZ += 1 + } else if affector.PZ < affected.PZ { + affected.VZ -= 1 + } + } + } +} + +type day12Moon struct { + PX, PY, PZ int64 + VX, VY, VZ int64 +} + +func day12MoonFromScan(scan string) (*day12Moon, error) { + grps := regexp.MustCompile(`^$`).FindStringSubmatch(scan) + if len(grps) != 4 { + return nil, errors.Errorf("Invalid input %q: %#v", scan, grps) + } + + var ( + err error + m = &day12Moon{} + ) + + if m.PX, err = strconv.ParseInt(grps[1], 10, 64); err != nil { + return nil, errors.Wrap(err, "Invalid X") + } + if m.PY, err = strconv.ParseInt(grps[2], 10, 64); err != nil { + return nil, errors.Wrap(err, "Invalid Y") + } + if m.PZ, err = strconv.ParseInt(grps[3], 10, 64); err != nil { + return nil, errors.Wrap(err, "Invalid Z") + } + + return m, nil +} + +func (d day12Moon) String() string { + return fmt.Sprintf( + "pos=, vel=", + d.PX, d.PY, d.PZ, + d.VX, d.VY, d.VZ, + ) +} + +func (d *day12Moon) clearKinetic() { d.VX, d.VY, d.VZ = 0, 0, 0 } + +func (d day12Moon) eq(t *day12Moon, component int) bool { + switch component { + case 0: + return d.PX == t.PX && d.VX == t.VX + case 1: + return d.PY == t.PY && d.VY == t.VY + case 2: + return d.PZ == t.PZ && d.VZ == t.VZ + default: + panic("Invalid component compared") + } +} + +func (d *day12Moon) move() { + d.PX += d.VX + d.PY += d.VY + d.PZ += d.VZ +} + +func (d day12Moon) totalEnergy() int64 { + return int64((math.Abs(float64(d.PX)) + math.Abs(float64(d.PY)) + math.Abs(float64(d.PZ))) * (math.Abs(float64(d.VX)) + math.Abs(float64(d.VY)) + math.Abs(float64(d.VZ)))) +} + +func solveDay12Part1(inFile string) (int64, error) { + moons, err := day12MoonSystemFromFile(inFile) + if err != nil { + return 0, errors.Wrap(err, "Unable to read system scan") + } + + moons.move(1000) + + return moons.totalEnergy(), nil +} + +func solveDay12Part2(inFile string) (int64, error) { + moons, err := day12MoonSystemFromFile(inFile) + if err != nil { + return 0, errors.Wrap(err, "Unable to read system scan") + } + + initMoons, _ := day12MoonSystemFromFile(inFile) + var ( + iterations [3]int64 + completed [3]bool + ) + + for { + moons.move(1) + + for c := 0; c < len(completed); c++ { + if completed[c] { + continue + } + + var matches = true + for i := range moons { + if !moons[i].eq(initMoons[i], c) { + matches = false + } + } + + iterations[c]++ + if matches { + completed[c] = true + } + } + + var allComplete = true + for _, c := range completed { + if !c { + allComplete = false + } + } + + if allComplete { + break + } + } + + return leastCommonMultiple(leastCommonMultiple(iterations[0], iterations[1]), iterations[2]), nil +} diff --git a/day12_input.txt b/day12_input.txt new file mode 100644 index 0000000..68f1e9d --- /dev/null +++ b/day12_input.txt @@ -0,0 +1,4 @@ + + + + diff --git a/day12_test.go b/day12_test.go new file mode 100644 index 0000000..3f6ad33 --- /dev/null +++ b/day12_test.go @@ -0,0 +1,86 @@ +package aoc2019 + +import ( + "strings" + "testing" +) + +func TestDay12MoonExample(t *testing.T) { + moons, err := day12MoonSystemFromReader(strings.NewReader("\n\n\n")) + if err != nil { + t.Fatalf("Unable to parse moon system: %s", err) + } + + if l := len(moons); l != 4 { + t.Fatalf("Unexpected moon count: exp=4 got=%d", l) + } + + // Check initial state + for i, expStr := range []string{ + "pos=, vel=", + "pos=, vel=", + "pos=, vel=", + "pos=, vel=", + } { + if s := moons[i].String(); s != expStr { + t.Errorf("Unexpected moon %d: exp=%q got=%q", i, expStr, s) + } + } + + // Move moons by one step + moons.move(1) + + // Check move state + for i, expStr := range []string{ + "pos=, vel=", + "pos=, vel=", + "pos=, vel=", + "pos=, vel=", + } { + if s := moons[i].String(); s != expStr { + t.Errorf("Unexpected moon %d: exp=%q got=%q", i, expStr, s) + } + } + + // Move moons by nine more step + moons.move(9) + + // Check move state + for i, expStr := range []string{ + "pos=, vel=", + "pos=, vel=", + "pos=, vel=", + "pos=, vel=", + } { + if s := moons[i].String(); s != expStr { + t.Errorf("Unexpected moon %d: exp=%q got=%q", i, expStr, s) + } + } + + // Check energy after step 10 + for i, expE := range []int64{ + 36, 45, 80, 18, + } { + if e := moons[i].totalEnergy(); e != expE { + t.Errorf("Unexpected energy on moon %d: exp=%d got=%d", i, expE, e) + } + } +} + +func TestCalculateDay12_Part1(t *testing.T) { + count, err := solveDay12Part1("day12_input.txt") + if err != nil { + t.Fatalf("Day 12 solver failed: %s", err) + } + + t.Logf("Solution Day 12 Part 1: %d", count) +} + +func TestCalculateDay12_Part2(t *testing.T) { + res, err := solveDay12Part2("day12_input.txt") + if err != nil { + t.Fatalf("Day 12 solver failed: %s", err) + } + + t.Logf("Solution Day 12 Part 2: %d", res) +} diff --git a/helpers.go b/helpers.go index 737a750..ade5597 100644 --- a/helpers.go +++ b/helpers.go @@ -11,6 +11,10 @@ func greatestCommonDivisor(a, b int64) int64 { return a } +func leastCommonMultiple(a, b int64) int64 { + return a * b / greatestCommonDivisor(a, b) +} + func manhattenDistance(x1, y1, x2, y2 int) int { return int(math.Abs(float64(x1-x2)) + math.Abs(float64(y1-y2))) }