<reponame>techartorg/Advent_of_code_2021
r'''
--- Day 6: Lanternfish ---
The sea floor is getting steeper. Maybe the sleigh keys got carried this way?

A massive school of glowing lanternfish swims past. They must spawn quickly to 
reach such large numbers - maybe exponentially quickly? You should model their 
growth rate to be sure.

Although you know nothing about this specific species of lanternfish, you make 
some guesses about their attributes. Surely, each lanternfish creates a new 
lanternfish once every 7 days.

However, this process isn't necessarily synchronized between every lanternfish
 - one lanternfish might have 2 days left until it creates another lanternfish, 
 while another might have 4. So, you can model each fish as a single number 
 that represents the number of days until it creates a new lanternfish.

Furthermore, you reason, a new lanternfish would surely need slightly longer 
before it's capable of producing more lanternfish: two more days for its first 
cycle.

So, suppose you have a lanternfish with an internal timer value of 3:

    - After one day, its internal timer would become 2.
    - After another day, its internal timer would become 1.
    - After another day, its internal timer would become 0.
    - After another day, its internal timer would reset to 6, and it would 
      create a new lanternfish with an internal timer of 8.
    - After another day, the first lanternfish would have an internal timer of 
      5, and the second lanternfish would have an internal timer of 7.

A lanternfish that creates a new fish resets its timer to 6, not 7 (because 0 
is included as a valid timer value). The new lanternfish starts with an 
internal timer of 8 and does not start counting down until the next day.

Realizing what you're trying to do, the submarine automatically produces a 
list of the ages of several hundred nearby lanternfish (your puzzle input). 
For example, suppose you were given the following list:

    3,4,3,1,2

This list means that the first fish has an internal timer of 3, the second 
fish has an internal timer of 4, and so on until the fifth fish, which has an 
internal timer of 2. Simulating these fish over several days would proceed as 
follows:

    Initial state: 3,4,3,1,2
    After  1 day:  2,3,2,0,1
    After  2 days: 1,2,1,6,0,8
    After  3 days: 0,1,0,5,6,7,8
    After  4 days: 6,0,6,4,5,6,7,8,8
    After  5 days: 5,6,5,3,4,5,6,7,7,8
    After  6 days: 4,5,4,2,3,4,5,6,6,7
    After  7 days: 3,4,3,1,2,3,4,5,5,6
    After  8 days: 2,3,2,0,1,2,3,4,4,5
    After  9 days: 1,2,1,6,0,1,2,3,3,4,8
    After 10 days: 0,1,0,5,6,0,1,2,2,3,7,8
    After 11 days: 6,0,6,4,5,6,0,1,1,2,6,7,8,8,8
    After 12 days: 5,6,5,3,4,5,6,0,0,1,5,6,7,7,7,8,8
    After 13 days: 4,5,4,2,3,4,5,6,6,0,4,5,6,6,6,7,7,8,8
    After 14 days: 3,4,3,1,2,3,4,5,5,6,3,4,5,5,5,6,6,7,7,8
    After 15 days: 2,3,2,0,1,2,3,4,4,5,2,3,4,4,4,5,5,6,6,7
    After 16 days: 1,2,1,6,0,1,2,3,3,4,1,2,3,3,3,4,4,5,5,6,8
    After 17 days: 0,1,0,5,6,0,1,2,2,3,0,1,2,2,2,3,3,4,4,5,7,8
    After 18 days: 6,0,6,4,5,6,0,1,1,2,6,0,1,1,1,2,2,3,3,4,6,7,8,8,8,8

Each day, a 0 becomes a 6 and adds a new 8 to the end of the list, while each 
other number decreases by 1 if it was present at the start of the day.

In this example, after 18 days, there are a total of 26 fish. After 80 days, 
there would be a total of 5934.

Find a way to simulate lanternfish. How many lanternfish would there be after 
80 days?

--- Part Two ---
Suppose the lanternfish live forever and have unlimited food and space. Would 
they take over the entire ocean?

After 256 days in the example above, there would be a total of 26984457539 
lanternfish!

How many lanternfish would there be after 256 days?

'''

from collections import defaultdict

test_input = r'''3,4,3,1,2'''
puzzle_input = open(__file__.replace('.py', '_input.txt')).read()


# Part One - The Naive Approached worked ok, but did not work for Part Two...
class Fish:
    def __init__(self, timer=None):
        if timer:
            self.timer = timer
        else:
            self.timer = 9
    
    def evaluate(self):
        countdown = self.timer - 1
        if countdown < 0:
            self.timer = 6
            return [Fish()]
        else:
            self.timer = countdown
        return []


def parse_input(input_):
    initial_population = []
    for x in input_.split(','):
        initial_population.append(Fish(timer=int(x)))
    return initial_population


def run(initial_population, length):
    day = 1
    end = length
    fish_population = list(initial_population)
    while day <= end:
        for f in fish_population:
            fish_population.extend(f.evaluate())
        day += 1

    return len(fish_population)


# Part Two - Having fun with dictionaries...
def part2(input_, days):
    fish_pop = defaultdict(int)
    for i in range(10):
        fish_pop[i] = 0
    
    for x in input_.split(','):
        fish_pop[int(x)] += 1
    
    for i in range(1, days+1):
        for timer, count in fish_pop.items():
            if count > 0:
                newtimer = timer - 1
                if newtimer < 0:
                    fish_pop[9] += 1 * count
                    fish_pop[7] += 1 * count
                    fish_pop[0] -= 1 * count
                else:
                    fish_pop[timer] -= 1 * count
                    fish_pop[newtimer] += 1 * count
    
    return get_population_count(fish_pop)


def get_population_count(pop_dict):
    return sum(v for v in pop_dict.values())



assert (run(parse_input(test_input), 18)) == 26
assert (run(parse_input(test_input), 80)) == 5934

assert (part2(test_input, 18)) == 26
assert (part2(test_input, 80)) == 5934
assert (part2(test_input, 256)) == 26984457539

#print(f'Part One (slow): {run(parse_input(puzzle_input), 80)}')
print(f'Part One: {part2(puzzle_input, 80)}')
print(f'Part Two: {part2(puzzle_input, 256)}')

r'''
--- Day 6: Lanternfish ---
The sea floor is getting steeper. Maybe the sleigh keys got carried this way?

A massive school of glowing lanternfish swims past. They must spawn quickly to 
reach such large numbers - maybe exponentially quickly? You should model their 
growth rate to be sure.

Although you know nothing about this specific species of lanternfish, you make 
some guesses about their attributes. Surely, each lanternfish creates a new 
lanternfish once every 7 days.

However, this process isn't necessarily synchronized between every lanternfish
 - one lanternfish might have 2 days left until it creates another lanternfish, 
 while another might have 4. So, you can model each fish as a single number 
 that represents the number of days until it creates a new lanternfish.

Furthermore, you reason, a new lanternfish would surely need slightly longer 
before it's capable of producing more lanternfish: two more days for its first 
cycle.

So, suppose you have a lanternfish with an internal timer value of 3:

    - After one day, its internal timer would become 2.
    - After another day, its internal timer would become 1.
    - After another day, its internal timer would become 0.
    - After another day, its internal timer would reset to 6, and it would 
      create a new lanternfish with an internal timer of 8.
    - After another day, the first lanternfish would have an internal timer of 
      5, and the second lanternfish would have an internal timer of 7.

A lanternfish that creates a new fish resets its timer to 6, not 7 (because 0 
is included as a valid timer value). The new lanternfish starts with an 
internal timer of 8 and does not start counting down until the next day.

Realizing what you're trying to do, the submarine automatically produces a 
list of the ages of several hundred nearby lanternfish (your puzzle input). 
For example, suppose you were given the following list:

    3,4,3,1,2

This list means that the first fish has an internal timer of 3, the second 
fish has an internal timer of 4, and so on until the fifth fish, which has an 
internal timer of 2. Simulating these fish over several days would proceed as 
follows:

    Initial state: 3,4,3,1,2
    After  1 day:  2,3,2,0,1
    After  2 days: 1,2,1,6,0,8
    After  3 days: 0,1,0,5,6,7,8
    After  4 days: 6,0,6,4,5,6,7,8,8
    After  5 days: 5,6,5,3,4,5,6,7,7,8
    After  6 days: 4,5,4,2,3,4,5,6,6,7
    After  7 days: 3,4,3,1,2,3,4,5,5,6
    After  8 days: 2,3,2,0,1,2,3,4,4,5
    After  9 days: 1,2,1,6,0,1,2,3,3,4,8
    After 10 days: 0,1,0,5,6,0,1,2,2,3,7,8
    After 11 days: 6,0,6,4,5,6,0,1,1,2,6,7,8,8,8
    After 12 days: 5,6,5,3,4,5,6,0,0,1,5,6,7,7,7,8,8
    After 13 days: 4,5,4,2,3,4,5,6,6,0,4,5,6,6,6,7,7,8,8
    After 14 days: 3,4,3,1,2,3,4,5,5,6,3,4,5,5,5,6,6,7,7,8
    After 15 days: 2,3,2,0,1,2,3,4,4,5,2,3,4,4,4,5,5,6,6,7
    After 16 days: 1,2,1,6,0,1,2,3,3,4,1,2,3,3,3,4,4,5,5,6,8
    After 17 days: 0,1,0,5,6,0,1,2,2,3,0,1,2,2,2,3,3,4,4,5,7,8
    After 18 days: 6,0,6,4,5,6,0,1,1,2,6,0,1,1,1,2,2,3,3,4,6,7,8,8,8,8

Each day, a 0 becomes a 6 and adds a new 8 to the end of the list, while each 
other number decreases by 1 if it was present at the start of the day.

In this example, after 18 days, there are a total of 26 fish. After 80 days, 
there would be a total of 5934.

Find a way to simulate lanternfish. How many lanternfish would there be after 
80 days?

--- Part Two ---
Suppose the lanternfish live forever and have unlimited food and space. Would 
they take over the entire ocean?

After 256 days in the example above, there would be a total of 26984457539 
lanternfish!

How many lanternfish would there be after 256 days?

'''

from collections import defaultdict

test_input = r'''3,4,3,1,2'''
puzzle_input = open(__file__.replace('.py', '_input.txt')).read()


# Part One - The Naive Approached worked ok, but did not work for Part Two...
class Fish:
    def __init__(self, timer=None):
        if timer:
            self.timer = timer
        else:
            self.timer = 9
    
    def evaluate(self):
        countdown = self.timer - 1
        if countdown < 0:
            self.timer = 6
            return [Fish()]
        else:
            self.timer = countdown
        return []


def parse_input(input_):
    initial_population = []
    for x in input_.split(','):
        initial_population.append(Fish(timer=int(x)))
    return initial_population


def run(initial_population, length):
    day = 1
    end = length
    fish_population = list(initial_population)
    while day <= end:
        for f in fish_population:
            fish_population.extend(f.evaluate())
        day += 1

    return len(fish_population)


# Part Two - Having fun with dictionaries...
def part2(input_, days):
    fish_pop = defaultdict(int)
    for i in range(10):
        fish_pop[i] = 0
    
    for x in input_.split(','):
        fish_pop[int(x)] += 1
    
    for i in range(1, days+1):
        for timer, count in fish_pop.items():
            if count > 0:
                newtimer = timer - 1
                if newtimer < 0:
                    fish_pop[9] += 1 * count
                    fish_pop[7] += 1 * count
                    fish_pop[0] -= 1 * count
                else:
                    fish_pop[timer] -= 1 * count
                    fish_pop[newtimer] += 1 * count
    
    return get_population_count(fish_pop)


def get_population_count(pop_dict):
    return sum(v for v in pop_dict.values())



assert (run(parse_input(test_input), 18)) == 26
assert (run(parse_input(test_input), 80)) == 5934

assert (part2(test_input, 18)) == 26
assert (part2(test_input, 80)) == 5934
assert (part2(test_input, 256)) == 26984457539

#print(f'Part One (slow): {run(parse_input(puzzle_input), 80)}')
print(f'Part One: {part2(puzzle_input, 80)}')
print(f'Part Two: {part2(puzzle_input, 256)}')
--- Day 6: Lanternfish --- The sea floor is getting steeper. Maybe the sleigh keys got carried this way?  A massive school of glowing lanternfish swims past. They must spawn quickly to  reach such large numbers - maybe exponentially quickly? You should model their  growth rate to be sure.  Although you know nothing about this specific species of lanternfish, you make  some guesses about their attributes. Surely, each lanternfish creates a new  lanternfish once every 7 days.  However, this process isn't necessarily synchronized between every lanternfish  - one lanternfish might have 2 days left until it creates another lanternfish,   while another might have 4. So, you can model each fish as a single number   that represents the number of days until it creates a new lanternfish.  Furthermore, you reason, a new lanternfish would surely need slightly longer  before it's capable of producing more lanternfish: two more days for its first  cycle.  So, suppose you have a lanternfish with an internal timer value of 3:      - After one day, its internal timer would become 2.     - After another day, its internal timer would become 1.     - After another day, its internal timer would become 0.     - After another day, its internal timer would reset to 6, and it would        create a new lanternfish with an internal timer of 8.     - After another day, the first lanternfish would have an internal timer of        5, and the second lanternfish would have an internal timer of 7.  A lanternfish that creates a new fish resets its timer to 6, not 7 (because 0  is included as a valid timer value). The new lanternfish starts with an  internal timer of 8 and does not start counting down until the next day.  Realizing what you're trying to do, the submarine automatically produces a  list of the ages of several hundred nearby lanternfish (your puzzle input).  For example, suppose you were given the following list:      3,4,3,1,2  This list means that the first fish has an internal timer of 3, the second  fish has an internal timer of 4, and so on until the fifth fish, which has an  internal timer of 2. Simulating these fish over several days would proceed as  follows:      Initial state: 3,4,3,1,2     After  1 day:  2,3,2,0,1     After  2 days: 1,2,1,6,0,8     After  3 days: 0,1,0,5,6,7,8     After  4 days: 6,0,6,4,5,6,7,8,8     After  5 days: 5,6,5,3,4,5,6,7,7,8     After  6 days: 4,5,4,2,3,4,5,6,6,7     After  7 days: 3,4,3,1,2,3,4,5,5,6     After  8 days: 2,3,2,0,1,2,3,4,4,5     After  9 days: 1,2,1,6,0,1,2,3,3,4,8     After 10 days: 0,1,0,5,6,0,1,2,2,3,7,8     After 11 days: 6,0,6,4,5,6,0,1,1,2,6,7,8,8,8     After 12 days: 5,6,5,3,4,5,6,0,0,1,5,6,7,7,7,8,8     After 13 days: 4,5,4,2,3,4,5,6,6,0,4,5,6,6,6,7,7,8,8     After 14 days: 3,4,3,1,2,3,4,5,5,6,3,4,5,5,5,6,6,7,7,8     After 15 days: 2,3,2,0,1,2,3,4,4,5,2,3,4,4,4,5,5,6,6,7     After 16 days: 1,2,1,6,0,1,2,3,3,4,1,2,3,3,3,4,4,5,5,6,8     After 17 days: 0,1,0,5,6,0,1,2,2,3,0,1,2,2,2,3,3,4,4,5,7,8     After 18 days: 6,0,6,4,5,6,0,1,1,2,6,0,1,1,1,2,2,3,3,4,6,7,8,8,8,8  Each day, a 0 becomes a 6 and adds a new 8 to the end of the list, while each  other number decreases by 1 if it was present at the start of the day.  In this example, after 18 days, there are a total of 26 fish. After 80 days,  there would be a total of 5934.  Find a way to simulate lanternfish. How many lanternfish would there be after  80 days?  --- Part Two --- Suppose the lanternfish live forever and have unlimited food and space. Would  they take over the entire ocean?  After 256 days in the example above, there would be a total of 26984457539  lanternfish!  How many lanternfish would there be after 256 days? 3,4,3,1,2 # Part One - The Naive Approached worked ok, but did not work for Part Two... # Part Two - Having fun with dictionaries... #print(f'Part One (slow): {run(parse_input(puzzle_input), 80)}')