diff --git a/2022/README.md b/2022/README.md index a90e517209f6960b8fd6903cdebc1d6b7fef2f94..9d7ef625411f9165b1be2b36985cd050aa573ed4 100644 --- a/2022/README.md +++ b/2022/README.md @@ -477,5 +477,42 @@ Monkey 3 inspected items 103 times. ## Day 12 +First, a closing thought on yesterday's problem. +The concept I stumbled upon is the [Chinese Remainder Theorem](https://en.wikipedia.org/wiki/Chinese_remainder_theorem) +which apparently has an [Advent](https://twitter.com/Lincoln81/status/1446897782351089675) +[of](https://twitter.com/fixedpointfae/status/1601842705541517313) +[Code](https://twitter.com/PeopleNArthax/status/1601936116055408640) +[reputation](https://twitter.com/DarkCisum/status/1338382928129224705). + +And now on to today's adventure. + +- [The problem](https://adventofcode.com/2022/day/12) +- [The solution](src/main/java/edu/unl/cse/bohn/year2022/Day12.java) + +### Part 1 + +Speaking of AOC staples, it looks like today we need to implement a shortest path algorithm. + +The subproblems are + +- Create locations and build a reachability graph (unweighted, directional) + - Compare heights +- Find the shortest path from the start to the finish + +My critique of my part 1 solution + +- I'm not thrilled that I'm storing the distance from the start as a `Location` attribute; I suppose the alternative is + to create a `Map<Location,Integer>` + +### Part 2 + +The difference is that we're now examining many possible starting locations. +A naïve approach would be to run the algorithm I developed for part 1, which starts at `start`, using each 'a' +`Location` as a starting point, in turn. +Alternatively, we could work backwards from `finish` and find the distance of each `Location` to the `finish`. +Then look for the shortest distance to the finish from the 'a' `Location`s. + +## Day 13 + (coming soon) diff --git a/2022/src/main/java/edu/unl/cse/bohn/year2022/Day12.java b/2022/src/main/java/edu/unl/cse/bohn/year2022/Day12.java new file mode 100644 index 0000000000000000000000000000000000000000..b2761b5070cf4adfe3cbb0c35a81ccd6b120ceb0 --- /dev/null +++ b/2022/src/main/java/edu/unl/cse/bohn/year2022/Day12.java @@ -0,0 +1,150 @@ +package edu.unl.cse.bohn.year2022; + +import edu.unl.cse.bohn.Puzzle; + +import java.util.*; +import java.util.stream.Collectors; + +@SuppressWarnings("unused") +public class Day12 extends Puzzle { + public Day12(boolean isProductionReady) { + super(isProductionReady); + //noinspection SpellCheckingInspection + sampleData = """ + Sabqponm + abcryxxl + accszExk + acctuvwj + abdefghi"""; + } + + private Location start = null, finish = null; + private Set<Location> locations; + + @Override + public long computePart1(List<String> data) { + buildReachabilityGraph(data); + computeDistancesToFinish(false); + return start.getDistanceToFinish(); + } + + @Override + public long computePart2(List<String> data) { + if (start == null) { + this.computePart1(data); + } + return locations.stream() + .filter(location -> location.getElevation() == 'a') + .map(Location::getDistanceToFinish) + .min(Integer::compareTo).orElseThrow(); + } + + private void computeDistancesToFinish(@SuppressWarnings("SameParameterValue") boolean verbose) { + int numberOfLocations = locations.size(); + PriorityQueue<Location> locationsToVisit = new PriorityQueue<>(); + finish.updateDistanceFromFinish(0); + locationsToVisit.add(finish); + Location currentLocation; + while (!locationsToVisit.isEmpty()) { + currentLocation = locationsToVisit.remove(); + for (Location reachableLocation : currentLocation.getReachableLocations()) { + if (reachableLocation.updateDistanceFromFinish(currentLocation.getDistanceToFinish() + 1)) { + locationsToVisit.remove(reachableLocation); // just in case it's already there + locationsToVisit.add(reachableLocation); + } + if (verbose) { + System.out.println("Number of locations to visit = " + locationsToVisit.size() + + " (out of " + numberOfLocations + ")"); + } + if (locationsToVisit.size() > numberOfLocations) { + throw new IllegalStateException("The queue contains more locations (" + locationsToVisit.size() + + ") than exist (" + numberOfLocations + ")."); + } + } + } + } + + private void buildReachabilityGraph(List<String> data) { + // create the locations + Location[][] locations = new Location[data.size()][data.get(0).length()]; + for (int i = 0; i < data.size(); i++) { + for (int j = 0; j < data.get(i).length(); j++) { + char elevation = data.get(i).charAt(j); + switch (elevation) { + case 'S' -> { + locations[i][j] = new Location('a'); + start = locations[i][j]; + } + case 'E' -> { + locations[i][j] = new Location('z'); + finish = locations[i][j]; + } + default -> locations[i][j] = new Location(elevation); + } + } + } + // build the graph + for (int i = 0; i < locations.length; i++) { + for (int j = 0; j < locations[i].length; j++) { + if (i > 0) { + locations[i][j].setLocationAsReachableIfNotTooTall(locations[i - 1][j]); + } + if (i < locations.length - 1) { + locations[i][j].setLocationAsReachableIfNotTooTall(locations[i + 1][j]); + } + if (j > 0) { + locations[i][j].setLocationAsReachableIfNotTooTall(locations[i][j - 1]); + } + if (j < locations[i].length - 1) { + locations[i][j].setLocationAsReachableIfNotTooTall(locations[i][j + 1]); + } + } + } + this.locations = Arrays.stream(locations).flatMap(Arrays::stream).collect(Collectors.toSet()); + } + + private static class Location implements Comparable<Location> { + private final Set<Location> reachableLocations; + private final char elevation; + private int distanceToFinish; + + public Location(char elevation) { + this.elevation = elevation; + this.reachableLocations = new HashSet<>(); + this.distanceToFinish = Integer.MAX_VALUE; + } + + public void setLocationAsReachableIfNotTooTall(Location otherLocation) { + if (this.elevation <= otherLocation.elevation + 1) { + reachableLocations.add(otherLocation); + } + } + + public boolean updateDistanceFromFinish(int possiblyShorterDistance) { + boolean newDistanceIsShorter = possiblyShorterDistance < distanceToFinish; + distanceToFinish = newDistanceIsShorter ? possiblyShorterDistance : distanceToFinish; + return newDistanceIsShorter; + } + + public int getDistanceToFinish() { + return distanceToFinish; + } + + public boolean canReach(Location destination) { + return reachableLocations.contains(destination); + } + + public Set<Location> getReachableLocations() { + return Collections.unmodifiableSet(reachableLocations); + } + + public char getElevation() { + return elevation; + } + + @Override + public int compareTo(Location other) { + return this.elevation - other.elevation; + } + } +}