-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPuzzle16.java
230 lines (201 loc) · 7.35 KB
/
Puzzle16.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
package advent2022;
import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.Long.max;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.graph.Graph;
import com.google.common.graph.GraphBuilder;
import com.google.common.graph.ImmutableGraph;
import com.google.common.io.CharStreams;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.Callable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Éamonn McManus
*/
public class Puzzle16 {
private static final String SAMPLE =
"""
Valve AA has flow rate=0; tunnels lead to valves DD, II, BB
Valve BB has flow rate=13; tunnels lead to valves CC, AA
Valve CC has flow rate=2; tunnels lead to valves DD, BB
Valve DD has flow rate=20; tunnels lead to valves CC, AA, EE
Valve EE has flow rate=3; tunnels lead to valves FF, DD
Valve FF has flow rate=0; tunnels lead to valves EE, GG
Valve GG has flow rate=0; tunnels lead to valves FF, HH
Valve HH has flow rate=22; tunnel leads to valve GG
Valve II has flow rate=0; tunnels lead to valves AA, JJ
Valve JJ has flow rate=21; tunnel leads to valve II
""";
private static final Map<String, Callable<Reader>> INPUT_PRODUCERS =
ImmutableMap.of(
"sample", () -> new StringReader(SAMPLE),
"problem",
() -> new InputStreamReader(Puzzle16.class.getResourceAsStream("puzzle16.txt")));
public static void main(String[] args) throws Exception {
for (var entry : INPUT_PRODUCERS.entrySet()) {
String name = entry.getKey();
try (Reader r = entry.getValue().call()) {
List<String> lines = CharStreams.readLines(r);
Puzzle16 puzzle = parseGraph(lines);
puzzle.part1(name);
puzzle.part2(name);
}
}
}
private final Graph<Valve> graph;
private final Valve start;
private final List<Valve> nonZeroValves;
Puzzle16(Graph<Valve> graph, List<Valve> nonZeroValves) {
this.graph = graph;
this.nonZeroValves = nonZeroValves;
this.start = graph.nodes().stream().filter(v -> v.name.equals("AA")).findFirst().get();
}
private void part1(String name) {
Map<ValveSet, Long> bestSets = bestSets(30);
long max = Collections.max(bestSets.values());
System.out.println("Max for " + name + " part 1 is " + max);
}
// For part 2, we compute all the possible sets of valves that can be open after 26 minutes, and
// we look for the pair of sets from within those that have no intersection and that have the
// highest combined total. That corresponds to the elephant and us each opening a distinct set
// of valves. I got this idea from Martijn Pieters here:
// https://www.reddit.com/r/adventofcode/comments/zo21au/2022_day_16_approaches_and_pitfalls_discussion/
// My previous solution was too literal, tracing all possible states of our position and the
// elephant's position and the total set of open valves. Even with optimization it took about
// 8 minutes to run, versus less than a second for the solution here.
private void part2(String name) {
Map<ValveSet, Long> bestSets = bestSets(26);
long max = 0;
for (ValveSet set1 : bestSets.keySet()) {
for (ValveSet set2 : bestSets.keySet()) {
if (set1.disjoint(set2)) {
long total = bestSets.get(set1) + bestSets.get(set2);
max = max(max, total);
}
}
}
System.out.println("Max for " + name + " part 2 is " + max);
}
private Map<ValveSet, Long> bestSets(int steps) {
Map<State, Long> currentStates = Map.of(new State(start, new ValveSet()), 0L);
for (int i = 1; i <= steps; i++) {
Map<State, Long> nextStates = new HashMap<>();
currentStates.forEach(
(state, total) -> {
long flowRate = state.open.flowRate();
// We move.
for (Valve ourNext : graph.successors(state.ourPos)) {
nextStates.merge(new State(ourNext, state.open), total + flowRate, Long::max);
}
// We open.
if (state.ourPos.flowRate > 0) {
nextStates.merge(
new State(state.ourPos, state.open.plus(state.ourPos)),
total + flowRate,
Long::max);
}
});
currentStates = nextStates;
}
Map<ValveSet, Long> result = new HashMap<>();
currentStates.forEach((state, total) -> result.merge(state.open, total, Long::max));
return result;
}
record State(Valve ourPos, ValveSet open) {}
private static final Pattern VALVE_PATTERN =
Pattern.compile("Valve (..) has flow rate=(\\d+); tunnels? leads? to valves? (.*)");
private static Puzzle16 parseGraph(List<String> lines) {
ImmutableGraph.Builder<Valve> builder = GraphBuilder.<Valve>undirected().immutable();
// Add the nodes
Map<String, Valve> map = new TreeMap<>();
ImmutableList.Builder<Valve> nonZeroValves = ImmutableList.builder();
int valveNumber = 0;
for (String line : lines) {
Matcher matcher = VALVE_PATTERN.matcher(line);
checkArgument(matcher.matches(), line);
Valve valve = new Valve(valveNumber, matcher.group(1), Integer.parseInt(matcher.group(2)));
map.put(valve.name, valve);
if (valve.flowRate > 0) {
nonZeroValves.add(valve);
valveNumber++;
}
builder.addNode(valve);
}
// Add the edges
for (String line : lines) {
Matcher matcher = VALVE_PATTERN.matcher(line);
checkArgument(matcher.matches(), line);
Valve valve = map.get(matcher.group(1));
List<String> targets = Splitter.on(", ").splitToList(matcher.group(3));
for (String target : targets) {
builder.putEdge(valve, map.get(target));
}
}
ImmutableGraph<Valve> graph = builder.build();
return new Puzzle16(graph, nonZeroValves.build());
}
record Valve(int valveNumber, String name, int flowRate) {
@Override
public String toString() {
return name + "(" + flowRate + ")";
}
@Override
public boolean equals(Object o) {
return o instanceof Valve that
&& this.valveNumber == that.valveNumber
&& this.name.equals(that.name);
}
@Override
public int hashCode() {
return valveNumber;
}
}
private class ValveSet {
private final long mask;
ValveSet(long mask) {
this.mask = mask;
}
ValveSet() {
this(0);
}
ValveSet plus(Valve valve) {
if (valve.flowRate == 0) {
return this;
}
long m = mask | (1L << valve.valveNumber);
return (m == mask) ? this : new ValveSet(m);
}
boolean disjoint(ValveSet that) {
return (this.mask & that.mask) == 0;
}
long flowRate() {
long total = 0;
long m = mask;
while (m != 0) {
int b = Long.numberOfTrailingZeros(m);
total += nonZeroValves.get(b).flowRate;
m &= ~(1L << b);
}
return total;
}
@Override
public boolean equals(Object o) {
return o instanceof ValveSet that && this.mask == that.mask;
}
@Override
public int hashCode() {
return Long.hashCode(mask);
}
}
;
}