advent_of_code_2021

My attempts to work through the 2021 Advent of Code problems.
git clone https://git.eamoncaddigan.net/advent_of_code_2021.git
Log | Files | Refs | README | LICENSE

day06_part1.py (3787B)


      1 #!/usr/bin/env python
      2 """Advent of Code 2021, day 6 (part 1): simulate an exponentially growing
      3 school of lanternfish using an initial list of fish and their ages"""
      4 
      5 # For this puzzle, the most important decision is the data structure to use to
      6 # represent the school of fish. Since there's exponential growth, any scheme
      7 # that uses an element for each fish could quickly get out of hand. Since the
      8 # set of possible ages is quite small, it's much more efficient to use counters
      9 # for each age. I'm using a pandas Series because of course I am (but also, I'm
     10 # confronting my aversion to pandas index attributes head on), but a simple
     11 # dict would work fine here too, or even a list if you're careful with updating
     12 # indices.
     13 
     14 import functools
     15 from utils import get_puzzle_input, convert_int_line_to_series
     16 
     17 def convert_input_to_series(input_string):
     18     """Parse the input (a sequence of fish with their age in days) and return a
     19     Series, where the indices represent fish ages (in days) and the values
     20     represent the count of fish that age"""
     21     return convert_int_line_to_series(input_string).value_counts()
     22 
     23 # For this implementation, we'll advance the fish in age and spawn new fish all
     24 # in one step--even though, conceptually, it would be nice to separate these
     25 # steps--because that way it'll be easier to separate the fish that are ready
     26 # to spawn from the babies who aren't.
     27 
     28 def step_one_day(fish_counts):
     29     """Given a Series of fish counts, simulate one day of ageing/growth of the
     30     school, and return a new Series"""
     31     # Once again, I'm trying to program more functionally, which means I'm
     32     # treating data structures as immutable (outside the function) even when it
     33     # might be more efficient to update things in-place. Theoretically we're
     34     # trading efficiency for "safety", although I'm perfectly aware that for
     35     # these puzzles it really doesn't matter. But inside the actual function, I
     36     # am editing things in-place, so that's a bit of a fail.
     37     # Implementation note: I looked at a couple methods of updating the axis
     38     # and was surprised by the results.
     39     # %timeit fish_counts.set_axis((fish_counts.index - 1) % 9)
     40     #> 305 µs ± 6.05 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
     41     # %timeit fish_counts.rename(lambda x: (x - 1) % 9)
     42     #> 93.1 µs ± 2.19 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
     43     updated_fish_counts = fish_counts.rename(lambda x: (x - 1) % 9)
     44     # We're cheating a bit here. Rather than resetting an adult's counter back
     45     # to 6 and then adding babies with a counter set to 8, we're resetting
     46     # adults' counters to 8 (representing the babies) and then adding the
     47     # adults back into the count. That's why we take the counter value modulo 9
     48     # instead of the obvious 7.
     49     if 8 in updated_fish_counts:
     50         if 6 in updated_fish_counts:
     51             updated_fish_counts[6] += updated_fish_counts[8]
     52         else:
     53             updated_fish_counts[6] = updated_fish_counts[8]
     54     return updated_fish_counts
     55 
     56 def count_fish_after(fish_counts, num_days):
     57     """Given a Series of fish counts and a number of days, return the total
     58     number of fish in the school as an integer"""
     59     return int(
     60         functools.reduce(lambda x, y: step_one_day(x),
     61                          range(num_days),
     62                          fish_counts)
     63         .sum()
     64     )
     65 
     66 def solve_puzzle(input_string):
     67     """Return the numeric solution to the puzzle"""
     68     return count_fish_after(convert_input_to_series(input_string), 80)
     69 
     70 def main():
     71     """Run when the file is called as a script"""
     72     assert solve_puzzle("3,4,3,1,2\n") == 5934
     73     print("Number of lanternfish after 80 days:",
     74           solve_puzzle(get_puzzle_input(6)))
     75 
     76 if __name__ == "__main__":
     77     main()