advent-of-code-2019/day10.py

150 lines
3.3 KiB
Python

#!/usr/bin/env python3
from math import gcd
from fractions import Fraction
from itertools import product
from functools import total_ordering
from collections import defaultdict
ASTEROID = '#'
def zrange(x, y, step):
if x == y == step == 0:
def yzero():
while True:
yield 0
return yzero()
return range(x, y, step)
def angle(v):
if v.x == 0:
return (2 * int(v.y > 0), 0)
halve = 1 if v.x > 0 else 3
return (halve, Fraction(v.y, v.x))
def rebase(base, vectors):
return (v - base for v in vectors if v != base)
@total_ordering
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return self + Vector(-other.x, -other.y)
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __lt__(self, other):
return (self.x**2 + self.y**2) < (other.x**2 + other.y**2)
def __str__(self):
return str((self.x, self.y))
def __repr__(self):
return str(self)
class Map:
def __init__(self, map):
self.map = map
def __getitem__(self, v):
return self.map[v.y][v.x]
def __str__(self):
return '\n'.join(self.map)
def is_asteroid(self, v):
return self[v] == ASTEROID
def vectors(self):
X, Y = len(self.map[0]), len(self.map)
return (Vector(x, y) for x, y in product(range(X), range(Y)))
def asteroid_vectors(self):
return filter(lambda v: self.is_asteroid(v), self.vectors())
def num_in_sight(self, v):
vs = rebase(v, self.asteroid_vectors())
return len(set(map(angle, vs)))
def best(self):
return max((self.num_in_sight(v), v) for v in self.asteroid_vectors())
def vaporize_seq(self, base):
vs = rebase(base, self.asteroid_vectors())
angles = defaultdict(list)
for v in vs:
angles[angle(v)].append(v)
for ang in angles.keys():
angles[ang].sort()
r = []
for i in range(max(map(len, angles.values()))):
r += [angles[ang][i] + base
for ang in sorted(angles.keys()) if i < len(angles[ang])]
return r
def preproc(puzzle_input):
asteroid_map = Map(puzzle_input.split())
score, asteroid = asteroid_map.best()
return asteroid_map, asteroid, score
def partI(packed):
_, _, score = packed
return score
def partII(packed):
asteroid_map, asteroid, _ = packed
v = asteroid_map.vaporize_seq(asteroid)[199]
return 100*v.x + v.y
from main import Tests
tests = Tests()
map1 = \
""".#..#
.....
#####
....#
...##"""
map2 = \
"""#.#...#.#.
.###....#.
.#....#...
##.#.#.#.#
....#.#.#.
.##..###.#
..#...##..
..##....##
......#...
.####.###.
"""
map3 = \
""".#..##.###...#######
##.############..##.
.#.######.########.#
.###.#######.####.#.
#####.##.#.##.###.##
..#####..#.#########
####################
#.####....###.#.#.##
##.#################
#####.##.###..####..
..######..##.#######
####.##.####...##..#
.#####..#.######.###
##...#.##########...
#.##########.#######
.####.#.###.###.#.##
....##.##.###..#####
.#.#.###########.###
#.#.#.#####.####.###
###.##.####.##.#..##
"""
tests.add(map1, partI=8)
tests.add(map2, partI=35)
tests.add(map3, partI=210, partII=802)