', 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
}