From 89a24af457d301da2a9274b97e51b8c6684356f7 Mon Sep 17 00:00:00 2001
From: Christopher Bohn <bohn@unl.edu>
Date: Wed, 15 Dec 2021 08:51:40 -0600
Subject: [PATCH] Day 12 complete

---
 2021/README.md                                |  14 ++
 .../java/edu/unl/cse/bohn/year2021/Day12.java | 167 ++++++++++++++++++
 2 files changed, 181 insertions(+)
 create mode 100644 2021/src/main/java/edu/unl/cse/bohn/year2021/Day12.java

diff --git a/2021/README.md b/2021/README.md
index 969c13e..6669c6d 100644
--- a/2021/README.md
+++ b/2021/README.md
@@ -134,3 +134,17 @@ of the step. Well, we'll find out...
 
 Part 2, isn't any more challenging than checking whether the number of flashes
 is equal to the number of octopi.
+
+# Day 12
+
+For part 1, I hope there aren't any big caves directly connected to other big
+caves, because that would introduce a cycle. If we only wanted the shortest
+path then I could use a BFS, and cycles wouldn't be a problem. But we want all
+paths, and I think cycle detection is probably beyond the scope of Advent of
+Code, especially before the halfway point. For part 1 I'm just going to build
+the path tree. I have a sneaking suspicion I really should build the cave graph
+for part 2, but we shall see...
+
+Good news! I was wrong about needing to build the cave graph for part 2. I just
+need to specialize my `Cave` inner class to change the rules for adding 
+children.
diff --git a/2021/src/main/java/edu/unl/cse/bohn/year2021/Day12.java b/2021/src/main/java/edu/unl/cse/bohn/year2021/Day12.java
new file mode 100644
index 0000000..5fb1915
--- /dev/null
+++ b/2021/src/main/java/edu/unl/cse/bohn/year2021/Day12.java
@@ -0,0 +1,167 @@
+package edu.unl.cse.bohn.year2021;
+
+import edu.unl.cse.bohn.Puzzle;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@SuppressWarnings("unused")
+public class Day12 extends Puzzle {
+    private Map<String, Set<String>> adjacencyMap;
+
+    @SuppressWarnings("CommentedOutCode")
+    public Day12(boolean isProductionReady) {
+        super(isProductionReady);
+        sampleData = """
+                start-A
+                start-b
+                A-c
+                A-b
+                b-d
+                A-end
+                b-end""";
+//        sampleData = """
+//                dc-end
+//                HN-start
+//                start-kj
+//                dc-start
+//                dc-HN
+//                LN-dc
+//                HN-end
+//                kj-sa
+//                kj-HN
+//                kj-dc""";
+//        sampleData = """
+//                fs-end
+//                he-DX
+//                fs-he
+//                start-DX
+//                pj-DX
+//                end-zg
+//                zg-sl
+//                zg-pj
+//                pj-he
+//                RW-he
+//                fs-DX
+//                pj-RW
+//                zg-RW
+//                start-pj
+//                he-WI
+//                zg-he
+//                pj-fs
+//                start-RW""";
+    }
+
+    private void buildMap(List<String> data) {
+        adjacencyMap = new HashMap<>();
+        for (String line : data) {
+            String[] termini = line.split("-");
+            if (!adjacencyMap.containsKey(termini[0])) {
+                adjacencyMap.put(termini[0], new HashSet<>());
+            }
+            adjacencyMap.get(termini[0]).add(termini[1]);
+            if (!adjacencyMap.containsKey(termini[1])) {
+                adjacencyMap.put(termini[1], new HashSet<>());
+            }
+            adjacencyMap.get(termini[1]).add(termini[0]);
+        }
+        adjacencyMap = Map.copyOf(adjacencyMap);    // make it unmodifiable
+    }
+
+    @Override
+    public long computePart1(List<String> data) {
+        buildMap(data);
+        Cave root = new Cave("start", null, new HashSet<>(), adjacencyMap);
+//        for (Cave cave : Cave.exits) {
+//            System.out.println(cave);
+//        }
+        return Cave.exits.size();
+    }
+
+    @Override
+    public long computePart2(List<String> data) {
+        RevisitableCave root = new RevisitableCave("start", null, new HashSet<>(), adjacencyMap, false);
+        return RevisitableCave.exits.size();
+    }
+
+    private static class Cave {
+        public static final Set<Cave> exits = new HashSet<>();
+
+        protected String caveName;
+        protected Cave parent;
+        protected Set<String> visitedSmallCaves;
+        protected List<? extends Cave> children;
+
+        private Cave() {
+            // this constructor exists to keep Java happy wrt the subclass whose public constructor has different
+            // parameters than the parent class's constructor
+            caveName = null;
+            parent = null;
+            visitedSmallCaves = null;
+            children = null;
+        }
+
+        public Cave(String caveName, Cave parent, Set<String> visitedSmallCaves,
+                    Map<String, Set<String>> adjacencyMap) {
+            this.caveName = caveName;
+            this.parent = parent;
+            this.visitedSmallCaves = visitedSmallCaves;
+            if (caveName.equals(caveName.toLowerCase())) {
+                this.visitedSmallCaves.add(caveName);
+            }
+            if (caveName.equals("end")) {
+                exits.add(this);
+                children = null;
+            } else {
+                children = adjacencyMap.get(caveName).stream()
+                        .filter(neighbor -> !visitedSmallCaves.contains(neighbor))
+                        .map(neighbor -> new Cave(neighbor, this, new HashSet<>(visitedSmallCaves), adjacencyMap))
+                        .toList();
+            }
+        }
+
+        public String toString() {
+            StringBuilder stringBuilder = new StringBuilder(caveName);
+            if (!caveName.equals("start")) {
+                stringBuilder.insert(0, parent.toString() + " - ");
+            }
+            return stringBuilder.toString();
+        }
+    }
+
+    private static class RevisitableCave extends Cave {
+        public static final Set<Cave> exits = new HashSet<>();
+
+        public RevisitableCave(String caveName, Cave parent, Set<String> visitedSmallCaves,
+                               Map<String, Set<String>> adjacencyMap, boolean haveVisitedSmallCaveTwice) {
+            this.caveName = caveName;
+            this.parent = parent;
+            this.visitedSmallCaves = visitedSmallCaves;
+            final boolean updatedHaveVisitedSmallCaveTwice;
+            if (caveName.equals(caveName.toLowerCase())) {
+                if (visitedSmallCaves.contains(caveName)) {
+                    updatedHaveVisitedSmallCaveTwice = true;
+                } else {
+                    updatedHaveVisitedSmallCaveTwice = haveVisitedSmallCaveTwice;
+                }
+                this.visitedSmallCaves.add(caveName);
+            } else {
+                updatedHaveVisitedSmallCaveTwice = haveVisitedSmallCaveTwice;
+            }
+            if (caveName.equals("end")) {
+                exits.add(this);
+                children = null;
+            } else {
+                children = adjacencyMap.get(caveName).stream()
+                        .filter(neighbor -> !neighbor.equals("start"))
+                        .filter(neighbor -> !(updatedHaveVisitedSmallCaveTwice && visitedSmallCaves.contains(neighbor)))
+                        .map(neighbor -> new RevisitableCave(neighbor, this,
+                                new HashSet<>(visitedSmallCaves), adjacencyMap, updatedHaveVisitedSmallCaveTwice))
+                        .toList();
+            }
+        }
+    }
+}
-- 
GitLab