One of The Historians needs to use the bathroom; fortunately, you know there’s a bathroom near an unvisited location on their list, and so you’re all quickly teleported directly to the lobby of Easter Bunny Headquarters.
Solution in Java
Full source can be found: in GitHub
Since the input is a robot I already added the following code before even starting any of the solution algorithms.
1private class Robot {
2 private final Point location;
3 private final Point velocity;
4
5 public Robot(Point location, Point velocity) {
6 this.location = location;
7 this.velocity = velocity;
8 }
9}
As well as some generic code to convert the input for today in a list of these Robot
s.
1private Stream<Robot> parseRobots() {
2 var pattern = Pattern.compile("p=(?<X>-?\\d+),(?<Y>-?\\d+) v=(?<vX>-?\\d+),(?<vY>-?\\d+)");
3 return inputLoader.splitOnNewLine()
4 .map(pattern::matcher)
5 .filter(Matcher::matches)
6 .map(matcher -> new Robot(
7 Point.of(Integer.parseInt(matcher.group("X")), Integer.parseInt(matcher.group("Y"))),
8 Point.of(Integer.parseInt(matcher.group("vX")), Integer.parseInt(matcher.group("vY")))));
9}
Part 1
To be able to produce the correct number of robots I added a method that divides the grid in to 4 quadrants and then counts the amount of robots per quadrant. The count of each quadrant is then multiplied to yield the final result.
1private long countRobotsInQuadrants(List<Point> robots) {
2 var middleWidth = bounds.width() / 2;
3 var middleHeight = bounds.height() / 2;
4
5 var bottomTop = middleHeight - 1;
6 var topBottom = middleHeight + 1;
7 var leftRight = middleWidth - 1;
8 var rightLeft = middleWidth + 1;
9
10 var leftBottom = new Bounds(0, 0, leftRight, bottomTop);
11 var rightBottom = new Bounds(rightLeft, 0, bounds.width(), bottomTop);
12
13 var leftTop = new Bounds(0, topBottom, leftRight, bounds.height());
14 var rightTop = new Bounds(rightLeft, topBottom, bounds.width(), bounds.height());
15
16 return robots.stream()
17 .collect(
18 Collectors.teeing(
19 Collectors.teeing(
20 Collectors.filtering(leftTop::inBounds, Collectors.counting()),
21 Collectors.filtering(rightTop::inBounds, Collectors.counting()),
22 (left, right) -> left * right),
23 Collectors.teeing(
24 Collectors.filtering(leftBottom::inBounds, Collectors.counting()),
25 Collectors.filtering(rightBottom::inBounds, Collectors.counting()),
26 (left, right) -> left * right),
27 (left, right) -> left * right
28 )
29 );
30}
Aside of the algorithm to compute the Robot
instances in each quadrant, I added some logic to the Robot
class to allow it to move. This method computes the new end position of the robot based on the amount of times it is to move.
1public Point move(int totalSteps) {
2 var x = (location.x() + velocity.x() * totalSteps) % bounds.width();
3 var y = (location.y() + velocity.y() * totalSteps) % bounds.height();
4
5 if (x < 0) {
6 x += bounds.width();
7 }
8 if (y < 0) {
9 y += bounds.height();
10 }
11 return Point.of(x, y);
12}
Using the added move()
and the countRobotsInQuadrants()
it is now possible to build the actual part 1 code.
1public void part1() {
2 bounds = new Bounds(0, 0, 101, 103);
3 var robots = parseRobots()
4 .map(robot -> robot.move(100))
5 .toList();
6
7 validator.part1(countRobotsInQuadrants(robots));
8}
Part 2
For the second part we look for a unique position of each robot.
1public void part2() {
2 bounds = new Bounds(0, 0, 101, 103);
3
4 var i = 0;
5 List<Point> newPositions;
6 var uniquePositions = new HashSet<Point>();
7 var robots = parseRobots().toList();
8 do {
9 final var finalI = ++i;
10 newPositions = robots.stream()
11 .map(robot -> robot.move(finalI))
12 .toList();
13
14 uniquePositions = new HashSet<>(newPositions);
15 } while (uniquePositions.size() != newPositions.size());
16
17 validator.part2(i);
18}