Advent 2017 through Day 20

This commit is contained in:
Peter Norvig authored and GitHub committed 2017-12-20 00:33:59 -08:00
1 parent 9fe15e5415
commit 8a82f9afe4
1 file changed
+292 -154
+292 -154
View File
@@ -49,6 +49,7 @@
"from itertools import (permutations, combinations, chain, cycle, product, islice, \n", "from itertools import (permutations, combinations, chain, cycle, product, islice, \n",
" takewhile, zip_longest, count as count_from)\n", " takewhile, zip_longest, count as count_from)\n",
"from heapq import heappop, heappush\n", "from heapq import heappop, heappush\n",
"from numba import jit\n",
"\n", "\n",
"identity = lambda x: x\n", "identity = lambda x: x\n",
"letters = 'abcdefghijklmnopqrstuvwxyz'\n", "letters = 'abcdefghijklmnopqrstuvwxyz'\n",
@@ -66,21 +67,25 @@
"def Input(day, year=2017):\n", "def Input(day, year=2017):\n",
" \"Open this day's input file.\"\n", " \"Open this day's input file.\"\n",
" return open('data/advent{}/input{}.txt'.format(year, day))\n", " return open('data/advent{}/input{}.txt'.format(year, day))\n",
"\n",
"def Inputstr(day, year=2017): \n",
" \"The contents of this day's input file as a str.\"\n",
" return Input(day, year).read().rstrip('\\n')\n",
" \n", " \n",
"def array(lines):\n", "def Array(lines):\n",
" \"Parse an iterable of str lines into a 2-D array. If `lines` is a str, splitlines.\"\n", " \"Parse an iterable of str lines into a 2-D array. If `lines` is a str, splitlines.\"\n",
" if isinstance(lines, str): lines = lines.splitlines()\n", " if isinstance(lines, str): lines = lines.splitlines()\n",
" return mapt(vector, lines)\n", " return mapt(Vector, lines)\n",
"\n", "\n",
"def vector(line):\n", "def Vector(line):\n",
" \"Parse a str into a tuple of atoms (numbers or str tokens).\"\n", " \"Parse a str into a tuple of atoms (numbers or str tokens).\"\n",
" return mapt(atom, line.replace(',', ' ').split())\n", " return mapt(Atom, line.replace(',', ' ').split())\n",
"\n", "\n",
"def integers(text): \n", "def Integers(text): \n",
" \"Return a tuple of all integers in a string.\"\n", " \"Return a tuple of all integers in a string.\"\n",
" return mapt(int, re.findall(r'\\b[-+]?\\d+\\b', text))\n", " return mapt(int, re.findall(r'-?\\b\\d+\\b', text))\n",
"\n", "\n",
"def atom(token):\n", "def Atom(token):\n",
" \"Parse a str token into a number, or leave it as a str.\"\n", " \"Parse a str token into a number, or leave it as a str.\"\n",
" try:\n", " try:\n",
" return int(token)\n", " return int(token)\n",
@@ -160,6 +165,10 @@
" \"Count how many times the predicate is true.\"\n", " \"Count how many times the predicate is true.\"\n",
" return sum(map(pred, iterable))\n", " return sum(map(pred, iterable))\n",
"\n", "\n",
"def length(iterable):\n",
" \"Same as len(list(iterable)), but without consuming memory.\"\n",
" return sum(1 for _ in iterable)\n",
"\n",
"def shuffled(iterable):\n", "def shuffled(iterable):\n",
" \"Create a new list out of iterable, and shuffle it.\"\n", " \"Create a new list out of iterable, and shuffle it.\"\n",
" new = list(iterable)\n", " new = list(iterable)\n",
@@ -194,7 +203,7 @@
" return range(start, end + 1)\n", " return range(start, end + 1)\n",
"\n", "\n",
"def floats(start, end, step=1.0):\n", "def floats(start, end, step=1.0):\n",
" \"Yields from start to end (inclusive), by increments of step.\"\n", " \"Yield floats from start to end (inclusive), by increments of step.\"\n",
" m = (1.0 if step >= 0 else -1.0)\n", " m = (1.0 if step >= 0 else -1.0)\n",
" while start * m <= end * m:\n", " while start * m <= end * m:\n",
" yield start\n", " yield start\n",
@@ -236,13 +245,13 @@
" (x-1, y), (x+1, y),\n", " (x-1, y), (x+1, y),\n",
" (x-1, y+1), (x, y+1), (x+1, y+1))\n", " (x-1, y+1), (x, y+1), (x+1, y+1))\n",
"\n", "\n",
"def cityblock_distance(p, q=origin): \n", "def cityblock_distance(P, Q=origin): \n",
" \"Manhatten distance between two points.\"\n", " \"Manhatten distance between two points.\"\n",
" return abs(X(p) - X(q)) + abs(Y(p) - Y(q))\n", " return sum(abs(p - q) for p, q in zip(P, Q))\n",
"\n", "\n",
"def distance(p, q=origin): \n", "def distance(P, Q=origin): \n",
" \"Hypotenuse distance between two points.\"\n", " \"Hypotenuse distance between two points.\"\n",
" return math.hypot(X(p) - X(q), Y(p) - Y(q))\n", " return sum((p - q) ** 2 for p, q in zip(P, Q)) ** 0.5\n",
"\n", "\n",
"################ Debugging \n", "################ Debugging \n",
"\n", "\n",
@@ -263,6 +272,10 @@
"class Struct:\n", "class Struct:\n",
" \"A structure that can have any fields defined.\"\n", " \"A structure that can have any fields defined.\"\n",
" def __init__(self, **entries): self.__dict__.update(entries)\n", " def __init__(self, **entries): self.__dict__.update(entries)\n",
" def __repr__(self): \n",
" fields = ['{}={}'.format(f, self.__dict__[f]) \n",
" for f in sorted(self.__dict__)]\n",
" return 'Struct({})'.format(', '.join(fields))\n",
"\n", "\n",
"################ A* and Breadth-First Search (tracking states, not actions)\n", "################ A* and Breadth-First Search (tracking states, not actions)\n",
"\n", "\n",
@@ -310,10 +323,10 @@
"source": [ "source": [
"def tests():\n", "def tests():\n",
" # Functions for Input, Parsing\n", " # Functions for Input, Parsing\n",
" assert array('''1 2 3\n", " assert Array('''1 2 3\n",
" 4 5 6''') == ((1, 2, 3), \n", " 4 5 6''') == ((1, 2, 3), \n",
" (4, 5, 6))\n", " (4, 5, 6))\n",
" assert vector('testing 1 2 3.') == ('testing', 1, 2, 3.0)\n", " assert Vector('testing 1 2 3.') == ('testing', 1, 2, 3.0)\n",
" \n", " \n",
" # Functions on Iterables\n", " # Functions on Iterables\n",
" assert first('abc') == first(['a', 'b', 'c']) == 'a'\n", " assert first('abc') == first(['a', 'b', 'c']) == 'a'\n",
@@ -385,7 +398,7 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"2014" "(2014, (3, 2, 9, 4, 1, 9, 9, 4, 7, 1))"
] ]
}, },
"execution_count": 3, "execution_count": 3,
@@ -394,9 +407,10 @@
} }
], ],
"source": [ "source": [
"digits = mapt(int, '3294199471327195994824832197564859876682638188889768298894243832665654681412886862234525991553276578641265589959178414218389329361496673991614673626344552179413995562266818138372393213966143124914469397692587251112663217862879233226763533911128893354536353213847122251463857894159819828724827969576432191847787772732881266875469721189331882228146576832921314638221317393256471998598117289632684663355273845983933845721713497811766995367795857965222183668765517454263354111134841334631345111596131682726196574763165187889337599583345634413436165539744188866156771585647718555182529936669683581662398618765391487164715724849894563314426959348119286955144439452731762666568741612153254469131724137699832984728937865956711925592628456617133695259554548719328229938621332325125972547181236812263887375866231118312954369432937359357266467383318326239572877314765121844831126178173988799765218913178825966268816476559792947359956859989228917136267178571776316345292573489873792149646548747995389669692188457724414468727192819919448275922166321158141365237545222633688372891451842434458527698774342111482498999383831492577615154591278719656798277377363284379468757998373193231795767644654155432692988651312845433511879457921638934877557575241394363721667237778962455961493559848522582413748218971212486373232795878362964873855994697149692824917183375545192119453587398199912564474614219929345185468661129966379693813498542474732198176496694746111576925715493967296487258237854152382365579876894391815759815373319159213475555251488754279888245492373595471189191353244684697662848376529881512529221627313527441221459672786923145165989611223372241149929436247374818467481641931872972582295425936998535194423916544367799522276914445231582272368388831834437562752119325286474352863554693373718848649568451797751926315617575295381964426843625282819524747119726872193569785611959896776143539915299968276374712996485367853494734376257511273443736433464496287219615697341973131715166768916149828396454638596713572963686159214116763')\n", "digits = mapt(int, Inputstr(1))\n",
"N = len(digits)\n", "N = len(digits)\n",
"N" "\n",
"N, digits[:10]"
] ]
}, },
{ {
@@ -465,7 +479,7 @@
}, },
"outputs": [], "outputs": [],
"source": [ "source": [
"rows2 = array('''790\t99\t345\t1080\t32\t143\t1085\t984\t553\t98\t123\t97\t197\t886\t125\t947\n", "rows2 = Array('''790\t99\t345\t1080\t32\t143\t1085\t984\t553\t98\t123\t97\t197\t886\t125\t947\n",
"302\t463\t59\t58\t55\t87\t508\t54\t472\t63\t469\t419\t424\t331\t337\t72\n", "302\t463\t59\t58\t55\t87\t508\t54\t472\t63\t469\t419\t424\t331\t337\t72\n",
"899\t962\t77\t1127\t62\t530\t78\t880\t129\t1014\t93\t148\t239\t288\t357\t424\n", "899\t962\t77\t1127\t62\t530\t78\t880\t129\t1014\t93\t148\t239\t288\t357\t424\n",
"2417\t2755\t254\t3886\t5336\t3655\t5798\t3273\t5016\t178\t270\t6511\t223\t5391\t1342\t2377\n", "2417\t2755\t254\t3886\t5336\t3655\t5798\t3273\t5016\t178\t270\t6511\t223\t5391\t1342\t2377\n",
@@ -946,8 +960,8 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"CPU times: user 6.21 s, sys: 24.6 ms, total: 6.24 s\n", "CPU times: user 6.15 s, sys: 19.9 ms, total: 6.17 s\n",
"Wall time: 6.28 s\n" "Wall time: 6.19 s\n"
] ]
}, },
{ {
@@ -994,7 +1008,7 @@
}, },
"outputs": [], "outputs": [],
"source": [ "source": [
"banks = vector('4\t10\t4\t1\t8\t4\t9\t14\t5\t1\t14\t15\t0\t15\t3\t5')\n", "banks = Vector('4\t10\t4\t1\t8\t4\t9\t14\t5\t1\t14\t15\t0\t15\t3\t5')\n",
"\n", "\n",
"def realloc(banks):\n", "def realloc(banks):\n",
" \"How many cycles until we reach a configuration we've seen before?\"\n", " \"How many cycles until we reach a configuration we've seen before?\"\n",
@@ -1349,7 +1363,7 @@
"source": [ "source": [
"# [Day 8](https://adventofcode.com/2017/day/8): Memory Reallocation \n", "# [Day 8](https://adventofcode.com/2017/day/8): Memory Reallocation \n",
"\n", "\n",
"This one looks easy: a simple interpreter for straight-line code where each instruction has 7 tokens. It is nice that my `array` function parses the whole program." "This one looks easy: a simple interpreter for straight-line code where each instruction has 7 tokens. It is nice that my `Array` function parses the whole program."
] ]
}, },
{ {
@@ -1369,7 +1383,7 @@
} }
], ],
"source": [ "source": [
"program8 = array(Input(8))\n", "program8 = Array(Input(8))\n",
"\n", "\n",
"def run8(program):\n", "def run8(program):\n",
" \"Run the program and return final value of registers.\"\n", " \"Run the program and return final value of registers.\"\n",
@@ -1446,8 +1460,8 @@
} }
], ],
"source": [ "source": [
"text1 = re.sub(r'!.', '', Input(9).read()) # Delete canceled characters\n", "text1 = re.sub(r'!.', '', Inputstr(9)) # Delete canceled characters\n",
"text2 = re.sub(r'<.*?>', '', text1) # Delete garbage\n", "text2 = re.sub(r'<.*?>', '', text1) # Delete garbage\n",
"\n", "\n",
"text2[:70]" "text2[:70]"
] ]
@@ -1781,7 +1795,7 @@
} }
], ],
"source": [ "source": [
"path = vector(Input(11).read())\n", "path = Vector(Inputstr(11))\n",
"\n", "\n",
"def follow(path):\n", "def follow(path):\n",
" \"Follow each step of the path; return final distance to origin.\"\n", " \"Follow each step of the path; return final distance to origin.\"\n",
@@ -1854,7 +1868,7 @@
"def groups(lines):\n", "def groups(lines):\n",
" \"Dict of {i: {directly_connected_to_i}\"\n", " \"Dict of {i: {directly_connected_to_i}\"\n",
" return {lhs: {lhs} | set(rhs)\n", " return {lhs: {lhs} | set(rhs)\n",
" for (lhs, _, *rhs) in array(lines)}\n", " for (lhs, _, *rhs) in Array(lines)}\n",
" \n", " \n",
"assert groups(Input(12))[0] == {0, 659, 737}" "assert groups(Input(12))[0] == {0, 659, 737}"
] ]
@@ -1988,7 +2002,7 @@
} }
], ],
"source": [ "source": [
"scanners = mapt(integers, Input(13))\n", "scanners = mapt(Integers, Input(13))\n",
"scanners[:5]" "scanners[:5]"
] ]
}, },
@@ -2127,16 +2141,18 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 61, "execution_count": 61,
"metadata": { "metadata": {},
"collapsed": true
},
"outputs": [], "outputs": [],
"source": [ "source": [
"def Grid(key, N=128+2):\n", "def Grid(key, N=128+2):\n",
" \"Make a grid, with a border around it.\"\n", " \"Make a grid, with a border around it.\"\n",
" rows = [['0'] + list(bits(key, i)) + ['0'] \n", " return border('0', (list(bits(key, i)) for i in range(128)))\n",
" for i in range(128)]\n", "\n",
" empty = ['0'] * len(rows[0])\n", "def border(fill, grid):\n",
" \"Surround a grid with a border of fill cells.\"\n",
" rows = [[fill] + list(row) + [fill] \n",
" for row in grid]\n",
" empty = [fill] * len(rows[0])\n",
" return [empty] + rows + [empty]" " return [empty] + rows + [empty]"
] ]
}, },
@@ -2220,8 +2236,8 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"CPU times: user 24.8 s, sys: 64 ms, total: 24.9 s\n", "CPU times: user 15.2 s, sys: 18.4 ms, total: 15.3 s\n",
"Wall time: 24.9 s\n" "Wall time: 15.3 s\n"
] ]
}, },
{ {
@@ -2236,6 +2252,7 @@
} }
], ],
"source": [ "source": [
"@jit # This was the slowest solution; @jit helps a bit.\n",
"def gen(prev, factor, m=2147483647):\n", "def gen(prev, factor, m=2147483647):\n",
" \"Generate a sequence of numbers according to the rules; stop at 0.\"\n", " \"Generate a sequence of numbers according to the rules; stop at 0.\"\n",
" while prev:\n", " while prev:\n",
@@ -2243,7 +2260,7 @@
" yield prev\n", " yield prev\n",
" \n", " \n",
"def judge(A, B, N=40*10**6, mask=2**16-1): \n", "def judge(A, B, N=40*10**6, mask=2**16-1): \n",
" \"How many of the first N pairs from A and B agree in the last b bits?\"\n", " \"How many of the first N numbers from A and B agree in the masked bits (default last 16)?\"\n",
" return quantify(a & mask == b & mask\n", " return quantify(a & mask == b & mask\n",
" for (a, b, _) in zip(A, B, range(N)))\n", " for (a, b, _) in zip(A, B, range(N)))\n",
"\n", "\n",
@@ -2257,6 +2274,8 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Notice I also decided to use `@jit` (i.e. `numba.jit`) to speed things up, since this is the slowest-running day yet.\n",
"\n",
"**Part Two**\n", "**Part Two**\n",
"\n", "\n",
"A small change: only consider numbers that match the **criteria** of being divisible by 4 or 8, respectively;" "A small change: only consider numbers that match the **criteria** of being divisible by 4 or 8, respectively;"
@@ -2271,8 +2290,8 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"CPU times: user 16.3 s, sys: 23.3 ms, total: 16.4 s\n", "CPU times: user 9.52 s, sys: 9 ms, total: 9.53 s\n",
"Wall time: 16.4 s\n" "Wall time: 9.54 s\n"
] ]
}, },
{ {
@@ -2329,7 +2348,7 @@
} }
], ],
"source": [ "source": [
"dance = vector(Input(16).read())\n", "dance = Vector(Inputstr(16))\n",
"dance[:10]" "dance[:10]"
] ]
}, },
@@ -2385,7 +2404,7 @@
" for move in dance:\n", " for move in dance:\n",
" op, arg = move[0], move[1:]\n", " op, arg = move[0], move[1:]\n",
" if op == 's': D.rotate(int(arg))\n", " if op == 's': D.rotate(int(arg))\n",
" elif op == 'x': swap(*integers(arg))\n", " elif op == 'x': swap(*Integers(arg))\n",
" elif op == 'p': swap(D.index(arg[0]), D.index(arg[2]))\n", " elif op == 'p': swap(D.index(arg[0]), D.index(arg[2]))\n",
" return cat(D)\n", " return cat(D)\n",
" \n", " \n",
@@ -2539,7 +2558,7 @@
"\n", "\n",
"**Part Two**\n", "**Part Two**\n",
"\n", "\n",
"But Part Two is not so easy, if we care about the run time. Insertion into a `list` has to move all the elements after the insertion down, so insertion is O(N) and `spinlock` is O(N<sup>2</sup>). That's no problem when N = 2017, but a big problem when N is 50 million. We're gonna need need a bigger boat, where by \"boat\" I mean algorithm or data structure. My first thought is a (circular) linked list, because insertion is O(1). I can implement the three key methods: `skip` to move ahead, `insert` to add a new node after the current one, and `find` to find a piece of data (with a linear search):" "But Part Two is not so easy, if we care about the run time. Insertion into a `list` has to move all the elements after the insertion down, so insertion is O(N) and `spinlock` is O(N<sup>2</sup>). That's no problem when N = 2017, but when N is 50 million? We're gonna need a bigger boat, where by \"boat\" I mean algorithm or data structure. My first thought is a (circular) linked list, because insertion is O(1). I can implement the three key methods: `skip` to move ahead, `insert` to add a new node after the current one, and `find` to find a piece of data (with a linear search):"
] ]
}, },
{ {
@@ -2641,14 +2660,14 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"CPU times: user 1.4 s, sys: 5.52 ms, total: 1.4 s\n", "CPU times: user 1.57 s, sys: 7.74 ms, total: 1.58 s\n",
"Wall time: 1.4 s\n" "Wall time: 1.58 s\n"
] ]
}, },
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"<__main__.Node at 0x108a9c780>" "<__main__.Node at 0x111355438>"
] ]
}, },
"execution_count": 77, "execution_count": 77,
@@ -2681,8 +2700,8 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"CPU times: user 5.55 s, sys: 16.7 ms, total: 5.57 s\n", "CPU times: user 5.48 s, sys: 8.31 ms, total: 5.49 s\n",
"Wall time: 5.57 s\n" "Wall time: 5.49 s\n"
] ]
}, },
{ {
@@ -2714,7 +2733,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"The moral of the story is *keep your eyes on the prize*. I got distracted because I asked the wrong question. I asked myself \"what's wrong with my solution in `spinlock`?\" and answered myself \"insertion is O(N<sup>2</sup>) and it should be O(N).\" I knew how to do that, but that was the wrong question. I should have asked myself \"how do I solve Part Two quickly,\" concentrating on solving the actual problem, and avoiding getting fixated on my solution to Part One." "The moral of the story is *keep your eyes on the prize*. I got distracted because I asked the wrong question. I asked myself \"how can I make my solution in `spinlock` faster?\" and answered myself \"insertion is O(N<sup>2</sup>) and it should be O(N).\" I knew how to do that, with a linked list, but that was the right answer to the wrong question. I should have asked myself \"how do I solve Part Two quickly,\" concentrating on solving the actual problem. Once I did that, I realized I didn't need all those insertions: not doing them at all is a better idea than doing them faster."
] ]
}, },
{ {
@@ -2723,7 +2742,7 @@
"source": [ "source": [
"# [Day 18](https://adventofcode.com/2017/day/17): Duet\n", "# [Day 18](https://adventofcode.com/2017/day/17): Duet\n",
"\n", "\n",
"First, read and take a peek at the input:" "First, read the input, and take a peak at it:"
] ]
}, },
{ {
@@ -2738,7 +2757,12 @@
" ('set', 'a', 1),\n", " ('set', 'a', 1),\n",
" ('mul', 'p', 17),\n", " ('mul', 'p', 17),\n",
" ('jgz', 'p', 'p'),\n", " ('jgz', 'p', 'p'),\n",
" ('mul', 'a', 2))" " ('mul', 'a', 2),\n",
" ('add', 'i', -1),\n",
" ('jgz', 'i', -2),\n",
" ('add', 'a', -1),\n",
" ('set', 'i', 127),\n",
" ('set', 'p', 826))"
] ]
}, },
"execution_count": 79, "execution_count": 79,
@@ -2747,8 +2771,8 @@
} }
], ],
"source": [ "source": [
"program18 = array(Input(18))\n", "program18 = Array(Input(18))\n",
"program18[:5]" "program18[:10]"
] ]
}, },
{ {
@@ -2776,22 +2800,21 @@
], ],
"source": [ "source": [
"def run18(program):\n", "def run18(program):\n",
" \"Interpret the assembly language program.\"\n", " \"Interpret the assembly language program; return recovered `snd`.\"\n",
" regs = defaultdict(int)\n", " regs = defaultdict(int)\n",
" pc = sound = 0\n", " pc = snd = 0\n",
" while True:\n", " while True:\n",
" instr = program[pc]\n", " instr = program[pc]\n",
" pc += 1\n",
" op, x, y = instr[0], instr[1], instr[-1]\n", " op, x, y = instr[0], instr[1], instr[-1]\n",
" vy = value(regs, y)\n", " vy = value(regs, y)\n",
" if op == 'snd': sound = regs[x]\n", " if op == 'snd': snd = regs[x]\n",
" elif op == 'set': regs[x] = vy\n", " elif op == 'set': regs[x] = vy\n",
" elif op == 'add': regs[x] += vy\n", " elif op == 'add': regs[x] += vy\n",
" elif op == 'mul': regs[x] *= vy\n", " elif op == 'mul': regs[x] *= vy\n",
" elif op == 'mod': regs[x] %= vy\n", " elif op == 'mod': regs[x] %= vy\n",
" elif op == 'jgz' and regs[x] > 0: pc += vy - 1\n", " elif op == 'jgz' and regs[x] > 0: pc += vy - 1\n",
" elif op == 'rcv' and regs[x] != 0: \n", " elif op == 'rcv' and regs[x] != 0: return snd\n",
" return sound\n",
" pc += 1\n",
" \n", " \n",
"def value(regs, y): return (y if isinstance(y, int) else regs[y])\n", "def value(regs, y): return (y if isinstance(y, int) else regs[y])\n",
"\n", "\n",
@@ -2802,12 +2825,22 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"That was easy. (One tricky bit: after every instruction the `pc` is incremented by 1, so for the `'jgz'` jump instruction, the increment is `vy - 1`, not just `vy`, so that the `pc += 1` will fix it.)\n", "That was easy. (One tricky bit: the `pc` is incremented by 1 every time through the loop, regardless of the instruction. Therefore, the `'jgz'` jump instruction increments by \"`vy - 1`\" so that the net increment is \"`vy`\".)\n",
"\n", "\n",
"**Part Two**\n", "**Part Two**\n",
"\n", "\n",
"Now we have to run two copies of the program, and send messages between them. I'll break up the loop in `run18` into\n", "In Part Two we have to run two copies of the program, and send messages between them. I'll break up the loop in `run18` into\n",
"two functions. First, `run18_2`, creates (in `ps`) two structures to hold the state variables necessary to run a program (`Struct` is my generic constructor of structures with named fields). `run18_2` then repeatedly calls `step18(program, p)` to execute one instruction of `program` with the state variables in `p`. At each step I will choose randomly which of the two programs to advance. (I could just as easily have alternated.) The function exits when neither copy of the program can run, according to their status. A program can have status `run` when it is ready to execute an instruction, `wait` when it is waiting for a value to arrive in its input queue, or `end` for when the `pc` has run off the end of the program and it is terminated." "two functions. First, `run18_2`, creates (in `ps`) two structures to hold the state variables necessary to run a program:\n",
"- `id`: The id number (0 or 1) of this copy of the program.\n",
"- `pc`: The program counter.\n",
"- `sends`: A count of the number of `snd` instructions executed.\n",
"- `regs`: A dict of the program registers (`a` to `z`).\n",
"- `status`: A program has a status which can be:\n",
" * `'run'` when it is ready to execute an instruction, \n",
" * `'wait'` when it is waiting for a value to arrive in its input queue, or \n",
" * `'end'` when the `pc` has run off the end of the program and it has terminated.\n",
"\n",
"`run18_2` repeatedly calls the second function, `step18(program, p)` to execute one instruction of `program` with the state variables in `p`. I choose randomly which of the two programs to step on each iteration. The function exits when neither copy of the program can run, according to their status. "
] ]
}, },
{ {
@@ -2821,10 +2854,10 @@
"def run18_2(program):\n", "def run18_2(program):\n",
" \"Run two copies of program, with different state variables. Return final states.\"\n", " \"Run two copies of program, with different state variables. Return final states.\"\n",
" Qs = [deque(), deque()]\n", " Qs = [deque(), deque()]\n",
" ps = [Struct(pc=0, sends=0, Qs=Qs, status='run', p=id, regs=defaultdict(int, p=id))\n", " ps = [Struct(id=id, pc=0, sends=0, regs=defaultdict(int, p=id), status='run')\n",
" for id in (0, 1)]\n", " for id in (0, 1)]\n",
" while 'run' in {ps[0].status, ps[1].status}:\n", " while any(p.status == 'run' for p in ps):\n",
" step18(program, random.choice(ps))\n", " step18(program, Qs, random.choice(ps))\n",
" return ps" " return ps"
] ]
}, },
@@ -2832,12 +2865,12 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"The second function, `step18`, has most of the guts of `run18`, but with a few changes:\n", "`step18` has most of the guts of thee previous `run18` function, but with a few changes:\n",
"- State variables are accessed indirectly: `p.pc` instead of just `pc`.\n", "- State variables are accessed indirectly: `p.pc` instead of just `pc`.\n",
"- If the `pc` is out of bounds, the program terminates; the status is set to `'end'`.\n", "- If the `pc` is out of bounds, the program terminates; the status is set to `'end'`.\n",
"- The `snd` instruction sends a value to the other program's queue.\n", "- The `snd` instruction sends a value to the other program's queue.\n",
"- The `rcv` instruction pops a value of the queue if there is one, otherwise the status is set to `'wait'`.\n", "- The `rcv` instruction pops a value off the queue if there is one, otherwise the status is set to `'wait'`.\n",
"- I was stuck for a *long* time on this problem, repeatedly getting the wrong answer. Finally I tried the strategy of *look carefully at the input*. I noticed an instruction, `\"jgz 1 3\"`, in which the X field was the integer 1. I had assumed the X field was always a register, but apparently it need not be, and I should be evaluating X with `value(regs, x)`, not `regs[x]`. Once I made that change, the program worked." "- The \"`X`\" in \"`jgz X Y`\" might be an integer, not a register name, so use `vx = value(p.regs, x)`. I was stuck for a *long* time before I realized this. Finally I tried the strategy of *look carefully at the input*. I noticed the instruction `\"jgz 1 3\"`, and it was a simple change to make the program work."
] ]
}, },
{ {
@@ -2857,26 +2890,26 @@
} }
], ],
"source": [ "source": [
"def step18(program, p):\n", "def step18(program, Qs, p):\n",
" \"Run one instruction in program, with state variables in p.\"\n", " \"Execute one instruction in program, using state variables in p.\"\n",
" if p.pc < 0 or p.pc > len(program):\n", " if p.pc < 0 or p.pc > len(program):\n",
" p.status = 'end'\n", " p.status = 'end'\n",
" else:\n", " else:\n",
" instr = program[p.pc]\n", " instr = program[p.pc]\n",
" op, x, y = instr[0], instr[1], instr[-1]\n", " op, x, y = instr[0], instr[1], instr[-1]\n",
" vx, vy = value(p.regs, x), value(p.regs, y)\n", " vx, vy = value(p.regs, x), value(p.regs, y)\n",
" if op == 'snd': p.Qs[1-p.p].append(vy); p.sends += 1\n", " if op == 'snd': Qs[1-p.id].append(vy); p.sends += 1\n",
" elif op == 'set': p.regs[x] = vy\n", " elif op == 'set': p.regs[x] = vy\n",
" elif op == 'add': p.regs[x] += vy\n", " elif op == 'add': p.regs[x] += vy\n",
" elif op == 'mul': p.regs[x] *= vy\n", " elif op == 'mul': p.regs[x] *= vy\n",
" elif op == 'mod': p.regs[x] %= vy\n", " elif op == 'mod': p.regs[x] %= vy\n",
" elif op == 'jgz' and vx > 0: p.pc += vy - 1\n", " elif op == 'jgz' and vx > 0: p.pc += vy - 1\n",
" elif op == 'rcv': \n", " elif op == 'rcv': \n",
" if not p.Qs[p.p]:\n", " if not Qs[p.id]:\n",
" p.status = 'wait'\n", " p.status = 'wait'\n",
" return\n", " return # don't update pc; try again next time\n",
" else:\n", " else:\n",
" p.regs[x] = p.Qs[p.p].popleft()\n", " p.regs[x] = Qs[p.id].popleft()\n",
" p.status = 'run'\n", " p.status = 'run'\n",
" p.pc += 1\n", " p.pc += 1\n",
" \n", " \n",
@@ -2894,67 +2927,34 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"At first I was confused; I thought this was a maze-following problem with choices of which direction to turn. Actually, the direction is always determined: keep going in the current direction as long as possible, but when we hit a `'+'` character, search for another direction to go in (there will only be one, but we have to try all the possibilities to find it). Leave breadcrumbs (the `'.'` character) so that we don't back up along a previously-followed path. Collect all the alphabetic characters into `result`. As in Day 14, the grid, `G`, is surrounded by space characters so that we don't have to worry about `(x, y)` going off the edge." "At first I was confused; I thought this was a maze-following problem where I had to make a choice of directions at every turn. Actually, the direction is always determined: keep going in the current direction as long as possible, but when we hit a `'+'` character, find the new direction to go in (there will only be one possibility). Leave breadcrumbs (the `'.'` character) so that we don't back up along a previously-followed path. As in Day 14, the grid is surrounded by a border of space characters so that we don't have to worry about `(x, y)` going off the edge."
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 83, "execution_count": 83,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [],
{
"data": {
"text/plain": [
"'VEBTPXCHLI'"
]
},
"execution_count": 83,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [ "source": [
"diagram = Input(19).read().splitlines()\n", "diagram = Inputstr(19)\n",
"\n", "\n",
"def path_follow(diagram):\n", "def follow_tubes(diagram):\n",
" \"Follow -|+ lines, and collect letters along the way.\"\n", " \"Follow [-+|] lines, yielding characters along the path.\"\n",
" result = []\n", " grid = border(' ', diagram.splitlines())\n",
" G = surround(diagram)\n", " x, y = grid[1].index('|'), 1\n",
" x, y = G[1].index('|'), 1\n",
" dx, dy = 0, 1\n", " dx, dy = 0, 1\n",
" while G[y][x] != ' ':\n", " while grid[y][x] != ' ':\n",
" c = G[y][x]\n", " yield grid[y][x]\n",
" if c.isalpha():\n", " if grid[y][x] == '+':\n",
" result.append(c) # Collect a letter\n", " dx, dy = new_direction(grid, x, y)\n",
" elif c == '+':\n", " grid[y][x] = '.' # Leave a breadcrumb\n",
" dx, dy = new_direction(G, x, y)\n",
" G[y][x] = '.' # Leave a breadcrumb\n",
" x += dx; y += dy\n", " x += dx; y += dy\n",
" return cat(result)\n", " \n",
" \n", "def new_direction(grid, x, y):\n",
"def surround(grid, fill=' '):\n",
" \"Put fill characters as a border all around grid.\"\n",
" rows = [[fill] + list(row) + [fill] \n",
" for row in grid]\n",
" empty = [fill] * len(rows[0])\n",
" return [empty] + rows + [empty]\n",
"\n",
"def new_direction(G, x, y):\n",
" \"Find a direction that continues the path.\"\n", " \"Find a direction that continues the path.\"\n",
" for (dx, dy) in (UP, DOWN, RIGHT, LEFT):\n", " for (dx, dy) in (UP, DOWN, RIGHT, LEFT):\n",
" if G[y+dy][x+dx] not in (' ', '.'):\n", " if grid[y+dy][x+dx] not in (' ', '.'):\n",
" return dx, dy\n", " return dx, dy"
"\n",
"path_follow(diagram)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Part Two**\n",
"\n",
"This is a surprisingly easy Part Two: all I have to do is manage a `steps += 1` in the loop to return the total number of steps taken."
] ]
}, },
{ {
@@ -2965,7 +2965,7 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"('VEBTPXCHLI', 18702)" "'VEBTPXCHLI'"
] ]
}, },
"execution_count": 84, "execution_count": 84,
@@ -2974,25 +2974,161 @@
} }
], ],
"source": [ "source": [
"def path_follow2(diagram):\n", "cat(filter(str.isalpha, follow_tubes(diagram)))"
" \"Follow -|+ lines, collect letters along the way, return (letters, steps).\"\n", ]
" result = []\n", },
" steps = 0\n", {
" G = surround(diagram)\n", "cell_type": "markdown",
" x, y = G[1].index('|'), 1\n", "metadata": {},
" dx, dy = 0, 1\n", "source": [
" while G[y][x] != ' ':\n", "That's the right answer.\n",
" steps += 1\n",
" c = G[y][x]\n",
" if c.isalpha(): \n",
" result.append(c) # Collect a letter\n",
" elif c == '+':\n",
" dx, dy = new_direction(G, x, y)\n",
" G[y][x] = '.' # Leave a breadcrumb\n",
" x += dx; y += dy\n",
" return cat(result), steps\n",
"\n", "\n",
"path_follow2(diagram)" "**Part Two**\n",
"\n",
"This is a surprisingly easy Part Two; I already generated the characters in the path; all I have to do is count them: "
]
},
{
"cell_type": "code",
"execution_count": 85,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"18702"
]
},
"execution_count": 85,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"length(follow_tubes(diagram))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# [Day 20](https://adventofcode.com/2017/day/20): Particle Swarm\n",
"\n",
"I'll create structures for particles, each will have fields for particle's number (`id`), position (`p`), velocity(`v`), and acceleration (`a`):"
]
},
{
"cell_type": "code",
"execution_count": 86,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Struct(a=(-7, 8, -10), id=0, p=(1199, -2918, 1457), v=(-13, 115, -8)),\n",
" Struct(a=(-6, -5, 6), id=1, p=(2551, 2418, -1471), v=(-106, -108, 39)),\n",
" Struct(a=(-6, 2, -9), id=2, p=(-73, 1626, 1321), v=(58, -118, -8)),\n",
" Struct(a=(3, 3, 11), id=3, p=(-3297, -894, -551), v=(183, 31, -61)),\n",
" Struct(a=(7, -12, -1), id=4, p=(-1425, 4298, 617), v=(32, -166, -32))]"
]
},
"execution_count": 86,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def particles(lines=list(Input(20))):\n",
" \"Parse the list of particles.\"\n",
" return [Particle(id, line) for id, line in enumerate(lines)]\n",
"\n",
"def Particle(id, line):\n",
" \"Parse a single Particle.\"\n",
" ns = Integers(line) # Assumes format \"p=<1,8,4>, v=<3,1,-8>, a=<7,8,-10>\" \n",
" return Struct(id=id, p=ns[0:3], v=ns[3:6], a=ns[6:9])\n",
"\n",
"particles()[:5]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"I'm not quite sure how to determine what \"in the long run\" means, so I'll just interpret it as meaning \"after 1000 time steps.\" On each step we update velocity and position of each particle. At the end, we determine the id number of the particle closest to the origin. Note that `evolve` generates an infinite sequence of particles, and we use `nth` to pick out the 1000th generation."
]
},
{
"cell_type": "code",
"execution_count": 87,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"243"
]
},
"execution_count": 87,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def evolve(particles):\n",
" \"Continually update particles, yielding them after each time step.\"\n",
" while True:\n",
" yield particles\n",
" for p in particles:\n",
" p.v = add(p.v, p.a)\n",
" p.p = add(p.p, p.v)\n",
" \n",
"def add(A, B): \n",
" \"Add two n-dimensional vectors.\"\n",
" return mapt(sum, zip(A, B))\n",
"\n",
"def closest(particles):\n",
" \"Find the particle closest to origin.\"\n",
" return min(particles, key=lambda p: sum(map(abs, p.p)))\n",
"\n",
"# Answer: the id of the particle closest to origin in the 1000th generation of `evolve`\n",
"closest(nth(evolve(particles()), 1000)).id"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Part Two**\n",
"\n",
"I can still use `evolve` unchanged, and I will add the function `remove_collisions` to eliminate particles that are in the same position as another particle. I use `map` to apply this to every generation. Note that `remove_collisions` both mutates the argument, `particles`, and also returns it. "
]
},
{
"cell_type": "code",
"execution_count": 88,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"648"
]
},
"execution_count": 88,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def remove_collisions(particles):\n",
" \"Mutate (and return) particles, keep only ones that have a unique position.\"\n",
" poscount = Counter(p.p for p in particles)\n",
" particles[:] = [p for p in particles if poscount[p.p] == 1]\n",
" return particles\n",
" \n",
"# Answer: remove collisions from every generation of evolving particles, \n",
"# select the 1000th generation, and say how many particles are in that generation.\n",
"len(nth(map(remove_collisions, evolve(particles())), 1000))"
] ]
}, },
{ {
@@ -3006,15 +3142,15 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 85, "execution_count": 89,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"CPU times: user 58.9 s, sys: 125 ms, total: 59 s\n", "CPU times: user 46.8 s, sys: 62.8 ms, total: 46.9 s\n",
"Wall time: 59.1 s\n" "Wall time: 46.9 s\n"
] ]
} }
], ],
@@ -3062,27 +3198,29 @@
" spinlock3(N=50*10**6)[1], 6154117)\n", " spinlock3(N=50*10**6)[1], 6154117)\n",
"day(18, run18(program18), 7071, \n", "day(18, run18(program18), 7071, \n",
" run18_2(program18)[1].sends, 8001)\n", " run18_2(program18)[1].sends, 8001)\n",
"day(19, path_follow(diagram), 'VEBTPXCHLI', \n", "day(19, cat(filter(str.isalpha, follow_tubes(diagram))), 'VEBTPXCHLI', \n",
" path_follow2(diagram)[1], 18702)" " quantify(follow_tubes(diagram)), 18702)\n",
"day(20, closest(nth(evolve(particles()), 1000)).id, 243,\n",
" len(nth(map(remove_collisions, evolve(particles())), 1000)), 648)"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"And here is a plot of the time taken to completely solve both parts of each puzzle each day, for me, the first person to finish, and the hundredth person. On days when I started late, the times are estimates, not exact (these include days 1, 3, 8, 13, 14, 17, 18)." "And here is a plot of the time taken to program solutions to both parts of each puzzle each day, for me, the first person to finish, and the hundredth person. On days when I started late, the times are estimates, not exact (these include days 1, 3, 8, 13, 14, 17, 18)."
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 86, "execution_count": 90,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"data": { "data": {
"image/png": "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 truncated "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYAAAAENCAYAAAAG6bK5AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzs3XdYFMf/B/D3cjRBxI7RKChqjCbGRDQaFUuIqLGgqCiC\niP5MMNGIvUSBWLAravLVaIy9RINgiSUSu2LB3hXkUIpYUDja1fn9sXJycse13bsD5vU89yS3tzvz\nYe/c2Z2d/QxDCCGgKIqiKhwrcwdAURRFmQdtACiKoioo2gBQFEVVULQBoCiKqqBoA0BRFFVB0QaA\noiiqgrLmq2CZTIZp06YhLS0N1tbWmDt3LgQCAaZPnw4rKys0adIE4eHhfFVPURRFacFbA3Dq1Cko\nFArs2rUL58+fx4oVKyCVSjFx4kR4eHggPDwccXFx8PLy4isEiqIoqhS8dQG5ublBLpeDEAKRSARr\na2vcvXsXHh4eAABPT0/Ex8fzVT1FURSlBW9XAI6OjkhNTUWPHj3w5s0brF27FgkJCSqfi0Qivqqn\nKIqitOCtAdi0aRM6deqECRMmIDMzE4GBgZBKpcrP8/LyUKVKFb6qpyiKorTgrQFwdnaGtTVbvJOT\nE2QyGZo3b45Lly6hbdu2OH36NNq1a1diuytXrvAVEkVRVLnWunVrvdZn+EoGl5+fj5kzZ+LFixeQ\nyWQICgpCixYtMGvWLEilUri7u2PevHlgGEZluytXruj9R1Capaeno27duuYOo9yg+5NbdH9yx5Bj\nJ29XAA4ODoiKiiqxfOvWrXxVSVEURemBPghGURRVQdEGgKIoqoKiDQBFUVQFRRsAiqKoCoo2ABRF\nURUUbQAoiqIqKNoAUBRFVVC0AaAoiqqgKkwDIJFIMGrUTEgkEl7KT0tLQ7NmzRAYGFjisxkzZqBZ\ns2Z48+YNL3VTFEUZosI0AKNHR2LzZi98990C3uqws7NDcnIyMjIylMsKCgpw9erVEikvKIoqH/g+\nueRThWgA/vwzBvv2tYJc3g2xsS2xcWMsL/VYWVmhV69e2L9/v3LZv//+i27duinfHz9+HIMHD8aA\nAQPg7++P69ev8xILRVGmYYqTS76U+wYgKSkZc+feQHa2DwAgO7s/5sy5jqSkZM7rYhgGPj4+Kg1A\nbGwsfH19AQBPnz7FihUrsH79euzduxdz5szB2LFjUVhYyHksFEXxz1Qnl3zhLRmcKUREaP/vjh0r\nIRTOU9lOKJyE0NDZaN06qtTtDdG8eXNYWVnh7t27qF69OvLz89G4cWMQQnD69Gm8ePECI0aMQFES\nVmtra6SkpOCjjz4yvFKKokzu3cllBICik8sIeHp+Bnf3huYNTkflogEo7X1g4Hh4eS2FUPjuQze3\nZYiKGg9399K3N1Tfvn2xb98+VK9eHX379lUut7KywldffYXly5crlz179gwuLi7cVExRlMmEhmo+\nuTxwoGQmZEtU7ruA3N0bIiysFZydYwAAzs4xCAtrxUsLXXRW37dvXxw5cgSHDx9Gnz59lJ+3adMG\n586dw+PHjwEAp06dQr9+/SAWizmPhaIofkVFjYeb21KVZUUnl2VFmb4C0FVwsA9OnozA9u1V4ONz\nE8HB4bzUUzTSx8XFBY0bN4aTk5Ny2kuGYdC4cWPMmTMHEydOBAAIBAKsWbMG9vb2vMRDURR/3N0b\nYvjwVlixIgYiUX9eTy75UiEaAABYv34mrK0jsGZNBC/l16tXD1evXlW+37Bhg8rn9+7dAwB4e3vD\n29ublxgoijKtBg180KZNBE6d4vfkki/lvguoiK2tLTZsiIStra25Q6EoqpwYNQo4fHgmfH3/w/Tp\nM8wdjt4qTANAURTFB1tbW3TvHonLl8veyWWF6QKiKIri0pYtQPv2QJMm7JVAWcRbAxATE4O9e/eC\nYRiIxWLcv38f27dvR2RkJKysrNCkSROEh5et/jKKoqgiCgVgY2PuKIzDWxdQ//79sXXrVmzZsgUt\nWrTArFmz8Ntvv2HixInYtm0bFAoF4uLi+KqeoiiKVyNGAG5u794nJAC3bpkrGsPwfg/g1q1bSExM\nxKBBg3Dnzh14eHgAADw9PREfH8939RRFUSaRlAQ8fWruKPTDewOwbt06jBs3rsRyR0dHiEQivqun\nKIri3JgxQLGkvwAAPz+gVy/zxGMoXhsAkUgEoVCINm3asJVZvasuLy9P+ZBUeTJjxgxs3LgRAKBQ\nKDB//nz07NkT3t7e2LVrl3K9lJQUDBs2DN9++y0GDx6sfDo4NzcXQUFByvXoPAIUZXl69ABq1TJ3\nFMbjdRTQ5cuX0a5dO+X7jz/+GJcvX0abNm1w+vRplc+KS09P5yyG8KXhuJ1xu8TyTz74BL9M/oWz\nep48eYKoqCjcu3cPLi4uSE9PR2xsLB4+fIg//vgDeXl5+PHHH+Hi4oKPPvoIP/30EwYNGoRu3brh\n0qVLGDNmDDZu3Ihnz57h5s2byn3AMAyePXuG/Px8g+ISiUSc7s+Kju5PbpXV/dmmDfD8ecnlx4/b\nwdlZgdatpaYPygC8NgDJycmoX7++8v20adMwe/ZsSKVSuLu7o0ePHmq3q1u3LmcxeHfxxo7YHch3\nfXcAdRA6YHLXyZzWs2HDBvj7+yM+Ph7Ozs6oW7cuEhIS4O/vj3r16gEA+vXrh/Pnz6N58+ZIS0tD\nQEAAAMDHxwerV69GdnY2oqKiIBaL8eOPPyI6OhqEEOzevRvXr19HdnY2Ro4ciWHDhukcV3p6Oqd/\nZ0VH9ye3ytv+rFEDqFYNMMeflPF+n5QOeG0ARr03ONbNzQ1bt27ls8oSfPv4YunWpbhILgIMAAJ8\nmvspBvQewGk9s2fPBgCVG9sZGRn44IMPlO9dXFzw8OFDPHv2DLVr11bZ3sXFBc+ePcOCBQvQp08f\nxMTEKD9r0KABwsLCcO/ePfj5+WHIkCEQCAScxk9RlHb5+UDHjsDFi+qHgPbsafqYjFGmHwSLOBnB\n/rdLRKnvJwdORlBsEPJd82GTbIMpw6eAYRit2xtLoVCUWGZlZaV2edFn6vTu3RsA24UmlUqRm5sL\nZ2dnTmKkKEp3lSoBu3eX/fH/Rcp2A/DegVrTe0KI8irgi/wvlGf/2rY3Vt26dfG8WEdhZmYm6tSp\ng7p16+LFixcq6xZ9po61terXVJR2mqIo02IYoHHj0tfZvBlo0ADo2tU0MRmjQuQCYhgGkwMnw+mE\nk/Ls3xS+/vprREdHQy6XIycnB4cOHYKXlxdcXFzQoEEDHDp0CABw5swZCAQCfPTRR7C2ttZ4hQDQ\ngz9FmYtCAegye2vjxua5B2CIMn0FoA/fPr5IuJbAed9/aYYOHYqnT5+iX79+kEqlGDp0qPJBuBUr\nVuDnn3/GmjVrYGdnh5UrVwIAatWqhY8//hi9evXCjh07SjRWpmq8KIpS9eABEBQEXLpU+nodOpgm\nHi4wxMJOKa9cuYLWrVubO4xyo7yNsjA3uj+5Vdb2p0QCWGpGeUOOnRWiC4iiKIoLuh78Fy0CDh/m\nNxYuVJguIIqiKENlZwMiEfDhh7qt37s3ULMmvzFxgV4BUBRFaXHlCrBwoe7rt2gBuLjwFw9X6BUA\nRVGUFt26sa/yhl4BUBRVJkkkEowaNRMSicTcoag1YQJQ7IF+i0QbAIqiyqTRoyOxebMXvvtuAa/1\n3LkD3Lyp/3aTJ1t+agjaAFAUVeb8+WcM9u1rBbm8G2JjW2Ljxlje6nr0CLhdMqGwVvXqAfb23MfD\nJdoAUBRVpiQlJWPu3BvIzvYBAGRn98ecOdeRlJTMS30+PoC/v2HbEgLI5dzGw6UK0wAQQrB4+nTe\nUykUnxBGk1u3biE8PJzXOCiqvAoNXQmhcLLKMqFwEkJDV5opIs38/YG3GV8sUoVpAI5GRyPjf//D\nv3v38lJ+UlISgoKCcOTIEa3rPnr0CJmZmbzEQVHlXVTUeLi5LVVZ5ua2DFFR4zmvKzoauHrV8O03\nbAD69OEuHq5ViAaAEIKjS5diuUiEI0uW8HIVsGPHDvj6+qpMcpOQkIBBgwbB19cXAwcOxLFjx/Ds\n2TOsXr0aV65cwcyZMzmPg6LKO3f3hggLawVnZ3aIjbNzDGbMaAV394ac12VtbVzqZwcH7mLhQ9lu\nACIi2JeW90ejo9Hj1i0wALyvXn13FaBtez3Mnj0bffv2VVn266+/Ijg4GNHR0Zg/fz4uXLiAOnXq\n4KeffkLr1q0RGRlpUF0UVdEFB/vgm29uQCD4Dx073kR0tA8v9fTrB3z6qXFlZGcDBQXcxMO1sv0g\n2PsHazXvCSE42r49lr+dU9dbKsXEJUvQfcAAMNq2N1LPnj0xZ84cHD9+HF999RUmTJjAafkUVZFV\nqTITXl4RiI6O0ClNs7n83/8BY8cCnTubO5KSyvYVgA6Kn/0D7KyQ3rdu8XYvoDg/Pz8cOHAAHTt2\nxNmzZ9G3b1/k5ubyXi9FVQQbNtjiyJFI2NnZgo8J8qZOBe7eNb6c3bst8+AP6HAFkJmZCZFIBIFA\ngPXr1yMwMBAff/yxKWLjxK1z55Dr4YH4Ynn0CSGofPYsvH19ea17yJAhGDNmDHx8fODl5YWuXbsi\nJycHAoEAMpmM17opqqJJT2cPtqGh3JTXsyc7lt9YljyFh9YGYNKkSRg7dix27NgBb29vREZG6jyx\n+7p163D8+HFIpVL4+/ujTZs2mD59OqysrNCkSROTDIWcsmIF73VoMnXqVMybNw8rV64EwzAYO3Ys\n6tati88//xxRUVEYN24cVq9ebbb4KKqsEouB1FTA3f3dsipV2HTNhHBz0OVySsd799hMok5O3JXJ\nCaJFQEAAkclkJCgoiBBCyPDhw7VtQggh5OLFiyQkJIQQQkheXh5ZvXo1CQkJIZcvXLine truncated
"text/plain": [ "text/plain": [
"<matplotlib.figure.Figure at 0x108a9eda0>" "<matplotlib.figure.Figure at 0x11135c358>"
] ]
}, },
"metadata": {}, "metadata": {},
@@ -3095,14 +3233,14 @@
" X = ints(1, len(times[0]) - 2)\n", " X = ints(1, len(times[0]) - 2)\n",
" for (label, mark, *Y) in times:\n", " for (label, mark, *Y) in times:\n",
" plt.plot(X, Y, mark, label=label)\n", " plt.plot(X, Y, mark, label=label)\n",
" plt.xlabel('Day Number'); plt.ylabel('Minutes to Solve Both')\n", " plt.xlabel('Day Number'); plt.ylabel('Minutes to Solve Both Parts')\n",
" plt.legend(loc='upper left')\n", " plt.legend(loc='upper left')\n",
"\n", "\n",
"x = None\n", "x = None\n",
"plot_times([\n", "plot_times([\n",
" ('Me', 'd:', 4, 6, 20, 5, 12, 30, 33, 10, 21, 40, 13, 12, 30, 41, 13, 64, 54, 74, 50),\n", " ('Me', 'd:', 4, 6, 20, 5, 12, 30, 33, 10, 21, 40, 13, 12, 30, 41, 13, 64, 54, 74, 50, 18),\n",
" ('100th', 'v:', 6, 6, 23, 4, 5, 9, 25, 8, 12, 25, 12, 9, 22, 25, 10, 27, 16, 41, 18),\n", " ('100th', 'v:', 6, 6, 23, 4, 5, 9, 25, 8, 12, 25, 12, 9, 22, 25, 10, 27, 16, 41, 18, 21),\n",
" ('1st', '^:', 1, 1, 4, 1, 2, 3, 10, 3, 4, 6, 3, 2, 6, 5, 2, 5, 5, 10, 5)])" " ('1st', '^:', 1, 1, 4, 1, 2, 3, 10, 3, 4, 6, 3, 2, 6, 5, 2, 5, 5, 10, 5, 7)])"
] ]
} }
], ],