From f0f55486799fa650accab9f90acf976ad91b6849 Mon Sep 17 00:00:00 2001
From: Christopher Bohn <bohn@unl.edu>
Date: Thu, 22 Dec 2022 13:09:45 -0600
Subject: [PATCH] Completed 2022 Day 18

---
 2022/README.md                                |  30 +++
 .../java/edu/unl/cse/bohn/year2022/Day18.java | 177 ++++++++++++++++++
 2 files changed, 207 insertions(+)
 create mode 100644 2022/src/main/java/edu/unl/cse/bohn/year2022/Day18.java

diff --git a/2022/README.md b/2022/README.md
index 8ca785a..4f46e0d 100644
--- a/2022/README.md
+++ b/2022/README.md
@@ -654,9 +654,39 @@ 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.
 
+There is almost certainly a cycle (with a period that is a multiple of both the number of rocks and the length of the 
+jet string?) -- if we can detect the cycle then we can determine the height as (height of prefix) + 
+(number of cycles) * (height of a cycle) + (height of suffix). Maybe later.
+
 ...
 
 ## Day 18
 
+- [The problem](https://adventofcode.com/2022/day/18)
+- [The solution](src/main/java/edu/unl/cse/bohn/year2022/Day18.java)
+
+### Part 1
+
+The subproblems are
+- Parse the input to determine where each cube is in space
+- For each cube, determine which faces are adjacent to another cube
+- Count the number of faces that are not adjacent to another cube
+
+Determining whether a face is adjacent to another cube should be straight-forward.
+Given axes *i*, *j*, and *k*, cube *c1* is adjacent to cube *c2* iff wlog *c1.i* = *c2.i* and *c1.j* = *c2.j* and
+*c1.k* = *c2.k* ± 1.
+
+### Part 2
+
+We need to determine how many cavities are present in the droplet.
+We can determine that by eliminating everything that is *not* a cavity.
+
+If we consider a bounding box, then every cube is either:
+- Lava, as computed in part 1
+- Exterior air, which has no lava between it and the box's bound in at least one direction
+- A cavity
+
+## Day 19
+
 (coming soon)
 
diff --git a/2022/src/main/java/edu/unl/cse/bohn/year2022/Day18.java b/2022/src/main/java/edu/unl/cse/bohn/year2022/Day18.java
new file mode 100644
index 0000000..3beae53
--- /dev/null
+++ b/2022/src/main/java/edu/unl/cse/bohn/year2022/Day18.java
@@ -0,0 +1,177 @@
+package edu.unl.cse.bohn.year2022;
+
+import edu.unl.cse.bohn.Puzzle;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.IntStream;
+
+@SuppressWarnings("unused")
+public class Day18 extends Puzzle {
+    @SuppressWarnings("CommentedOutCode")
+    public Day18(boolean isProductionReady) {
+        super(isProductionReady);
+//        sampleData = """
+//                1,1,1
+//                2,1,1""";
+        sampleData = """
+                2,2,2
+                1,2,2
+                3,2,2
+                2,1,2
+                2,3,2
+                2,2,1
+                2,2,3
+                2,2,4
+                2,2,6
+                1,2,5
+                3,2,5
+                2,1,5
+                2,3,5""";
+    }
+
+    @Override
+    public long computePart1(List<String> data) {
+        for (String description : data) {
+            new Cube(description);
+        }
+        for (Cube cube1 : Cube.lava) {
+            for (Cube cube2 : Cube.lava) {
+                cube1.checkIfAdjacentTo(cube2);
+            }
+        }
+        return Cube.lava.stream().mapToLong(Cube::countExposedFaces).sum();
+    }
+
+    @Override
+    public long computePart2(List<String> data) {
+        // assume (correctly) that lava has already been created
+        int upperBound = Cube.maximumLength + 2;    // plenty
+        for (int i = 0; i < upperBound; i++) {
+            for (int j = 0; j < upperBound; j++) {
+                for (int k = 0; k < upperBound; k++) {
+                    new Cube(i, j, k);
+                }
+            }
+        }
+        Cube.removeExteriorAir();
+        for (Cube lavaCube : Cube.lava) {
+            for (Cube airCube : Cube.air) {
+                lavaCube.checkIfAdjacentTo(airCube);
+            }
+        }
+        return Cube.lava.stream().mapToLong(Cube::countExposedFaces).sum();
+    }
+
+    private static class Cube {
+        public static final Set<Cube> lava = new HashSet<>();
+        public static final Set<Cube> air = new HashSet<>();
+        static int maximumLength = Integer.MIN_VALUE;
+
+        private final int[] coordinates;
+        private final boolean[] faceIsExposed;
+
+        public Cube(String lavaDescription) {
+            // use for lava
+            coordinates = Arrays.stream(lavaDescription.split(",")).mapToInt(Integer::parseInt).toArray();
+            faceIsExposed = new boolean[]{true, true, true, true, true, true};
+            maximumLength = Math.max(maximumLength, Arrays.stream(coordinates).max().orElseThrow());
+            air.remove(this);
+            lava.add(this);
+        }
+
+        public Cube(int x, int y, int z) {
+            // use for air
+            coordinates = new int[]{x, y, z};
+            faceIsExposed = new boolean[]{true, true, true, true, true, true};
+            if (!lava.contains(this)) {
+                air.add(this);
+            }
+        }
+
+        public static void removeExteriorAir() {
+            Set<Cube> exteriorAir = new HashSet<>();
+            for (Cube airCube : air) {
+                for (Cube lavaCube : lava) {
+                    if (airCube.isAlignedInTwoDimensionsWith(lavaCube, 1, 2)
+                            && airCube.distanceInOneDimension(lavaCube, 0) != 0) {
+                        airCube.faceIsExposed[airCube.distanceInOneDimension(lavaCube, 0) < 0 ? 0 : 1] = false;
+                    }
+                    if (airCube.isAlignedInTwoDimensionsWith(lavaCube, 0, 2)
+                            && airCube.distanceInOneDimension(lavaCube, 1) != 0) {
+                        airCube.faceIsExposed[2 + (airCube.distanceInOneDimension(lavaCube, 1) < 0 ? 0 : 1)] = false;
+                    }
+                    if (airCube.isAlignedInTwoDimensionsWith(lavaCube, 0, 1)
+                            && airCube.distanceInOneDimension(lavaCube, 2) != 0) {
+                        airCube.faceIsExposed[4 + (airCube.distanceInOneDimension(lavaCube, 2) < 0 ? 0 : 1)] = false;
+                    }
+                }
+                if (airCube.countExposedFaces() > 0) {
+                    exteriorAir.add(airCube);
+                }
+            }
+            air.removeAll(exteriorAir);
+            // if we stopped now, then air in twisty passages would be treated as cavities
+            // any air that is adjacent to exterior air must also be exterior air
+            Set<Cube> newlyDiscoveredExteriorAir;
+            do {
+                newlyDiscoveredExteriorAir = new HashSet<>();
+                for (Cube cavityCube : air) {
+                    for (Cube exteriorCube : exteriorAir) {
+                        if (cavityCube.checkIfAdjacentTo(exteriorCube)) {
+                            newlyDiscoveredExteriorAir.add(cavityCube);
+                        }
+                    }
+                }
+                exteriorAir.addAll(newlyDiscoveredExteriorAir);
+                air.removeAll(newlyDiscoveredExteriorAir);
+            } while (!newlyDiscoveredExteriorAir.isEmpty());
+        }
+
+        private int distanceInOneDimension(Cube other, int dimension) {
+            return other.coordinates[dimension] - this.coordinates[dimension];
+        }
+
+        private boolean isAlignedInTwoDimensionsWith(Cube other, int dimension1, int dimension2) {
+            return distanceInOneDimension(other, dimension1) == 0 && distanceInOneDimension(other, dimension2) == 0;
+        }
+
+        @SuppressWarnings("UnusedReturnValue")
+        private boolean checkIfAdjacentTo(Cube other) {
+            if (isAlignedInTwoDimensionsWith(other, 1, 2)
+                    && Math.abs(distanceInOneDimension(other, 0)) == 1) {
+                faceIsExposed[(1 + distanceInOneDimension(other, 0)) / 2] = false;
+                return true;
+            }
+            if (isAlignedInTwoDimensionsWith(other, 0, 2)
+                    && Math.abs(distanceInOneDimension(other, 1)) == 1) {
+                faceIsExposed[2 + (1 + distanceInOneDimension(other, 1)) / 2] = false;
+                return true;
+            }
+            if (isAlignedInTwoDimensionsWith(other, 0, 1)
+                    && Math.abs(distanceInOneDimension(other, 2)) == 1) {
+                faceIsExposed[4 + (1 + distanceInOneDimension(other, 2)) / 2] = false;
+                return true;
+            }
+            return false;
+        }
+
+        private long countExposedFaces() {
+            return IntStream.range(0, faceIsExposed.length).filter(i -> faceIsExposed[i]).count();
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (this == other) return true;
+            if (!(other instanceof Cube cube)) return false;
+            return Arrays.equals(coordinates, cube.coordinates);
+        }
+
+        @Override
+        public int hashCode() {
+            return Arrays.hashCode(coordinates);
+        }
+    }
+}
-- 
GitLab