Posts: Advent of code day 25 & reflections

This commit is contained in:
Jez Cope 2018-01-02 21:08:47 +00:00
parent e9d30069bb
commit c880b41a3e
3 changed files with 197 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

View File

@ -0,0 +1,150 @@
---
title: "The Halting Problem — Python — #adventofcode Day 25"
description: "In which Turing and I have a little chat about machines."
slug: day-25
date: 2018-01-02T20:47:19+00:00
tags:
- Technology
- Learning
- Advent of Code
- Python
series: aoc2017
---
[Today's challenge](http://adventofcode.com/2017/day/25), takes us back to a bit of computing history: a good old-fashioned [Turing Machine](http://en.wikipedia.org/wiki/Turing_Machine).
[→ Full code on GitHub](https://github.com/jezcope/aoc2017/blob/master/25-the-halting-problem.py)
!!! commentary
Today's challenge was a nice bit of nostalgia, taking me back to my university days learning about the theory of computing. Turing Machines are a classic bit of computing theory, and are provably able to compute any value that is possible to compute: a value is computable *if and only if* a Turing Machine can be written that computes it (though in practice anything non-trivial is mind-bendingly hard to write as a TM).
A bit of a library-fest today, compared to other days!
```python
from collections import deque, namedtuple
from collections.abc import Iterator
from tqdm import tqdm
import re
import fileinput as fi
```
These regular expressions are used to parse the input that defines the transition table for the machine.
```python
RE_ISTATE = re.compile(r'Begin in state (?P<state>\w+)\.')
RE_RUNTIME = re.compile(
r'Perform a diagnostic checksum after (?P<steps>\d+) steps.')
RE_STATETRANS = re.compile(
r"In state (?P<state>\w+):\n"
r" If the current value is (?P<read0>\d+):\n"
r" - Write the value (?P<write0>\d+)\.\n"
r" - Move one slot to the (?P<move0>left|right).\n"
r" - Continue with state (?P<next0>\w+).\n"
r" If the current value is (?P<read1>\d+):\n"
r" - Write the value (?P<write1>\d+)\.\n"
r" - Move one slot to the (?P<move1>left|right).\n"
r" - Continue with state (?P<next1>\w+).")
MOVE = {'left': -1, 'right': 1}
```
A `namedtuple` to provide some sugar when using a transition rule.
```python
Rule = namedtuple('Rule', 'write move next_state')
```
The `TuringMachine` class does all the work.
```python
class TuringMachine:
def __init__(self, program=None):
self.tape = deque()
self.transition_table = {}
self.state = None
self.runtime = 0
self.steps = 0
self.pos = 0
self.offset = 0
if program is not None:
self.load(program)
def __str__(self):
return f"Current: {self.state}; steps: {self.steps} of {self.runtime}"
```
Some jiggery-pokery to allow us to use `self[pos]` to reference an infinite tape.
```python
def __getitem__(self, i):
i += self.offset
if i < 0 or i >= len(self.tape):
return 0
else:
return self.tape[i]
def __setitem__(self, i, x):
i += self.offset
if i >= 0 and i < len(self.tape):
self.tape[i] = x
elif i == -1:
self.tape.appendleft(x)
self.offset += 1
elif i == len(self.tape):
self.tape.append(x)
else:
raise IndexError('Tried to set position off end of tape')
```
Parse the program and set up the transtion table.
```python
def load(self, program):
if isinstance(program, Iterator):
program = ''.join(program)
match = RE_ISTATE.search(program)
self.state = match['state']
match = RE_RUNTIME.search(program)
self.runtime = int(match['steps'])
for match in RE_STATETRANS.finditer(program):
self.transition_table[match['state']] = {
int(match['read0']): Rule(write=int(match['write0']),
move=MOVE[match['move0']],
next_state=match['next0']),
int(match['read1']): Rule(write=int(match['write1']),
move=MOVE[match['move1']],
next_state=match['next1']),
}
```
Run the program for the required number of steps (given by `self.runtime`). `tqdm` isn't in the standard library but it should be: it shows a lovely text-mode progress bar as we go.
```python
def run(self):
for _ in tqdm(range(self.runtime),
desc="Running", unit="steps", unit_scale=True):
read = self[self.pos]
rule = self.transition_table[self.state][read]
self[self.pos] = rule.write
self.pos += rule.move
self.state = rule.next_state
```
Calculate the "diagnostic checksum" required for the answer.
```python
@property
def checksum(self):
return sum(self.tape)
```
Aaand GO!
```python
machine = TuringMachine(fi.input())
machine.run()
print("Checksum:", machine.checksum)
```

View File

@ -0,0 +1,47 @@
---
title: "Reflections on #aoc2017"
description: "In which I look back on 25 days of regular coding."
slug: reflections
date: 2018-01-02T21:02:37+00:00
type: post
tags:
- Technology
- Learning
- Advent of Code
- Reflection
series: aoc2017
---
{{% figure alt="Trees reflected in a lake"
src="/images/posts/2018-01-02-reflection.jpg"
caption="Trees reflected in a lake"
attr="Joshua Reddekopp on Unsplash"
attrlink="https://unsplash.com/photos/-3uIUqsR-Rw"
class="main-illustration fr" %}}
It seems like ages ago, but [way back in November](../advent-of-code-2017/) I committed to completing [Advent of Code](https://adventofcode.com). I managed it all, and it was fun!
All of my code is [available on GitHub](https://jezcope/aoc2017) if youre interested in seeing what I did, and I managed to get out a blog post for every one with a bit more commentary, which you can see in the series list above.
## How did I approach it?
Ive not really done any serious programming challenges before. I dont get to write a lot of code at the moment, so all I wanted from AoC was an excuse to do some proper problem-solving.
I never really intended to take a polyglot approach, though I did think that I might use mainly Python with a bit of Haskell. In the end, though, I used: Python (×12); Haskell (×7); Rust (×4); Go; C++; Ruby; Julia; and Coconut.
For the most part, my priorities were getting the right answer, followed by writing readable code. I didnt specifically focus on performance but did try to avoid falling into traps that I knew about.
## What did I learn?
I found Python the easiest to get on with: its the language I know best and although I cant always remember exact method names and parameters I know whats available and where to look to remind myself, as well as most of the common idioms and some performance traps to avoid. Python was therefore the language that let me focus most on solving the problem itself. C++ and Ruby were more challenging, and it was harder to write good idiomatic code but I can still remember quite a lot.
Haskell I havent used since university, and just like back then I really enjoyed working out how to solve problems in a functional style while still being readable and efficient (not always something I achieved...). I learned a lot about core Haskell concepts like monads & functors, and Im really amazed by the way the Haskell community and ecosystem has grown up in the last decade.
I also wanted to learn at least one modern, memory-safe compiled language, so I tried both Go and Rust. Both seem like useful languages, but Rust really intrigued me with its conceptual similarities to both Haskell and C++ and its promise of memory safety without a garbage collector. I struggled a lot initially with the “borrow checker” (the component that enforces memory safety at compile time) but eventually started thinking in terms of ownership and lifetimes after which things became easier. The Rust community seems really vibrant and friendly too.
## What next?
I really want to keep this up, so Im going to look out some more programming challenges ([Project Euler](https://projecteuler.net/) looks interesting). It turns out theres a regular Code Dojo meetup in Leeds, so hopefully Ill try that out too. Id like to do more realistic data-science stuff, so Ill be taking a closer look at stuff like [Kaggle](https://kaggle.com/) too, and figuring out how to do a bit more analysis at work.
Im also feeling motivated to find an open source project to contribute to and/or release a project of my own, so well see if that goes anywhere! Ive always found the advice to “scratch your own itch” difficult to follow because everything I think of myself has already been done better. Most of the projects I use enough to want to contribute to tend to be pretty well developed with big communities and any bugs that might be accessible to me will be picked off and fixed before I have a chance to get started. Maybe its time to get over myself and just reimplement something that already exists, just for the fun of it!