diff --git a/2022/README.md b/2022/README.md index 26dc5a0d961375044400067c14a9c80d9bba1de0..a90e517209f6960b8fd6903cdebc1d6b7fef2f94 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. + + + +Maybe the answer to whether there is *no* modifier in part 2 is "both." + + + +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 e08cb452eb2ed5d7b72695aa29cce4e9b431dae5..50c266a66197b08c9dc707ca0ed00e938be39e5a 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); }