Solution to problem 15 in Python

This commit is contained in:
David Doblas Jiménez 2023-09-11 19:35:05 +02:00
parent 9eeba7ff77
commit c88edc6f06
4 changed files with 283 additions and 0 deletions

105
src/Year_2016/P15.py Normal file
View File

@ -0,0 +1,105 @@
# --- Day 15: Timing is Everything ---
# 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 disc 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?
import re
with open("files/P15.txt") as f:
arrangements = [
tuple(map(int, re.findall("\d+", line))) for line in f.readlines()
]
def part_1():
tik = 0
while True:
for i, (_, positions, _, position) in enumerate(arrangements, start=1):
if not (position + tik + i) % positions:
continue
break
else:
print(tik)
break
tik += 1
# --- 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?
def part_2():
arrangements.append((1, 11, 0, 0))
tik = 0
while True:
for i, (_, positions, _, position) in enumerate(arrangements, start=1):
if not (position + tik + i) % positions:
continue
break
else:
print(tik)
break
tik += 1
if __name__ == "__main__":
part_1()
part_2()

View File

@ -0,0 +1,6 @@
Disc #1 has 7 positions; at time=0, it is at position 0.
Disc #2 has 13 positions; at time=0, it is at position 0.
Disc #3 has 3 positions; at time=0, it is at position 2.
Disc #4 has 5 positions; at time=0, it is at position 2.
Disc #5 has 17 positions; at time=0, it is at position 0.
Disc #6 has 19 positions; at time=0, it is at position 7.

170
src/Year_2017/P15.py Normal file
View File

@ -0,0 +1,170 @@
# --- Day 15: Dueling Generators ---
# Here, you encounter a pair of dueling generators. The generators, called
# generator A and generator B, are trying to agree on a sequence of numbers.
# However, one of them is malfunctioning, and so the sequences don't always
# match.
# As they do this, a judge waits for each of them to generate its next value,
# compares the lowest 16 bits of both values, and keeps track of the number of
# times those parts of the values match.
# The generators both work on the same principle. To create its next value, a
# generator will take the previous value it produced, multiply it by a factor
# (generator A uses 16807; generator B uses 48271), and then keep the remainder
# of dividing that resulting product by 2147483647. That final remainder is the
# value it produces next.
# To calculate each generator's first value, it instead uses a specific starting
# value as its "previous value" (as listed in your puzzle input).
# For example, suppose that for starting values, generator A uses 65, while
# generator B uses 8921. Then, the first five pairs of generated values are:
# --Gen. A-- --Gen. B--
# 1092455 430625591
# 1181022009 1233683848
# 245556042 1431495498
# 1744312007 137874439
# 1352636452 285222916
# In binary, these pairs are (with generator A's value first in each pair):
# 00000000000100001010101101100111
# 00011001101010101101001100110111
# 01000110011001001111011100111001
# 01001001100010001000010110001000
# 00001110101000101110001101001010
# 01010101010100101110001101001010
# 01100111111110000001011011000111
# 00001000001101111100110000000111
# 01010000100111111001100000100100
# 00010001000000000010100000000100
# Here, you can see that the lowest (here, rightmost) 16 bits of the third value
# match: 1110001101001010. Because of this one match, after processing these
# five pairs, the judge would have added only 1 to its total.
# To get a significant sample, the judge would like to consider 40 million
# pairs. (In the example above, the judge would eventually find a total of 588
# pairs that match in their lowest 16 bits.)
# After 40 million pairs, what is the judge's final count?
import re
from copy import copy
with open("files/P15.txt") as f:
gens = list(map(int, re.findall("\d+", f.read())))
def part_1():
a, b = copy(gens)
npairs = 0
for _ in range(40000000):
a = (a * 16807) % 2147483647
b = (b * 48271) % 2147483647
if a & 0xFFFF == b & 0xFFFF:
npairs += 1
print(f"The judge final count is {npairs}")
# --- Part Two ---
# In the interest of trying to align a little better, the generators get more
# picky about the numbers they actually give to the judge.
# They still generate values in the same way, but now they only hand a value to
# the judge when it meets their criteria:
# Generator A looks for values that are multiples of 4.
# Generator B looks for values that are multiples of 8.
# Each generator functions completely independently: they both go through values
# entirely on their own, only occasionally handing an acceptable value to the
# judge, and otherwise working through the same sequence of values as before
# until they find one.
# The judge still waits for each generator to provide it with a value before
# comparing them (using the same comparison method as before). It keeps track of
# the order it receives values; the first values from each generator are
# compared, then the second values from each generator, then the third values,
# and so on.
# Using the example starting values given above, the generators now produce the
# following first five values each:
# --Gen. A-- --Gen. B--
# 1352636452 1233683848
# 1992081072 862516352
# 530830436 1159784568
# 1980017072 1616057672
# 740335192 412269392
# These values have the following corresponding binary values:
# 01010000100111111001100000100100
# 01001001100010001000010110001000
# 01110110101111001011111010110000
# 00110011011010001111010010000000
# 00011111101000111101010001100100
# 01000101001000001110100001111000
# 01110110000001001010100110110000
# 01100000010100110001010101001000
# 00101100001000001001111001011000
# 00011000100100101011101101010000
# Unfortunately, even though this change makes more bits similar on average,
# none of these values' lowest 16 bits match. Now, it's not until the 1056th
# pair that the judge finds the first match:
# --Gen. A-- --Gen. B--
# 1023762912 896885216
# 00111101000001010110000111100000
# 00110101011101010110000111100000
# This change makes the generators much slower, and the judge is getting
# impatient; it is now only willing to consider 5 million pairs. (Using the
# values from the example above, after five million pairs, the judge would
# eventually find a total of 309 pairs that match in their lowest 16 bits.)
# After 5 million pairs, but using this new generator logic, what is the judge's
# final count?
def part_2():
a, b = copy(gens)
npairs = 0
for _ in range(5000000):
while True:
a = (a * 16807) % 2147483647
if a % 4 == 0:
break
while True:
b = (b * 48271) % 2147483647
if b % 8 == 0:
break
if a & 0xFFFF == b & 0xFFFF:
npairs += 1
print(f"The judge final count is {npairs}")
if __name__ == "__main__":
part_1()
part_2()

View File

@ -0,0 +1,2 @@
Generator A starts with 618
Generator B starts with 814