Post: Advent of Code 2017 intro

This commit is contained in:
Jez Cope 2017-11-29 18:36:05 +00:00
parent a73a846b5f
commit 1c701c5593
1 changed files with 211 additions and 0 deletions

View File

@ -0,0 +1,211 @@
---
title: "Advent of Code 2017: introduction"
description: "In which I plan to take part in the annual daily coding challenge for the month of December."
slug: advent-of-code-2017
date: 2017-11-29T18:33:08+00:00
tags:
- Technology
- Learning
- Advent of Code
- Python
- Haskell
- Emacs
---
It's a common lament of mine that I don't get to write a lot of code in my day-to-day job. I like the feeling of making something from nothing, and I often look for excuses to write bits of code, both at work and outside it.
[Advent of Code][] is a daily series of programming challenges for the month of December, and is about to start its third annual incarnation. I discovered it too late to take part in any serious way last year, but I'm going to give it a try this year.
[Advent of Code]: http://adventofcode.com
There are no restrictions on programming language (so of course some people delight in using esoteric languages like [Brainf**k](https://en.wikipedia.org/wiki/Brainfuck)), but I think I'll probably stick with Python for the most part. That said, I miss my Haskell days and I'm intrigued by new kids on the block Go and Rust, so I might end up throwing in a few of those on some of the simpler challenges.
I'd like to focus a bit more on *how* I solve the puzzles. They generally come in two parts, with the second part only being revealed after successful completion of the first part. With that in mind, test-driven development makes a lot of sense, because I can verify that I haven't broken the solution to the first part in modifying to solve the second.
I may also take a literate programming approach with `org-mode` or Jupyter notebooks to document my solutions a bit more, and of course that will make it easier to publish solutions here so I'll do that as much as I can make time for.
---
On that note, here are some solutions for 2016 that I've done recently as a warmup.
## Day 1: Python
[Day 1 instructions](http://adventofcode.com/2016/day/1)
```python
import numpy as np
import pytest as t
import sys
TURN = {
'L': np.array([[0, 1],
[-1, 0]]),
'R': np.array([[0, -1],
[1, 0]])
}
ORIGIN = np.array([0, 0])
NORTH = np.array([0, 1])
class Santa:
def __init__(self, location, heading):
self.location = np.array(location)
self.heading = np.array(heading)
self.visited = [(0,0)]
def execute_one(self, instruction):
start_loc = self.location.copy()
self.heading = self.heading @ TURN[instruction[0]]
self.location += self.heading * int(instruction[1:])
self.mark(start_loc, self.location)
def execute_many(self, instructions):
for i in instructions.split(','):
self.execute_one(i.strip())
def distance_from_start(self):
return sum(abs(self.location))
def mark(self, start, end):
for x in range(min(start[0], end[0]), max(start[0], end[0])+1):
for y in range(min(start[1], end[1]), max(start[1], end[1])+1):
if any((x, y) != start):
self.visited.append((x, y))
def find_first_crossing(self):
for i in range(1, len(self.visited)):
for j in range(i):
if self.visited[i] == self.visited[j]:
return self.visited[i]
def distance_to_first_crossing(self):
crossing = self.find_first_crossing()
if crossing is not None:
return abs(crossing[0]) + abs(crossing[1])
def __str__(self):
return f'Santa @ {self.location}, heading {self.heading}'
def test_execute_one():
s = Santa(ORIGIN, NORTH)
s.execute_one('L1')
assert all(s.location == np.array([-1, 0]))
assert all(s.heading == np.array([-1, 0]))
s.execute_one('L3')
assert all(s.location == np.array([-1, -3]))
assert all(s.heading == np.array([0, -1]))
s.execute_one('R3')
assert all(s.location == np.array([-4, -3]))
assert all(s.heading == np.array([-1, 0]))
s.execute_one('R100')
assert all(s.location == np.array([-4, 97]))
assert all(s.heading == np.array([0, 1]))
def test_execute_many():
s = Santa(ORIGIN, NORTH)
s.execute_many('L1, L3, R3')
assert all(s.location == np.array([-4, -3]))
assert all(s.heading == np.array([-1, 0]))
def test_distance():
assert Santa(ORIGIN, NORTH).distance_from_start() == 0
assert Santa((10, 10), NORTH).distance_from_start() == 20
assert Santa((-17, 10), NORTH).distance_from_start() == 27
def test_turn_left():
east = NORTH @ TURN['L']
south = east @ TURN['L']
west = south @ TURN['L']
assert all(east == np.array([-1, 0]))
assert all(south == np.array([0, -1]))
assert all(west == np.array([1, 0]))
def test_turn_right():
west = NORTH @ TURN['R']
south = west @ TURN['R']
east = south @ TURN['R']
assert all(east == np.array([-1, 0]))
assert all(south == np.array([0, -1]))
assert all(west == np.array([1, 0]))
if __name__ == '__main__':
instructions = sys.stdin.read()
santa = Santa(ORIGIN, NORTH)
santa.execute_many(instructions)
print(santa)
print('Distance from start:', santa.distance_from_start())
print('Distance to target: ', santa.distance_to_first_crossing())
```
## Day 2: Haskell
[Day 2 instructions](http://adventofcode.com/2016/day/2)
```haskell
module Main where
data Pos = Pos Int Int deriving (Show)
-- Magrittr-style pipe operator
(|>) :: a -> (a -> b) -> b
x |> f = f x
swapPos :: Pos -> Pos
swapPos (Pos x y) = Pos y x
clamp :: Int -> Int -> Int -> Int
clamp lower upper x | x < lower = lower
| x > upper = upper
| otherwise = x
clampH :: Pos -> Pos
clampH (Pos x y) = Pos x' y'
where y' = clamp 0 4 y
r = abs (2 - y')
x' = clamp r (4-r) x
clampV :: Pos -> Pos
clampV = swapPos . clampH . swapPos
buttonForPos :: Pos -> String
buttonForPos (Pos x y) = [buttons !! y !! x]
where buttons = [" D ",
" ABC ",
"56789",
" 234 ",
" 1 "]
decodeChar :: Pos -> Char -> Pos
decodeChar (Pos x y) 'R' = clampH $ Pos (x+1) y
decodeChar (Pos x y) 'L' = clampH $ Pos (x-1) y
decodeChar (Pos x y) 'U' = clampV $ Pos x (y+1)
decodeChar (Pos x y) 'D' = clampV $ Pos x (y-1)
decodeLine :: Pos -> String -> Pos
decodeLine p "" = p
decodeLine p (c:cs) = decodeLine (decodeChar p c) cs
makeCode :: String -> String
makeCode instructions = lines instructions -- split into lines
|> scanl decodeLine (Pos 1 1) -- decode to positions
|> tail -- drop start position
|> concatMap buttonForPos -- convert to buttons
main = do
input <- getContents
putStrLn $ makeCode input
```