Advent Of Code 2016 Day 1 Part 1: No Time for a Taxicab

  • 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: 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]<distance> 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()


Easter Bunny HQ is 12 blocks away.
Easter Bunny HQ is 44 blocks away.

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]<distance> 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()


Easter Bunny HQ is 12.0 blocks away.
Easter Bunny HQ is 44.0 blocks away.

In [3]:
import cmath
from math import radians

In [4]:
cmath.rect(10., radians(30))


Out[4]:
(8.660254037844387+4.999999999999999j)

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]<distance> 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()


Easter Bunny HQ is 12 blocks away.
Easter Bunny HQ is 44 blocks away.

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]<distance> 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()


Easter Bunny HQ is 12 blocks away.
Easter Bunny HQ is 44 blocks away.

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]<distance> 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()


Easter Bunny HQ is 12 blocks away.
Easter Bunny HQ is 44 blocks away.

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]<distance> 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()


Easter Bunny HQ is 12 blocks away.
Easter Bunny HQ is 44 blocks away.

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]<distance> 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()


Easter Bunny HQ is 12 blocks away.
Easter Bunny HQ is 44 blocks away.

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]<distance> 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()


Easter Bunny HQ is 12 blocks away.
Easter Bunny HQ is 44 blocks away.

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]<distance> 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()


Easter Bunny HQ is 12 blocks away.
Easter Bunny HQ is 44 blocks away.