From 6e55cf34261bc308750a5da755e1c8ae730235c0 Mon Sep 17 00:00:00 2001
From: Christopher Bohn <bohn@unl.edu>
Date: Mon, 13 Dec 2021 09:17:59 -0600
Subject: [PATCH] Day 8 complete

---
 2021/README.md                                |  30 ++++
 .../java/edu/unl/cse/bohn/year2021/Day8.java  | 146 ++++++++++++++++++
 2 files changed, 176 insertions(+)
 create mode 100644 2021/src/main/java/edu/unl/cse/bohn/year2021/Day8.java

diff --git a/2021/README.md b/2021/README.md
index 49a7860..8b64c0b 100644
--- a/2021/README.md
+++ b/2021/README.md
@@ -55,3 +55,33 @@ work.
 Part 2 looks a bit more interesting.  We *could* go for an
 O(num_crabs * max_distance^2) solution, or we could recognize what Euler
 recognized: ∑_{i=1}^{n}i = n(n+1)/2.
+
+### Day 8
+
+Seven-segment displays are cool. This has the potential to be a pain. Part 1
+looks okay -- examining only the output patterns, count the number of patterns
+of length 2, 3, 4, or 7.
+
+For part 2, I think set operations are the way to go. For simplicity in this
+explanation, I'm going to use numerals to depict the sets of illuminated segments.
+- Those we know from length alone:
+  - 1 is the only digit of length 2
+  - 7 is the only digit of length 3
+  - 4 is the only digit of length 4
+  - 8 is the only digit of length 7
+- 7\1 gives us the top segment
+- Considering the length-6 digits:
+  - 9 is the only one for which 4\digit = ø, giving us the lower-left segment
+  - 6 is the only one for which 1\digit ≠ ø, giving us the upper-right segment
+  - 0 remains, giving us the central segment
+- (9\7)\4 gives us the bottom segment
+- 1\{upper-right segment} gives us the lower-right segment
+- The upper-left segment remains
+- Considering the length-5 digits:
+  - 3 is the only digit for which digit U 1 = digit
+  - 5 is the only remaining digit for which digit U 4 = 9
+  - 2 remains
+
+It turns out we can ascertain the digits without needing to record the segments.
+That's nifty.
+
diff --git a/2021/src/main/java/edu/unl/cse/bohn/year2021/Day8.java b/2021/src/main/java/edu/unl/cse/bohn/year2021/Day8.java
new file mode 100644
index 0000000..a961402
--- /dev/null
+++ b/2021/src/main/java/edu/unl/cse/bohn/year2021/Day8.java
@@ -0,0 +1,146 @@
+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;
+import java.util.stream.Collectors;
+
+@SuppressWarnings("unused")
+public class Day8 extends Puzzle {
+    @SuppressWarnings("SpellCheckingInspection")
+    public Day8(boolean isProductionReady) {
+        super(isProductionReady);
+//        sampleData = "acedgfb cdfbe gcdfa fbcad dab cefabd cdfgeb eafb cagedb ab | cdfeb fcadb cdfeb cdbaf";
+        sampleData = """
+                be cfbegad cbdgef fgaecd cgeb fdcge agebfd fecdb fabcd edb | fdgacbe cefdb cefbgd gcbe
+                edbfga begcd cbg gc gcadebf fbgde acbgfd abcde gfcbed gfec | fcgedb cgb dgebacf gc
+                fgaebd cg bdaec gdafb agbcfd gdcbef bgcad gfac gcb cdgabef | cg cg fdcagb cbg
+                fbegcd cbd adcefb dageb afcb bc aefdc ecdab fgdeca fcdbega | efabcd cedba gadfec cb
+                aecbfdg fbg gf bafeg dbefa fcge gcbea fcaegb dgceab fcbdga | gecf egdcabf bgf bfgea
+                fgeab ca afcebg bdacfeg cfaedg gcfdb baec bfadeg bafgc acf | gebdcfa ecba ca fadegcb
+                dbcfg fgd bdegcaf fgec aegbdf ecdfab fbedc dacgb gdcebf gf | cefg dcbef fcge gbcadfe
+                bdfegc cbegaf gecbf dfcage bdacg ed bedf ced adcbefg gebcd | ed bcgafe cdgba cbgef
+                egadfb cdbfeg cegd fecab cgb gbdefca cg fgcdab egfdb bfceg | gbdfcae bgc cg cgb
+                gcafb gcf dcaebfg ecagb gf abcdeg gaef cafbge fdbac fegbdc | fgae cfgab fg bagce""";
+    }
+
+    @Override
+    public long computePart1(List<String> data) {
+        Set<Integer> suitableDigitLengths = Set.of(2, 3, 4, 7);
+        long numberOfMatchingDigits = 0;
+        for (String line : data) {
+            String[] halves = line.split(" \\| ");
+            for (String digit : halves[1].split(" ")) {
+                if (suitableDigitLengths.contains(digit.length())) {
+                    numberOfMatchingDigits++;
+                }
+            }
+        }
+        return numberOfMatchingDigits;
+    }
+
+    private String sortSegments(String segments) {
+        return segments.chars()
+                .sorted()
+                .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
+                .toString();
+    }
+
+    private Set<Character> stringToSet(String segments) {
+        return new HashSet<>(segments.chars().mapToObj(c -> (char)c).toList());
+    }
+
+    @Override
+    public long computePart2(List<String> data) {
+        long sumOfValues = 0L;
+        for(String line:data) {
+            Map<String, Long> numerals = new HashMap<>();
+            String[] halves = line.split(" \\| ");
+            for (String segmentPattern : halves[0].split(" ")) {
+                numerals.put(sortSegments(segmentPattern), null);
+            }
+            Set<String> segmentPatterns = numerals.keySet();
+
+            // Identify which patterns correspond to which digits. See the README for the reasoning.
+            // First the patterns whose length are unique
+            String one = segmentPatterns.stream().filter(pattern -> pattern.length() == 2).findAny().orElseThrow();
+            numerals.put(one, 1L);
+            String seven = segmentPatterns.stream().filter(pattern -> pattern.length() == 3).findAny().orElseThrow();
+            numerals.put(seven, 7L);
+            String four = segmentPatterns.stream().filter(pattern -> pattern.length() == 4).findAny().orElseThrow();
+            numerals.put(four, 4L);
+            String eight = segmentPatterns.stream().filter(pattern -> pattern.length() == 7).findAny().orElseThrow();
+            numerals.put(eight, 8L);
+            // Then the patterns of length 6
+            Set<String> candidates = segmentPatterns.stream()
+                    .filter(pattern -> pattern.length() == 6)
+                    .collect(Collectors.toSet());
+            String nine = "";
+            for (String candidate : candidates) {
+                Set<Character> candidateSet = stringToSet(candidate);
+                Set<Character> copyOfFour = new HashSet<>(stringToSet(four));
+                copyOfFour.removeAll(candidateSet);
+                if (copyOfFour.isEmpty()) {
+                    nine = candidate;
+                    numerals.put(nine, 9L);
+                }
+            }
+            candidates.remove(nine);
+            String six = "";
+            for (String candidate : candidates) {
+                Set<Character> candidateSet = stringToSet(candidate);
+                Set<Character> copyOfOne = new HashSet<>(stringToSet(one));
+                copyOfOne.removeAll(candidateSet);
+                if (!copyOfOne.isEmpty()) {
+                    six = candidate;
+                    numerals.put(six, 6L);
+                }
+            }
+            candidates.remove(six);
+            String zero = candidates.stream().findAny().orElseThrow();
+            numerals.put(zero, 0L);
+            // The remaining patterns are all length 5
+            candidates = segmentPatterns.stream()
+                    .filter(pattern -> pattern.length() == 5)
+                    .collect(Collectors.toSet());
+            String three = "";
+            for (String candidate : candidates) {
+                Set<Character> candidateSet = stringToSet(candidate);
+                Set<Character> copyOfCandidateSet = new HashSet<>(candidateSet);
+                candidateSet.addAll(stringToSet(one));
+                if (candidateSet.equals(copyOfCandidateSet)) {
+                    three = candidate;
+                    numerals.put(three, 3L);
+                }
+            }
+            candidates.remove(three);
+            String five = "";
+            for (String candidate : candidates) {
+                Set<Character> candidateSet = stringToSet(candidate);
+                candidateSet.addAll(stringToSet(four));
+                if (candidateSet.equals(stringToSet(nine))) {
+                    five = candidate;
+                    numerals.put(five, 5L);
+                }
+            }
+            candidates.remove(five);
+            String two = candidates.stream().findAny().orElseThrow();
+            numerals.put(two, 2L);
+
+            // Now determine the output value
+            long lineValue = 0L;
+            long magnitude = 1000L;
+            for (String displayDigit : halves[1].split(" ")) {
+                long digitValue = numerals.get(sortSegments(displayDigit));
+                lineValue += magnitude * digitValue;
+                magnitude /= 10;
+            }
+            sumOfValues += lineValue;
+        }
+        return sumOfValues;
+    }
+}
-- 
GitLab