Advertisement
Guest User

AOC 2021 Day 5

a guest
Dec 8th, 2021
483
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 4.55 KB | None | 0 0
  1. # aoc202105.py
  2.  
  3. import pathlib
  4. import re
  5. import sys
  6. import numpy as np
  7. from dataclasses import dataclass
  8.  
  9.  
  10. @dataclass
  11. class Point:
  12.     x: int
  13.     y: int
  14.  
  15.  
  16. @dataclass
  17. class Vent:
  18.     start: Point
  19.     end: Point
  20.  
  21.  
  22. def intify(list_of_strings: list[str]) -> list[int]:
  23.     """ Convert a list of strings into one of ints """
  24.     return [int(c) for c in list_of_strings]
  25.  
  26.  
  27. def arrange_coords(x1: int, y1: int, x2: int, y2: int) -> tuple[int]:
  28.     """ Make sure coordinates go from L -> R and U -> D """
  29.     if x1 > x2:
  30.         x1, y1, x2, y2 = x2, y2, x1, y1
  31.     elif x1 == x2 and y1 > y2:
  32.         y1, y2 = y2, y1
  33.     return x1, y1, x2, y2
  34.  
  35.  
  36. def parse(puzzle_input: str) -> list[int]:
  37.     """ Parse input """
  38.     data = []
  39.     lines = puzzle_input.splitlines()
  40.     pattern = re.compile(r'(\d+),(\d+) -> (\d+),(\d+)')
  41.     for line in lines:
  42.         match = re.search(pattern, line)
  43.         nums = intify(match.groups())
  44.         x1, y1, x2, y2 = arrange_coords(*nums)
  45.         start, end = Point(x1, y1), Point(x2, y2)
  46.         data.append(Vent(start, end))
  47.     return data
  48.  
  49.  
  50. def get_straights(lines: list['Vent']) -> list['Vent']:
  51.     """ Return a list that meets the critieria in spec:
  52.        namely start.x == end.x or start.y == end.y """
  53.     straights = []
  54.     for line in lines:
  55.         if any([line.start.x == line.end.x, line.start.y == line.end.y]):
  56.             straights.append(line)
  57.     return straights
  58.  
  59.  
  60. def is_diagonal(x1: int, y1: int, x2: int, y2: int) -> bool:
  61.     """ Return true if line is diagonal """
  62.     return any([x1 + y2 == y1 + x2, x1 + x2 == y1 + y2, x1 + y1 == x2 + y2])
  63.  
  64.  
  65. def get_diagonals(lines: list['Vent']) -> list['Vent']:
  66.     """ get 45 degree diagonal vents """
  67.     diagonals = []
  68.     for line in lines:
  69.         if is_diagonal(line.start.x, line.start.y, line.end.x, line.end.y):
  70.             diagonals.append(line)
  71.     return diagonals
  72.  
  73.  
  74. def make_grid(lines: list['Vent']) -> 'np.ndarray[int]':
  75.     """ Get top left and bottom right points """
  76.     x_values, y_values = [], []
  77.     for line in lines:
  78.         x_coord = [line.start.x, line.end.x]
  79.         y_coord = [line.start.y, line.end.y]
  80.         x_values.extend(x_coord)
  81.         y_values.extend(y_coord)
  82.     return np.zeros((max(y_values) + 1, max(x_values) + 1), dtype=int)
  83.  
  84.  
  85. def mark_grid(grid: 'np.ndarray[int]', start: int, end: int, r: int,
  86.               c: int) -> None:
  87.     """ Update an array along rows or columns by one """
  88.     for i in range(start, end):
  89.         if r is not None and c is not None:
  90.             # A hack to cancel out the negative value
  91.             grid[abs(r)][c] += 1
  92.             r += 1
  93.             c += 1
  94.         elif r is not None and c is None:
  95.             grid[r][i] += 1
  96.         elif c is not None and r is None:
  97.             grid[i][c] += 1
  98.  
  99.  
  100. def make_paths(grid: 'np.ndarray[int]', v: 'Vent') -> None:
  101.     """ Set up paths from vents """
  102.     # For diagonals
  103.     if is_diagonal(v.start.x, v.start.y, v.end.x, v.end.y):
  104.         initial_point = v.start.x
  105.         end_point = v.end.x + 1
  106.         row = v.start.y if v.start.y < v.end.y else -v.start.y
  107.         col = v.start.x
  108.     # For horizontals
  109.     elif v.start.y == v.end.y:
  110.         initial_point = v.start.x
  111.         end_point = v.end.x + 1
  112.         row, col = v.start.y, None
  113.     # For verticals
  114.     elif v.start.x == v.end.x:
  115.         initial_point = v.start.y
  116.         end_point = v.end.y + 1
  117.         row, col = None, v.start.x
  118.     mark_grid(grid, initial_point, end_point, row, col)
  119.  
  120.  
  121. def part1(data: list[int]) -> int:
  122.     """ Solve part 1 """
  123.     eligible_vents = get_straights(data)
  124.     grid = make_grid(eligible_vents)
  125.     for vent in eligible_vents:
  126.         make_paths(grid, vent)
  127.     return np.count_nonzero(grid > 1)
  128.  
  129.  
  130. def part2(data: list[int]) -> int:
  131.     """ Solve part 2 """
  132.     eligible_vents = get_straights(data) + get_diagonals(data)
  133.     grid = make_grid(eligible_vents)
  134.     for vent in eligible_vents:
  135.         make_paths(grid, vent)
  136.     return np.count_nonzero(grid > 1)
  137.  
  138.  
  139. def solve(puzzle_input: str) -> tuple[int, int]:
  140.     """ Solve the puzzle for the given input """
  141.     data = parse(puzzle_input)
  142.     solution1 = part1(data)  # Correct answer was 7085 (with my data)
  143.     solution2 = part2(data)  # Correct answer was 20271 (with my data)
  144.  
  145.     return solution1, solution2
  146.  
  147.  
  148. if __name__ == "__main__":
  149.     for path in sys.argv[1:]:
  150.         print(f"{path}:")
  151.         puzzle_input = pathlib.Path(path).read_text().strip()
  152.         solutions = solve(puzzle_input)
  153.         print('\n'.join(str(solution) for solution in solutions))
  154.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement