Advent 2017 through Day 21

This commit is contained in:
Peter Norvig authored and GitHub committed 2017-12-21 14:01:55 -08:00
1 parent 8a82f9afe4
commit 0b083438cc
1 file changed
+420 -35
+420 -35
View File
@@ -28,9 +28,7 @@
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": true
},
"metadata": {},
"outputs": [],
"source": [
"# Python 3.x Utility Functions\n",
@@ -94,10 +92,14 @@
" return float(token)\n",
" except ValueError:\n",
" return token\n",
" \n",
"def error(err=RuntimeError, *args): raise err(*args)\n",
"\n",
"################ Functions on Iterables\n",
"\n",
"def first(iterable, default=None): return next(iter(iterable), default)\n",
"def first(iterable, default=None): \n",
" \"The first item in an iterable, or default if it is empty.\"\n",
" return next(iter(iterable), default)\n",
"\n",
"def first_true(iterable, pred=None, default=None):\n",
" \"\"\"Returns the first true value in the iterable.\n",
@@ -176,6 +178,14 @@
" return new\n",
" \n",
"flatten = chain.from_iterable\n",
"\n",
"def repeat(n, fn, arg):\n",
" \"Repeat arg=fn(arg) n times.\"\n",
" for i in range(n):\n",
" arg = fn(arg)\n",
" return arg\n",
"\n",
"################ Making immutable objects\n",
" \n",
"class Set(frozenset):\n",
" \"A frozenset, but with a prettier printer.\"\n",
@@ -960,8 +970,8 @@
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 6.15 s, sys: 19.9 ms, total: 6.17 s\n",
"Wall time: 6.19 s\n"
"CPU times: user 6.2 s, sys: 8.37 ms, total: 6.21 s\n",
"Wall time: 6.21 s\n"
]
},
{
@@ -1422,16 +1432,16 @@
}
],
"source": [
"def run82(program):\n",
"def run8_2(program):\n",
" registers = defaultdict(int)\n",
" highest = 0\n",
" for r, inc, delta, _if, r2, cmp, amount in program:\n",
" for (r, inc, delta, _if, r2, cmp, amount) in program:\n",
" if operations[cmp](registers[r2], amount):\n",
" registers[r] += delta * (+1 if inc == 'inc' else -1)\n",
" highest = max(highest, registers[r])\n",
" return highest\n",
"\n",
"run82(program8)"
"run8_2(program8)"
]
},
{
@@ -2236,8 +2246,8 @@
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 15.2 s, sys: 18.4 ms, total: 15.3 s\n",
"Wall time: 15.3 s\n"
"CPU times: user 15.1 s, sys: 26.4 ms, total: 15.1 s\n",
"Wall time: 15.2 s\n"
]
},
{
@@ -2290,8 +2300,8 @@
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 9.52 s, sys: 9 ms, total: 9.53 s\n",
"Wall time: 9.54 s\n"
"CPU times: user 9.39 s, sys: 8.62 ms, total: 9.4 s\n",
"Wall time: 9.4 s\n"
]
},
{
@@ -2660,14 +2670,14 @@
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 1.57 s, sys: 7.74 ms, total: 1.58 s\n",
"Wall time: 1.58 s\n"
"CPU times: user 1.35 s, sys: 5.54 ms, total: 1.35 s\n",
"Wall time: 1.35 s\n"
]
},
{
"data": {
"text/plain": [
"<__main__.Node at 0x111355438>"
"<__main__.Node at 0x110c44be0>"
]
},
"execution_count": 77,
@@ -2700,8 +2710,8 @@
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 5.48 s, sys: 8.31 ms, total: 5.49 s\n",
"Wall time: 5.49 s\n"
"CPU times: user 5.47 s, sys: 6.62 ms, total: 5.48 s\n",
"Wall time: 5.48 s\n"
]
},
{
@@ -3040,12 +3050,10 @@
"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",
" return [Particle(id, *grouper(Integers(line), 3)) \n",
" 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",
"def Particle(id, p, v, a): return Struct(id=id, p=p, v=v, a=a) \n",
"\n",
"particles()[:5]"
]
@@ -3131,6 +3139,381 @@
"len(nth(map(remove_collisions, evolve(particles())), 1000))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# [Day 21](https://adventofcode.com/2017/day/21): Fractal Art\n",
"\n",
"First I'll create a `dict` of enhancement rules. I'll translate `'#'` and `'.'` pixels to 1 and 0, and represent a grid as a tuple of tuples (e.g. `((0, 0), (1, 1))`), so that it can be a `dict` key. I have to deal with rotations and flips; I could do that at lookup time, but it will be faster to do it at rule creation time:"
]
},
{
"cell_type": "code",
"execution_count": 89,
"metadata": {},
"outputs": [],
"source": [
"def Enhancements(lines):\n",
" \"Create a dict of {square: enhanced_square}\"\n",
" enhancements = {}\n",
" for line in lines:\n",
" lhs, rhs = map(Key, line.split('=>'))\n",
" for r in range(4):\n",
" enhancements[lhs] = enhancements[flip(lhs)] = rhs\n",
" lhs = rotate(lhs)\n",
" return enhancements\n",
" \n",
"def Key(line): \n",
" \"Key('../##') => ((0, 0), (1, 1))\"\n",
" bits = {'#': 1, '.': 0}\n",
" return tuple(tuple(bits[p] for p in row.strip())\n",
" for row in line.split('/'))\n",
" \n",
"def rotate(key): return tuple(zip(*reversed(key)))\n",
"\n",
"def flip(L): return tuple(tuple(reversed(row)) for row in L)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's test this on just the enhancement rules with 2x2 squares; with rotations and flips there should be 16 entries:"
]
},
{
"cell_type": "code",
"execution_count": 90,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{((0, 0), (0, 0)): ((0, 0, 0), (0, 1, 0), (0, 1, 0)),\n",
" ((0, 0), (0, 1)): ((0, 0, 0), (1, 0, 0), (1, 0, 0)),\n",
" ((0, 0), (1, 0)): ((0, 0, 0), (1, 0, 0), (1, 0, 0)),\n",
" ((0, 0), (1, 1)): ((1, 0, 1), (0, 1, 0), (0, 1, 0)),\n",
" ((0, 1), (0, 0)): ((0, 0, 0), (1, 0, 0), (1, 0, 0)),\n",
" ((0, 1), (0, 1)): ((1, 0, 1), (0, 1, 0), (0, 1, 0)),\n",
" ((0, 1), (1, 0)): ((1, 1, 0), (1, 1, 0), (0, 0, 0)),\n",
" ((0, 1), (1, 1)): ((0, 1, 1), (1, 1, 1), (1, 0, 0)),\n",
" ((1, 0), (0, 0)): ((0, 0, 0), (1, 0, 0), (1, 0, 0)),\n",
" ((1, 0), (0, 1)): ((1, 1, 0), (1, 1, 0), (0, 0, 0)),\n",
" ((1, 0), (1, 0)): ((1, 0, 1), (0, 1, 0), (0, 1, 0)),\n",
" ((1, 0), (1, 1)): ((0, 1, 1), (1, 1, 1), (1, 0, 0)),\n",
" ((1, 1), (0, 0)): ((1, 0, 1), (0, 1, 0), (0, 1, 0)),\n",
" ((1, 1), (0, 1)): ((0, 1, 1), (1, 1, 1), (1, 0, 0)),\n",
" ((1, 1), (1, 0)): ((0, 1, 1), (1, 1, 1), (1, 0, 0)),\n",
" ((1, 1), (1, 1)): ((0, 1, 1), (1, 0, 0), (1, 1, 0))}"
]
},
"execution_count": 90,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Enhancements('''\n",
"../.. => .../.#./.#.\n",
"#./.. => .../#../#..\n",
"##/.. => #.#/.#./.#.\n",
".#/#. => ##./##./...\n",
"##/#. => .##/###/#..\n",
"##/## => .##/#../##.\n",
"'''.strip().splitlines())"
]
},
{
"cell_type": "code",
"execution_count": 91,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"16"
]
},
"execution_count": 91,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"len(_)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Looks good; let's create my complete table. There should be 2<sup>4</sup> + 2<sup>9</sup> entries:"
]
},
{
"cell_type": "code",
"execution_count": 92,
"metadata": {},
"outputs": [],
"source": [
"enhancements = Enhancements(Input(21))\n",
"\n",
"assert len(enhancements) == 2 ** 4 + 2 ** 9"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now on each iteration we `enhance` the grid by first `expand`-ing it (looking up each `slice` in the `enhancements` rules) and then `stitch` the pieces together:"
]
},
{
"cell_type": "code",
"execution_count": 93,
"metadata": {},
"outputs": [],
"source": [
"def enhance(grid): \n",
" \"Expand small pieces into bigger ones and stitch them together.\"\n",
" return stitch(expand(grid))\n",
"\n",
"def expand(grid):\n",
" \"Slice the grid into d x d pieces and enhance each piece.\"\n",
" N = len(grid[0])\n",
" d = (2 if N % 2 == 0 else 3 if N % 3 == 0 else error())\n",
" return [[enhancements[slice(grid, r, c, d)]\n",
" for c in range(0, N, d)]\n",
" for r in range(0, N, d)]\n",
"\n",
"def stitch(pieces): \n",
" \"Stitch the pieces back into one big grid.\"\n",
" N = sum(map(len, pieces[0]))\n",
" return tuple(tuple(get(pieces, r, c) \n",
" for c in range(N))\n",
" for r in range(N))\n",
"\n",
"def get(pieces, r, c):\n",
" \"The pixel at location (r, c), from a matrix of d x d pieces.\"\n",
" d = len(pieces[0][0])\n",
" row = pieces[r // d]\n",
" cell = row[c // d]\n",
" return cell[r % d][c % d]\n",
"\n",
"def slice(grid, r, c, d):\n",
" \"The d x d slice of grid starting at position (r, c)\"\n",
" return tuple(row[c:c+d] for row in grid[r:r+d])"
]
},
{
"cell_type": "code",
"execution_count": 94,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"((0, 1, 0), (0, 0, 1), (1, 1, 1))"
]
},
"execution_count": 94,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"grid = Key('.#./..#/###')\n",
"grid"
]
},
{
"cell_type": "code",
"execution_count": 95,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[[((1, 0, 1, 0), (0, 0, 1, 0), (0, 1, 0, 1), (0, 1, 0, 0))]]"
]
},
"execution_count": 95,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"expand(_)"
]
},
{
"cell_type": "code",
"execution_count": 96,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"((1, 0, 1, 0), (0, 0, 1, 0), (0, 1, 0, 1), (0, 1, 0, 0))"
]
},
"execution_count": 96,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"stitch(_)"
]
},
{
"cell_type": "code",
"execution_count": 97,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[[((0, 0, 0), (1, 0, 0), (1, 0, 0)), ((1, 0, 1), (0, 1, 0), (0, 1, 0))],\n",
" [((1, 0, 1), (0, 1, 0), (0, 1, 0)), ((0, 0, 0), (1, 0, 0), (1, 0, 0))]]"
]
},
"execution_count": 97,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"expand(_)"
]
},
{
"cell_type": "code",
"execution_count": 98,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"((0, 0, 0, 1, 0, 1),\n",
" (1, 0, 0, 0, 1, 0),\n",
" (1, 0, 0, 0, 1, 0),\n",
" (1, 0, 1, 0, 0, 0),\n",
" (0, 1, 0, 1, 0, 0),\n",
" (0, 1, 0, 1, 0, 0))"
]
},
"execution_count": 98,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"stitch(_)"
]
},
{
"cell_type": "code",
"execution_count": 99,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"12"
]
},
"execution_count": 99,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sum(flatten(_))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"That looks right; Let's try to solve the puzzle:"
]
},
{
"cell_type": "code",
"execution_count": 100,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"147"
]
},
"execution_count": 100,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sum(flatten(repeat(5, enhance, grid)))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Part Two**\n",
"\n",
"It looked like I didn't need to change any code to do Part Two, just change the `5` to an `18`. I ran it, and got an answer in a few seconds&mdash;but the answer was wrong. I went back and carefully looked over my code, and realized there was a place in `expand` where I had swapped the order of `r` and `c` (the code there is fixed now). Apparently there is some symmetry over the first 5 enhancements that yields the correct answer either way, but that breaks down over 18 enhancements. (Also: if I had guessed that `sum(flatten(repeat(n, enhance, grid)))` would be used in both parts, I probably would have created a function for it. But I was guessing that Part Two would involve `enhance`, but not necessarily in the same way as Part One.)"
]
},
{
"cell_type": "code",
"execution_count": 101,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"1936582"
]
},
"execution_count": 101,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sum(flatten(repeat(18, enhance, grid)))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# A Note on Reuse\n",
"\n",
"One interesting question about these two-part problems: how does the code in Part Two use the code in Part One? \n",
"Here are my answers:\n",
"\n",
"* **All new**: All the code for Part Two (except possibly reading and parsing the input) is brand new: \n",
"<br>Days 1, 2, 4, 7\n",
"\n",
"* **Reuse as is**: The major function defined in Part One is called again in Part Two (and some new code\n",
"is also written):\n",
"<br>Days 3 (`spiral`), 6 (`spread`, but `realloc2` is copy-paste-edit), 9, 12, 14 (`bits`), \n",
"15 (`A, B, gen, judge`), 16 (`perform`), 19 (`follow_tubes`), 20 (`evolve, particles`), 21 (`expands`, `gridsum`)\n",
"\n",
"* **Generalize**: A major function from Part One is generalized in Part Two (e.g. by adding an optional parameter):\n",
"<br>Days 13 (`caught`)\n",
"\n",
"* **Copy-paste-edit**: The major function from Part One is copied and edited for Part Two:\n",
"<br>Days 5 (`run2`), 8 (`run82`), 10 (`knothash2`), 11 (`follow2`), 17 (`spinlock2`), 18 (`step18`)\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
@@ -3142,15 +3525,15 @@
},
{
"cell_type": "code",
"execution_count": 89,
"execution_count": 106,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 46.8 s, sys: 62.8 ms, total: 46.9 s\n",
"Wall time: 46.9 s\n"
"CPU times: user 54.6 s, sys: 156 ms, total: 54.7 s\n",
"Wall time: 54.9 s\n"
]
}
],
@@ -3177,7 +3560,7 @@
"day(7, first(programs - set(flatten(above.values()))), 'wiapj', \n",
" correct(wrongest(programs)), 1072)\n",
"day(8, max(run8(program8).values()), 6828, \n",
" run82(program8), 7234)\n",
" run8_2(program8), 7234)\n",
"day(9, total_score(text2), 9662, \n",
" len(text1) - len(text3), 4903)\n",
"day(10, knothash(stream), 4480, \n",
@@ -3201,26 +3584,28 @@
"day(19, cat(filter(str.isalpha, follow_tubes(diagram))), 'VEBTPXCHLI', \n",
" 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)"
" len(nth(map(remove_collisions, evolve(particles())), 1000)), 648)\n",
"day(21, sum(flatten(repeat(5, enhance, grid))), 147,\n",
" sum(flatten(repeat(18, enhance, grid))), 1936582)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"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)."
"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 my estimates, not official times (these include days 1, 3, 8, 13, 14, 17, 18, 21)."
]
},
{
"cell_type": "code",
"execution_count": 90,
"execution_count": 104,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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 truncated
"image/png": "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 truncated
"text/plain": [
"<matplotlib.figure.Figure at 0x11135c358>"
"<matplotlib.figure.Figure at 0x110fb68d0>"
]
},
"metadata": {},
@@ -3238,9 +3623,9 @@
"\n",
"x = None\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, 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, 7)])"
" ('Me', 'd:', 4, 6, 20, 5, 12, 30, 33, 10, 21, 40, 13, 12, 30, 41, 13, 64, 54, 74, 50, 18, 40),\n",
" ('100th', 'v:', 6, 6, 23, 4, 5, 9, 25, 8, 12, 25, 12, 9, 22, 25, 10, 27, 16, 41, 18, 21, 45),\n",
" ('1st', '^:', 1, 1, 4, 1, 2, 3, 10, 3, 4, 6, 3, 2, 6, 5, 2, 5, 5, 10, 5, 7, 10)])"
]
}
],