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.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy