From bfe94864e9fb57fdc35f576f1a30b50864ab7049 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Ytterstr=C3=B6m?= Date: Mon, 6 Dec 2021 07:49:46 +0100 Subject: [PATCH] Solve 2021:6 "Lanternfish" part 2 Part 2 requires more clever code. In the first part, I kept track on every single fish on order, much like the output of the example in the puzzle. However, by increasing the day count to 256, my code became obese and would not run to the end: the OS killed it before it was done. It halted around 150 days. Browsing the sub reddit, I quickly realised that the current state of each fish individually did not matter. For example: 3,4,3,1,2 This says 2 fishes has a timer set to 3, and 1 fish each has a timer of 1, 2 and 4. Add one day, and we have 2,3,2,0,1 This says 2 fishes has a timer set to 2, and 1 fish each has a timer of 0, 1 and 3. Notice how **the relative count does not change** (2, 1, 1, 1). By using the excellent collections.counter, you get >>> from collections import Counter >>> Counter([3,4,3,1,2]) Counter({3: 2, 4: 1, 1: 1, 2: 1}) >>> Counter([2,3,2,0,1]) Counter({2: 2, 3: 1, 0: 1, 1: 1}) >>> If visualized in a tabulat view, you get this: 0 1 2 3 4 5 6 ---------------------------------- Initial 1 1 2 1 Day 1 1 1 2 1 The sequence moves **one step to the left each day**. Add another day, and something extra happens. 0 1 2 3 4 5 6 7 8 ---------------------------------------- Initial 1 1 2 1 Day 1 1 1 2 1 Day 2 1 2 1 1 1 2 things: * A value higher than zero pops out on the left. By rules, 1 fish has created 1 new fish, and resets its timer to 6. * The new fish created has a timer of 8, since it will need 2 days before it is able to start the create timer. There are now 6 fishes. Now, what happens on day 3? 0 1 2 3 4 5 6 7 8 ---------------------------------------- Initial 1 1 2 1 Day 1 1 1 2 1 Day 2 1 2 1 1 1 Day 3 2 1 1 1 1 1 Now this happened: * The fish on 0 creates a new fish, resets to 6. * The created fish, once again, has a timer of 8. * The fish created on day 2 decreases timer to 7. There are now 7 fishes. And so it continues. In Python, the most memory efficient and performant method of leftpadding a list of values is to use collections.deque, with the functions popleft and append. --- 2021-python/solutions/day_06.py | 16 ++++++++++------ 2021-python/tests/test_day_06.py | 4 ++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/2021-python/solutions/day_06.py b/2021-python/solutions/day_06.py index d8d160c..31f2ea2 100644 --- a/2021-python/solutions/day_06.py +++ b/2021-python/solutions/day_06.py @@ -1,4 +1,5 @@ from solutions import BaseSolution +from collections import Counter, deque class Solution(BaseSolution): @@ -17,12 +18,15 @@ class Solution(BaseSolution): return self._produce(puzzle_input, 256) def _produce(self, puzzle_input, tf): - f = [*puzzle_input] - for _ in range(tf): - n = [8 for __ in range(sum(x <= 0 and x % 7 == 0 for x in f))] - f = list(map(lambda x: x - 1, f)) - f.extend(n) - return len(f) + f = Counter(puzzle_input) + s = deque([0] * 9) + for i in range(9): + s[i] = f[i] + for _d in range(tf): + e = s.popleft() + s[6] += e + s.append(e) + return sum(s) if __name__ == "__main__": diff --git a/2021-python/tests/test_day_06.py b/2021-python/tests/test_day_06.py index 7b4fd3d..d6417cb 100644 --- a/2021-python/tests/test_day_06.py +++ b/2021-python/tests/test_day_06.py @@ -21,8 +21,8 @@ class Day06TestCase(unittest.TestCase): def test_solve_first_part(self): assert self.solution.solve(self.puzzle_input) == 5934 - # def test_solve_second_part(self): - # assert self.solution.solve_again(self.puzzle_input) == 26984457539 + def test_solve_second_part(self): + assert self.solution.solve_again(self.puzzle_input) == 26984457539 if __name__ == "__main__":