From b552498bd6067a9474b5cf299674a1853c35a4b2 Mon Sep 17 00:00:00 2001
From: Christopher Bohn <bohn@unl.edu>
Date: Sun, 11 Dec 2022 15:24:39 -0600
Subject: [PATCH] Completed Year 2022 Day 11

---
 2022/README.md                                |  97 +++++++++++++++--
 .../java/edu/unl/cse/bohn/year2022/Day11.java | 100 +++++++++++++-----
 2 files changed, 161 insertions(+), 36 deletions(-)

diff --git a/2022/README.md b/2022/README.md
index 26dc5a0..a90e517 100644
--- a/2022/README.md
+++ b/2022/README.md
@@ -345,11 +345,11 @@ The subproblems are
 - Parse the input to obtain, for each monkey, its
     - starting items -- not only the value (worry level), but also the order
     - relationship between the new and old worry levels
-      - after examining the input, all operations appear to be addition or multiplication, and the first operand is
-        always the old value
+        - after examining the input, all operations appear to be addition or multiplication, and the first operand is
+          always the old value
     - test operation and the true/false target monkeys
-      - after examining the input, all tests are whether the worry level is divisible by some integer
-    - 
+        - after examining the input, all tests are whether the worry level is divisible by some integer
+    -
 - Determine which monkey will throw an item next
 - Reduce an item's worry level immediately before it is thrown
 - Track how many (probably non-unique) items a monkey inspects across the many rounds
@@ -357,7 +357,7 @@ The subproblems are
 
 ### Part 2
 
-The part 2 description seems to be forecasting that the `Monkey` class will be used and modified again in the coming 
+The part 2 description seems to be forecasting that the `Monkey` class will be used and modified again in the coming
 day(s).
 If that happens, then I'll move `Monkey` to be an outer class instead of an inner class.
 For now, I just need to modify it so that the boredom modification is optional.
@@ -367,27 +367,33 @@ For now, I just need to modify it so that the boredom modification is optional.
 Since parts 1 & 2 differ only in whether to use the boredom modification and the number of rounds,
 I can create a parameterized method.
 
-### Possible misinterpretation
+### There's more to it than that
 
-It looks like part 2 isn't saying to abandon the divisor *now* to replace the modifier later;
-I guess we're supposed to figure out the new modifier for of part 2.
+It looks like part 2 isn't saying to abandon the divisor *now* to replace the modifier later.
+
+I guess we're supposed to figure out the new modifier for part 2.
 That is more interesting, to say the least.
 
 Both the description and my code (without a boredom modifier) agree that after 1 round:
+
 ```
 Monkey 0 inspected items 2 times.
 Monkey 1 inspected items 4 times.
 Monkey 2 inspected items 3 times.
 Monkey 3 inspected items 6 times.
 ```
+
 but after 20, the description says
+
 ```
 Monkey 0 inspected items 99 times.
 Monkey 1 inspected items 97 times.
 Monkey 2 inspected items 8 times.
 Monkey 3 inspected items 103 times.
 ```
+
 but my code (without a boredom modifier) says
+
 ```
 Monkey 0 inspected items 96 times.
 Monkey 1 inspected items 100 times.
@@ -395,8 +401,79 @@ Monkey 2 inspected items 7 times.
 Monkey 3 inspected items 103 times.
 ```
 
-I wonder if this suggests the final pre-throw modifier is history based; that is, no modifier on the first inspection,
-but then the monkey gets a little bored at something it's seen before.
+#### Thought #1
+
+I wonder if what we're seeing suggests the final pre-throw modifier is history based; that is, no modifier on the first
+inspection, but then the monkey gets a little bored at something it's seen before. The obvious thing to try is to have
+no modification the first time that the monkey inspects an item (preserving the correct result from round 1) and then
+divide by 3 if the monkey has seen it before.
+
+No, that isn't it, either. After 20 rounds:
+
+```
+Monkey 0 inspected items 96 times.
+Monkey 1 inspected items 100 times.
+Monkey 2 inspected items 10 times.
+Monkey 3 inspected items 102 times.
+```
+
+#### Thought #2
+
+It doesn't seem likely that the AOC creator would want us to blindly guess at possible modifications.
+
+![Winnie the Pooh, thinking](https://media.tenor.com/BdcwU6gstRoAAAAM/pooh-think.gif)
+
+Maybe the answer to whether there is *no* modifier in part 2 is "both."
+
+!["Why not both?" meme](https://media.tenor.com/odyVsZbC-OYAAAAM/why-not-both-why-not.gif)
+
+Perhaps the hint is in the test for which monkey will receive the item -- it's a test for whether the worry level is
+divisible by some value.
+We can keep the worry levels down to manageable values while also preserving the outcome of this test *for any given
+inspection* by assigning the worry level to the remainder of that notional division.
+Then the remainder is some other value between 0 and the original worry level, which will be the same as the modulus
+operator's result (and this remainder will also produce the same modulus result).
+
+How will this affect subsequent throws? All operations are multiplication or addition.
+Suppose the original value is $$i = n \times testDivisor + remainder$$ for some value $$n$$.
+In the case of addition, $$i + j = n \times testDivisor + remainder + j$$ and the resulting modulus is unchanged.
+In the case of multiplication, if we view it as repeated addition then the same argument holds.
+
+Either I'm barking up the wrong tree, or we were seeing overflow errors earlier.
+
+```
+== After round 20 ==
+Monkey 0 inspected items 95 times.
+Monkey 1 inspected items 101 times.
+Monkey 2 inspected items 9 times.
+Monkey 3 inspected items 99 times.
+```
+
+Shazbot.
+
+### Thought #3
+
+I'm sure I'm on the right track.
+I think I was off-base with my answer to how it will affect subsequent throws.
+The problem is that taking the remainder of $$i \div testDivisor_x$$ might not preserve the modulus of
+$$i \div testDivisor_y$$.
+
+We need to use a divisor that would preserve both -- that is, the divisor must be a multiple of both $$testDivisor_x$$
+and $$testDivisor_y$$.
+
+Okay, *seriously!?* How does Java not have a least common multiple function?
+
+And let's make the worry levels `long` instead of `int` just to give us a little more room to grow.
+
+```
+== After round 20 ==
+Monkey 0 inspected items 99 times.
+Monkey 1 inspected items 97 times.
+Monkey 2 inspected items 8 times.
+Monkey 3 inspected items 103 times.
+```
+
+***Shack!***
 
 ## Day 12
 
diff --git a/2022/src/main/java/edu/unl/cse/bohn/year2022/Day11.java b/2022/src/main/java/edu/unl/cse/bohn/year2022/Day11.java
index e08cb45..50c266a 100644
--- a/2022/src/main/java/edu/unl/cse/bohn/year2022/Day11.java
+++ b/2022/src/main/java/edu/unl/cse/bohn/year2022/Day11.java
@@ -43,22 +43,37 @@ public class Day11 extends Puzzle {
 
     @Override
     public long computePart1(List<String> data) {
+        Monkey.setTerse();
         //noinspection MagicNumber
         return measureMonkeyBusiness(data, 20, true);
     }
 
     @Override
     public long computePart2(List<String> data) {
+        Monkey.setTerse();
+        System.out.println("== After round 1 ==");
         measureMonkeyBusiness(data, 1, false);
         System.out.println();
+        System.out.println("== After round 20 ==");
+//        Monkey.setVerbose();
+        Monkey.setTerse();
         //noinspection MagicNumber
-        return measureMonkeyBusiness(data, 20, false);
+        measureMonkeyBusiness(data, 20, false);
+        System.out.println();
+        System.out.println("== After round 1000 ==");
+        Monkey.setTerse();
+//        //noinspection MagicNumber
+        measureMonkeyBusiness(data, 1000, false);
+        System.out.println();
+        System.out.println("== After round 10000 ==");
+        Monkey.setTerse();
+        //noinspection MagicNumber
+        return measureMonkeyBusiness(data, 10000, false);
     }
 
     private static long measureMonkeyBusiness(List<String> data, int numberOfRounds, boolean useBoredomModifier) {
         List<Monkey> monkeys = new LinkedList<>();
         Monkey.clearMonkeys();
-        Monkey.setTerse();
         if (useBoredomModifier) {
             Monkey.setBoredomModification();
         } else {
@@ -76,12 +91,12 @@ public class Day11 extends Puzzle {
         }
 //        Monkey.printMonkeys();
         Monkey.setVerbose();
-        LinkedList<Integer> inspectionCounts = new LinkedList<>();
-        for (Monkey monkey: monkeys) {
+        LinkedList<Long> inspectionCounts = new LinkedList<>();
+        for (Monkey monkey : monkeys) {
             inspectionCounts.add(monkey.getNumberOfInspections());
         }
-        inspectionCounts.sort(Integer::compareTo);
-        return (long) inspectionCounts.removeLast() * (long) inspectionCounts.removeLast();
+        inspectionCounts.sort(Long::compareTo);
+        return inspectionCounts.removeLast() * inspectionCounts.removeLast();
     }
 
     private static class Monkey {
@@ -89,13 +104,14 @@ public class Day11 extends Puzzle {
         private static boolean useBoredomModifier = true;
         private static boolean verbose = false;
         private static final List<Monkey> monkeys = new LinkedList<>();
+        private static int lcmDivisor;
 
         private final int monkeyNumber;
-        private final List<Integer> items;
-        private final UnaryOperator<Integer> worryLevelModifier;
+        private final List<Long> items;
+        private final UnaryOperator<Long> worryLevelModifier;
         private final int testDivisor;
         private final Map<Boolean, Integer> targetMonkeys;
-        private int inspectionCount;
+        private long inspectionCount;
 
         public static void setVerbose() {
             verbose = true;
@@ -113,6 +129,26 @@ public class Day11 extends Puzzle {
             useBoredomModifier = false;
         }
 
+        private static int lcm(List<Integer> factors) {
+            return lcm(factors, 0);
+        }
+
+        private static int lcm(List<Integer> factors, int index) {
+            // modified from https://www.geeksforgeeks.org/lcm-of-given-array-elements/
+            // lcm(a,b) = (a*b/gcd(a,b))
+            if (index == factors.size() - 1) {
+                return factors.get(index);
+            }
+            int currentFactor = factors.get(index);
+            int lcmOfRemainingFactors = lcm(factors, index + 1);
+            return (currentFactor * lcmOfRemainingFactors / gcd(currentFactor, lcmOfRemainingFactors));
+        }
+
+        private static int gcd(int m, int n) {
+            // modified from https://www.geeksforgeeks.org/lcm-of-given-array-elements/
+            return n == 0 ? m : gcd(n, m % n);
+        }
+
         public static void printMonkeys() {
             for (Monkey monkey : monkeys) {
                 System.out.println("Monkey " + monkey.getMonkeyNumber() + ": " + monkey.getItems());
@@ -124,13 +160,13 @@ public class Day11 extends Puzzle {
             int intendedMonkeyNumber = Integer.parseInt(description.get(0).strip().split(" ")[1].split(":")[0]);
             // >  Starting items: 79, 98
             String[] itemStrings = description.get(1).split(":")[1].strip().split(", ");
-            List<Integer> items = Arrays.stream(itemStrings)
-                    .map(Integer::parseInt)
+            List<Long> items = Arrays.stream(itemStrings)
+                    .map(Long::parseLong)
                     .collect(Collectors.toCollection(LinkedList::new));
             // >  Operation: new = old * 19
             String operator = description.get(2).strip().split(" ")[4];
             String operand = description.get(2).strip().split(" ")[5];
-            UnaryOperator<Integer> worryLevelModifier = switch (operator) {
+            UnaryOperator<Long> worryLevelModifier = switch (operator) {
                 case "+" -> operand.equals("old") ? (n -> n + n) : (n -> n + Integer.parseInt(operand));
                 case "*" -> operand.equals("old") ? (n -> n * n) : (n -> n * Integer.parseInt(operand));
                 default -> throw new UnsupportedOperationException("Unhandled operator: " + operator);
@@ -155,6 +191,8 @@ public class Day11 extends Puzzle {
                 System.out.println(monkey);
             }
             monkeys.add(monkey);
+            List<Integer> testDivisors = monkeys.stream().map(m -> m.testDivisor).toList();
+            lcmDivisor = lcm(testDivisors);
             return monkey;
         }
 
@@ -162,7 +200,7 @@ public class Day11 extends Puzzle {
             monkeys.clear();
         }
 
-        private Monkey(List<Integer> items, UnaryOperator<Integer> worryLevelModifier,
+        private Monkey(List<Long> items, UnaryOperator<Long> worryLevelModifier,
                        int testDivisor, Map<Boolean, Integer> targetMonkeys) {
             this.monkeyNumber = monkeys.size();
             this.items = items;
@@ -176,11 +214,11 @@ public class Day11 extends Puzzle {
             return monkeyNumber;
         }
 
-        private List<Integer> getItems() {
+        private List<Long> getItems() {
             return Collections.unmodifiableList(items);
         }
 
-        public int getNumberOfInspections() {
+        public long getNumberOfInspections() {
             if (verbose) {
                 System.out.println("Monkey " + monkeyNumber + " inspected items " + inspectionCount + " times.");
             }
@@ -195,9 +233,9 @@ public class Day11 extends Puzzle {
             items.clear();  // probably should remove each item in `throwItem()` but that could screw with the iterator
         }
 
-        private void throwItem(Integer item) {
+        private void throwItem(Long item) {
             inspectionCount++;
-            int worryLevel = item;
+            long worryLevel = item;
             if (verbose) {
                 System.out.println("  Monkey inspects an item with a worry level of " + worryLevel + ".");
             }
@@ -206,16 +244,9 @@ public class Day11 extends Puzzle {
                 System.out.println("    Worry level is modified by (" + worryLevelModifier
                         + ") to " + worryLevel + ".");
             }
-            worryLevel = useBoredomModifier ? worryLevel / BOREDOM_DIVISOR : worryLevel;
+            worryLevel = modifyWorryLevel(worryLevel);
             boolean worryLevelIsDivisibleByTestDivisor = worryLevel % testDivisor == 0;
             if (verbose) {
-                if (useBoredomModifier) {
-                    System.out.println("    Monkey gets bored with item. Worry level is divided by "
-                            + BOREDOM_DIVISOR + " to " + worryLevel + ".");
-                } else {
-                    System.out.println("    Monkey doesn't get bored with item. Worry level remains "
-                            + worryLevel + ".");
-                }
                 System.out.println("    Current worry level is "
                         + (worryLevelIsDivisibleByTestDivisor ? "" : "not ")
                         + "divisible by " + testDivisor + ".");
@@ -225,7 +256,24 @@ public class Day11 extends Puzzle {
             monkeys.get(targetMonkeys.get(worryLevelIsDivisibleByTestDivisor)).receiveItem(worryLevel);
         }
 
-        private void receiveItem(Integer item) {
+        private long modifyWorryLevel(long worryLevel) {
+            if (useBoredomModifier) {
+                worryLevel = worryLevel / BOREDOM_DIVISOR;
+                if (verbose) {
+                    System.out.println("    Monkey gets bored with item. Worry level is divided by "
+                            + BOREDOM_DIVISOR + " to " + worryLevel + ".");
+                }
+            } else {
+                worryLevel = worryLevel % lcmDivisor;
+                if (verbose) {
+                    System.out.println("    Monkey doesn't get bored with item. Worry level is regulated (modulated)"
+                            + " by " + lcmDivisor + " to " + worryLevel + ".");
+                }
+            }
+            return worryLevel;
+        }
+
+        private void receiveItem(Long item) {
             items.add(item);
         }
 
-- 
GitLab