Advertisement
alexandrajay2002

Advent of Code 2024 day 14 part 2

Dec 14th, 2024
69
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 3.71 KB | Source Code | 0 0
  1. from argparse import ArgumentParser, FileType
  2.  
  3.  
  4. def parse_number(line, index):
  5.     '''Parse the (potentially negative) number starting at line[index] and the
  6.       first index after it.'''
  7.     if line[index] == '-':
  8.         acc = '-'
  9.         index += 1
  10.     else:
  11.         acc = ''
  12.  
  13.     while index < len(line) and line[index].isdigit():
  14.         acc += line[index]
  15.         index += 1
  16.  
  17.     return int(acc), index
  18.  
  19.  
  20. def parse_pair(line, index):
  21.     '''Parse a pair of numbers separated by a single character and the first
  22.       index after them.'''
  23.     x, index = parse_number(line, index)
  24.     y, index = parse_number(line, index + 1)  # skip the comma
  25.  
  26.     return (x, y), index
  27.  
  28.  
  29. def parse_line(line):
  30.     '''Parse a line of input into the start position and velocity of a
  31.       robot.'''
  32.     # skip past 'p=' and parse the start coords
  33.     start, index = parse_pair(line, 2)
  34.  
  35.     # skip past ' v=' and parse the velocity
  36.     velocity, _ = parse_pair(line, index + 3)
  37.  
  38.     return start, velocity
  39.  
  40.  
  41. def parse(src):
  42.     '''Parse the input into a list of robots.'''
  43.     return [parse_line(line) for line in src.split('\n') if line != '']
  44.  
  45.  
  46. def position(x0, y0, dx, dy, width, height, time):
  47.     '''Return the position of a robot in the grid at the given time.'''
  48.     return (x0 + dx * time) % width, (y0 + dy * time) % height
  49.  
  50.  
  51. def expand_blob(todo):
  52.     '''The size of one contiguous group of pixels in todo.'''
  53.     frontier = [todo.pop()]
  54.     size = 1
  55.     while len(frontier) > 0:
  56.         x, y = frontier.pop()
  57.         for dx, dy in ((0, -1), (1, 0), (0, 1), (-1, 0)):
  58.             neighbour = (x + dx, y + dy)
  59.             if neighbour in todo:
  60.                 frontier.append(neighbour)
  61.                 todo.remove(neighbour)
  62.                 size += 1
  63.  
  64.     return size
  65.  
  66.  
  67. def find_blob(coords):
  68.     '''The size of the largest contiguous group of pixels in coords.'''
  69.     todo = set(coords)
  70.     largest = 0
  71.     while len(todo) > 0:
  72.         largest = max(largest, expand_blob(todo))
  73.  
  74.     # if the blob is larger than the threshold, it might be a christmas tree...
  75.     return largest >= 50
  76.  
  77.  
  78. def write_image(coords, width, height, time):
  79.     '''Write the grid to a file as a ppm image, points in coords will be white
  80.       and the rest black.'''
  81.     with open(f'{time}.ppm', 'w') as f:
  82.         print(f'P3\n{width} {height}\n255', file=f)
  83.  
  84.         for y in range(height):
  85.             for x in range(width):
  86.                 val = '255' if (x, y) in coords else '0'
  87.                 print(val, val, val, file=f)
  88.  
  89.  
  90. def main(robots, width, height, time, max_candidates):
  91.     '''find potential christmas trees, write them to disk as images, and return
  92.       the time they appear.'''
  93.     candidates = list()
  94.     for gen in range(time):
  95.         if candidates == max_candidates:
  96.             break
  97.  
  98.         coords = {position(*start, *velocity, width, height, gen)
  99.                   for start, velocity in robots}
  100.  
  101.         # a christmas tree is basically a big blob
  102.         if find_blob(coords):
  103.             # if candidate, write to file as ppm image
  104.             write_image(coords, width, height, gen)
  105.             candidates.append(gen)
  106.  
  107.     return candidates
  108.  
  109.  
  110. parser = ArgumentParser()
  111. parser.add_argument('src', type=FileType('r'))
  112. parser.add_argument('max_candidates', nargs='?', type=int, default=50)
  113. parser.add_argument('width', nargs='?', type=int, default=101)
  114. parser.add_argument('height', nargs='?', type=int, default=103)
  115. parser.add_argument('time', nargs='?', type=int, default=10000)
  116.  
  117. if __name__ == '__main__':
  118.     args = parser.parse_args()
  119.     robots = parse(args.src.read())
  120.     print(
  121.         *main(robots, args.width, args.height, args.time, args.max_candidates)
  122.     )
  123.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement