Advent of Code – Day 2

Previous:

Advent Of Code 2016 – Day 1

Day 2

Start: 12/4/2016

Finish 12/4/2016

Language: Python3

SPOILER ALERT: If you have any inkling, any whatsoever, to work on the Advent of Code…. DO NOT READ THIS BLOG POST.  DO NOT LOOK AT MY GITHUB PROJECT.  It is no fun if you’re not solving it yourself, and you’ll feel so much better about yourself if you can figure it out without looking up an answer.  This blog post is not to give away the answer, but instead, is there for people to learn from.

As always, the following code is in my GitHub:

 

Problem Statement

The problem started off simple enough.  There is a 9 digit keypad listed:

123
456
789

 

If you start at the 5 key, and are given directions to move up, down, right or left, multiple times, what are the keys that you end at after each iteration of directions?

Not too bad, and I was picking a language in my wheelhouse, Python.  I chose Python 3, since I don’t get much experience with it, but unfortunately, this program did not stretch my knowledge of Python 3.  There wasn’t enough meat to the problem to really dive in.

So let’s look at the code:

 


def parse_inputs():
    with open("day2.txt") as in_file:
        return in_file.read().split("\n")

def move_key(keypad, move):
    if move == "U":
        keypad["y"] = max(0, keypad["y"] - 1)
    elif move == "D":
        keypad["y"] = min(2, keypad["y"] + 1)
    elif move == "R":
        keypad["x"] = min(2, keypad["x"] + 1)
    elif move == "L":
        keypad["x"] = max(0, keypad["x"] - 1)

def get_key(keypad, directions):
    for direction in directions:
        move_key(keypad, direction)
    return get_keypad_key(keypad)

def get_keypad_key(keypad):
    return str(keypad["y"] * 3 + keypad["x"] + 1)

def main():
    directions_list = parse_inputs()
    keypad = {"x":1, "y": 1}
    keys = [get_key(keypad, directions) for directions in directions_list]
    return "".join(keys)

if __name__ == "__main__":
    print(main())

This is pretty straight forward:

Read the lines from the file

For each line in the file, move keys around until you end and return the key

Concatenate all the keys together to give you the code.

I start off with a dictionary that holds a cartesian coordinate, and update it from there.

The only real big piece of this was the ugly looking move_key, where there was rudimentary range checking.


def move_key(keypad, move):
    if move == "U":
        keypad["y"] = max(0, keypad["y"] - 1)
    elif move == "D":
        keypad["y"] = min(2, keypad["y"] + 1)
    elif move == "R":
        keypad["x"] = min(2, keypad["x"] + 1)
    elif move == "L":
        keypad["x"] = max(0, keypad["x"] - 1)

I’m making sure that I can’t move past the bounds of my nice little grid.
I submitted my answer, and it was right.

Challenge 2

As soon as I got the answer right, I looked ahead, and saw that the keypad just got a lot harder.


  1
 234
56789
 ABC
  D

 

I couldn’t rely on my nifty little range checking anymore.  So I reverted the move_key function to something much simpler:

 


def move_key(keypad, move):
    if move == "U":
        keypad["y"] -= 1
    elif move == "D":
        keypad["y"] += 1
    elif move == "R":
        keypad["x"] += 1
    elif move == "L":
        keypad["x"] -= 1

 
Then, I decided to try something different in get_key.  I’d make a move, and if it was successful, keep it.  Otherwise, revert back to before you made the move.   This way, I could define my own is valid function and keep most of the rest of the code the same.

 


def get_key(keypad, directions):
    for direction in directions:
        old_keypad = dict(keypad)
        move_key(keypad, direction)
        if not is_valid(keypad):
            keypad = dict(old_keypad)
    return get_keypad_key(keypad)

def get_edges():
    return [(0, 1), (1, 0), (2, -1), (3, 0), (4, 1), (-1, 2),
            (5, 2), (0, 3), (4, 3), (1, 4), (3, 4), (2, 5)]

def is_valid(keypad):
    return (keypad["x"], keypad["y"]) not in get_edges()

My is valid function just checked that I never hit one of my predefined edges.

I also had to change my get_keypad_key to map in characters (rather than using a math trick that was more coincidence than anything)


def get_keypad_key(keypad):
    keypad_map = {(2, 0) : "1", (1, 1) : "2", (2, 1) : "3", (3, 1) : "4",
                  (0, 2) : "5", (1, 2) : "6", (2, 2) : "7", (3, 2) : "8",
                  (4, 2) : "9", (1, 3) : "A", (2, 3) : "B", (3, 3) : "C",
                  (2, 4) : "D"}
    return keypad_map[(keypad["x"], keypad["y"])]

 
However, I hit a bug that cost me about twenty minutes.  Did you see it in the above code?  I’m setting keypad to dict(old_keypad), but that doesn’t actually update the reference of the keypad argument, but rather rebinds it.  If I change it to an update method, it works fine.  Shame on me for mutating state like that.  I should know better.


def get_key(keypad, directions):
    for direction in directions:
        old_keypad = dict(keypad)
        move_key(keypad, direction)
        if not is_valid(keypad):
            keypad.update(old_keypad)
    return get_keypad_key(keypad)

 

Closing Thoughts

I blew through the original challenge pretty quickly, and the second one hung me up on a stupid state bug.  If I had been clean throughout and kept my values immutable, I would never have run into that.  I give myself another B for this project, as I got the answers right the first time I submitted them, but I should have known better with Python.

Next up, I think I’ll go with one of my favorite Lisp-like languages, Clojure.

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s