diff --git a/2022/README.md b/2022/README.md index 80c3a009b87455d1e5f6543fe58d11d59ee1b5d1..cf6a50a44bb447019ceb05294436558b3539d541 100644 --- a/2022/README.md +++ b/2022/README.md @@ -606,5 +606,22 @@ I don't think I'll do that, though, since I can optimize the part 2 solution for ## Day 16 +- [The problem](https://adventofcode.com/2022/day/16) +- [The solution](src/main/java/edu/unl/cse/bohn/year2022/Day16.java) + +### Part 1 + +The subproblems are +- Parse the input to determine how much flow is possible with each valve and to create a graph of the tunnels +- Find the (partial) walk that maximizes the flow within the allotted time + +I'm feeling a dynamic programming solution is in order. + +### Part 2 + +... + +## Day 17 + (coming soon) diff --git a/2022/src/main/java/edu/unl/cse/bohn/year2022/Day16.java b/2022/src/main/java/edu/unl/cse/bohn/year2022/Day16.java new file mode 100644 index 0000000000000000000000000000000000000000..572eb1d0dd302684786a53bbba6047ec8274fedf --- /dev/null +++ b/2022/src/main/java/edu/unl/cse/bohn/year2022/Day16.java @@ -0,0 +1,110 @@ +package edu.unl.cse.bohn.year2022; + +import edu.unl.cse.bohn.Puzzle; + +import java.util.*; + +@SuppressWarnings("unused") +public class Day16 extends Puzzle { + + public static final int TIME_REMAINING = 30; + + public Day16(boolean isProductionReady) { + super(isProductionReady); + sampleData = """ + 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"""; + } + + @Override + public long computePart1(List<String> data) { + Valve.createValveNetwork(data); + String startingLocation = data.get(0).substring(6, 8); + return Valve.getOptimalFlow(TIME_REMAINING, "AA", false); + } + + @Override + public long computePart2(List<String> data) { + return 0; + } + + private static class Valve { + private static Map<String, Valve> valves = null; + private static Map<Integer, Map<String, Map<Boolean, Integer>>> optimalFlows = null; + + @SuppressWarnings("FieldCanBeLocal") + private final String name; + private final int flowRate; + private final Set<String> adjacentValves; + + private Valve(String name, int flowRate, String[] adjacentValves) { + 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) { + String name = description.substring(6, 8); + String[] descriptionHalves = description.split(";"); + int flowRate = Integer.parseInt(descriptionHalves[0].split("=")[1]); + String neighborsString = descriptionHalves[1].split("valve")[1].strip(); + if (neighborsString.startsWith("s ")) { + neighborsString = neighborsString.substring(2); + } + String[] adjacentValves = neighborsString.split(", "); + valves.put(name, new Valve(name, flowRate, adjacentValves)); + } + } + + public static int getOptimalFlow(int timeRemaining, String valveName, boolean valveIsAlreadyOpen) { + if (optimalFlows == null) { + throw new IllegalStateException("Cannot calculate optimal flow without creating valve network first."); + } + int depthPreviouslyCalculated = optimalFlows.size(); + if (timeRemaining < depthPreviouslyCalculated) { + return optimalFlows.get(timeRemaining).get(valveName).get(valveIsAlreadyOpen); + } + /* TODO: incorporate time to determine "eventual total pressure release" */ + for (int t = depthPreviouslyCalculated; t <= timeRemaining; t++) { + HashMap<String, Map<Boolean, Integer>> depthOptima = new HashMap<>(); + for (String valve : valves.keySet()) { + HashMap<Boolean, Integer> valveOptima = new HashMap<>(); + if (t == 0) { + valveOptima.put(true, 0); + valveOptima.put(false, 0); + } else { + int depth = t - 1; + // if the valve is already open then we must move + int optimalFlow = valves.get(valve).adjacentValves.stream() + .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); + } + } +}