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

day05_part1.py (3743B)


      1 #!/usr/bin/env python
      2 """Advent of Code 2021, day 5 (part 1): the hydrothermal vents. Given the start
      3 and end coordinates of lines of vents, get information about the field."""
      4 
      5 # For Part 1, we don't need to operate on any 2D representation of the field
      6 # directly, so I won't bother building one. Again I'm sticking to pandas and
      7 # doing as much as I can with DFs.
      8 
      9 import numpy as np
     10 import pandas as pd
     11 from utils import get_puzzle_input
     12 
     13 def convert_input_to_df(input_string):
     14     """The input is a series of start and ending coordinates. Parse these and
     15     return a df with the columns `x1`, `y1`, `x2`, `y2`"""
     16     return (
     17         pd.Series([x for x in input_string.split('\n') if x])
     18         .str.split(',| -> ', n=4, expand=True)
     19         .rename(columns={0: 'x1', 1: 'y1', 2: 'x2', 3: 'y2'})
     20         .astype(np.int32)
     21     )
     22 
     23 def find_points_x(x_1, y_1, x_2, y_2):
     24     """Given a line segment's start and end coordinates, return a numby array
     25     of x coordinates for the points along segment"""
     26     # This is easy because we're only dealing with horizontal and vertical
     27     # lines (for now)
     28     if x_1 == x_2:
     29         return np.repeat(x_1, abs(y_2 - y_1) + 1)
     30     return np.arange(min(x_1, x_2), max(x_1, x_2)+1)
     31 
     32 def find_points_y(x_1, y_1, x_2, y_2):
     33     """Given a line segment's start and end coordinates, return a numby array
     34     of x coordinates for the points along segment"""
     35     return find_points_x(x_1=y_1, y_1=x_1, x_2=y_2, y_2=x_2)
     36 
     37 def find_line_points(endpoints_df):
     38     """Given a df with the start and end coordinates of the line segments, add
     39     new columns, `x` and `y`, that give the coordinates of all points covered
     40     by the line (note that line data will be dropped but indices are
     41     retained)"""
     42     # In Part 1 (I sense that Part 2 will be different), we only transform the
     43     # horizontal and verticl line segments. We'll remove the diagonal line
     44     # segments from the df.
     45     return (
     46         endpoints_df
     47         .loc[(endpoints_df['x1'] == endpoints_df['x2']) |
     48              (endpoints_df['y1'] == endpoints_df['y2'])]
     49         .assign(x=lambda df: [find_points_x(*x[1].to_list()) \
     50                               for x in df[['x1', 'y1', 'x2', 'y2']].iterrows()],
     51                 y=lambda df: [find_points_y(*x[1].to_list()) \
     52                               for x in df[['x1', 'y1', 'x2', 'y2']].iterrows()])
     53         .explode(['x', 'y'])
     54         .drop(columns=['x1', 'y1', 'x2', 'y2'])
     55     )
     56 
     57 def count_point_coverage(points_df):
     58     """Given a df with the `x` and `y` coordinates of all covered points, find
     59     out how many times each point is covered (among points covered at least
     60     once)"""
     61     return (
     62         points_df
     63         .groupby(['x', 'y']).size()
     64         .reset_index()
     65         .rename(columns={0: 'n'})
     66     )
     67 
     68 def count_danger_points(coverage_df):
     69     """Given a df with the total coverage for each coordinate, count the number
     70     of points with a coverage > 2"""
     71     return len(coverage_df.loc[coverage_df['n'] > 1])
     72 
     73 def solve_puzzle(input_string):
     74     """Return the numeric solution to the puzzle"""
     75     return count_danger_points(
     76         count_point_coverage(
     77             find_line_points(
     78                 convert_input_to_df(input_string)
     79             )
     80         )
     81     )
     82 
     83 if __name__ == "__main__":
     84     assert solve_puzzle(
     85         "\n".join(("0,9 -> 5,9",
     86                    "8,0 -> 0,8",
     87                    "9,4 -> 3,4",
     88                    "2,2 -> 2,1",
     89                    "7,0 -> 7,4",
     90                    "6,4 -> 2,0",
     91                    "0,9 -> 2,9",
     92                    "3,4 -> 1,4",
     93                    "0,0 -> 8,8",
     94                    "5,5 -> 8,2",
     95                    "\n"))) == 5
     96 
     97     print("Number of points to avoid:",
     98           solve_puzzle(get_puzzle_input(5)))