Add solution to day 10 part 2
parent
0bd38fd546
commit
4a62f46f28
|
@ -1,12 +1,14 @@
|
|||
import sys
|
||||
import copy
|
||||
import fractions
|
||||
import math
|
||||
from typing import Tuple, Set
|
||||
from typing import List, Iterable, Tuple
|
||||
|
||||
|
||||
ASTEROID_CHAR = '#'
|
||||
SPACE_CHAR = '.'
|
||||
|
||||
|
||||
# Get the coordinates of all items in a line
|
||||
def get_items_in_slope(space_map: Tuple[Tuple[str]], start_row: int, start_col: int, rise: int, run: int) -> Tuple[Tuple[int, int]]:
|
||||
row = start_row
|
||||
col = start_col
|
||||
|
@ -24,24 +26,34 @@ def get_items_in_slope(space_map: Tuple[Tuple[str]], start_row: int, start_col:
|
|||
return tuple(items)
|
||||
|
||||
|
||||
def search_for_visible_in_all_directions(space_map: Tuple[Tuple[str, ...]], start_row: int, start_col: int, rise: int, run: int) -> Set[Tuple[int, int]]:
|
||||
asteroids = set()
|
||||
# Spread a set of coordinates into all quadrants (e.g. (1,1) will produce (1,1) (1,-1) (-1,1) (-1,-1)
|
||||
def put_items_in_all_quadrants(items: Iterable[Tuple[int, int]]) -> List[Tuple[int, int]]:
|
||||
mirrored = set()
|
||||
for item in items:
|
||||
for i in (-1, 1):
|
||||
for j in (-1, 1):
|
||||
line_items = get_items_in_slope(space_map, start_row, start_col, i * rise, j * run)
|
||||
for row, col in line_items:
|
||||
if space_map[row][col] == ASTEROID_CHAR:
|
||||
asteroids.add((row, col))
|
||||
break
|
||||
mirrored.add((i * item[0], j * item[1]))
|
||||
|
||||
return asteroids
|
||||
return list(mirrored)
|
||||
|
||||
|
||||
def get_visible_asteroid_count(space_map: Tuple[Tuple[str, ...]], start_row: int, start_col: int) -> int:
|
||||
asteroids = set()
|
||||
# Start a list of slopes clockwise, starting at pi/2
|
||||
def sort_slopes_clockwise(items: List[Tuple[int, int]]) -> List[Tuple[int, int]]:
|
||||
# math.atan(-x[0]/abs(x[0])) allows us to get the correct positive/negative infinity for arctan
|
||||
# (remember, our coords are flipped from a standard coordinate plane)
|
||||
sort_key = lambda x: math.atan(-x[0]/abs(x[0]) * math.inf if x[1] == 0 else -x[0]/x[1])
|
||||
quad_1_3_items = sorted((item for item in items if item[1] >= 0), reverse=True, key=sort_key)
|
||||
quad_2_4_items = sorted((item for item in items if item[1] < 0), reverse=True, key=sort_key)
|
||||
|
||||
return quad_1_3_items + quad_2_4_items
|
||||
|
||||
|
||||
def generate_all_slopes(height: int, width: int) -> Tuple[Tuple[int, int]]:
|
||||
# Get all slopes in the first quadrant
|
||||
quad_slopes = set()
|
||||
used_slopes = set()
|
||||
for rise in range(len(space_map)):
|
||||
for run in range(len(space_map[0])):
|
||||
for rise in range(height):
|
||||
for run in range(width):
|
||||
effective_rise, effective_run = rise, run
|
||||
if rise == 0 and run == 0:
|
||||
continue
|
||||
|
@ -55,23 +67,58 @@ def get_visible_asteroid_count(space_map: Tuple[Tuple[str, ...]], start_row: int
|
|||
continue
|
||||
|
||||
used_slopes.add(slope_fraction)
|
||||
found_asteroids = search_for_visible_in_all_directions(space_map, start_row, start_col, effective_rise, effective_run)
|
||||
asteroids = asteroids.union(found_asteroids)
|
||||
quad_slopes.add((effective_rise, effective_run))
|
||||
|
||||
return len(asteroids)
|
||||
# Spread them across the rest of the coordinates
|
||||
slopes = put_items_in_all_quadrants(quad_slopes)
|
||||
|
||||
return sort_slopes_clockwise(slopes)
|
||||
|
||||
|
||||
def part1(space_map: Tuple[Tuple[int, ...]]):
|
||||
# Get all asteroids that are visible from a certian point, marking whether to destroy them on scan or not
|
||||
def get_visible_asteroids(space_map: List[List[int]], start_row: int, start_col: int, destroy=False) -> List[Tuple[int, int]]:
|
||||
asteroids = []
|
||||
slopes = generate_all_slopes(len(space_map), len(space_map[0]))
|
||||
for slope in slopes:
|
||||
rise, run = slope
|
||||
line_items = get_items_in_slope(space_map, start_row, start_col, rise, run)
|
||||
for row, col in line_items:
|
||||
if space_map[row][col] == ASTEROID_CHAR:
|
||||
if destroy:
|
||||
space_map[row][col] = SPACE_CHAR
|
||||
if (row, col) not in asteroids:
|
||||
asteroids.append((row, col))
|
||||
break
|
||||
|
||||
return asteroids
|
||||
|
||||
|
||||
def part1(input_space_map: List[List[str]]) -> Tuple[Tuple[int, int], int]:
|
||||
space_map = copy.deepcopy(input_space_map)
|
||||
best_count = None
|
||||
best_pos = None
|
||||
for i, row in enumerate(space_map):
|
||||
for j, item in enumerate(row):
|
||||
if item != ASTEROID_CHAR:
|
||||
continue
|
||||
visible_asteroid_count = get_visible_asteroid_count(space_map, i, j)
|
||||
visible_asteroid_count = len(get_visible_asteroids(space_map, i, j))
|
||||
if best_count is None or visible_asteroid_count > best_count:
|
||||
best_pos = (i, j)
|
||||
best_count = visible_asteroid_count
|
||||
|
||||
return best_count
|
||||
return best_pos, best_count
|
||||
|
||||
|
||||
def part2(input_space_map: Tuple[Tuple[str, ...]], station_pos: Tuple[int, int]) -> int:
|
||||
space_map = copy.deepcopy(input_space_map)
|
||||
destroyed_asteroids = []
|
||||
station_row, station_col = station_pos
|
||||
while len(destroyed_asteroids) < 200:
|
||||
destroyed_asteroids += get_visible_asteroids(space_map, station_row, station_col, True)
|
||||
|
||||
asteroid = destroyed_asteroids[199]
|
||||
|
||||
return asteroid[1] * 100 + asteroid[0]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -80,10 +127,11 @@ if __name__ == "__main__":
|
|||
sys.exit(1)
|
||||
|
||||
with open(sys.argv[1]) as f:
|
||||
inputs = tuple(tuple(line.strip()) for line in f.readlines())
|
||||
inputs = [list(line.strip()) for line in f.readlines()]
|
||||
|
||||
for row in inputs:
|
||||
print(''.join(row))
|
||||
|
||||
# print(get_visible_asteroid_count(inputs, 0, 1))
|
||||
print(part1(inputs))
|
||||
best_pos, best_count = part1(inputs)
|
||||
print(best_count)
|
||||
print(part2(inputs, best_pos))
|
||||
|
|
Loading…
Reference in New Issue