Skip to content
Snippets Groups Projects
Commit 644f2198 authored by Christopher Bohn's avatar Christopher Bohn :thinking:
Browse files

Day 17 part 1 complete; thinking about part 2

parent e188270c
No related branches found
No related tags found
No related merge requests found
...@@ -617,11 +617,46 @@ The subproblems are ...@@ -617,11 +617,46 @@ The subproblems are
I'm feeling a dynamic programming solution is in order. I'm feeling a dynamic programming solution is in order.
Ugh. The search space is big. My initial approach allows valves to be re-opened.
Instead of keying to boolean, maybe use a bit vector.
This will require remembering which bit position corresponds to which valve.
Unfortunately, that'll require too much space/time to calculate the full solution space.
Perhaps instead we focus on getting all the valves (with non-zero flow) open as soon as possible.
The particular order is affected by how much flow each contributes.
Once they're all open, we can kill the remaining time doing whatever.
(not yet complete)
### Part 2 ### Part 2
... ...
## Day 17 ## Day 17
- [The problem](https://adventofcode.com/2022/day/17)
- [The solution](src/main/java/edu/unl/cse/bohn/year2022/Day17.java)
### Part 1
The subproblems are:
- Simulate the movements of the rock
- Detect collisions
### Part 2
Processing a trillion rocks will require some changes.
Besides changing a few `int`s to `long`s, we'll need to keep our memory use under control.
We can do this by "throwing away" a few rows from the bottom of the tunnel whenever the tunnel is blocked.
No. That solves the memory problem but not the time problem.
Even if we could process 4 billion rocks per second (which we can't) then it would take a little over 4 minutes to
process 1 trillion rocks, and AOC solutions shouldn't take that long.
...
## Day 18
(coming soon) (coming soon)
...@@ -3,11 +3,13 @@ package edu.unl.cse.bohn.year2022; ...@@ -3,11 +3,13 @@ package edu.unl.cse.bohn.year2022;
import edu.unl.cse.bohn.Puzzle; import edu.unl.cse.bohn.Puzzle;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
@SuppressWarnings("unused") @SuppressWarnings({"unused"})
public class Day16 extends Puzzle { public class Day16 extends Puzzle {
public static final int TIME_REMAINING = 30; public static final boolean verbose = true;
public static final int TIME_AVAILABLE = 30;
public Day16(boolean isProductionReady) { public Day16(boolean isProductionReady) {
super(isProductionReady); super(isProductionReady);
...@@ -26,9 +28,15 @@ public class Day16 extends Puzzle { ...@@ -26,9 +28,15 @@ public class Day16 extends Puzzle {
@Override @Override
public long computePart1(List<String> data) { public long computePart1(List<String> data) {
Valve.createValveNetwork(data); Map<ValvePair, Long> shortestDistances = Valve.createValveNetwork(data);
String startingLocation = data.get(0).substring(6, 8); Valve startingLocation = Valve.getValve(data.get(0).substring(6, 8));
return Valve.getOptimalFlow(TIME_REMAINING, "AA", false); Set<Valve> openValves = Valve.getValves().stream()
.filter(valve -> valve.getFlowRate() == 0)
.collect(Collectors.toSet());
Set<Valve> closedValves = Valve.getValves().stream()
.filter(valve -> valve.getFlowRate() > 0)
.collect(Collectors.toSet());
return findOptimalFlow(startingLocation, shortestDistances, TIME_AVAILABLE, closedValves, openValves);
} }
@Override @Override
...@@ -36,24 +44,53 @@ public class Day16 extends Puzzle { ...@@ -36,24 +44,53 @@ public class Day16 extends Puzzle {
return 0; return 0;
} }
private long findOptimalFlow(Valve startingLocation, Map<ValvePair, Long> shortestDistances, long timeRemaining,
Set<Valve> closedValves, Set<Valve> openValves) {
if (verbose) {
System.out.println("At " + startingLocation + " with " + timeRemaining + " minutes left. Open: "
+ openValves + " Closed: " + closedValves);
}
if (timeRemaining < 1) {
return 0L;
}
// with one minute remaining, even opening a valve would relieve no pressure.
// also, if all valves are open, might as well just kill time
long currentFlowRate = openValves.stream().mapToLong(Valve::getFlowRate).sum();
if (timeRemaining == 1 || closedValves.isEmpty()) {
return timeRemaining * currentFlowRate;
}
long maximumFlow = Long.MIN_VALUE;
// we know how long it takes to get from here to anywhere else, and
// the only locations worth visiting are those with open valves --
// which valve should we open next?
for (Valve valve : closedValves) {
Set<Valve> newClosedValves = new HashSet<>(closedValves);
newClosedValves.remove(valve);
Set<Valve> newOpenValves = new HashSet<>(openValves);
newOpenValves.add(valve);
// lose one minute to turn valve, and more time to travel to next location
long newTimeRemaining = timeRemaining - shortestDistances.get(new ValvePair(startingLocation, valve)) - 1;
if (newTimeRemaining > 0) {
long flowDuringTravel = (timeRemaining - newTimeRemaining) * currentFlowRate;
maximumFlow = Math.max(maximumFlow,
findOptimalFlow(valve, shortestDistances, newTimeRemaining, newClosedValves, newOpenValves));
}
}
// would we have been better off walking in circles?
maximumFlow = Math.max(maximumFlow, timeRemaining * currentFlowRate);
return maximumFlow;
}
private static class Valve { private static class Valve {
private static Map<String, Valve> valves = null; private static final Set<Valve> valves = new HashSet<>();
private static Map<Integer, Map<String, Map<Boolean, Integer>>> optimalFlows = null; private static final Map<ValvePair, Long> shortestDistances = new HashMap<>();
@SuppressWarnings("FieldCanBeLocal")
private final String name; private final String name;
private final int flowRate; private final int flowRate;
private final Set<String> adjacentValves; private final Set<String> adjacentValveNames;
private Set<Valve> adjacentValves = null;
private Valve(String name, int flowRate, String[] adjacentValves) { public static Map<ValvePair, Long> createValveNetwork(List<String> descriptions) {
this.name = name;
this.flowRate = flowRate;
this.adjacentValves = Set.of(adjacentValves);
}
public static void createValveNetwork(List<String> descriptions) {
valves = new HashMap<>();
optimalFlows = new HashMap<>();
for (String description : descriptions) { for (String description : descriptions) {
String name = description.substring(6, 8); String name = description.substring(6, 8);
String[] descriptionHalves = description.split(";"); String[] descriptionHalves = description.split(";");
...@@ -63,48 +100,75 @@ public class Day16 extends Puzzle { ...@@ -63,48 +100,75 @@ public class Day16 extends Puzzle {
neighborsString = neighborsString.substring(2); neighborsString = neighborsString.substring(2);
} }
String[] adjacentValves = neighborsString.split(", "); String[] adjacentValves = neighborsString.split(", ");
valves.put(name, new Valve(name, flowRate, adjacentValves)); valves.add(new Valve(name, flowRate, adjacentValves));
} }
} // initially assume it takes forever to get from anywhere to anywhere
// Valve.valves.forEach(from ->
public static int getOptimalFlow(int timeRemaining, String valveName, boolean valveIsAlreadyOpen) { // valves.forEach(to ->
if (optimalFlows == null) { // shortestDistances.put(new ValvePair(from, to), Long.MAX_VALUE)));
throw new IllegalStateException("Cannot calculate optimal flow without creating valve network first."); // oops, that had an overflow problem during addition
} Valve.valves.forEach(from ->
int depthPreviouslyCalculated = optimalFlows.size(); valves.forEach(to ->
if (timeRemaining < depthPreviouslyCalculated) { shortestDistances.put(new ValvePair(from, to), 2L * valves.size())));
return optimalFlows.get(timeRemaining).get(valveName).get(valveIsAlreadyOpen); // but we know some locations are just one minute apart
} valves.forEach(from ->
/* TODO: incorporate time to determine "eventual total pressure release" */ from.getAdjacentValves().forEach(to ->
for (int t = depthPreviouslyCalculated; t <= timeRemaining; t++) { shortestDistances.put(new ValvePair(from, to), 1L)));
HashMap<String, Map<Boolean, Integer>> depthOptima = new HashMap<>(); // just in case we want to go from where we are to the same location, why take a tunnel?
for (String valve : valves.keySet()) { valves.forEach(valve -> shortestDistances.put(new ValvePair(valve, valve), 0L));
HashMap<Boolean, Integer> valveOptima = new HashMap<>(); // find the shortest travel time from anywhere to anywhere
if (t == 0) { for (Valve valve : valves) {
valveOptima.put(true, 0); for (Valve from : valves) {
valveOptima.put(false, 0); for (Valve to : valves) {
} else { ValvePair valvePair = new ValvePair(from, to);
int depth = t - 1; shortestDistances.put(valvePair, Math.min(shortestDistances.get(valvePair),
// if the valve is already open then we must move shortestDistances.get(new ValvePair(from, valve))
int optimalFlow = valves.get(valve).adjacentValves.stream() + shortestDistances.get(new ValvePair(valve, to)))
.map(name -> optimalFlows.get(depth).get(name).get(true)) );
.max(Integer::compareTo).orElseThrow(); }
valveOptima.put(true, optimalFlow); }
// if the valve is still closed then...
// option 1: move to an adjacent valve
int optimalFlowByMoving = valves.get(valve).adjacentValves.stream()
.map(name -> optimalFlows.get(depth).get(name).get(false))
.max(Integer::compareTo).orElseThrow();
// option 2: turn the current valve
int optimalFlowByOpeningValve = valves.get(valve).flowRate
+ optimalFlows.get(depth).get(valve).get(true);
valveOptima.put(false, Math.max(optimalFlowByMoving, optimalFlowByOpeningValve));
}
depthOptima.put(valve, valveOptima);
}
optimalFlows.put(t, depthOptima);
}
return optimalFlows.get(timeRemaining).get(valveName).get(valveIsAlreadyOpen);
} }
return shortestDistances;
}
public static Set<Valve> getValves() {
return Collections.unmodifiableSet(valves);
}
public static Valve getValve(String name) {
return valves.stream().filter(valve -> valve.name.equals(name)).findAny().orElse(null);
}
private Valve(String name, int flowRate, String[] adjacentValves) {
this.name = name;
this.flowRate = flowRate;
this.adjacentValveNames = Set.of(adjacentValves);
}
public String getName() {
return name;
}
public int getFlowRate() {
return flowRate;
}
public Set<Valve> getAdjacentValves() {
if (adjacentValves == null) {
adjacentValves = adjacentValveNames.stream()
.map(valveName ->
valves.stream().filter(v -> v.name.equals(valveName)).findFirst().orElseThrow())
.collect(Collectors.toSet());
}
return adjacentValves;
}
@Override
public String toString() {
return name + "(" + flowRate + ")";
}
}
private record ValvePair(Valve from, Valve to) {
} }
} }
package edu.unl.cse.bohn.year2022;
import edu.unl.cse.bohn.Puzzle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.stream.IntStream;
@SuppressWarnings("unused")
public class Day17 extends Puzzle {
public static final boolean veryVerbose = false;
public static final boolean verbose = false;
public Day17(boolean isProductionReady) {
super(isProductionReady);
sampleData = ">>><<><>><<<>><>>><<<>>><<<><<<>><>><<>>";
}
public static final List<Sprite> sprites = List.of(
new Sprite(List.of("####")),
new Sprite(List.of(".#.", "###", ".#.")),
new Sprite(List.of("###", "..#", "..#")),
new Sprite(List.of("#", "#", "#", "#")),
new Sprite(List.of("##", "##"))
);
public static final int NUMBER_OF_ROCKS_TO_DROP = 2022;
@Override
public long computePart1(List<String> data) {
List<Character> jets = data.get(0).chars().mapToObj(c -> (char) c).toList();
return dropRocks(jets, NUMBER_OF_ROCKS_TO_DROP);
}
@Override
public long computePart2(List<String> data) {
return 0;
}
@SuppressWarnings({"OverlyLongMethod", "SameParameterValue"})
private int dropRocks(List<Character> jets, int numberOfRocksToDrop) {
Iterator<Character> jetIterator = jets.iterator();
Iterator<Sprite> spriteIterator = sprites.iterator();
List<int[]> tunnel = new ArrayList<>(numberOfRocksToDrop * 3);
int[] floor = {1, 1, 1, 1, 1, 1, 1, 1, 1};
int[] walls = {1, 0, 0, 0, 0, 0, 0, 0, 1};
int height = 0;
final int horizontalOffset = 3;
final int verticalOffset = 4;
int x = 0, y = 0;
int[][] rock = null;
boolean rockIsAtRest = true;
int numberOfRocksAtRest = 0;
tunnel.add(floor);
while (numberOfRocksAtRest < numberOfRocksToDrop) {
if (rockIsAtRest) {
rockIsAtRest = false;
// get next rock
if (!spriteIterator.hasNext()) {
spriteIterator = sprites.iterator();
}
rock = spriteIterator.next().rockOrNot;
// expand the tunnel as necessary
for (int i = tunnel.size(); i < height + rock.length + verticalOffset; i++) {
tunnel.add(walls.clone());
}
// place the rock
x = horizontalOffset;
y = height + verticalOffset;
for (int i = 0; i < rock.length; i++) {
System.arraycopy(rock[i], 0, tunnel.get(y + i), x, rock[i].length);
}
if (veryVerbose || verbose) {
System.out.println(System.lineSeparator()
+ "-- " + numberOfRocksAtRest + " rocks at rest -- new rock dropped --");
IntStream.iterate(tunnel.size() - 1, i -> i >= 0, i -> i - 1)
.mapToObj(i -> Arrays.toString(tunnel.get(i))).forEach(System.out::println);
}
} else {
if (!jetIterator.hasNext()) {
jetIterator = jets.iterator();
}
char jet = jetIterator.next();
// horizontal motion
eraseRock(tunnel, rock, x, y);
x += jet == '>' ? 1 : -1;
drawRock(tunnel, rock, x, y);
boolean foundCollision = detectCollision(tunnel, rock, y);
if (foundCollision) {
eraseRock(tunnel, rock, x, y);
x += jet == '>' ? -1 : 1;
drawRock(tunnel, rock, x, y);
}
// vertical motion
eraseRock(tunnel, rock, x, y);
y--;
drawRock(tunnel, rock, x, y);
foundCollision = detectCollision(tunnel, rock, y);
if (foundCollision) {
eraseRock(tunnel, rock, x, y);
y++;
drawRock(tunnel, rock, x, y);
rockIsAtRest = true;
numberOfRocksAtRest++;
height = Math.max(height, y + rock.length - 1);
}
if (veryVerbose) {
System.out.println(System.lineSeparator() + jet);
IntStream.iterate(tunnel.size() - 1, i -> i >= 0, i -> i - 1)
.mapToObj(i -> Arrays.toString(tunnel.get(i))).forEach(System.out::println);
}
}
}
if (veryVerbose || verbose) {
System.out.println(System.lineSeparator()
+ "-- " + numberOfRocksAtRest + " rocks at rest -- complete --");
IntStream.iterate(tunnel.size() - 1, i -> i >= 0, i -> i - 1)
.mapToObj(i -> Arrays.toString(tunnel.get(i))).forEach(System.out::println);
}
return height;
}
private static boolean detectCollision(List<int[]> tunnel, int[][] rock, int y) {
boolean foundCollision = false;
for (int i = 0; i < rock.length; i++) {
int[] row = tunnel.get(y + i);
for (int positionValue : row) {
foundCollision = foundCollision || (positionValue == 2);
}
}
return foundCollision;
}
private static void drawRock(List<int[]> tunnel, int[][] rock, int x, int y) {
for (int i = 0; i < rock.length; i++) {
int[] row = tunnel.get(y + i);
for (int j = 0; j < rock[i].length; j++) {
row[x + j] += rock[i][j];
}
}
}
private static void eraseRock(List<int[]> tunnel, int[][] rock, int x, int y) {
for (int i = 0; i < rock.length; i++) {
int[] row = tunnel.get(y + i);
for (int j = 0; j < rock[i].length; j++) {
row[x + j] -= rock[i][j];
}
}
}
private static class Sprite {
public final int[][] rockOrNot;
public Sprite(List<String> description) {
rockOrNot = new int[description.size()][description.get(0).length()];
for (int i = 0; i < description.size(); i++) {
for (int j = 0; j < description.get(i).length(); j++) {
rockOrNot[i][j] = description.get(i).charAt(j) == '#' ? 1 : 0;
}
}
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment