Day 15: Timing is Everything

author: Harshvardhan Pandit

license: MIT

link to problem statement

The halls open into an interior plaza containing a large kinetic sculpture. The sculpture is in a sealed enclosure and seems to involve a set of identical spherical capsules that are carried to the top and allowed to bounce through the maze of spinning pieces.

Part of the sculpture is even interactive! When a button is pressed, a capsule is dropped and tries to fall through slots in a set of rotating discs to finally go through a little hole at the bottom and come out of the sculpture. If any of the slots aren't aligned with the capsule as it passes, the capsule bounces off the disc and soars away. You feel compelled to get one of those capsules.

The discs pause their motion each second and come in different sizes; they seem to each have a fixed number of positions at which they stop. You decide to call the position with the slot 0, and count up for each position it reaches next.

Furthermore, the discs are spaced out so that after you push the button, one second elapses before the first disc is reached, and one second elapses as the capsule passes from one disk to the one below it. So, if you push the button at time=100, then the capsule reaches the top disc at time=101, the second disc at time=102, the third disc at time=103, and so on.

The button will only drop a capsule at an integer time - no fractional seconds allowed.

For example, at time=0, suppose you see the following arrangement:

Disc #1 has 5 positions; at time=0, it is at position 4.
Disc #2 has 2 positions; at time=0, it is at position 1.

If you press the button exactly at time=0, the capsule would start to fall; it would reach the first disc at time=1. Since the first disc was at position 4 at time=0, by time=1 it has ticked one position forward. As a five-position disc, the next position is 0, and the capsule falls through the slot.

Then, at time=2, the capsule reaches the second disc. The second disc has ticked forward two positions at this point: it started at position 1, then continued to position 0, and finally ended up at position 1 again. Because there's only a slot at position 0, the capsule bounces away.

If, however, you wait until time=5 to push the button, then when the capsule reaches each disc, the first disc will have ticked forward 5+1 = 6 times (to position 0), and the second disc will have ticked forward 5+2 = 7 times (also to position 0). In this case, the capsule would fall through the discs and come out of the machine.

However, your situation has more than two discs; you've noted their positions in your puzzle input. What is the first time you can press the button to get a capsule?

Solution logic

The discs are rotating around with their holes having position 0. The capsule will fall through each of these holes to come out. The capsule takes 1 time-unit each to reach the disc. So we need to calculate the position of the disc relative to the position of the capsule.

The ideal or end state is when the coin falls through. This will happen only when it encounters position 0 for each disc it falls through. As each disc is rotating, we need to wrap around after the last position back to 0. We do this using mod

disc position = (current position + 1) % total disc positions

Releasing the capsule at time x, the capsule will reach disc 1 at x + 1, disc 2 at x + 2 and so on. We then need to check if the position of the disc is 0 when the capsule reaches it. We only need one disc position to be invalid or non-zero to move on to the next iteration.

We declare a class Disc to hold the its total positions, and its current position. The total positions are immutable as they do not change.


In [1]:
class Disc:
    def __init__(self, total_positions, starting_position):
        self._total_positions = total_positions
        self.current_position = starting_position
    
    @property
    def total_positions(self):
        return self._total_positions
    
    def __repr__(self):
        return 'total: {} current: {}'.format(self.total_positions, self.current_position)

We declare a list to hold the discs according to the specified order.


In [2]:
discs = []

Reading the input and creating discs


In [3]:
from copy import deepcopy
import re
numbers = re.compile('(\d+)')

with open('../inputs/day15.txt', 'r') as f:
    for line in f.readlines():
        disc_no, total_positions, time, starting_position =\
            map(int, numbers.findall(line))
        discs.append(Disc(total_positions, starting_position))
    # preserve input data
    input_data = deepcopy(discs)

In [4]:
for index, disc in enumerate(discs):
    print('disc#', index, disc)


disc# 0 total: 13 current: 10
disc# 1 total: 17 current: 15
disc# 2 total: 19 current: 17
disc# 3 total: 7 current: 1
disc# 4 total: 5 current: 0
disc# 5 total: 3 current: 1

We now iterate through each time unit to find when the capsule can be dropped through all discs. Ideally, there should not be an infinite loop, but if required, just set it to some VERY high index so that it will terminate at some point.


In [5]:
def run():
    for time in range(0, 10000000):
        if not any((
                (discs[i].current_position + 1 + i) % discs[i].total_positions
                for i in range(0, len(discs)))):
            return time
            break
        for disc in discs:
            disc.current_position = (disc.current_position + 1) % disc.total_positions

In [6]:
print('answer', run())


answer 203660

Part Two

After getting the first capsule (it contained a star! what great fortune!), the machine detects your success and begins to rearrange itself.

When it's done, the discs are back in their original configuration as if it were time=0 again, but a new disc with 11 positions and starting at position 0 has appeared exactly one second below the previously-bottom disc.

With this new disc, and counting again starting from time=0 with the configuration in your puzzle input, what is the first time you can press the button to get another capsule?

Solution logic

Since there is no change in logic, we simply add the new disc to the disc list and run the algorithm again.


In [7]:
discs = input_data
discs.append(Disc(11, 0))

In [8]:
print('asnwer', run())


asnwer 2408135

== END ==