#!/usr/bin/env python # coding: utf-8 # [Advent Of Code](http://adventofcode.com/) [2016](http://adventofcode.com/2016/) [Day 1 Part 1: No Time for a Taxicab](http://adventofcode.com/2016/day/1) # # - Cell #1 has decent, straightforward, readable code. # - Cell #2 uses vector math with numpy to be more readable. # - Cell #5 is more readable yet, # but requires understanding of complex numbers. # - Cell #9 uses namedtuples. # - Cell #10 uses a class based on namedtuples. # - Cell #11 uses a classes based on complex numbers. # - Cells #6 through #8 explore functional approaches, # descending into obfuscation. # Definitions: # # - [course](https://en.wikipedia.org/wiki/Course_(navigation%29): the direction one is going # - deflection: how much one turns right or left # In[1]: #!/usr/bin/env python3 '''First, a very straightforward readable solution. Coordinates are in tuples.''' from math import sin, cos, radians deflection = { # Unit of values is one degree clockwise. 'R': +90, 'L': -90, } NORTH = 0 def manhattan_distance(*points): return sum( abs(coordinate1 - coordinate2) for coordinate1, coordinate2 in zip(*points)) def follow_route( moves=None, # iterable of [RL] strings # Unit of distance is 1 block. starting_course=None, # Unit is 1 degree clockwise. 0 is North. starting_location=(0., 0.), # (east, north) Unit is 1 block for each coordinate. ): course = starting_course location = starting_location for turn_code, *distance_chars in moves: course += deflection[turn_code] distance = float(''.join(distance_chars)) location = tuple( coordinate + distance * func(radians(course)) for coordinate, func in zip(location, (sin, cos))) return location def process_route(raw_instructions): instructions = (s.strip() for s in raw_instructions.split(',')) starting_location = (0, 0) destination = follow_route( moves=instructions, starting_course=NORTH, starting_location=starting_location, ) distance = round(manhattan_distance(destination, starting_location)) print('Easter Bunny HQ is {distance} blocks away.'.format( distance=distance)) def main(): raw_routes = ( ('20161202-aoc-day1part1-test', 'R5, L5, R5, R3'), # from https://github.com/hakim89/adventofcode/raw/master/day01/part-01.py ('20161202-aoc-day1part1-long', ''' R4, R5, L5, L5, L3, R2, R3, R2, L1, R5, R1, L5, R2, L2, L4, R3, L1, R4, L5, R4, R3, L5, L3, R4, R2, L5, L5, R2, R3, R5, R4, R2, R1, L1, L5, L2, L3, L4, L5, L4, L5, L1, R3, R4, R5, R3, L5, L4, L3, L1, L4, R2, R5, R5, R4, L2, L4, R3, R1, L2, R5, L5, R1, R1, L1, L5, L5, L2, L1, R5, R2, L4, L1, R4, R3, L3, R1, R5, L1, L4, R2, L3, R5, R3, R1, L3'''), ) for filename, raw_instructions in raw_routes: with open(filename, 'w') as f: f.write(raw_instructions) for filename, _ in raw_routes: with open(filename) as f: raw_instructions = f.read() process_route(raw_instructions) if __name__ == '__main__': main() # In[2]: #!/usr/bin/env python3 'Use vector math with numpy.' from math import sin, cos, radians from numpy import array deflection = { # Unit of values is one degree clockwise. 'R': +90, 'L': -90, } NORTH = 0 def manhattan_distance(vector): return sum(map(abs, vector)) def follow_route( moves=None, # iterable of [RL] strings # Unit of distance is 1 block. starting_course=None, # Unit is 1 degree clockwise. 0 is North. starting_location=array([0., 0.]), # [east, north] Unit is 1 block for each coordinate. ): course = starting_course location = starting_location.copy() for turn_code, *distance_chars in moves: course += deflection[turn_code] distance = float(''.join(distance_chars)) v = array([func(radians(course)) for func in (sin, cos)]) location += distance * v return location def process_route(raw_instructions): instructions = (s.strip() for s in raw_instructions.split(',')) starting_location=array([0., 0.]) destination = follow_route( moves=instructions, starting_course=NORTH, starting_location=starting_location, ) distance = round(manhattan_distance(destination - starting_location)) print('Easter Bunny HQ is {distance} blocks away.'.format( distance=distance)) def main(): route_filenames = ( '20161202-aoc-day1part1-test', '20161202-aoc-day1part1-long', ) for filename in route_filenames: with open(filename) as f: raw_instructions = f.read() process_route(raw_instructions) if __name__ == '__main__': main() # In[3]: import cmath from math import radians # In[4]: cmath.rect(10., radians(30)) # In[5]: #!/usr/bin/env python3 '''Use complex numbers to simplify code. The location calculation is particularly nice.''' from math import radians import cmath deflection = { # Unit of values is one degree clockwise. 'R': +90, 'L': -90, } NORTH = 0 def manhattan_distance(vector): return sum(map(abs, (vector.real, vector.imag))) def follow_route( moves=None, # iterable of [RL] strings # Unit of distance is 1 block. starting_course=None, # Unit is 1 degree clockwise. 0 is North. starting_location=0+0j, # north + east j Unit is 1 block. ): course = starting_course location = starting_location for turn_code, *distance_chars in moves: course += deflection[turn_code] distance = float(''.join(distance_chars)) location += cmath.rect(distance, radians(course)) return location def process_route(raw_instructions): instructions = (s.strip() for s in raw_instructions.split(',')) starting_location=0.+0.j destination = follow_route( moves=instructions, starting_course=NORTH, starting_location=starting_location, ) distance = round(manhattan_distance(destination - starting_location)) print('Easter Bunny HQ is {distance} blocks away.'.format( distance=distance)) def main(): route_filenames = ( '20161202-aoc-day1part1-test', '20161202-aoc-day1part1-long', ) for filename in route_filenames: with open(filename) as f: raw_instructions = f.read() process_route(raw_instructions) if __name__ == '__main__': main() # In[6]: #!/usr/bin/env python3 '''Use functional approach to obfuscate the code. Look ma, no for loops.''' from math import radians import cmath from itertools import accumulate, starmap, chain, islice NORTH = 0 def manhattan_distance(vector): return sum(map(abs, (vector.real, vector.imag))) def deflection(move): deflection_of_turn = { # Unit of values is one degree clockwise. 'R': +90, 'L': -90, } turn_code = move[:1] return deflection_of_turn[turn_code] def distance(move): distance = move[1:] return float(distance) def follow_route( moves=None, # sequence of [RL] strings # Unit of distance is 1 block. starting_course=None, # Unit is 1 degree clockwise. 0 is North. starting_location=0+0j, # north + east j Unit is 1 block. ): distances = map(distance, moves) deflections = map(deflection, moves) courses = islice(accumulate(chain( [starting_course], deflections)), 1, None) courses = map(radians, courses) legs = starmap(cmath.rect, zip(distances, courses)) return starting_location + sum(legs) def process_route(raw_instructions): instructions = tuple(map( lambda s: s.strip(), raw_instructions.split(','))) starting_location=0+0j destination = follow_route( moves=instructions, starting_course=NORTH, starting_location=starting_location, ) distance = round(manhattan_distance(destination - starting_location)) print('Easter Bunny HQ is {distance} blocks away.'.format( distance=distance)) def main(): route_filenames = ( '20161202-aoc-day1part1-test', '20161202-aoc-day1part1-long', ) list(map( process_route, map(lambda f: f.read(), map(open, route_filenames)))) if __name__ == '__main__': main() # In[7]: #!/usr/bin/env python3 '''Continue functional approach to obfuscate the code. This time with no temporary variables in follow_route().''' from math import radians import cmath from itertools import accumulate, starmap, chain, islice NORTH = 0 def manhattan_distance(vector): return sum(map(abs, (vector.real, vector.imag))) def deflection(move): deflection_of_turn = { # Unit of values is one degree clockwise. 'R': +90, 'L': -90, } turn_code = move[:1] return deflection_of_turn[turn_code] def distance(move): distance = move[1:] return float(distance) def follow_route( moves=None, # sequence of [RL] strings # Unit of distance is 1 block. starting_course=None, # Unit is 1 degree clockwise. 0 is North. starting_location=0+0j, # north + east j Unit is 1 block. ): return starting_location + sum( starmap( cmath.rect, zip( map(distance, moves), map(radians, islice( accumulate(chain( [starting_course], map(deflection, moves))), 1, None))))) def process_route(raw_instructions): instructions = tuple(map( lambda s: s.strip(), raw_instructions.split(','))) starting_location=0+0j destination = follow_route( moves=instructions, starting_course=NORTH, starting_location=starting_location, ) distance = round(manhattan_distance(destination - starting_location)) print('Easter Bunny HQ is {distance} blocks away.'.format( distance=distance)) def main(): route_filenames = ( '20161202-aoc-day1part1-test', '20161202-aoc-day1part1-long', ) list(map( process_route, map(lambda f: f.read(), map(open, route_filenames)))) if __name__ == '__main__': main() # In[8]: #!/usr/bin/env python3 'Use lambdas for more obfuscation.' from math import radians import cmath from itertools import accumulate, starmap, chain, islice NORTH = 0 def manhattan_distance(vector): return sum(map(abs, (vector.real, vector.imag))) deflection = dict(map( lambda x: (x[0][0], x[1]), zip( ('RIGHT', 'LEFT'), (+90, -90) # Unit of values is one degree clockwise. ) )) def follow_route( moves=None, # sequence of [RL] strings # Unit of distance is 1 block. starting_course=None, # Unit is 1 degree clockwise. 0 is North. starting_location=0+0j, # north + east j Unit is 1 block. ): return starting_location + sum(starmap( cmath.rect, zip( map(lambda move: float(''.join(move[1:])), moves), map( radians, islice( accumulate(chain( [starting_course], map(lambda move: deflection[move[:1]], moves) )), 1, None))))) def process_route(raw_instructions): instructions = tuple(map( lambda s: s.strip(), raw_instructions.split(','))) starting_location=0.+0.j destination = follow_route( moves=instructions, starting_course=NORTH, starting_location=starting_location, ) distance = round(manhattan_distance(destination - starting_location)) print('Easter Bunny HQ is {distance} blocks away.'.format( distance=distance)) def main(): route_filenames = ( '20161202-aoc-day1part1-test', '20161202-aoc-day1part1-long', ) list(map( process_route, map(lambda f: f.read(), map(open, route_filenames)))) if __name__ == '__main__': main() # --- # Back to trying to write clean code. Explores using namedtuple and classes based on namedtuples and complex numbers. # In[9]: #!/usr/bin/env python3 'Use namedtuple.' from math import sin, cos, radians from collections import namedtuple deflection = { # Unit of values is one degree clockwise. 'R': +90, 'L': -90, } NORTH = 0 Point = namedtuple('Point', ['East', 'North']) def manhattan_distance(*points): return sum( abs(coordinate1 - coordinate2) for coordinate1, coordinate2 in zip(*points)) def follow_route( moves=None, # iterable of [RL] strings # Unit of distance is 1 block. starting_course=None, # Unit is 1 degree clockwise. 0 is North. starting_location=Point(0., 0.), # Unit is 1 block for each coordinate. ): course = starting_course location = starting_location for turn_code, *distance_chars in moves: course += deflection[turn_code] distance = float(''.join(distance_chars)) location = Point(*( coordinate + distance * func(radians(course)) for coordinate, func in zip(location, (sin, cos)))) return location def process_route(raw_instructions): instructions = (s.strip() for s in raw_instructions.split(',')) starting_location = Point(0., .0) destination = follow_route( moves=instructions, starting_course=NORTH, starting_location=starting_location, ) distance = round(manhattan_distance(destination, starting_location)) print('Easter Bunny HQ is {distance} blocks away.'.format( distance=distance)) def main(): route_filenames = ( '20161202-aoc-day1part1-test', '20161202-aoc-day1part1-long', ) for filename in route_filenames: with open(filename) as f: raw_instructions = f.read() process_route(raw_instructions) if __name__ == '__main__': main() # In[10]: #!/usr/bin/env python3 'Use class based on namedtuple.' from math import sin, cos, radians from collections import namedtuple deflection = { # Unit of values is one degree clockwise. 'R': +90, 'L': -90, } NORTH = 0 class Point(object): Point = namedtuple('Point', ['North', 'East']) def __init__(self, north, east): self.point = self.Point(North=north, East=east) def __len__(self): return len(self.point) def __getitem__(self, i): return self.point[i] def __str__(self): return '{p.North}N, {p.East}E'.format(p=self.point) def __repr__(self): return '{p.North} North, {p.East} East'.format(p=self.point) def __add__(self, value): return Point(*( c1 + c2 for c1, c2 in zip(self.point, value.point))) def __sub__(self, value): return Point(*( c1 - c2 for c1, c2 in zip(self.point, value.point))) def __round__(self, ndigits=0): return Point(*( round(coordinate, ndigits=ndigits) for coordinate in self.point )) def __abs__(self): 'manhattan distance' return sum(map(abs, self.point)) def follow_route( moves=None, # iterable of [RL] strings # Unit of distance is 1 block. starting_course=None, # Unit is 1 degree clockwise. 0 is North. starting_location=Point(0.,0.), # Unit is 1 block for each coordinate. ): course = starting_course location = starting_location for turn_code, *distance_chars in moves: course += deflection[turn_code] distance = float(''.join(distance_chars)) location = Point(*( coordinate + distance * func(radians(course)) for coordinate, func in zip(location, (cos, sin)))) return location def process_route(raw_instructions): instructions = (s.strip() for s in raw_instructions.split(',')) starting_location = Point(0., .0) destination = follow_route( moves=instructions, starting_course=NORTH, starting_location=starting_location, ) print('Easter Bunny HQ is {distance} blocks away.'.format( distance=round(abs(destination - starting_location)))) def main(): route_filenames = ( '20161202-aoc-day1part1-test', '20161202-aoc-day1part1-long', ) for filename in route_filenames: with open(filename) as f: raw_instructions = f.read() process_route(raw_instructions) if __name__ == '__main__': main() # In[11]: #!/usr/bin/env python3 'Use classes based on complex numbers.' from math import sin, cos, radians import cmath deflection = { # Unit of values is one degree clockwise. 'R': +90, 'L': -90, } NORTH = 0 class Point(object): def __init__(self, north=None, east=None): self.point = north + east*1j def __str__(self): return '{p.real}N {p.imag}E'.format(p=self.point) def __repr__(self): return '{p.real} North, {p.imag} East'.format(p=self.point) def __mul__(self, value): p = self.point * value return Point(p.real, p.imag) def __rmul__(self, value): return self.__mul__(value) def __add__(self, value): p = self.point + value.point return Point(p.real, p.imag) def __sub__(self, value): p = self.point - value.point return Point(p.real, p.imag) def __round__(self, ndigits=0): return Point( round(self.point.real, ndigits=ndigits), round(self.point.imag, ndigits=ndigits), ) def __abs__(self): 'manhattan distance' return sum(map(abs, (self.point.real, self.point.imag))) class Vector(Point): def __init__(self, length=1., azimuth=0.): self.point = cmath.rect(length, azimuth) def follow_route( moves=None, # iterable of [RL] strings # Unit of distance is 1 block. starting_course=None, # Unit is 1 degree clockwise. 0 is North. starting_location=Point(0.,0.), # Unit is 1 block for each coordinate. ): course = starting_course location = starting_location for turn_code, *distance_chars in moves: course += deflection[turn_code] distance = float(''.join(distance_chars)) location += distance * Vector(azimuth=radians(course)) # The following works also. # location += Vector(length=distance, azimuth=radians(course)) return location def process_route(raw_instructions): instructions = (s.strip() for s in raw_instructions.split(',')) starting_location = Point(0., .0) destination = follow_route( moves=instructions, starting_course=NORTH, starting_location=starting_location, ) print('Easter Bunny HQ is {distance} blocks away.'.format( distance=round(abs(destination - starting_location)))) def main(): route_filenames = ( '20161202-aoc-day1part1-test', '20161202-aoc-day1part1-long', ) for filename in route_filenames: with open(filename) as f: raw_instructions = f.read() process_route(raw_instructions) if __name__ == '__main__': main()