Ryan is an engineer in the Sacramento Area with a focus in Python, Ruby, and Rust. Bash/Python Exercism mentor. Coding, physics, calculus, music, woodworking. Looking for work!
Things have settled down a little bit since Christmas, so I've got some time to actually complete AOC2019. Better 4 months late than never! Here's my solution to Day 10. Had some trouble deciding how to handle vertical slopes gracefully, and switching back and forth from global to relative coordinates, but finally got it handled.
"""Figure out the best asteroid to look at other asteroids. And then BLAST THEM!"""fromdataclassesimportdataclassfrommathimportgcdfromtypingimportList,Set@dataclass(frozen=True)classPoint:"""An X, Y coordinate on a grid"""x:inty:intdefparse(text:str)->Set[Point]:"""Expects a 2D grid of characters. '#' denote asteroids.
Note: Here, Y+ is down.
"""asteroids=set()rows=text.splitlines()fory,rowinenumerate(rows):forx,cellinenumerate(row):ifcell=="#":asteroids.add(Point(x,y))returnasteroidsdeffind_best(asteroids:Set[Point])->(Point,int):"""Finds the asteroid that can see the most other asteroids.
Returns that asteroid and how many others it can see.
"""scores=[(a,count_visible(a,asteroids))forainasteroids]returnmax(scores,key=lambdax:x[1])defcount_visible(base:Point,asteroids:Set[Point])->int:"""Counts how many asteroids are in line of sight."""returnsum(1forasteroidinasteroidsifcan_see(base,asteroid,asteroids))defcan_see(a:Point,b:Point,asteroids:Set[Point])->bool:"""Determines whether or not an asteroid can see another in an asteroid field."""# Asteroids can't see themselves.
ifa==b:returnFalsedx=b.x-a.xdy=b.y-a.ydivisor=gcd(dy,dx)ifdivisor!=0:# Line isn't vertical or horizontal. Get basic slope.
dx//=divisordy//=divisorelse:# Line is vertical or horizontal. Divisor becomes simply
# the magnitude of the non-zero component.
divisor=max(dx,dy)# Step over every cell on the line between a and b and see if
# it's an asteroid. If any of them are, then b can't be seen from a.
forstepinrange(1,divisor):ifPoint(a.x+step*dx,a.y+step*dy)inasteroids:returnFalsereturnTrue@dataclass(order=True)classLaserPriority:"""A helper class for prioritizing which asteroids get blasted first.
Quadrant assumes X+ is right and Y+ is up, with base at the origin.
Slope uses Y/X, but gets multiplied by -1 so that Laser Priorities
get sorted based on quadrant and then slope in descending order.
"""quadrant:intslope:floatdeflaser_priority_sort_key(asteroid:Point)->LaserPriority:"""Prioritize asteroids based on how they clock around the origin.
Starting at Y+ (up) and going CW.
This function assumes all asteroids being compared are visible.
"""result=LaserPriority(0,0)# Simulate vertical slope as ">> size of grid"
# because ~~~InFiNiTy Is HaRd FoR cOmPuTeRs~~~
ifasteroid.x==0:result.slope=1000else:result.slope=asteroid.y/asteroid.xresult.slope*=-1# Flip so sort goes largest to smallest
ifasteroid.x>=0andasteroid.y>0:result.quadrant=1elifasteroid.x>0andasteroid.y<=0:result.quadrant=2elifasteroid.x<=0andasteroid.y<0:result.quadrant=3else:result.quadrant=4returnresultdefcreate_laser_plan(base:Point,asteroids:Set[Point])->List[Point]:"""Specify the order in which asteroids get BLASTED, assuming we start
at 12 o'clock and go CW, blasting only one asteroid at a time for a
particular clocking before moving on.
"""asteroids=asteroids-{base}# Convert everything so that Y+ is up and base is at 0, 0
relative_asteroids={Point(a.x-base.x,-(a.y-base.y))forainasteroids}plan=[]# Blast asteroids in rounds, removing the ones that get blasted
# after every cycle
whilerelative_asteroids:visible={aforainrelative_asteroidsifcan_see(Point(0,0),a,relative_asteroids)}plan+=sorted(visible,key=laser_priority_sort_key)relative_asteroids-=visible# Unconvert back to global coordinates with Y+ down
plan=[Point(base.x+a.x,base.y-a.y)forainplan]returnplanif__name__=="__main__":withopen("data/day10.txt")asf:grid=f.read()asteroids=parse(grid)best,score=find_best(asteroids)print(f"Best is {best} with {score} visible.")plan=create_laser_plan(best,asteroids)answer=plan[199]# O-based counting!!!
print(f"Lucky number 200 is {answer}.")print(f"Magic number is {answer.x * 100 + answer.y}.")
For further actions, you may consider blocking this person and/or reporting abuse
We're a place where coders share, stay up-to-date and grow their careers.
Things have settled down a little bit since Christmas, so I've got some time to actually complete AOC2019. Better 4 months late than never! Here's my solution to Day 10. Had some trouble deciding how to handle vertical slopes gracefully, and switching back and forth from global to relative coordinates, but finally got it handled.