Skip to content

Commit 3207f46

Browse files
committed
day 15
1 parent 360dc68 commit 3207f46

File tree

11 files changed

+353
-0
lines changed

11 files changed

+353
-0
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,7 @@ Today I randomly discovered
8080
[local classes](https://docs.oracle.com/javase/tutorial/java/javaOO/localclasses.html) and
8181
[records](https://docs.oracle.com/en/java/javase/17/language/records.html). This allows you to scope, say, an intermediate record for a stream transformation, to a single method
8282
and avoid clutter at the class level. Pretty cool!
83+
84+
### 12/15/2024
85+
86+
Day 14 Part 2 had me stumped, at least for now. Moving on to Day 15.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.codefork.aoc2024.day15;
2+
3+
public record Box(Position pos, int width) {
4+
5+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.codefork.aoc2024.day15;
2+
3+
public enum Direction {
4+
Up, Right, Down, Left;
5+
6+
public static Direction parse(String s) {
7+
return switch (s) {
8+
case "^" -> Up;
9+
case ">" -> Right;
10+
case "v" -> Down;
11+
case "<" -> Left;
12+
default -> throw new RuntimeException("unrecognized direction");
13+
};
14+
}
15+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package com.codefork.aoc2024.day15;
2+
3+
import com.codefork.aoc2024.util.WithIndex;
4+
5+
import java.util.ArrayList;
6+
import java.util.HashSet;
7+
import java.util.List;
8+
import java.util.stream.Stream;
9+
10+
import static com.codefork.aoc2024.util.FoldLeft.foldLeft;
11+
12+
public record Document(Warehouse warehouse, List<Direction> moves) {
13+
14+
private static final List<String> mapChars = List.of("O", "#", "@", ".");
15+
16+
/**
17+
* @param data
18+
* @param width interpret walls and boxes to be as wide as this value
19+
* @return
20+
*/
21+
public static Document parse(Stream<String> data, int width) {
22+
return data
23+
.map(WithIndex.indexed())
24+
.filter(lineWithIndex -> !lineWithIndex.value().isEmpty())
25+
.collect(foldLeft(
26+
() -> new Document(new Warehouse(new HashSet<>(), new HashSet<>(), new Position(-1, -1)), new ArrayList<>()),
27+
(acc, lineWithIndex) -> {
28+
var y = lineWithIndex.index();
29+
var line = lineWithIndex.value();
30+
if (mapChars.stream().anyMatch(line::contains)) {
31+
return line.chars()
32+
.boxed()
33+
.map(WithIndex.indexed())
34+
.collect(foldLeft(
35+
() -> acc,
36+
(acc2, chWithIndex) -> {
37+
var x = chWithIndex.index() * width;
38+
var ch = String.valueOf((char) chWithIndex.value().intValue());
39+
var pos = new Position(x, y);
40+
switch (ch) {
41+
case "#" -> {
42+
var newWalls = new HashSet<>(acc2.warehouse().walls());
43+
for (var n = 0; n < width; n++) {
44+
newWalls.add(new Position(pos.x() + n, pos.y()));
45+
}
46+
return new Document(acc2.warehouse().withWalls(newWalls), acc2.moves());
47+
}
48+
case "O" -> {
49+
var newBoxes = new HashSet<>(acc2.warehouse().boxes());
50+
newBoxes.add(new Box(pos, width));
51+
return new Document(acc2.warehouse().withBoxes(newBoxes), acc2.moves());
52+
}
53+
case "@" -> {
54+
return new Document(acc2.warehouse().withRobot(pos), acc2.moves());
55+
}
56+
}
57+
return acc2;
58+
})
59+
);
60+
} else {
61+
return line.chars()
62+
.boxed()
63+
.collect(foldLeft(
64+
() -> acc,
65+
(acc2, charInt) -> {
66+
var ch = String.valueOf((char) charInt.intValue());
67+
var dir = Direction.parse(ch);
68+
var newMoves = acc2.moves();
69+
newMoves.add(dir);
70+
return new Document(acc2.warehouse(), newMoves);
71+
})
72+
);
73+
}
74+
})
75+
);
76+
}
77+
78+
/**
79+
* do all the moves and return the final state of the warehouse
80+
**/
81+
public Warehouse doMoves() {
82+
return moves().stream()
83+
.collect(foldLeft(
84+
this::warehouse,
85+
Warehouse::move)
86+
);
87+
}
88+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.codefork.aoc2024.day15;
2+
3+
import com.codefork.aoc2024.Problem;
4+
import com.codefork.aoc2024.util.Assert;
5+
6+
import java.util.stream.Stream;
7+
8+
public class Part01 extends Problem {
9+
10+
public String solve(Stream<String> data) {
11+
var document = Document.parse(data, 1);
12+
var finalWarehouse = document.doMoves();
13+
return String.valueOf(finalWarehouse.getSumBoxCoordinates());
14+
}
15+
16+
@Override
17+
public String solve() {
18+
Assert.assertEquals("2028", solve(getFileAsStream("small_sample")));
19+
Assert.assertEquals("10092", solve(getSampleInput()));
20+
return solve(getInput());
21+
}
22+
23+
public static void main(String[] args) {
24+
new Part01().run();
25+
}
26+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.codefork.aoc2024.day15;
2+
3+
import com.codefork.aoc2024.Problem;
4+
import com.codefork.aoc2024.util.Assert;
5+
6+
import java.util.stream.Stream;
7+
8+
public class Part02 extends Problem {
9+
10+
public String solve(Stream<String> data) {
11+
var document = Document.parse(data, 2);
12+
var finalWarehouse = document.doMoves();
13+
return String.valueOf(finalWarehouse.getSumBoxCoordinates());
14+
}
15+
16+
@Override
17+
public String solve() {
18+
Assert.assertEquals("9021", solve(getSampleInput()));
19+
return solve(getInput());
20+
}
21+
22+
public static void main(String[] args) {
23+
new Part02().run();
24+
}
25+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.codefork.aoc2024.day15;
2+
3+
public record Position(int x, int y) {
4+
5+
public Position withChangedX(int dX) {
6+
return new Position(x() + dX, y());
7+
}
8+
9+
public Position withChangedY(int dY) {
10+
return new Position(x(), y() + dY);
11+
}
12+
13+
}
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
package com.codefork.aoc2024.day15;
2+
3+
import java.util.ArrayList;
4+
import java.util.HashSet;
5+
import java.util.List;
6+
import java.util.Set;
7+
import java.util.stream.IntStream;
8+
import java.util.stream.Stream;
9+
10+
public record Warehouse(Set<Position> walls, Set<Box> boxes, Position robot) {
11+
12+
public Warehouse withWalls(Set<Position> newWalls) {
13+
return new Warehouse(newWalls, boxes, robot);
14+
}
15+
16+
public Warehouse withBoxes(Set<Box> newBoxes) {
17+
return new Warehouse(walls, newBoxes, robot);
18+
}
19+
20+
public Warehouse withRobot(Position newRobot) {
21+
return new Warehouse(walls, boxes, newRobot);
22+
}
23+
24+
/**
25+
* return true if any of the positions are walls
26+
*/
27+
public boolean areWalls(List<Position> positions) {
28+
return positions.stream().anyMatch(walls::contains);
29+
}
30+
31+
public Warehouse moveVertical(int dY) {
32+
var boxWidth = boxes.stream().findFirst().orElseThrow().width();
33+
var foundEmpty = false;
34+
// order matters for boxesToMove, so it's a List
35+
var boxesToMove = new ArrayList<Box>();
36+
var boxesToCheck = IntStream.range(0, boxWidth).boxed().map(i ->
37+
new Position(robot.x() - i, robot.y() + dY)
38+
).toList();
39+
var wallsToCheck = List.of(new Position(robot.x(), robot.y() + dY));
40+
while (!areWalls(wallsToCheck)) {
41+
// check if boxesToCheck are boxes
42+
final var _toCheck = boxesToCheck;
43+
var foundBoxes = boxes.stream().filter(box -> _toCheck.contains(box.pos())).toList();
44+
// no boxes found? we're done, we can move!
45+
if(foundBoxes.isEmpty()) {
46+
foundEmpty = true;
47+
break;
48+
}
49+
boxesToMove.addAll(foundBoxes);
50+
// set boxesToCheck for the boxes we just found
51+
boxesToCheck = foundBoxes.stream().flatMap(box -> {
52+
// TODO: I hard coded these possibilities because I'm tired,
53+
// but they should really be calculated based on boxWidth
54+
if(boxWidth == 1) {
55+
return Stream.of(box.pos().withChangedY(dY));
56+
} else if(boxWidth == 2) {
57+
return Stream.of(
58+
box.pos().withChangedY(dY).withChangedX(-1),
59+
box.pos().withChangedY(dY),
60+
box.pos().withChangedY(dY).withChangedX(1)
61+
);
62+
}
63+
throw new RuntimeException("can't handle boxWidth=" + boxWidth);
64+
}).toList();
65+
wallsToCheck = foundBoxes.stream().flatMap(box -> {
66+
// TODO: I hard coded these possibilities because I'm tired,
67+
// but they should really be calculated based on boxWidth
68+
if(boxWidth == 1) {
69+
return Stream.of(box.pos().withChangedY(dY));
70+
} else if(boxWidth == 2) {
71+
return Stream.of(box.pos().withChangedY(dY), box.pos().withChangedY(dY).withChangedX(1));
72+
}
73+
throw new RuntimeException("can't handle boxWidth=" + boxWidth);
74+
}).toList();
75+
}
76+
if (foundEmpty) {
77+
var newBoxes = new HashSet<>(boxes);
78+
for (var box : boxesToMove.reversed()) {
79+
newBoxes.remove(box);
80+
newBoxes.add(new Box(new Position(box.pos().x(), box.pos().y() + dY), boxWidth));
81+
}
82+
return new Warehouse(walls, newBoxes, new Position(robot.x(), robot.y() + dY));
83+
} else {
84+
return this;
85+
}
86+
}
87+
88+
public Warehouse moveHorizontal(int dX) {
89+
var boxWidth = boxes.stream().findFirst().orElseThrow().width();
90+
var foundEmpty = false;
91+
// order matters for boxesToMove, so it's a List
92+
var boxesToMove = new ArrayList<Box>();
93+
var boxToCheck = dX < 0 ?
94+
new Box(new Position(robot.x() + (dX * boxWidth), robot.y()), boxWidth) :
95+
new Box(new Position(robot.x() + dX, robot.y()), boxWidth);
96+
var wallsToCheck = List.of(new Position(robot.x() + dX, robot.y()));
97+
while (!areWalls(wallsToCheck)) {
98+
var isBox = boxes.contains(boxToCheck);
99+
// not a box? we found empty space, so we can move
100+
if(!isBox) {
101+
foundEmpty = true;
102+
break;
103+
}
104+
boxesToMove.add(boxToCheck);
105+
106+
boxToCheck = new Box(new Position(boxToCheck.pos().x() + (dX * boxWidth), boxToCheck.pos().y()), boxWidth);
107+
// TODO: I hard coded these possibilities because I'm tired,
108+
// but they should really be calculated based on boxWidth
109+
if(boxWidth == 1) {
110+
wallsToCheck = List.of(boxToCheck.pos());
111+
} else if (boxWidth == 2) {
112+
wallsToCheck = dX < 0 ?
113+
List.of(boxToCheck.pos().withChangedX(1)) :
114+
List.of(boxToCheck.pos());
115+
}
116+
}
117+
if (foundEmpty) {
118+
var newBoxes = new HashSet<>(boxes);
119+
for (var box : boxesToMove.reversed()) {
120+
newBoxes.remove(box);
121+
var moved = new Box(new Position(box.pos().x() + dX, box.pos().y()), boxWidth);
122+
newBoxes.add(moved);
123+
}
124+
return new Warehouse(walls, newBoxes, new Position(robot.x() + dX, robot.y()));
125+
} else {
126+
return this;
127+
}
128+
}
129+
130+
/**
131+
* move the robot in the specified direction and return the new state of the warehouse
132+
* @param direction
133+
* @return
134+
*/
135+
public Warehouse move(Direction direction) {
136+
var newWarehouse = switch (direction) {
137+
case Direction.Up -> moveVertical(-1);
138+
case Direction.Down -> moveVertical(1);
139+
case Direction.Right -> moveHorizontal(1);
140+
case Direction.Left -> moveHorizontal(-1);
141+
};
142+
//System.out.println("moved " + direction + ":");
143+
//newWarehouse.print();
144+
return newWarehouse;
145+
}
146+
147+
public int getSumBoxCoordinates() {
148+
return boxes.stream()
149+
.map(box -> 100 * box.pos().y() + box.pos().x())
150+
.mapToInt(i -> i)
151+
.sum();
152+
}
153+
154+
public void print() {
155+
int height = walls.stream().map(Position::y).max(Integer::compareTo).orElseThrow() + 1;
156+
int width = walls.stream().map(Position::x).max(Integer::compareTo).orElseThrow() + 1;
157+
158+
for (var y = 0; y < height; y++) {
159+
for (var x = 0; x < width; x++) {
160+
var pos = new Position(x, y);
161+
if (walls.contains(pos)) {
162+
System.out.print("#");
163+
} else if (boxes.contains(new Box(pos, 1))) {
164+
System.out.print("O");
165+
} else if (boxes.contains(new Box(pos, 2))) {
166+
System.out.print("[]");
167+
x += 1;
168+
} else if (pos.equals(robot)) {
169+
System.out.print("@");
170+
} else {
171+
System.out.print(".");
172+
}
173+
}
174+
System.out.println();
175+
}
176+
}
177+
}

src/main/resources/day15/input

22.1 KB
Binary file not shown.

src/main/resources/day15/sample

843 Bytes
Binary file not shown.

src/main/resources/day15/small_sample

111 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)