{ "cells": [ { "cell_type": "markdown", "metadata": { "toc": "true" }, "source": [ "# Table of Contents\n", "

1  Advent of Code 2017
1.1  Utility functions
1.1.1  Iteration helpers
1.1.2  Running Examples
1.1.3  Data
1.1.4  Here strings
1.1.5  Other utilities
1.2  Day 1: Inverse Captcha
1.2.1  Part 2
1.2.2  Alternatives
1.3  Day 2: Corruption Checksum
1.3.1  Part 2
1.4  Day 3: Spiral Memory
1.4.1  Part 2
1.4.2  Alternatives
1.5  Day 4: High-Entropy Passphrases
1.6  Day 5: A Maze of Twisty Trampolines, All Alike
1.7  Day 6: Memory Reallocation
1.7.1  Part 2
1.8  Day 7: Recursive Circus
1.9  Day 8: I Heard You Like Registers
1.9.1  Part 2
1.10  Day 9: Stream Processing
1.10.1  Part 2
1.11  Day 10: Knot Hash
1.11.1  Part 2
1.12  Day 11: Hex Ed
1.12.1  Part 2
1.13  Day 12: Digital Plumber
1.13.1  Part 2
1.14  Day 13: Packet Scanners
1.14.1  Part 2
1.15  Day 14: Disk Defragmentation
1.15.1  Part 2
1.16  Day 15: Dueling Generators
1.16.1  Part 2
1.17  Day 16: Permutation Promenade
1.17.1  Part 2
1.17.2  Alternative
1.18  Day 17: Spinlock
1.19  Day 18: Duet
1.19.1  Part 2
1.19.2  Alternative
1.20  Day 19: A Series of Tubes
1.20.1  Part 2
1.21  Day 20: Particle Swarm
1.21.1  Part 2
1.22  Day 21: Fractal Art
1.22.1  Part 2
1.23  Day 22: Sporifica Virus
1.23.1  Part 2
1.24  Day 23: Coprocessor Conflagration
1.24.1  Part 2
1.25  Day 24: Electromagnetic Moat
1.25.1  Part 2
1.26  Day 25: The Halting Problem
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Advent of Code 2017\n", "Solutions to [Advent of Code 2017](http://adventofcode.com/2017).\n", "\n", "Many days contain code duplication between part 1 and part 2.\n", "In general I didn't re-write part 1 once I saw part 2." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Utility functions\n", "These were written as needed for a specific day, and then moved here if it looked like they'd be useful for a subsequent problem." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Iteration helpers" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "ExecuteTime": { "end_time": "2017-12-27T15:34:56.682167Z", "start_time": "2017-12-27T10:34:56.663908-05:00" } }, "outputs": [], "source": [ "from itertools import zip_longest\n", "\n", "def iter_index(iterable, value):\n", " \"Return the 0-based index of value in iterable.\"\n", " for i, item in enumerate(iterable):\n", " if item == value:\n", " return i\n", " raise ValueError()\n", "\n", "def iter_last(iterable, defaultvalue=None):\n", " \"Return an iterable's last item; defaultvalue if the iterable is empty.\"\n", " item = defaultvalue\n", " for item in iterable:\n", " pass\n", " return item\n", "\n", "def iter_len(iterable):\n", " \"Return the length of an iterable.\"\n", " return sum(1 for _ in iterable)\n", "\n", "def iter_nth(iterable, n):\n", " \"Return an iterable's nth item.\"\n", " return next(x for i, x in enumerate(iterable) if i == n)\n", "\n", "def repeatedly(fn, initial):\n", " \"Yield fn(initial), fn(fn(initial)), ...\"\n", " while True:\n", " initial = fn(initial)\n", " yield initial\n", "\n", "# from itertools recipes\n", "def grouper(iterable, n, fillvalue=None):\n", " \"Collect data into fixed-length chunks or blocks\"\n", " args = [iter(iterable)] * n\n", " return zip_longest(*args, fillvalue=fillvalue)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Running Examples" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "ExecuteTime": { "end_time": "2017-12-27T15:34:56.690615Z", "start_time": "2017-12-27T10:34:56.683994-05:00" } }, "outputs": [], "source": [ "# Better than assert, because it prints the expected value.\n", "# There's plenty of test runners that due this, but using them inside Jupyter was beyond\n", "# me during the course of the contest.\n", "def example(actual, expected=True):\n", " \"Print message if action != expected.\"\n", " if actual != expected:\n", " raise Exception('Actual = {} ≠ {} = expected'.format(actual, expected))\n", "\n", "example(abs(-2), 2)\n", "\n", "def examples(fn, *pairs):\n", " assert len(pairs) % 2 == 0, \"expected an even number of input, expected arguments; received {}\".format(len(pairs))\n", " for arg, expected in grouper(pairs, 2):\n", " example(fn(arg), expected)\n", "\n", "examples(abs,\n", " 2, 2,\n", " -2, 2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Data" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": true }, "outputs": [], "source": [ "class InputStr(str):\n", " def __init__(self, day):\n", " self.fname = 'data/advent-2017/input-{}.txt'.format(day)\n", "\n", " @property\n", " def lines(self):\n", " return self.splitlines()\n", "\n", " @property\n", " def grid(self):\n", " \"Given a text matrix with newlines, return a list of list of ints.\"\n", " return [list(map(int, line.split()))\n", " for line in self.splitlines()]\n", "\n", " def count_filtered(self, pred):\n", " return sum(map(pred, self.lines))\n", "\n", "def Input(day):\n", " with open('data/advent-2017/input-{}.txt'.format(day)) as f:\n", " return InputStr(f.read().rstrip('\\n'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Here strings\n", "\n", "Beef up multiline string literals." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "['Reads a multiline string,', 'ignoring an initial and final', 'line break']" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def Here(s):\n", " return InputStr(s.strip('\\n'))\n", "\n", "def here_lines(s):\n", " return Here(s).lines\n", "\n", "here_lines('''\n", "Reads a multiline string,\n", "ignoring an initial and final\n", "line break''')" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "[[1, 2, 3], [4, 5]]" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def here_grid(s):\n", " return Here(s).grid\n", "\n", "here_grid('''\n", "1 2 3\n", "4 5\n", "''')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Other utilities" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def rot(seq, n):\n", " \"Rotate left by n\"\n", " return seq[n:] + seq[:n]\n", "\n", "example(rot('abcdef', 2), 'cdefab')" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import re\n", "from collections import namedtuple\n", "\n", "class converting_tuple(object):\n", " \"\"\"Like a namedtuple, but converts each value as it's stored.\n", " \n", " It's not actually a class like namedtuple, but it pretends to have a constructor.\"\"\"\n", " def __init__(self, name, matcher=None, **converters):\n", " self.converters = converters\n", " self.constr = namedtuple(name, converters.keys())\n", " self.pattern = matcher\n", " self.matcher = re.compile(matcher).match if isinstance(matcher, str) else matcher\n", " \n", " def __call__(self, d):\n", " if isinstance(d, str):\n", " m = self.matcher(d)\n", " assert m, \"Couldn't match {} against {}\".format(d, self.pattern)\n", " d = m.groupdict()\n", " return self.constr(*(fn(d[k]) for k, fn in self.converters.items()))\n", "\n", "TestTuple = converting_tuple('testtuple', matcher=r'(?P.+)=(?P\\d+)', var=str.lower, val=int)\n", "t = TestTuple('X=10')\n", "\n", "example(t.var, 'x')\n", "example(t.val, 10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Day 1: [Inverse Captcha](http://adventofcode.com/2017/day/1)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1119" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def sum_same_as_next(digits):\n", " return sum(int(c)\n", " for i, c in enumerate(digits)\n", " if c == digits[(i + 1) % len(digits)])\n", "\n", "examples(sum_same_as_next,\n", " '1122', 3,\n", " '1111', 4,\n", " '1234', 0,\n", " '91212129', 9)\n", "\n", "sum_same_as_next(Input(1))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Part 2" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "1420" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def sum_same_as_antipode(digits, delta=None):\n", " delta = delta or len(digits) // 2\n", " return sum(int(c)\n", " for i, c in enumerate(digits)\n", " if c == digits[(i + delta) % len(digits)])\n", "\n", "examples(sum_same_as_antipode,\n", " '1212', 6,\n", " '1221', 0,\n", " '123425', 4,\n", " '123123', 12,\n", " '12131415', 4)\n", "\n", "sum_same_as_antipode(Input(1))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If I'd seen Part 2 before I wrote Part 1, I would have defined `sum_same_as_next` in terms of `sum_same_as_antipode`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Alternatives\n", "Here's some alternate implementations that I also had in my head when I wrote the one above. With less time pressure, I'd write them all, and keep the one that looked more maintainable.\n", "\n", "These can trivially be extended to `sum_same_as_antipode`." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1119" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def sum_same_as_next(digits):\n", " buffered = digits + digits[:1]\n", " return sum(int(c)\n", " for i, c in enumerate(digits)\n", " if c == buffered[i + 1])\n", "\n", "def sum_same_as_next(digits):\n", " return sum(int(c1)\n", " for c1, c2 in zip(digits, digits[1:] + digits[:1])\n", " if c1 == c2)\n", "\n", "examples(sum_same_as_next,\n", " '1122', 3,\n", " '1111', 4,\n", " '1234', 0,\n", " '91212129', 9)\n", "\n", "sum_same_as_next(Input(1))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Day 2: [Corruption Checksum](http://adventofcode.com/2017/day/2)" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "41887" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def checksum(rows):\n", " return sum(max(row) - min(row) for row in rows)\n", "\n", "example(checksum(here_grid('''\n", "5 1 9 5\n", "7 5 3\n", "2 4 6 8''')), 18)\n", "\n", "checksum(Input(2).grid)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Part 2" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "226" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from itertools import combinations\n", "\n", "def evenly(rows):\n", " ints = lambda ns: next(p // q for q, p in combinations(sorted(set(ns)), 2) if p % q == 0)\n", " return sum(map(ints, rows))\n", "\n", "example(evenly(here_grid('''\n", "5 9 2 8\n", "9 4 7 3\n", "3 8 6 5''')), 9)\n", "\n", "evenly(Input(2).grid)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Day 3: [Spiral Memory](http://adventofcode.com/2017/day/3)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " 37 36 35 34 33 32 31\n", " 38 17 16 15 14 13 30\n", " 39 18 5 4 3 12 29\n", " 40 19 6 1 2 11 28\n", " 41 20 7 8 9 10 27\n", " 42 21 22 23 24 25 26\n", " 43 44 45 46 47 48 49\n" ] }, { "data": { "text/plain": [ "326" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from itertools import accumulate, chain, count, repeat\n", "\n", "def spiral_steps():\n", " p, r = 1, 1j\n", " # one step right, one up, two left, two down, three right, three up, etc.\n", " for w in count(1):\n", " yield from repeat(p, w)\n", " p *= r\n", " yield from repeat(p, w)\n", " p *= r\n", "\n", "def spiral_positions():\n", " \"Generate (x, y) position along the spiral, starting at (0, 0).\"\n", " def c_to_pair(c):\n", " return (int(c.real), int(c.imag))\n", " return map(c_to_pair, accumulate(chain([0j], spiral_steps())))\n", "\n", "def steps(n):\n", " \"Return the Manhattan distance from 1-based nth element to the center.\"\n", " x, y = iter_nth(spiral_positions(), n-1)\n", " return abs(x) + abs(y)\n", "\n", "def print_spiral(w):\n", " cells = {}\n", " for n, pos in enumerate(spiral_positions(), 1):\n", " x, y = pos\n", " if max(abs(x), abs(y)) > w:\n", " break\n", " cells[pos] = n\n", " for y in range(w, -w-1, -1):\n", " ns = (cells.get((x, y), None) for x in range(-w, w+1))\n", " xs = ('{:4}'.format(n or '') for n in ns)\n", " print(' '.join(xs))\n", "\n", "print_spiral(3)\n", "\n", "examples(steps,\n", " 1, 0,\n", " 12, 3,\n", " 23, 2,\n", " 1024, 31)\n", "\n", "steps(361527)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Part 2" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "363010" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from collections import defaultdict\n", "\n", "def neighbors(pos):\n", " x, y = pos\n", " return ((x + dx, y + dy)\n", " for dx in (-1, 0, 1)\n", " for dy in (-1, 0, 1)\n", " if dx or dy)\n", "\n", "def spiral_sums():\n", " m = defaultdict(int)\n", " for pos in spiral_positions():\n", " a = sum(m[n] for n in neighbors(pos)) or 1\n", " m[pos] = a\n", " yield a\n", "\n", "next(n for n in spiral_sums() if n > 361527)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Alternatives\n", "\n", "I kind of like these better, but they read more like Haskell, less like idiomatic Python (as evident from the imports).\n", "\n", "Where I to start over, I'd change `spiral_positions` to return complex numbers instead of tuples, and change its consumers to expect them." ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "37 36 35 34 33 32 31\n", "38 17 16 15 14 13 30\n", "39 18 5 4 3 12 29\n", "40 19 6 1 2 11 28\n", "41 20 7 8 9 10 27\n", "42 21 22 23 24 25 26\n", "43 44 45 46 47 48 49\n" ] } ], "source": [ "from functools import partial, reduce\n", "from itertools import accumulate, chain, islice\n", "from operator import add\n", "\n", "def spiral_positions():\n", " turns = (1j ** n for n in count())\n", " sides = (w for n in count(1) for w in (n, n)) # number of steps in each direction\n", " steps = (d for n, d in zip(sides, turns) for _ in range(n))\n", " yield (0, 0)\n", " yield from ((int(c.real), int(c.imag)) for c in accumulate(steps, add))\n", "\n", "def print_spiral(w):\n", " h = w // 2\n", " cells = {pos: n for n, pos in enumerate(islice(spiral_positions(), w**2), 1)}\n", " row = lambda y:[cells.get((x, y), '') for x in range(-h, h+1)]\n", " cell_width = max(len(str(n)) for n in cells.values())\n", " fmt = partial('{:{w}}'.format, w=cell_width)\n", " for y in range(h, -h-1, -1):\n", " print(' '.join(map(fmt, row(y))))\n", "\n", "print_spiral(7)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Day 4: [High-Entropy Passphrases](http://adventofcode.com/2017/day/4)" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "383" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def valid_passphrase(phrase):\n", " ws = phrase.split()\n", " return len(ws) == len(set(ws))\n", "\n", "examples(valid_passphrase,\n", " 'aa bb cc dd ee', True,\n", " 'aa bb cc dd aa', False,\n", " 'aa bb cc dd aaa', True)\n", "\n", "Input(4).count_filtered(valid_passphrase)" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "265" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def valid_passphrase(phrase):\n", " ws = phrase.split()\n", " return len(ws) == len(set(''.join(sorted(w)) for w in ws))\n", "\n", "examples(valid_passphrase,\n", " 'abcde fghij', True,\n", " 'abcde xyz ecdab', False,\n", " 'a ab abc abd abf abj', True,\n", " 'iiii oiii ooii oooi oooo', True,\n", " 'oiii ioii iioi iiio', False)\n", "\n", "Input(4).count_filtered(valid_passphrase)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Day 5: [A Maze of Twisty Trampolines, All Alike](http://adventofcode.com/2017/day/5)" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "372139" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def iter_jumps(lines):\n", " jumps = list(map(int, lines))\n", " pc = 0\n", " while 0 <= pc < len(jumps):\n", " yield pc\n", " n = jumps[pc]\n", " jumps[pc] += 1\n", " pc += n\n", "\n", "def jump_count(lines):\n", " return iter_len(iter_jumps(lines))\n", "\n", "EXAMPLE5 = here_lines('''\n", "0\n", "3\n", "0\n", "1\n", "-3''')\n", "\n", "example(jump_count(EXAMPLE5), 5)\n", "\n", "jump_count(Input(5).lines)" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "29629538" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def iter_jumps(lines):\n", " jumps = list(map(int, lines))\n", " pc = 0\n", " while 0 <= pc < len(jumps):\n", " yield pc\n", " n = jumps[pc]\n", " jumps[pc] += -1 if n >= 3 else 1\n", " pc += n\n", "\n", "example(jump_count(EXAMPLE5), 10)\n", "\n", "jump_count(Input(5).lines)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Day 6: [Memory Reallocation](http://adventofcode.com/2017/day/6)" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "5042" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from operator import add\n", "from itertools import count, starmap\n", "\n", "def redistribute(banks):\n", " N = len(banks)\n", " m = max(banks)\n", " b = banks.index(m)\n", " q, r = divmod(m, N)\n", " zeroed = banks[:b] + (0,) + banks[b+1:] # banks, with the donor set to zero\n", " deltas = [q + 1] * r + [q] * (N - r)\n", " return tuple(starmap(add, zip(zeroed, rot(deltas, -b-1))))\n", "\n", "def iter_configs(banks):\n", " banks = tuple(banks)\n", " seen = set()\n", " while banks not in seen:\n", " yield banks\n", " seen.add(banks)\n", " banks = tuple(redistribute(banks))\n", "\n", "def count_configs(banks):\n", " return iter_len(iter_configs(banks))\n", "\n", "example(count_configs([0, 2, 7, 0]), 5)\n", "\n", "count_configs(map(int, Input(6).split()))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Part 2" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1086" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def cycle_size(banks):\n", " c = iter_last(iter_configs(banks))\n", " return(iter_len(iter_configs(c)))\n", "\n", "example(cycle_size([0, 2, 7, 0]), 4)\n", "\n", "cycle_size(map(int, Input(6).split()))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Day 7: [Recursive Circus](http://adventofcode.com/2017/day/7)" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'rqwgj'" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import operator\n", "import re\n", "from collections import namedtuple\n", "from functools import reduce\n", "\n", "Disc = namedtuple('Disc', ['name', 'weight', 'child_names'])\n", "\n", "def parse(lines):\n", " discs = {}\n", " for line in lines:\n", " name, weight, children = re.match(r'(\\S+) \\((\\d+)\\)(?: -> (.+))?', line).groups()\n", " children = children and children.split(', ') or ()\n", " discs[name] = Disc(name, int(weight), frozenset(children))\n", " return discs\n", "\n", "def bottom(lines):\n", " tower = parse(lines)\n", " discs = tower.values()\n", " bottoms = {d.name for d in discs} - reduce(operator.ior, (d.child_names for d in discs))\n", " assert len(bottoms) == 1\n", " return list(bottoms)[0]\n", "\n", "example(bottom(here_lines('''\n", "pbga (66)\n", "xhth (57)\n", "ebii (61)\n", "havc (66)\n", "ktlj (57)\n", "fwft (72) -> ktlj, cntj, xhth\n", "qoyq (66)\n", "padx (45) -> pbga, havc, qoyq\n", "tknk (41) -> ugml, padx, fwft\n", "jptl (61)\n", "ugml (68) -> gyxo, ebii, jptl\n", "gyxo (61)\n", "cntj (57)''')), 'tknk')\n", "\n", "bottom(Input(7).lines)" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "scrolled": false }, "outputs": [ { "data": { "text/plain": [ "34386" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from functools import lru_cache\n", "from collections import Counter\n", "\n", "def mismatched(tower):\n", " \"Generates pairs (disc, weight) of discs that would need weight to match their siblings.\"\n", " def children(disc):\n", " return [tower[s] for s in disc.child_names]\n", " @lru_cache()\n", " def inclusive_weight(disc):\n", " return disc.weight + sum(map(inclusive_weight, children(disc)))\n", " for d in tower.values():\n", " weights = [inclusive_weight(c) for c in children(d)]\n", " if len(set(weights)) > 1:\n", " # if there's two weights, the one occurs only once (not checked here); the other is good\n", " bad_weight, good_weight = (w for w, _ in sorted(Counter(weights).items(), key=lambda t:t[1]))\n", " bad_child = next(c for c in children(d) if inclusive_weight(c) == bad_weight)\n", " yield bad_child, bad_child.weight + good_weight - bad_weight\n", "\n", "def correction(lines):\n", " tower = parse(lines)\n", " candidates = list(mismatched(tower))\n", " def descendant_names(d):\n", " return reduce(operator.ior, (descendant_names(tower[c]) for c in d.child_names), set(d.child_names))\n", " subsumed = reduce(operator.ior, (descendant_names(d) for d, _ in candidates))\n", " w, = [w for d, w in candidates if d.name not in subsumed]\n", " return w\n", "\n", "example(correction(here_lines('''\n", "pbga (66)\n", "xhth (57)\n", "ebii (61)\n", "havc (66)\n", "ktlj (57)\n", "fwft (72) -> ktlj, cntj, xhth\n", "qoyq (66)\n", "padx (45) -> pbga, havc, qoyq\n", "tknk (41) -> ugml, padx, fwft\n", "jptl (61)\n", "ugml (68) -> gyxo, ebii, jptl\n", "gyxo (61)\n", "cntj (57)''')), 60)\n", "\n", "correction(Input(7).lines)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Day 8: [I Heard You Like Registers](http://adventofcode.com/2017/day/8)" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4877" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from collections import defaultdict, namedtuple\n", "from operator import ge, le, gt, lt, eq, ne, add, sub\n", "\n", "ops = {'inc': add, 'dec': sub}\n", "rels = {'>': gt, '<': lt, '>=': ge, '<=': le, '==': eq, '!=': ne}\n", "Instr = converting_tuple(\n", " 'instr',\n", " r'(?P\\w+) (?Pinc|dec) (?P-?\\d+) if (?P\\w+) (?P\\S+) (?P-?\\d+)',\n", " v1=str, v2=str, k1=int, k2=int, rel=rels.__getitem__, op=ops.__getitem__)\n", "\n", "def max_register_value(lines):\n", " regs = defaultdict(int)\n", " for instr in map(Instr, lines):\n", " if instr.rel(regs[instr.v2], instr.k2):\n", " regs[instr.v1] = instr.op(regs[instr.v1], instr.k1)\n", " return max(regs.values())\n", "\n", "example(max_register_value(here_lines('''\n", "b inc 5 if a > 1\n", "a inc 1 if b < 5\n", "c dec -10 if a >= 1\n", "c inc -20 if c == 10''')), 1)\n", "\n", "max_register_value(Input(8).lines)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Part 2" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "5471" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# I would have written part 1's max_register_value in terms of these, if I'd known what was coming.\n", "# Instead, this funciton is remixed from that one.\n", "def iter_registers_map(lines):\n", " regs = defaultdict(int)\n", " for instr in map(Instr, lines):\n", " if instr.rel(regs[instr.v2], instr.k2):\n", " regs[instr.v1] = instr.op(regs[instr.v1], instr.k1)\n", " yield regs\n", "\n", "def max_register_value(lines):\n", " return max(v for regs in iter_registers_map(lines) for v in regs.values())\n", "\n", "example(max_register_value(here_lines('''\n", "b inc 5 if a > 1\n", "a inc 1 if b < 5\n", "c dec -10 if a >= 1\n", "c inc -20 if c == 10''')), 10)\n", "\n", "max_register_value(Input(8).lines)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Day 9: [Stream Processing](http://adventofcode.com/2017/day/9)" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "7616" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def count_groups(s):\n", " s = re.sub(r'!.', '', s)\n", " s = re.sub(r'<.*?>', '', s)\n", " return s.count('{')\n", "\n", "examples(count_groups,\n", " '{}', 1,\n", " '{{{}}}', 3,\n", " '{{},{}}', 3,\n", " '{{{},{},{{}}}}', 6,\n", " '{<{},{},{{}}>}', 1,\n", " '{,,,}', 1,\n", " '{{},{},{},{}}', 5,\n", " '{{},{},{},{}}', 2,\n", " )\n", "\n", "def iter_group_depths(s):\n", " s = re.sub(r'!.', '', s)\n", " s = re.sub(r'<.*?>', '', s)\n", " depth = 0\n", " deltas = {'{': 1, '}': -1}\n", " yield depth\n", " for c in s:\n", " if c in deltas:\n", " delta = deltas[c]\n", " depth += delta\n", " if delta > 0:\n", " yield depth\n", "\n", "def sum_groups(line):\n", " return sum(iter_group_depths(line))\n", "\n", "examples(sum_groups,\n", " '{}', 1,\n", " '{{{}}}', 6,\n", " '{{},{}}', 5,\n", " '{{{},{},{{}}}}', 16,\n", " '{,,,}', 1,\n", " '{{},{},{},{}}', 9,\n", " '{{},{},{},{}}', 9,\n", " '{{},{},{},{}}', 3,\n", " )\n", "\n", "sum_groups(Input(9))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Part 2" ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "3838" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def sum_garbage_lengths(s):\n", " s = re.sub(r'!.', '', s)\n", " return sum(map(len, re.findall(r'<(.*?)>', s)))\n", " \n", "examples(sum_garbage_lengths,\n", " '<>', 0,\n", " '', 17,\n", " '<<<<>', 3,\n", " '<{!>}>', 2,\n", " '', 0,\n", " '>', 0,\n", " '<{o\"i!a,<{i', 10,\n", " )\n", "\n", "sum_garbage_lengths(Input(9))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Day 10: [Knot Hash](http://adventofcode.com/2017/day/10)" ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "40602" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def knot_hash(lengths, lst=range(256)):\n", " lst = list(lst)\n", " pos = 0\n", " for skip, n in enumerate(lengths):\n", " lst = rot(lst, pos)\n", " lst[:n] = lst[n-1::-1]\n", " lst = rot(lst, -pos)\n", " pos += n + skip\n", " pos %= len(lst)\n", " return lst\n", "\n", "def knot_hash_check(lengths, lst=range(256)):\n", " a, b, *_ = knot_hash(lengths, lst)\n", " return a * b\n", "\n", "assert knot_hash([3, 4, 1, 5], range(5),) == [3, 4, 2, 1, 0]\n", "assert knot_hash_check([3, 4, 1, 5], range(5),) == 12\n", "\n", "knot_hash_check(map(int, Input(10).split(',')))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Part 2" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "scrolled": false }, "outputs": [ { "data": { "text/plain": [ "'35b028fe2c958793f7d5a61d07a008c8'" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import operator\n", "from functools import reduce\n", "\n", "def knot_hash2(chars, lst=range(256)):\n", " lengths = list(map(ord, chars)) + [17, 31, 73, 47, 23]\n", " sparse = knot_hash(lengths * 64, lst)\n", " dense = (reduce(operator.xor, block) for block in grouper(sparse, 16))\n", " return ''.join(map('{:02x}'.format, dense))\n", "\n", "examples(knot_hash2,\n", " '', 'a2582a3a0e66e6e86e3812dcb672a272',\n", " 'AoC 2017', '33efeb34ea91902bb2f59c9920caa6cd',\n", " '1,2,3', '3efbe78a8d82f29979031a4aa0b16a9d',\n", " '1,2,4', '63960835bcdc130f0b66d7ff4f6a5a8e'\n", " )\n", "\n", "knot_hash2(Input(10))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Day 11: [Hex Ed](http://adventofcode.com/2017/day/11)" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "687" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "HEX_DIRS = {'n': 1, 's': -1, 'ne': 1j, 'sw': -1j, 'nw': 1-1j, 'se': -1+1j}\n", "\n", "def walk(stepstr):\n", " \"Return axial coordinates.\"\n", " return sum(map(HEX_DIRS.get, stepstr.split(',')))\n", "\n", "def hex_len(p):\n", " \"Return the Manhattan length of a hex grid axial vector.\"\n", " q, r = p.real, p.imag\n", " return int(max(map(abs, (q, r, q + r))))\n", "\n", "def hex_distance(stepstr):\n", " return hex_len(walk(stepstr))\n", "\n", "examples(hex_distance,\n", " 'ne,ne,ne', 3,\n", " 'ne,ne,sw,sw', 0,\n", " 'ne,ne,s,s', 2,\n", " 'se,sw,se,sw,sw', 3\n", " )\n", "\n", "hex_distance(Input(11))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Part 2" ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "1483" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from itertools import accumulate\n", "\n", "def iter_walk(stepstr):\n", " yield from accumulate(map(HEX_DIRS.get, stepstr.split(',')))\n", "\n", "max(map(hex_len, iter_walk(Input(11))))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Day 12: [Digital Plumber](http://adventofcode.com/2017/day/12)" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "152" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def groups(lines):\n", " Pipe = converting_tuple('pipe', matcher=r'(?P

\\S+) <-> (?P.+)',\n", " p=int, ps=lambda s:set(map(int, s.split(', '))))\n", " groups = set()\n", " for pipe in map(Pipe, lines):\n", " new = pipe.ps | {pipe.p}\n", " merged = {g for g in groups if g & new}\n", " groups -= merged\n", " groups |= {frozenset(reduce(operator.ior, merged, new))}\n", " return groups\n", "\n", "def pipe_count(lines):\n", " return len(next(p for p in groups(lines) if 0 in p))\n", "\n", "example(pipe_count(here_lines('''\n", "0 <-> 2\n", "1 <-> 1\n", "2 <-> 0, 3, 4\n", "3 <-> 2, 4\n", "4 <-> 2, 3, 6\n", "5 <-> 6\n", "6 <-> 4, 5''')), 6)\n", "\n", "pipe_count(Input(12).lines)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Part 2" ] }, { "cell_type": "code", "execution_count": 33, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "186" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "example(len(groups(here_lines('''\n", "0 <-> 2\n", "1 <-> 1\n", "2 <-> 0, 3, 4\n", "3 <-> 2, 4\n", "4 <-> 2, 3, 6\n", "5 <-> 6\n", "6 <-> 4, 5'''))), 2)\n", "\n", "len(groups(Input(12).lines))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Day 13: [Packet Scanners](http://adventofcode.com/2017/day/13)\n", "\n", "This ended up being overkill. It's not necessary to know the scanner position, just whether it's at zero." ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1928" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Layer = converting_tuple('layer',\n", " matcher=r'(?P\\d+): (?P\\d+)',\n", " depth=int, range=int)\n", "\n", "def read_layers(lines):\n", " return {l.depth: l.range for l in map(Layer, lines)}\n", "\n", "def iter_caught(layers, delay=0):\n", " def pos(l, t):\n", " \"Scanner position of layer #l at time t; None if no layer.\"\n", " if l not in layers:\n", " return None\n", " r = layers[l]\n", " p = t % (2 * (r - 1))\n", " if p >= r - 1:\n", " p = r - 2 - (p - r)\n", " return p\n", " for i in range(max(layers.keys()) + 1):\n", " if 0 == pos(i, i + delay):\n", " yield i, layers[i]\n", "\n", "def severity(lines):\n", " layers = read_layers(lines)\n", " return sum(l * r for l, r in iter_caught(layers))\n", "\n", "EXAMPLE13 = here_lines('''\n", "0: 3\n", "1: 2\n", "4: 4\n", "6: 4''')\n", "\n", "example(severity(EXAMPLE13), 24)\n", "\n", "severity(Input(13).lines)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Part 2" ] }, { "cell_type": "code", "execution_count": 35, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "3830344" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from itertools import count\n", "\n", "def caught(layers, delay=0):\n", " return next((l * r for l, r in iter_caught(layers, delay=delay)), None) is not None\n", "\n", "ex13 = read_layers(EXAMPLE13)\n", "\n", "example(caught(ex13, delay=0), True)\n", "example(caught(ex13, delay=10), False)\n", "\n", "def find_delay(layers):\n", " return next(i for i in count() if not caught(layers, delay=i))\n", "\n", "example(find_delay(ex13), 10)\n", "\n", "find_delay(read_layers(Input(13).lines))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Day 14: [Disk Defragmentation](http://adventofcode.com/2017/day/14)" ] }, { "cell_type": "code", "execution_count": 36, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "##.#.#..\n", ".#.#.#.#\n", "....#.#.\n", "#.#.##.#\n", ".##.#...\n", "##..#..#\n", ".#...#..\n", "##.#.##.\n" ] }, { "data": { "text/plain": [ "8292" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def hash_bin_digits(h, places=128):\n", " s = bin(int(h, 16))[2:]\n", " yield from (c == '1' for c in '0' * (places - len(s)) + s)\n", "\n", "print('\\n'.join(''.join('#' if c else '.'\n", " for c in hash_bin_digits(knot_hash2('flqrgnkx-{}'.format(i))))[:8]\n", " for i in range(8)))\n", "\n", "example(\n", " sum(d for i in range(128) for d in hash_bin_digits(knot_hash2('flqrgnkx-{}'.format(i)))),\n", " 8108)\n", "\n", "sum(d for i in range(128) for d in hash_bin_digits(knot_hash2('ugkiagan-{}'.format(i))))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Part 2" ] }, { "cell_type": "code", "execution_count": 37, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "1069" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def count_regions(key):\n", " dirs = (0, -1j, 1j, -1, 1)\n", " regions = set()\n", " squares = (i+j*1j\n", " for j in range(128)\n", " for i, d in enumerate(hash_bin_digits(knot_hash2('{}-{}'.format(key, j))))\n", " if d)\n", " for s in squares:\n", " used = frozenset(s + d for d in dirs)\n", " overlaps = [r for r in regions if s in r]\n", " if overlaps:\n", " regions -= set(overlaps)\n", " regions.add(reduce(operator.ior, overlaps, used))\n", " else:\n", " regions.add(used)\n", " return len(regions)\n", "\n", "example(count_regions('flqrgnkx'), 1242)\n", "\n", "count_regions('ugkiagan')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Day 15: [Dueling Generators](http://adventofcode.com/2017/day/15)" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "650" ] }, "execution_count": 38, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def gen(factor, start):\n", " yield from repeatedly(lambda n:n * factor % 2147483647, start)\n", "\n", "def matches(starts, count):\n", " factors = (16807, 48271)\n", " g1, g2 = map(gen, factors, starts)\n", " return sum((a ^ b) & 0xffff == 0 for a, b in islice(zip(g1, g2), count))\n", "\n", "example(matches((65, 8921), 5))\n", "\n", "matches((783, 325), int(4e7))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Part 2" ] }, { "cell_type": "code", "execution_count": 39, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "336" ] }, "execution_count": 39, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def gen2(factor, start, divisor):\n", " yield from (n for n in gen(factor, start) if n % divisor == 0)\n", "\n", "def matches2(starts, count):\n", " factors = (16807, 48271)\n", " divisors = (4, 8)\n", " g1, g2 = map(gen2, factors, starts, divisors)\n", " return sum((a ^ b) & 0xffff == 0 for a, b in islice(zip(g1, g2), count))\n", "\n", "example(matches2((65, 8921), 1055), 0)\n", "example(matches2((65, 8921), 1056), 1)\n", "example(matches2((65, 8921), int(5e6)), 309)\n", "\n", "matches2((783, 325), int(5e6))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Day 16: [Permutation Promenade](http://adventofcode.com/2017/day/16)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "I made things unnecessarily difficult by making `apply_move` a pure function that constructs a new string, instead of mutating a list.\n", "\n", "The latter could have been done with `state[i], state[j] = state[j], state[i]`." ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'glnacbhedpfjkiom'" ] }, "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def apply_move(move, state):\n", " m, a = move[0], move[1:]\n", " if '/' in a: a, b = a.split('/')\n", " if m == 's': return rot(state, -int(a))\n", " elif m == 'x': i, j = int(a), int(b)\n", " elif m == 'p': i, j = state.index(a), state.index(b)\n", " else: raise Exception(\"unknown move {} in {}\".format(m, move))\n", " i, j = sorted([i, j])\n", " a, b = state[i:i+1], state[j:j+1]\n", " pre, mid, post = state[:i], state[i+1:j], state[j+1:]\n", " return pre + b + mid + a + post\n", "\n", "example(apply_move('s3', 'abcde'), 'cdeab')\n", "example(apply_move('s1', 'abcde'), 'eabcd')\n", "example(apply_move('x3/4', 'eabcd'), 'eabdc')\n", "example(apply_move('pe/b', 'eabdc'), 'baedc')\n", "\n", "def apply_moves(moves, state):\n", " for m in moves.split(','):\n", " state = apply_move(m, state)\n", " return state\n", "\n", "example(apply_moves('s1,x3/4,pe/b', 'abcde'), 'baedc')\n", "\n", "apply_moves(Input(16), 'abcdefghijklmnop')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Part 2" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'fmpanloehgkdcbji'" ] }, "execution_count": 41, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def find_period(moves, state):\n", " initial = state\n", " for i in count(1):\n", " state = apply_moves(moves, state)\n", " if state == initial:\n", " return i\n", "\n", "def nth_state(moves, state, n=10**9):\n", " p = find_period(moves, state)\n", " for _ in range(n % p):\n", " state = apply_moves(moves, state)\n", " return state\n", "\n", "nth_state(Input(16), 'abcdefghijklmnop')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Alternative\n", "This approach relies more on higher-order functions and iterators." ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'fmpanloehgkdcbji'" ] }, "execution_count": 42, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def find_period(moves, state):\n", " states = repeatedly(lambda s:apply_moves(moves, s), state)\n", " return 1 + iter_index(states, state)\n", "\n", "def nth_state(moves, state, n=10**9):\n", " p = find_period(moves, state)\n", " states = repeatedly(lambda s:apply_moves(moves, s), state)\n", " return iter_nth(states, n % p - 1)\n", "\n", "nth_state(Input(16), 'abcdefghijklmnop')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Day 17: [Spinlock](http://adventofcode.com/2017/day/17)" ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "600" ] }, "execution_count": 43, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def spinlock(stride, count):\n", " buf = []\n", " pos = 0\n", " for i in range(count):\n", " buf.insert(pos + 1, i)\n", " pos += stride + 1\n", " pos %= len(buf)\n", " return buf\n", "\n", "example(spinlock(3, 4), [0, 2, (3), 1])\n", "example(spinlock(3, 10), [0, (9), 5, 7, 2, 4, 3, 8, 6, 1])\n", "\n", "def after(steps, last=2017):\n", " buf = spinlock(steps, last+1)\n", " return (buf + buf)[buf.index(last) + 1]\n", "\n", "example(after(3), 638)\n", "\n", "after(337)" ] }, { "cell_type": "code", "execution_count": 44, "metadata": { "scrolled": false }, "outputs": [ { "data": { "text/plain": [ "31220910" ] }, "execution_count": 44, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from numba import jit\n", "\n", "@jit # 9.19 s -> 427 ms\n", "def spinlock2(stride, last):\n", " buflen = 0\n", " pos = 0\n", " x = None\n", " for i in range(last + 1):\n", " if pos == 0:\n", " x = i\n", " buflen += 1\n", " pos += stride + 1\n", " pos %= buflen\n", " return x\n", "\n", "spinlock2(337, 50000000)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Day 18: [Duet](http://adventofcode.com/2017/day/18)" ] }, { "cell_type": "code", "execution_count": 45, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "3188" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from collections import defaultdict\n", "import string\n", "import operator\n", "\n", "ops = {'add': operator.add, 'mul': operator.mul, 'mod': operator.mod}\n", "\n", "def iplay(lines):\n", " R = defaultdict(int)\n", " snd = None\n", " pc = 0\n", " while 0 <= pc < len(lines):\n", " line = lines[pc]\n", " op, *args = line.split()\n", " if len(args) < 2: args += args\n", " x, y = args\n", " X = R[x]\n", " Y = int(y) if y[0] in string.digits + '-' else R[y]\n", " if op in ops: R[x] = ops[op](X, Y)\n", " elif op == 'snd': snd = X\n", " elif op == 'set': R[x] = Y\n", " elif op == 'rcv':\n", " if X > 0: yield snd\n", " elif op == 'jgz':\n", " if X > 0: pc += Y - 1\n", " else: raise Exception(\"Unknown op: {} in {}\".format(op, lines[pc]))\n", " pc += 1\n", "\n", "def play(lines):\n", " return next(iplay(lines))\n", "\n", "example(play(here_lines('''\n", "set a 1\n", "add a 2\n", "mul a a\n", "mod a 5\n", "snd a\n", "set a 0\n", "rcv a\n", "jgz a -1\n", "set a 1\n", "jgz a -2''')), 4)\n", "\n", "play(Input(18).lines)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Part 2" ] }, { "cell_type": "code", "execution_count": 46, "metadata": { "collapsed": true }, "outputs": [ { "data": { "text/plain": [ "7112" ] }, "execution_count": 46, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from queue import Queue\n", "\n", "def performer(lines, n, qin, qout):\n", " def ev(s): return int(s) if s[0] in string.digits + '-' else R[s]\n", " R = defaultdict(int, {'p': n})\n", " pc = 0\n", " while 0 <= pc < len(lines):\n", " line = lines[pc]\n", " op, x, y, *_ = line.split() + ['0']\n", " X, Y = ev(x), ev(y)\n", " def assign(val): R[x] = val\n", " yield op\n", " if op in ops: assign(ops[op](X, Y))\n", " elif op == 'snd': qout.put(X)\n", " elif op == 'set': assign(Y)\n", " elif op == 'rcv':\n", " while qin.empty():\n", " yield 'blocked'\n", " assign(qin.get())\n", " elif op == 'jgz':\n", " if X > 0:\n", " pc += Y - 1\n", " else:\n", " raise Exception(\"Unknown op: {} in {}\".format(op, lines[pc]))\n", " pc += 1\n", "\n", "def duet(lines):\n", " q1, q2 = Queue(), Queue()\n", " g1 = performer(lines, 0, q2, q1)\n", " g2 = performer(lines, 1, q1, q2)\n", " s1 = s2 = None\n", " n = 0\n", " while s1 != 'blocked' or s2 != 'blocked':\n", " s1 = next(g1)\n", " s2 = next(g2)\n", " if s2 == 'snd':\n", " n += 1\n", " return n\n", "\n", "example(duet(here_lines('''\n", "snd 1\n", "snd 2\n", "snd p\n", "rcv a\n", "rcv b\n", "rcv c\n", "rcv d''')), 3)\n", "\n", "duet(Input(18).lines)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Alternative" ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "7112" ] }, "execution_count": 47, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from itertools import takewhile, zip_longest\n", "\n", "def duet(lines):\n", " q1, q2 = Queue(), Queue()\n", " g1 = performer(lines, 0, q2, q1)\n", " g2 = performer(lines, 1, q1, q2)\n", " all_blocked_set = {'blocked'}\n", " any_running = lambda states:set(states) != all_blocked_set\n", " state_pairs = takewhile(any_running, zip_longest(g1, g2))\n", " return sum(s == 'snd' for _, s in state_pairs)\n", "\n", "example(duet(here_lines('''\n", "snd 1\n", "snd 2\n", "snd p\n", "rcv a\n", "rcv b\n", "rcv c\n", "rcv d''')), 3)\n", "\n", "duet(Input(18).lines)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Day 19: [A Series of Tubes](http://adventofcode.com/2017/day/19)" ] }, { "cell_type": "code", "execution_count": 48, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "'KGPTMEJVS'" ] }, "execution_count": 48, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import string\n", "\n", "def ifollow(rows):\n", " # which cell values are valid for a given direction\n", " valid = {d: string.ascii_letters + '+' + ('-' if d.real else '|')\n", " for d in (1, -1, 1j, -1j)}\n", " p, d = rows[0].index('|'), 1j # start at the first-row |, heading south\n", " def at(p):\n", " try:\n", " return rows[int(p.imag)][int(p.real)]\n", " except IndexError:\n", " return ' '\n", " while True:\n", " p0 = p\n", " p += d\n", " c = at(p)\n", " if c == ' ':\n", " for d1 in (1j * d, -1j * d):\n", " p1 = p0 + d1\n", " if at(p1) in valid[d1]:\n", " p, d = p1, d1\n", " c = at(p) or ' '\n", " break\n", " if c == ' ': return\n", " yield p, c\n", "\n", "def follow(rows):\n", " return ''.join(c for _, c in ifollow(rows) if c in string.ascii_letters)\n", "\n", "example(follow(here_lines('''\n", " | \n", " | +--+ \n", " A | C \n", " F---|----E|--+ \n", " | | | D \n", " +B-+ +--+ \n", "''')), 'ABCDEF')\n", "\n", "follow(Input(19).lines)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Part 2" ] }, { "cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "16328" ] }, "execution_count": 49, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def steps(rows):\n", " return 1 + iter_len(ifollow(rows))\n", "\n", "example(steps(here_lines('''\n", " | \n", " | +--+ \n", " A | C \n", " F---|----E|--+ \n", " | | | D \n", " +B-+ +--+ \n", "''')), 38)\n", "\n", "steps(Input(19).lines)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Day 20: [Particle Swarm](http://adventofcode.com/2017/day/20)" ] }, { "cell_type": "code", "execution_count": 50, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "119" ] }, "execution_count": 50, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import numpy as np\n", "\n", "def closest(lines):\n", " S = np.array([list(map(int, re.findall(r'-?\\d+', line))) for line in lines])\n", " P, V, A = S[:,:3], S[:,3:6], S[:,6:]\n", " # TODO repeat until sign(P) or sign(V) == sign(V) && sign(V) or sign(A) == sign(A).\n", " # Then return the slowest particle.\n", " for _ in range(10000):\n", " V += A\n", " P += V\n", " return abs(P).sum(1).argmin()\n", "\n", "example(closest(here_lines('''\n", "p=< 3,0,0>, v=< 2,0,0>, a=<-1,0,0>\n", "p=< 4,0,0>, v=< 0,0,0>, a=<-2,0,0>\n", "''')), 0)\n", "\n", "closest(Input(20).lines)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Part 2" ] }, { "cell_type": "code", "execution_count": 51, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "471" ] }, "execution_count": 51, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def closest(lines):\n", " S = np.array([list(map(int, re.findall(r'-?\\d+', line))) for line in lines])\n", " P, V, A = (S[:,n:n+3] for n in range(0, 9, 3))\n", " for _ in range(1000):\n", " V += A\n", " P += V\n", " D = np.array([np.all(P == P[i,:], axis = 1) for i in range(S.shape[0])]).sum(axis=1)\n", " S = S[D == 1]\n", " return S.shape[0]\n", "\n", "example(closest(here_lines('''\n", "p=<-6,0,0>, v=< 3,0,0>, a=< 0,0,0> \n", "p=<-4,0,0>, v=< 2,0,0>, a=< 0,0,0>\n", "p=<-2,0,0>, v=< 1,0,0>, a=< 0,0,0>\n", "p=< 3,0,0>, v=<-1,0,0>, a=< 0,0,0>''')), 1)\n", "\n", "closest(Input(20).lines)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Day 21: [Fractal Art](http://adventofcode.com/2017/day/21)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This ended up with much more code than the other days. It was fun to right, but it makes me suspect I'm doing it wrong.\n", "\n", "Some of these methods are assertions and printable representations that aren't strictly necessary to compute the answer, but that I needed in order to keep from getting lost along the way. I'm not counting those as the “much more code”." ] }, { "cell_type": "code", "execution_count": 52, "metadata": { "scrolled": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ ".#.\n", "..#\n", "### \n", "\n", "transpose =\n", "..#\n", "#.#\n", ".## \n", "\n", "tiles =\n", "[[Grid('12/56'), Grid('9A/DE')], [Grid('34/78'), Grid('BC/FG')]] \n", "\n", "untile ->\n", "1234\n", "5678\n", "9ABC\n", "DEFG \n", "\n", "symmetries =\n", "[Grid('12/34'), Grid('34/12'), Grid('21/43'), Grid('43/21'), Grid('13/24'), Grid('24/13'), Grid('31/42'), Grid('42/31')]\n" ] } ], "source": [ "import operator\n", "from functools import reduce\n", "from itertools import product\n", "\n", "class Grid(list):\n", " \"A Grid is a sequence of sequences of characters.\"\n", " \n", " def __init__(self, rows):\n", " \"\"\"Construct a grid from a sequence of sequence of characters, a sequence of strings, or a string\n", " 'abc/def/ghi.\"\"\"\n", " if isinstance(rows, str):\n", " rows = rows.split('/')\n", " rows = list(map(list, rows))\n", " assert all(isinstance(c, str) and len(c) == 1 for row in rows for c in row), \"cells must be characters\"\n", " assert set(map(len, rows)) == {len(rows)}, \"must be square\"\n", " return super().__init__(rows)\n", "\n", " def __repr__(self):\n", " return 'Grid({!r})'.format('/'.join(map(''.join, self)))\n", " \n", " def __str__(self):\n", " return '\\n'.join(map(''.join, self))\n", " \n", " @property\n", " def key(self):\n", " \"A value (it happens to be a string) used as a key in the rule table.\"\n", " return '/'.join(map(''.join, self))\n", " \n", " def transpose(self):\n", " \"Return the transpose of the grid, as a grid.\"\n", " return Grid(zip(*self))\n", " \n", " @property\n", " def T(self):\n", " \"A property synonym for Grid.transpose(). By analogy with numpy.\"\n", " return self.transpose()\n", " \n", " @property\n", " def tiles(self):\n", " \"Find the smallest non-unary divisor N, and tile the grid into tiles N✖️N in size.\"\n", " N = len(self)\n", " w = next(i for i in count(2) if N % i == 0)\n", " return [[Grid(self[i+x][j:j+w] for x in range(w))\n", " for i in range(0, N, w)]\n", " for j in range(0, N, w)]\n", "\n", " @classmethod\n", " def untile(k, tiles):\n", " \"Create a Grid from a list of list of tiles.\"\n", " return k(reduce(operator.add, r) for t in zip(*tiles) for r in zip(*t))\n", "\n", " @property\n", " def symmetries(self):\n", " \"Generate the dihedral symmetries.\"\n", " flips = (lambda x:x, lambda x:x[::-1])\n", " for g, hflip, vflip in product((self, self.T), flips, flips):\n", " yield Grid(vflip(list(map(hflip, g))))\n", "\n", "print(Grid('.#./..#/###'), '\\n')\n", "print('transpose ='); print(Grid('.#./..#/###').T, '\\n')\n", "print('tiles ='); print(Grid('1234/5678/9ABC/DEFG').tiles, '\\n')\n", "print('untile ->'); print(Grid.untile(Grid('1234/5678/9ABC/DEFG').tiles), '\\n')\n", "print('symmetries ='); print(list(Grid('12/34').symmetries))" ] }, { "cell_type": "code", "execution_count": 53, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "{'###/#../.#.': Grid('#..#/..../..../#..#'),\n", " '###/..#/.#.': Grid('#..#/..../..../#..#'),\n", " '##./#.#/#..': Grid('#..#/..../..../#..#'),\n", " '#../#.#/##.': Grid('#..#/..../..../#..#'),\n", " '#./..': Grid('##./#../...'),\n", " '.##/#.#/..#': Grid('#..#/..../..../#..#'),\n", " '.#./#../###': Grid('#..#/..../..../#..#'),\n", " '.#./..#/###': Grid('#..#/..../..../#..#'),\n", " '.#/..': Grid('##./#../...'),\n", " '..#/#.#/.##': Grid('#..#/..../..../#..#'),\n", " '../#.': Grid('##./#../...'),\n", " '../.#': Grid('##./#../...')}" ] }, "execution_count": 53, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def parse_rules(lines):\n", " return {sym.key: Grid(rhs)\n", " for line in lines\n", " for lhs, rhs in [line.split(' => ')]\n", " for sym in Grid(lhs).symmetries}\n", "\n", "EXAMPLE_RULES = here_lines('''\n", "../.# => ##./#../...\n", ".#./..#/### => #..#/..../..../#..#''')\n", "\n", "parse_rules(EXAMPLE_RULES)" ] }, { "cell_type": "code", "execution_count": 54, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Grid('#..#/..../..../#..#')" ] }, "execution_count": 54, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def enhance(grid, rules):\n", " return Grid.untile([rules[tile.key] for tile in row] for row in grid.tiles)\n", "\n", "enhance(Grid('.#./..#/###'), parse_rules(EXAMPLE_RULES))" ] }, { "cell_type": "code", "execution_count": 55, "metadata": { "scrolled": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "##.##.\n", "#..#..\n", "......\n", "##.##.\n", "#..#..\n", "......\n" ] }, { "data": { "text/plain": [ "186" ] }, "execution_count": 55, "metadata": {}, "output_type": "execute_result" } ], "source": [ "FRACTAL_ART_INITIAL = Grid('.#./..#/###')\n", "\n", "def art(rules='', n=1, grid=FRACTAL_ART_INITIAL):\n", " rules = parse_rules(rules)\n", " for _ in range(n):\n", " grid = enhance(grid, rules)\n", " return grid\n", "\n", "def count_pixels(rules, n=5):\n", " return str(art(rules=rules, n=n)).count('#')\n", "\n", "print(art(EXAMPLE_RULES, 2))\n", "\n", "example(count_pixels(EXAMPLE_RULES, 2), 12)\n", "\n", "count_pixels(Input(21).lines)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Part 2" ] }, { "cell_type": "code", "execution_count": 56, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3018423" ] }, "execution_count": 56, "metadata": {}, "output_type": "execute_result" } ], "source": [ "count_pixels(Input(21).lines, n=18)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Day 22: [Sporifica Virus](http://adventofcode.com/2017/day/22)" ] }, { "cell_type": "code", "execution_count": 57, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "5330" ] }, "execution_count": 57, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def show(infections, p):\n", " #x0, x1, y0, y1 = map(int, (m(map(coord, infections))\n", " # for coord in (lambda c:c.real, lambda c:c.imag)\n", " # for m in (min, max)))\n", " x0, x1, y0, y1 = -5, 5, -5, 5\n", " print('-' * 60)\n", " for y in range(y0, y1 + 1):\n", " print(''.join(([' {} ', '[{}]'][-y + x*1j == p]).format('.#'[-y + x*1j in infections])\n", " for x in range(x0, x1 + 2)))\n", "\n", "def virus(lines, n=10000):\n", " enum = lambda seq:enumerate(seq, -(len(seq) // 2))\n", " infections = {-y + x*1j\n", " for y, line in enum(lines)\n", " for x, c in enum(line)\n", " if c == '#'}\n", " p, d = 0, 1\n", " a = 0\n", " for _ in range(n):\n", " infected = p in infections\n", " d *= 1j if infected else -1j\n", " (set.remove if infected else set.add)(infections, p)\n", " if not infected: a += 1\n", " p += d\n", " return a\n", "\n", "EXAMPLE22 = here_lines('''\n", "..#\n", "#..\n", "...''')\n", "\n", "example(virus(EXAMPLE22, 70), 41)\n", "\n", "virus(Input(22).lines)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Part 2" ] }, { "cell_type": "code", "execution_count": 58, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "2512103" ] }, "execution_count": 58, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from collections import defaultdict\n", "\n", "def show(states, p):\n", " print('-' * 60)\n", " x0, x1, y0, y1 = -5, 5, -5, 5\n", " for y in range(y0, y1 + 1):\n", " print(''.join(([' {} ', '[{}]'][-y + x*1j == p]).format(states[-y + x*1j]) for x in range(x0, x1 + 2)))\n", "\n", "def virus(lines, n=10000000):\n", " enum = lambda seq:enumerate(seq, -(len(seq) // 2))\n", " states = defaultdict(lambda:'.',\n", " ((-y + x*1j, c)\n", " for y, line in enum(lines)\n", " for x, c in enum(line)))\n", " transitions = {'.': ('W', -1j), 'W': ('#', 1), '#': ('F', 1j), 'F': ('.', -1)}\n", " counts = defaultdict(int)\n", " p, d = 0, 1\n", " for _ in range(n):\n", " s, t = transitions[states[p]]\n", " counts[s] += 1\n", " states[p] = s\n", " d *= t\n", " p += d\n", " return counts['#']\n", "\n", "example(virus(EXAMPLE22, 100), 26)\n", "\n", "virus(Input(22).lines)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Day 23: [Coprocessor Conflagration](http://adventofcode.com/2017/day/23)" ] }, { "cell_type": "code", "execution_count": 59, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3969" ] }, "execution_count": 59, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def co1(lines, a=0):\n", " def ev(s): return int(s) if s[0] in string.digits + '-' else R[s]\n", " R = defaultdict(int, {'a': 0})\n", " pc = 0\n", " while 0 <= pc < len(lines):\n", " line = lines[pc]\n", " op, x, y, *_ = line.split()\n", " yield pc, op, x, y, R\n", " X, Y = ev(x), ev(y)\n", " if op == 'set': R[x] = Y\n", " elif op == 'sub': R[x] -= Y\n", " elif op == 'mul': R[x] *= Y\n", " elif op == 'jnz':\n", " if X != 0:\n", " pc += Y - 1\n", " else: raise Exception(\"Unknown op: {} in {}\".format(op, lines[pc]))\n", " pc += 1\n", "\n", "sum(op == 'mul' for _, op, *_ in co1(Input(23).lines))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Part 2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "I punted on part 2. It's obviously engineered to take too long to simulate. The right way to do it is either to disassemble the code, or run it for a while and look for patterns. Neither of these looked fun, so I'm slowly developing code to collect a higher-level view of what's going on instead." ] }, { "cell_type": "code", "execution_count": 60, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "14 True a=0 (0) b=65 (0) c=65 (0) d=2 (0) e=2 (0) f=1 (0) g=-61 (0)\n", "19 True a=0 (0) b=65 (0) c=65 (0) d=2 (0) e=3 (1) f=1 (0) g=-62 (-1)\n", "14 True a=0 (0) b=65 (0) c=65 (0) d=2 (0) e=3 (0) f=1 (0) g=-59 (3)\n", "19 True a=0 (0) b=65 (0) c=65 (0) d=2 (0) e=4 (1) f=1 (0) g=-61 (-2)\n", "14 True a=0 (0) b=65 (0) c=65 (0) d=2 (0) e=4 (0) f=1 (0) g=-57 (4)\n" ] } ], "source": [ "countdown = 5\n", "spies = [14, 19]\n", "prev = {}\n", "for pc, op, x, y, R in co1(Input(23).lines, 1):\n", " #print(op, x, R.get(y, y), ' '.join(map(lambda x:'{}={}'.format(x[0], x[1]), sorted(R.items()))))\n", " if (pc in spies if spies else op == 'jnz'):\n", " #print(pc, R[x] != 0, ' '.join(map(lambda kv:'{}={}'.format(*kv), sorted(R.items()))))\n", " print(pc, R[x] != 0, ' '.join(map(lambda kv:'{}={} ({})'.format(kv[0], kv[1], kv[1] - prev.get(kv[0], kv[1])), sorted(R.items()))))\n", " prev = R.copy()\n", " if R[x] == 0: break\n", " countdown -= 1\n", " if countdown <= 0: break" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## [Day 24: Electromagnetic Moat](http://adventofcode.com/2017/day/24)" ] }, { "cell_type": "code", "execution_count": 61, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "1868" ] }, "execution_count": 61, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def bridges(lines):\n", " components = list(tuple(map(int, l.split('/'))) for l in lines)\n", " # Assume <=1 component per (a, b) pair. Else rewrite to use [] for components\n", " # or add a uid.\n", " assert len(components) == len(set(components))\n", " d = defaultdict(set) # {port: [components that connect to that port]}\n", " for c in components:\n", " for p in c:\n", " d[p].add(c)\n", " def extend(bridge, p):\n", " \"Generate all the bridges that extend bridge, that ends at port p.\"\n", " yield bridge\n", " yield from (b\n", " for c in d[p]\n", " if c not in bridge\n", " for b in extend(bridge + (c,), sorted(c, key=lambda p0:p0==p)[0]))\n", " return extend((), 0)\n", "\n", "def strength(bridge):\n", " return sum(p for c in bridge for p in c)\n", "\n", "def strongest(lines):\n", " return max(map(strength, bridges(lines)))\n", "\n", "data24 = here_lines('''\n", "0/2\n", "2/2\n", "2/3\n", "3/4\n", "3/5\n", "0/1\n", "10/1\n", "9/10\n", "''')\n", "\n", "example(strongest(data24), 31)\n", "\n", "strongest(Input(24).lines)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The function above was re-written after a false start involving dynamic programming. Building a table of bridge $i \\leftrightarrow j$, and iteratively joining $i \\leftrightarrow j \\leftrightarrow k$, ended up taking longer than the implementation above.\n", "\n", "Using `set` for the component and/or bridge produces only a marginal improvement.\n", "Using a dotted pair class (with `__slots__`) is about 50% slower." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Part 2" ] }, { "cell_type": "code", "execution_count": 62, "metadata": { "collapsed": true }, "outputs": [ { "data": { "text/plain": [ "1841" ] }, "execution_count": 62, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def strongest_longest(lines):\n", " return strength(max(bridges(lines), key=lambda b:(len(b), strength(b))))\n", "\n", "example(strongest_longest(data24), 19)\n", "\n", "strongest_longest(Input(24).lines)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Day 25: [The Halting Problem](http://adventofcode.com/2017/day/25)" ] }, { "cell_type": "code", "execution_count": 63, "metadata": {}, "outputs": [], "source": [ "MACHINE_SOURCE = '''\n", "Begin in state A.\n", "Perform a diagnostic checksum after 6 steps.\n", "\n", "In state A:\n", " If the current value is 0:\n", " - Write the value 1.\n", " - Move one slot to the right.\n", " - Continue with state B.\n", " If the current value is 1:\n", " - Write the value 0.\n", " - Move one slot to the left.\n", " - Continue with state B.\n", "\n", "In state B:\n", " If the current value is 0:\n", " - Write the value 1.\n", " - Move one slot to the left.\n", " - Continue with state A.\n", " If the current value is 1:\n", " - Write the value 1.\n", " - Move one slot to the right.\n", " - Continue with state A.'''.strip()" ] }, { "cell_type": "code", "execution_count": 64, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3145" ] }, "execution_count": 64, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import re\n", "from collections import defaultdict, Counter\n", "\n", "def parse(source):\n", " q0 = re.match(r'Begin in state (\\S+?)\\.', source).group(1)\n", " N = int(re.search(r'Perform a diagnostic checksum after (\\d+)', source).group(1))\n", " matcher = re.compile('.*?'.join([r'If the current value is (\\w+):',\n", " r'Write the value (\\w+)',\n", " r'Move .*? to the (\\w+)',\n", " 'Continue with state (\\w+)']),\n", " re.DOTALL)\n", " def transitions(text):\n", " return {s: t for s, *t in matcher.findall(text)}\n", " verses = re.findall(r'In state (\\S+?):(.+?)(?:\\n\\n|$)', source, re.DOTALL)\n", " return q0, N, {q: transitions(body) for q, body in verses}\n", "\n", "def run(source):\n", " q, N, δ = parse(source)\n", " Γ, j = defaultdict(lambda:'0'), 0\n", " for _ in range(N):\n", " Γ[j], shift, q = δ[q][Γ[j]]\n", " j += {'left': -1, 'right': 1}[shift]\n", " return Counter(Γ.values())['1']\n", "\n", "example(run(MACHINE_SOURCE), 3)\n", "\n", "run(Input(25))" ] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.5.2" } }, "nbformat": 4, "nbformat_minor": 2 }