From 637d3eeb18bc7884252a0560862734ae8d43daf6 Mon Sep 17 00:00:00 2001
From: Christopher Bohn <bohn@unl.edu>
Date: Sun, 12 Dec 2021 13:15:57 -0600
Subject: [PATCH] Day 4 complete

---
 2021/README.md                                |  11 +-
 .../java/edu/unl/cse/bohn/year2021/Day1.java  |   3 +-
 .../java/edu/unl/cse/bohn/year2021/Day2.java  |   3 +-
 .../java/edu/unl/cse/bohn/year2021/Day3.java  |   7 +-
 .../java/edu/unl/cse/bohn/year2021/Day4.java  | 152 ++++++++++++++++++
 5 files changed, 166 insertions(+), 10 deletions(-)
 create mode 100644 2021/src/main/java/edu/unl/cse/bohn/year2021/Day4.java

diff --git a/2021/README.md b/2021/README.md
index 10d9f6a..9f4b90a 100644
--- a/2021/README.md
+++ b/2021/README.md
@@ -1,4 +1,4 @@
-# 2021 Advent of Coding Solutions
+# 2021 Advent of Code Solutions
 
 ## Day 1
 
@@ -14,4 +14,11 @@ switch statements.
 ## Day 3
 
 My CSCE 231 students should be able to do this problem in their sleep -- bit
-masks are cool.
\ No newline at end of file
+masks are cool.
+
+## Day 4
+
+This problem is a little more interesting since there are two stringly types in
+the data, and one of them (the Bingo cards) requires a delimiter between
+instances. Defining a custom class to represent Bingo cards simplifies the
+post-parsing problem.
\ No newline at end of file
diff --git a/2021/src/main/java/edu/unl/cse/bohn/year2021/Day1.java b/2021/src/main/java/edu/unl/cse/bohn/year2021/Day1.java
index 65aaee9..a19ed99 100644
--- a/2021/src/main/java/edu/unl/cse/bohn/year2021/Day1.java
+++ b/2021/src/main/java/edu/unl/cse/bohn/year2021/Day1.java
@@ -4,7 +4,6 @@ import edu.unl.cse.bohn.Puzzle;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.stream.Collectors;
 
 @SuppressWarnings("unused")
 public class Day1 extends Puzzle {
@@ -40,7 +39,7 @@ public class Day1 extends Puzzle {
 
     @Override
     public int computePart1(List<String> data) {
-        depths = data.stream().map(Integer::parseInt).collect(Collectors.toList());
+        depths = data.stream().map(Integer::parseInt).toList();
         return countIncreases(depths);
     }
 
diff --git a/2021/src/main/java/edu/unl/cse/bohn/year2021/Day2.java b/2021/src/main/java/edu/unl/cse/bohn/year2021/Day2.java
index c0a5fc1..43ef3f3 100644
--- a/2021/src/main/java/edu/unl/cse/bohn/year2021/Day2.java
+++ b/2021/src/main/java/edu/unl/cse/bohn/year2021/Day2.java
@@ -3,7 +3,6 @@ package edu.unl.cse.bohn.year2021;
 import edu.unl.cse.bohn.Puzzle;
 
 import java.util.List;
-import java.util.stream.Collectors;
 
 @SuppressWarnings("unused")
 public class Day2 extends Puzzle {
@@ -35,7 +34,7 @@ public class Day2 extends Puzzle {
                 .map(instruction -> new NavigationInstruction(
                         instruction.split(" ")[0],
                         Integer.parseInt(instruction.split(" ")[1])))
-                .collect(Collectors.toList());
+                .toList();
         range = 0;
         depth = 0;
         for (NavigationInstruction instruction : navigationInstructions) {
diff --git a/2021/src/main/java/edu/unl/cse/bohn/year2021/Day3.java b/2021/src/main/java/edu/unl/cse/bohn/year2021/Day3.java
index 6bb074f..292ead5 100644
--- a/2021/src/main/java/edu/unl/cse/bohn/year2021/Day3.java
+++ b/2021/src/main/java/edu/unl/cse/bohn/year2021/Day3.java
@@ -5,7 +5,6 @@ import edu.unl.cse.bohn.Puzzle;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.function.BiPredicate;
-import java.util.stream.Collectors;
 
 @SuppressWarnings("unused")
 public class Day3 extends Puzzle {
@@ -33,7 +32,7 @@ public class Day3 extends Puzzle {
 
     @Override
     public int computePart1(List<String> data) {
-        diagnosticReports = data.stream().map(report -> Integer.parseInt(report, 2)).collect(Collectors.toList());
+        diagnosticReports = data.stream().map(report -> Integer.parseInt(report, 2)).toList();
         reportLength = data.get(0).length();
         ones = new int[]{
                 0, 0, 0, 0, 0, 0, 0, 0,
@@ -76,11 +75,11 @@ public class Day3 extends Puzzle {
             if (predicate.test(numberOfOnes, numberOfZeroes)) {
                 workingSet = workingSet.stream()
                         .filter(report -> (report & mask) != 0)
-                        .collect(Collectors.toList());
+                        .toList();
             } else {
                 workingSet = workingSet.stream()
                         .filter(report -> (report & mask) == 0)
-                        .collect(Collectors.toList());
+                        .toList();
             }
             bit--;
             // Need to re-count the ones and zeroes since the old counts are now wrong
diff --git a/2021/src/main/java/edu/unl/cse/bohn/year2021/Day4.java b/2021/src/main/java/edu/unl/cse/bohn/year2021/Day4.java
new file mode 100644
index 0000000..27b1ee3
--- /dev/null
+++ b/2021/src/main/java/edu/unl/cse/bohn/year2021/Day4.java
@@ -0,0 +1,152 @@
+package edu.unl.cse.bohn.year2021;
+
+import edu.unl.cse.bohn.Puzzle;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+
+@SuppressWarnings("unused")
+public class Day4 extends Puzzle {
+    private List<Integer> numbers;
+    private List<BingoCard> cards;
+
+    public Day4() {
+        day = 4;
+        sampleData = """
+                7,4,9,5,11,17,23,2,0,14,21,24,10,16,13,6,15,25,12,22,18,20,8,19,3,26,1
+                                
+                22 13 17 11  0
+                 8  2 23  4 24
+                21  9 14 16  7
+                 6 10  3 18  5
+                 1 12 20 15 19
+                                
+                 3 15  0  2 22
+                 9 18 13 17  5
+                19  8  7 25 23
+                20 11 10 24  4
+                14 21 16 12  6
+                                
+                14 21 17 24  4
+                10 16 15  9 19
+                18  8 23 26 20
+                22 11 13  6  5
+                 2  0 12  3  7""";
+        isProductionReady = true;
+    }
+
+    private void parseData(List<String> data) {
+        cards = new LinkedList<>();
+        int[][] card = null;
+        int startingLine = 0;
+        for (int lineNumber = 0; lineNumber < data.size(); lineNumber++) {
+            String line = data.get(lineNumber);
+            if (lineNumber == 0) {
+                numbers = Arrays.stream(line.split(",")).map(Integer::parseInt).toList();
+            } else {
+                if (line.isBlank()) {
+                    if (lineNumber != 1) {
+                        cards.add(new BingoCard(Objects.requireNonNull(card)));
+                    }
+                    card = null;
+                } else {
+                    int[] row = new int[0];
+                    try {
+                        row = Arrays.stream(line.trim().split("\s+")).mapToInt(Integer::parseInt).toArray();
+                    } catch (NumberFormatException e) {
+                        System.err.println("oops");
+                    }
+                    if (card == null) {
+                        startingLine = lineNumber;
+                        // We're assuming square Bingo cards
+                        card = new int[row.length][row.length];
+                    }
+                    card[lineNumber - startingLine] = row;
+                }
+            }
+        }
+        cards.add(new BingoCard(Objects.requireNonNull(card)));
+    }
+
+    @Override
+    public int computePart1(List<String> data) {
+        parseData(data);
+        Iterator<Integer> numberIterator = numbers.iterator();
+        while (cards.stream().noneMatch(BingoCard::hasBingo) && numberIterator.hasNext()) {
+            int number = numberIterator.next();
+            for (BingoCard card : cards) {
+                card.markCard(number);
+            }
+        }
+        BingoCard winningCard = cards.stream().filter(BingoCard::hasBingo).findAny().orElseThrow();
+        return winningCard.score();
+    }
+
+    @Override
+    public int computePart2(List<String> data) {
+        Iterator<Integer> numberIterator = numbers.iterator();
+        while ((cards.stream().filter(BingoCard::hasBingo).count() < cards.size() - 1) && numberIterator.hasNext()) {
+            int number = numberIterator.next();
+            for (BingoCard card : cards) {
+                card.markCard(number);
+            }
+        }
+        BingoCard losingCard = cards.stream().filter(card -> !card.hasBingo()).findAny().orElseThrow();
+        while (!losingCard.hasBingo() && numberIterator.hasNext()) {
+            losingCard.markCard(numberIterator.next());
+        }
+        return losingCard.score();
+    }
+
+    public static class BingoCard {
+        private final int[][] numbers;
+        private final boolean[][] markers;
+        private int lastNumberCalled = -1;
+
+        public BingoCard(int[][] cardNumbers) {
+            markers = new boolean[cardNumbers.length][cardNumbers[0].length];
+            for (boolean[] row : markers) {
+                Arrays.fill(row, false);
+            }
+            numbers = Arrays.stream(cardNumbers).map(int[]::clone).toArray(int[][]::new);
+        }
+
+        public void markCard(int number) {
+            lastNumberCalled = number;
+            for (int i = 0; i < numbers.length; i++) {
+                for (int j = 0; j < numbers[i].length; j++) {
+                    if (numbers[i][j] == number) {
+                        markers[i][j] = true;
+                    }
+                }
+            }
+        }
+
+        public boolean hasBingo() {
+            boolean bingo = false;
+            // We're going to assume square Bingo cards
+            for (int i = 0; i < markers.length; i++) {
+                int rowMarkerCount = 0, columnMarkerCount = 0;
+                for (int j = 0; j < markers.length; j++) {
+                    rowMarkerCount += markers[i][j] ? 1 : 0;
+                    columnMarkerCount += markers[j][i] ? 1 : 0;
+                }
+                bingo = bingo || (rowMarkerCount == markers.length) || (columnMarkerCount == markers.length);
+            }
+            return bingo;
+        }
+
+        public int score() {
+            int rawScore = 0;
+            for (int i = 0; i < numbers.length; i++) {
+                for (int j = 0; j < numbers[i].length; j++) {
+                    rawScore += markers[i][j] ? 0 : numbers[i][j];
+                }
+            }
+            return rawScore * lastNumberCalled;
+        }
+    }
+}
-- 
GitLab