Advent 2017 through Day 12
This commit is contained in:
parent
70452f56db
commit
548c1e21da
@ -8,7 +8,17 @@
|
|||||||
"\n",
|
"\n",
|
||||||
"Peter Norvig\n",
|
"Peter Norvig\n",
|
||||||
"\n",
|
"\n",
|
||||||
"I'm doing the [Advent of Code](https://adventofcode.com) puzzles, just like [last year](https://github.com/norvig/pytudes/blob/master/ipynb/Advent%20of%20Code.ipynb). But this time, I won't write up my interpretation of each day's the puzzle description; you'll have to follow the links in the section headers (e.g. **[Day 1](https://adventofcode.com/2017/day/1)**) to read those. I just show my solutions.\n",
|
"I'm doing the [Advent of Code](https://adventofcode.com) puzzles, just like [last year](https://github.com/norvig/pytudes/blob/master/ipynb/Advent%20of%20Code.ipynb). This time, my terms of engagement are a bit different:\n",
|
||||||
|
"\n",
|
||||||
|
"* I won't write a summary of each day's puzzle description. Follow the links in the section headers (e.g. **[Day 1](https://adventofcode.com/2017/day/1)**) to understand what each puzzle is asking. \n",
|
||||||
|
"* What you see is mostly the algorithm I first came up with first, although sometimes I go back and refactor if I think the original is unclear.\n",
|
||||||
|
"* I do clean up the code a bit even after I solve the puzzle: adding docstrings, changing variable names, changing input boxes to `assert` statements.\n",
|
||||||
|
"* I will describe my errors that slowed me down.\n",
|
||||||
|
"* Some days I start on time and try to code very quickly (although I know that people at the top of the leader board will be much faster than me); other days I end up starting late and don't worry about going quickly.\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# Day 0: Imports and Utility Functions\n",
|
"# Day 0: Imports and Utility Functions\n",
|
||||||
"\n",
|
"\n",
|
||||||
@ -57,13 +67,13 @@
|
|||||||
" return open('data/advent{}/input{}.txt'.format(year, day))\n",
|
" return open('data/advent{}/input{}.txt'.format(year, day))\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, do 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.split())\n",
|
" return mapt(atom, line.replace(',', ' ').split())\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",
|
||||||
@ -127,7 +137,7 @@
|
|||||||
" return overlapping(iterable, 2)\n",
|
" return overlapping(iterable, 2)\n",
|
||||||
"\n",
|
"\n",
|
||||||
"def sequence(iterable, type=tuple):\n",
|
"def sequence(iterable, type=tuple):\n",
|
||||||
" \"Coerce iterable to sequence: leave it alone if it is already a sequence, else make it of type.\"\n",
|
" \"Coerce iterable to sequence: leave alone if already a sequence, else make it `type`.\"\n",
|
||||||
" return iterable if isinstance(iterable, abc.Sequence) else type(iterable)\n",
|
" return iterable if isinstance(iterable, abc.Sequence) else type(iterable)\n",
|
||||||
"\n",
|
"\n",
|
||||||
"def join(iterable, sep=''):\n",
|
"def join(iterable, sep=''):\n",
|
||||||
@ -255,7 +265,7 @@
|
|||||||
"def always(value): return (lambda *args: value)\n",
|
"def always(value): return (lambda *args: value)\n",
|
||||||
"\n",
|
"\n",
|
||||||
"def Astar(start, moves_func, h_func, cost_func=always(1)):\n",
|
"def Astar(start, moves_func, h_func, cost_func=always(1)):\n",
|
||||||
" \"Find a shortest sequence of states from start to a goal state (a state s with h_func(s) == 0).\"\n",
|
" \"Find a shortest sequence of states from start to a goal state (where h_func(s) == 0).\"\n",
|
||||||
" frontier = [(h_func(start), start)] # A priority queue, ordered by path length, f = g + h\n",
|
" frontier = [(h_func(start), start)] # A priority queue, ordered by path length, f = g + h\n",
|
||||||
" previous = {start: None} # start state has no previous state; other states will\n",
|
" previous = {start: None} # start state has no previous state; other states will\n",
|
||||||
" path_cost = {start: 0} # The cost of the best path to a state.\n",
|
" path_cost = {start: 0} # The cost of the best path to a state.\n",
|
||||||
@ -323,7 +333,8 @@
|
|||||||
" assert isqrt(9) == 3 == isqrt(10)\n",
|
" assert isqrt(9) == 3 == isqrt(10)\n",
|
||||||
" assert ints(1, 100) == range(1, 101)\n",
|
" assert ints(1, 100) == range(1, 101)\n",
|
||||||
" assert identity('anything') == 'anything'\n",
|
" assert identity('anything') == 'anything'\n",
|
||||||
" assert set(powerset({1, 2, 3})) == {(), (1,), (1, 2), (1, 2, 3), (1, 3), (2,), (2, 3), (3,)}\n",
|
" assert set(powerset({1, 2, 3})) == {\n",
|
||||||
|
" (), (1,), (1, 2), (1, 2, 3), (1, 3), (2,), (2, 3), (3,)}\n",
|
||||||
" assert quantify(['testing', 1, 2, 3, int, len], callable) == 2 # int and len are callable\n",
|
" assert quantify(['testing', 1, 2, 3, int, len], callable) == 2 # int and len are callable\n",
|
||||||
" assert quantify([0, False, None, '', [], (), {}, 42]) == 1 # Only 42 is truish\n",
|
" assert quantify([0, False, None, '', [], (), {}, 42]) == 1 # Only 42 is truish\n",
|
||||||
" assert set(shuffled('abc')) == set('abc')\n",
|
" assert set(shuffled('abc')) == set('abc')\n",
|
||||||
@ -359,7 +370,9 @@
|
|||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"# [Day 1](https://adventofcode.com/2017/day/1): Inverse Captcha\n"
|
"# [Day 1](https://adventofcode.com/2017/day/1): Inverse Captcha\n",
|
||||||
|
"\n",
|
||||||
|
"This was easier than I remember last year's puzzles being:\n"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -426,14 +439,7 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"sum(digits[i] \n",
|
"sum(digits[i] \n",
|
||||||
" for i in range(N) \n",
|
" for i in range(N) \n",
|
||||||
" if digits[i] == digits[i - N//2])"
|
" if digits[i] == digits[i - N // 2])"
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"This was an easy warmup puzzle. "
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -527,16 +533,18 @@
|
|||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"This day was also very easy. In Part One, I was slowed down by a typo: I had `\"=\"` instead of `\"-\"` in `\"max(row) - min(row)\"`. I was confused by Python's misleading error message, which said `\"SyntaxError: keyword can't be an expression\"`. Later on, Alex Martelli explained to me that the message meant that in `abs(max(row)=...)` it thought that `max(row)` was a keyword argument to `abs`, as in `abs(x=-1)`.\n",
|
"This day was also very easy. It was nice that my pre-defined `array` function did the whole job of parsing the input. In Part One, I was slowed down by a typo: I had `\"=\"` instead of `\"-\"` in `\"max(row) - min(row)\"`. I was confused by Python's misleading error message, which said `\"SyntaxError: keyword can't be an expression\"`. Later on, Alex Martelli explained to me that the message meant that in `abs(max(row)=...)` it thought that `max(row)` was a keyword argument to `abs`, as in `abs(x=-1)`.\n",
|
||||||
"\n",
|
"\n",
|
||||||
"In Part Two, note that to check that `a/b` is an exact integer, I used `a // b == a / b`, which I think is more clear and less error-prone than the expression one would typically use here, `a % b == 0`, which requires you to think about two things: division and the modulus operator (is it `a % b` or `b % a`?)."
|
"In Part Two, note that to check that `a/b` is an exact integer, I used `a // b == a / b`, which I think is more clear than the marginally-faster expression one would typically use here, `a % b == 0`, which requires you to think about two things: division and the modulus operator (is it `a % b` or `b % a`?)."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"# [Day 3](https://adventofcode.com/2017/day/3): Spiral Memory"
|
"# [Day 3](https://adventofcode.com/2017/day/3): Spiral Memory\n",
|
||||||
|
"\n",
|
||||||
|
"For today the data is just one number:"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -554,16 +562,14 @@
|
|||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"This one takes some thinking, not just fast typing. I analyzed the problem as having three parts:\n",
|
"This puzzle takes some thinking, not just fast typing. I decided to break the problem into three parts:\n",
|
||||||
"- Generate a spiral\n",
|
"- Generate a spiral (by writing a new function called `spiral`).\n",
|
||||||
"- Find the Nth square on the spiral. \n",
|
"- Find the Nth square on the spiral (with my function `nth`).\n",
|
||||||
"- Find the distance from that square to the center.\n",
|
"- Find the distance from that square to the center (with my function `cityblock_distance`).\n",
|
||||||
"\n",
|
"\n",
|
||||||
"I suspect many people will do all three of these in one function. That's probably the best way to get the answer quickly, but I'd rather be clear than quick, so I'll factor out each part, according to the single responsibility principle. My function `spiral()` will generate the coordinates of squares on an infinite spiral, in order, going out from the center square, `(0, 0)`.\n",
|
"I suspect many people will do all three of these in one function. That's probably the best way to get the answer really quickly, but I'd rather be clear than quick (and I'm anticipating that `spiral` will come in handy in Part Two), so I'll factor out each part, obeying the *single responsibility principle*. \n",
|
||||||
"\n",
|
"\n",
|
||||||
"How to make a spiral? My analysis is that, after the center square, the spiral goes 1 square right, then 1 square up, then 2 square left, then 2 square down, to complete one revolution; the next revolution starts with 3 square going up, and so on. I'll call each of these a `leg`, so `spiral` consists of four calls to `leg`, with increments to the `length` after every two legs. \n",
|
"Now I need to make `spiral()` generate the coordinates of squares on an infinite spiral, in order, going out from the center square, `(0, 0)`. After the center square, the spiral goes 1 square right, then 1 square up, then 2 square left, then 2 square down, thus completing one revolution; then it does subsequent revolutions. In general if the previous revolution ended with *s* squares down, then the next revolution consists of *s*+1 squares right, *s*+1 squares up, *s*+2 squares left and *s*+2 down. A small test confirms that this matches the example diagram in the puzzle description (although I had a bug on my first try because I only incremented `s` once per revolution, not twice):"
|
||||||
"\n",
|
|
||||||
"One thing is less clear than I would like: the variable `square` is modified by the function `leg` (in other words, it is an in/out parameter). A small test confirms that this matches the puzzle description:"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -577,15 +583,15 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"text/plain": [
|
"text/plain": [
|
||||||
"[(0, 0),\n",
|
"[(0, 0),\n",
|
||||||
" (1, 0),\n",
|
|
||||||
" (1, 1),\n",
|
|
||||||
" (0, 1),\n",
|
" (0, 1),\n",
|
||||||
" (-1, 1),\n",
|
" (-1, 1),\n",
|
||||||
" (-1, 0),\n",
|
" (-1, 0),\n",
|
||||||
" (-1, -1),\n",
|
" (-1, -1),\n",
|
||||||
" (0, -1),\n",
|
" (0, -1),\n",
|
||||||
" (1, -1),\n",
|
" (1, -1),\n",
|
||||||
" (2, -1)]"
|
" (1, 0),\n",
|
||||||
|
" (1, 1),\n",
|
||||||
|
" (1, 2)]"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 10,
|
"execution_count": 10,
|
||||||
@ -595,24 +601,16 @@
|
|||||||
],
|
],
|
||||||
"source": [
|
"source": [
|
||||||
"def spiral():\n",
|
"def spiral():\n",
|
||||||
" \"Yield the (x, y) coordinates of successive points in an infinite spiral.\"\n",
|
" \"Yield successive (x, y) coordinates of squares on a spiral.\"\n",
|
||||||
" length = 1\n",
|
" x = y = s = 0 # (x, y) is the position; s is the side length.\n",
|
||||||
" square = [0, 0]\n",
|
" yield (x, y)\n",
|
||||||
" yield tuple(square)\n",
|
|
||||||
" while True:\n",
|
" while True:\n",
|
||||||
" yield from leg(square, length, RIGHT)\n",
|
" for (dx, dy) in (RIGHT, UP, LEFT, DOWN):\n",
|
||||||
" yield from leg(square, length, UP)\n",
|
" if dy: s += 1 # Increment side length before RIGHT and LEFT\n",
|
||||||
" length += 1\n",
|
" for _ in range(s):\n",
|
||||||
" yield from leg(square, length, LEFT)\n",
|
" x += dx; y += dy\n",
|
||||||
" yield from leg(square, length, DOWN)\n",
|
" yield (x, y)\n",
|
||||||
" length += 1 \n",
|
"\n",
|
||||||
" \n",
|
|
||||||
"def leg(square, length, delta):\n",
|
|
||||||
" \"Complete one leg of given length, mutating `square` and yielding a copy at each step.\"\n",
|
|
||||||
" for _ in range(length):\n",
|
|
||||||
" square[:] = (X(square) + X(delta), Y(square) + Y(delta))\n",
|
|
||||||
" yield tuple(square) \n",
|
|
||||||
" \n",
|
|
||||||
"list(islice(spiral(), 10))"
|
"list(islice(spiral(), 10))"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -620,7 +618,7 @@
|
|||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"Now we can find the `N`th square. As this is Python, indexes start at 0, whereas the problem starts at 1, so I have to subtract 1. Then I can find the distance to the origin:"
|
"Now we can find the `N`th square. As this is Python, indexes start at 0, whereas the puzzle description starts counting at 1, so I have to subtract 1. Then I can find the distance to the origin:"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -633,7 +631,7 @@
|
|||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"text/plain": [
|
"text/plain": [
|
||||||
"(212, -263)"
|
"(263, 212)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 11,
|
"execution_count": 11,
|
||||||
@ -671,8 +669,6 @@
|
|||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"That's the right answer. I was slow arriving at it because I forgot the second `length += 1` in `spiral` and it took a while to debug. (I had the right analysis, but just left out a line of code.)\n",
|
|
||||||
"\n",
|
|
||||||
"For **Part Two** I can re-use my `spiral` generator, yay! Here's a function to sum the neighboring squares (I can use my `neighbors8` function, yay!):"
|
"For **Part Two** I can re-use my `spiral` generator, yay! Here's a function to sum the neighboring squares (I can use my `neighbors8` function, yay!):"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -771,11 +767,11 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"source": [
|
"source": [
|
||||||
"def isvalid(line):\n",
|
"def is_valid(line): return is_unique(line.split())\n",
|
||||||
" words = line.split()\n",
|
|
||||||
" return len(words) == len(set(words))\n",
|
|
||||||
"\n",
|
"\n",
|
||||||
"quantify(Input(4), isvalid)"
|
"def is_unique(items): return len(items) == len(set(items))\n",
|
||||||
|
"\n",
|
||||||
|
"quantify(Input(4), is_valid)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -804,18 +800,16 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"source": [
|
"source": [
|
||||||
"def isvalid2(line):\n",
|
"def is_valid2(line): return is_unique(mapt(canon, line.split()))\n",
|
||||||
" words = mapt(canon, line.split())\n",
|
|
||||||
" return len(words) == len(set(words))\n",
|
|
||||||
"\n",
|
"\n",
|
||||||
"quantify(Input(4), isvalid2)"
|
"quantify(Input(4), is_valid2)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"That was easy, but the leaders were three times faster than me."
|
"That was easy, and I started on time, but the leaders were still three times faster than me!"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1167,7 +1161,7 @@
|
|||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"# [Day 7](https://adventofcode.com/2017/day/7): Memory Reallocation "
|
"# [Day 7](https://adventofcode.com/2017/day/7): Recursive Circus"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1275,7 +1269,10 @@
|
|||||||
"\n",
|
"\n",
|
||||||
"def siblings(p): \n",
|
"def siblings(p): \n",
|
||||||
" \"The other programs at the same level as this one.\"\n",
|
" \"The other programs at the same level as this one.\"\n",
|
||||||
" return [] if p not in below else [s for s in above[below[p]] if s != p]\n",
|
" if p not in below:\n",
|
||||||
|
" return [] # the root has no siblings\n",
|
||||||
|
" else:\n",
|
||||||
|
" return [s for s in above[below[p]] if s != p]\n",
|
||||||
"\n",
|
"\n",
|
||||||
"below = {a: b for b in programs for a in above[b]}"
|
"below = {a: b for b in programs for a in above[b]}"
|
||||||
]
|
]
|
||||||
@ -1457,7 +1454,7 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"# [Day 9](https://adventofcode.com/2017/day/9): Stream Processing\n",
|
"# [Day 9](https://adventofcode.com/2017/day/9): Stream Processing\n",
|
||||||
"\n",
|
"\n",
|
||||||
"For this problem I could have a complex finite-state machine that handles all the characters `'{<!>}'`, but I think it is easier to clean up the garbage first:"
|
"For this problem I could have a single finite-state machine that handles all five magic characters, `'{<!>}'`, but I think it is easier to first clean up the garbage, using regular expressions:"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1470,7 +1467,7 @@
|
|||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"text/plain": [
|
"text/plain": [
|
||||||
"'{{{{{{{},},{{},}},{{{{}},{{{{{}}},{}},},{{{{},{,{{{}}}}},},{{{}},{{}}}}},{{,{}},{{},},{}}}},{{{{{}}},{}},{{{{},},{}},{{{}},{{{},},{}},{}},{{}}},{{,},{{{}},{{{{}},{{{},},{}}},{{{{},},{}}},{{{}},{{}}}}}}},{{{},{{}},{{{{},{}}},}},{{{}},{}},{{},{{},{}}},{{{}},{{}}}}},{{{{},{}}},{{},{{{{},{}},{{{},{{}}}}}},{{}}},{},{{{}}}},{{{{},{{{},{{},{}}}},{{{{},{}},{{}}}}}},{{{},{{{{}},{{},{}}},{{{,},{{}}},{{}},{{}}},{{}},{{{{},{},{{}}},{{{{}},{{{{{},{}}},{{}}},{,{}},{}},{}},{},{{{},{}}}},{{{{{},},{}},{,},{{}}},{,},{{{},},{{}}}},{{,},{{{{}},{}},{}},{{{},{}}}}},{{{},},{{},{{}}}},{},{{{{{},{}},{},{{}}},{,},{{},}},{{},{}},{{{}},{}}}}},{{{},},{{},{,{{,{}},}}}}},{},{{},{{},{}}},{{},{,},{,{}}}}},{{{{{{},{}}},{,{}},{{},{,{{}}}}},{{},{}}},{{{}},{{},{}},{{}}},{{{},{{},{}}},{{,}},{{,{}},{{{}},{}},{,{,{}}}}},{{{},{}}}},{{{{{}}},{}},{{{},{{{}},{}},{{},}},{{{},},{{,{}}},{{},{{},{}}}},{{{{}}}},{{{,},{}},{},{{},{{{},},{}}},{{{{{}},{{{},{{},}},}},{{},{}},{,{{},{}}}},{{,{}},{{,{}},}},{{,{}}}}}}}},{{{{{},},{{{{},{}}}}},{{{},},{{,{}},{{},{,{}}}}},{{{},{{{{}}},{}}},{{},{},{,}},{{},{{},}},{{{{}},{}},{{},{}},{{{{}}},{}}}},{{{}},{{},{}}}},{{{{{{},{}},{}},{{,},},{{,}}}},{},{},{{{,{}},{{},{}},{{{}},}},{{},{,},{}},{{{},{}}},{{{{}},},{{},},{{}}}}},{{{{{}},{,},{}},{{},{{{{{},},{{{},{}}}},{{},{{},}}},{},{{{{},{}},{}}}}}},{{},{{},{}}},{{{}},{{},},{,{{}}}}}},{{},{{{},{{}},{,{,}}},{{{}}},{{{{},{}},}}},{{{{,{}},{{,{}},{}}},{{{,{,}},{,}},{}},{{{{{},{}}},{{},{}},{{},{{{},}}}},{{{}}},{{{},{{},{}}}}},{}},{{}}},{{{,{{},{}}},{{},}},{{{}},{}},{{{},{}},{{{{}},{{},},{{{{}},{},{{{,{}},{}}}}}}},{,}},{{},{{{},{}},}}}},{{{{{{,{}},{{}},{{}}},{{{{{},{}},{{},{}},{{}}},{{}},{}},{{},},{,{}}},{{}},{{{}},{,},{,{}}}},{{{{}}},{{{,{}}},{{{}},{,}},{{},{{{}},{{},}},{{{}},{{},}},{{{,},{{{},{}},{}},{}},{{{}},{{},}},{{{{{{{},}}}},{{{,{{}}},{}},{,},{{{{}}},{{}}}},{{{}},{,{{},{}}}}}}}}},{{{}},{{},{}}},{{,},{,{,{}}}}},{{{}}},{{},{{}},{,}}},{{{{{{{},},{},{{{,{}},{}},}}},{},{{{}}}},{{,},{{,{}}},{{{},},}},{{{}},{{{,}},{}},{{}}}},{{},{,{}},{{},{{}}}},{{}},{{{},{{},},{}},{{}},{{},{}}}},{{{{}},{{},}},{{{{}},{{},{}}},{}},{{{{}},{},{{{{,{}}},{}}}},{{,{}},{{{{{},},{{{{}}},{,{}},{{}}}},{,},{}},{{}}},{{},}},{{{{},{{,{}},}},{{}},{{},}}},{{{},{{,{}}}},{{{},},},{{}}}}}},{{{{},},{{}}},{{{{{},{}}},{{,}}},{}},{{},{},{}}},{{{{{}},{{}},{}},{{},{{},{{{}},{{,},{{{},{}},{}},{{},}},{}},{{{{},{{{}},{{}}}}}},{{{},{,{{}}}},{{{,}}},{{{{{{},},{{{}},}},{}},{{}},{{{{}},{,{}}},}},{{{,}},{{{{}}},{},{{},}},{{{,{}},{,{}}},{{{}},{{{{},{}}},{{{{,},},}}},{{}}},{}}},{{},{{,{{},}},{{},}},{{{},{}},}},{{{},{}},{,{}},{,}}},{{{{},{{,{}}}}},{{}},{,}}}},{{,},{{},{}},{}},{{{{{{},{}},{}},{{{{},{}},{{}}},{,{{},{}}},{{},{,}}}},{{},{}}},{,},{{{},{}},{{},{}}}}},{{}},{{{{{{}},{{},{}},{{{},{{}}},{}}}},{{{{{},{{}}},{}}}},{{{}},{},{,{}}},{{{{}},{}},{{},{{{{},{{}}}}},{{{},{{}},{,{}}}}}}}}},{{{{{}},},{},{}},{}}},{{},{{{,{}},{,},{{}}},{}},{{,{}},{{{},},{}},{{{}},}},{{{{{}},},{{{,{}},},{,},{{{}},}},{}},{{{{}},{}},{{}}}}}},{{{{{,{}},{},{}},{{}}}},{{{,},{{{},{}}},{{{{},{{{},}}},{}},{{{},},{{,}}},{,{}}}},{{{{},{}},},{{,{{}}}},{{},{}}},{{{},}}},{},{{{{},},{{{}},{}},{{{{{}},{{}}},{}}}},{{{,},{{},{}}},{{},{{}}}},{{{},{{},{}}},{{{},}},{{{}},{},{,{}}},{{{},{{},{}},{,{}}}}}}}}\\n'"
|
"'{{{{{{{},},{{},}},{{{{}},{{{{{}}},{}},},{{{{},{,{{{}}}}},},{{{}},{{}}}'"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 37,
|
"execution_count": 37,
|
||||||
@ -1479,22 +1476,22 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"source": [
|
"source": [
|
||||||
"text1 = Input(9).read() # Read text\n",
|
"text1 = re.sub(r'!.', '', Input(9).read()) # Delete canceled characters\n",
|
||||||
"text2 = re.sub(r'!.', '', text1) # Delete canceled characters\n",
|
"text2 = re.sub(r'<.*?>', '', text1) # Delete garbage\n",
|
||||||
"text3 = re.sub(r'<.*?>', '', text2) # Delete garbage\n",
|
"\n",
|
||||||
"text3"
|
"text2[:70]"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"Now I can deal with nested braces (which can't be handled with regular expressions):"
|
"Now I can deal with the nested braces (which can't be handled with regular expressions):"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 38,
|
"execution_count": 54,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"collapsed": false
|
"collapsed": false
|
||||||
},
|
},
|
||||||
@ -1505,7 +1502,7 @@
|
|||||||
"9662"
|
"9662"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 38,
|
"execution_count": 54,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"output_type": "execute_result"
|
"output_type": "execute_result"
|
||||||
}
|
}
|
||||||
@ -1514,17 +1511,16 @@
|
|||||||
"def total_score(text):\n",
|
"def total_score(text):\n",
|
||||||
" \"Total of group scores; each group scores one more than the group it is nested in.\"\n",
|
" \"Total of group scores; each group scores one more than the group it is nested in.\"\n",
|
||||||
" total = 0\n",
|
" total = 0\n",
|
||||||
" outer = [0] # Stack of scores of groups nested outside current group\n",
|
" level = 0 # Level of nesting\n",
|
||||||
" for c in text:\n",
|
" for c in text:\n",
|
||||||
" if c == '{':\n",
|
" if c == '{':\n",
|
||||||
" score = outer[-1] + 1\n",
|
" total += level + 1\n",
|
||||||
" total += score\n",
|
" level += 1\n",
|
||||||
" outer.append(score)\n",
|
|
||||||
" elif c == '}':\n",
|
" elif c == '}':\n",
|
||||||
" outer.pop()\n",
|
" level -= 1\n",
|
||||||
" return total\n",
|
" return total\n",
|
||||||
"\n",
|
"\n",
|
||||||
"total_score(text3)"
|
"total_score(text2)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1555,19 +1551,19 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"source": [
|
"source": [
|
||||||
"len(text2) - len(text3)"
|
"len(text1) - len(text2)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"But that turned out to be wrong; that counts all of `'<...>'`, whereas I'm not suppossed to count the opening and closing angle brackets, just the `'...'` in the middle. So that would be:"
|
"But this turned out to be wrong; it counts the angle brackets themselves s being deleted, whereas the puzzle is actually asking how many character between the angle brackets are deleted. So that would be:"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 41,
|
"execution_count": 40,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"collapsed": false
|
"collapsed": false
|
||||||
},
|
},
|
||||||
@ -1578,15 +1574,459 @@
|
|||||||
"4903"
|
"4903"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 41,
|
"execution_count": 40,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"output_type": "execute_result"
|
"output_type": "execute_result"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"source": [
|
"source": [
|
||||||
"text4 = re.sub(r'<.*?>', '<>', text2) # Delete inner garbage\n",
|
"text3 = re.sub(r'<.*?>', '<>', text1) # Delete garbage inside brackets, but not brackets\n",
|
||||||
"\n",
|
"\n",
|
||||||
"len(text2) - len(text4)"
|
"len(text1) - len(text3)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# [Day 10](https://adventofcode.com/2017/day/10): Stream Processing"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"I have to do a bunch of reversals of substrings of `stream`. It looks complicated so I will include a `verbose` argument to `knothash` and confirm it works on the example puzzle. I break out the reversal into a separate function, `rev`. The way I handle reversal interacting with wraparound is that I first move all the items before the reversal position to the end of the list, then I do the reversal, then I move them back."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 41,
|
||||||
|
"metadata": {
|
||||||
|
"collapsed": false
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"stream = (63,144,180,149,1,255,167,84,125,65,188,0,2,254,229,24)\n",
|
||||||
|
"\n",
|
||||||
|
"def knothash(lengths, N=256, verbose=False):\n",
|
||||||
|
" \"Do a reversal for each of the numbers in `lengths`.\"\n",
|
||||||
|
" nums = list(range(N))\n",
|
||||||
|
" pos = skip = 0\n",
|
||||||
|
" for L in lengths:\n",
|
||||||
|
" nums = rev(nums, pos, L)\n",
|
||||||
|
" if verbose: print(nums)\n",
|
||||||
|
" pos = (pos + L + skip) % N\n",
|
||||||
|
" skip += 1\n",
|
||||||
|
" return nums[0] * nums[1]\n",
|
||||||
|
" \n",
|
||||||
|
"def rev(nums, pos, L):\n",
|
||||||
|
" \"Reverse nums[pos:pos+L], handling wrap-around.\"\n",
|
||||||
|
" # Move first pos elements to end, reverse first L, move pos elements back\n",
|
||||||
|
" nums = nums[pos:] + nums[:pos]\n",
|
||||||
|
" nums[:L] = reversed(nums[:L])\n",
|
||||||
|
" nums = nums[-pos:] + nums[:-pos]\n",
|
||||||
|
" return nums"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 42,
|
||||||
|
"metadata": {
|
||||||
|
"collapsed": false
|
||||||
|
},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"[2, 1, 0, 3, 4]"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 42,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"# Reverse [0, 1, 2]:\n",
|
||||||
|
"rev(list(range(5)), 0, 3)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 43,
|
||||||
|
"metadata": {
|
||||||
|
"collapsed": false
|
||||||
|
},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"[0, 4, 2, 3, 1]"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 43,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"# Test that rev works when we wrap around: reverse [4, 0, 1]\n",
|
||||||
|
"rev(list(range(5)), 4, 3) "
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 44,
|
||||||
|
"metadata": {
|
||||||
|
"collapsed": false
|
||||||
|
},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"[2, 1, 0, 3, 4]\n",
|
||||||
|
"[4, 3, 0, 1, 2]\n",
|
||||||
|
"[4, 3, 0, 1, 2]\n",
|
||||||
|
"[3, 4, 2, 1, 0]\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"12"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 44,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"# Duplicate the example output\n",
|
||||||
|
"knothash((3, 4, 1, 5), N=5, verbose=True)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"That's correct, but the first time through I got it wrong because I forgot the `\"% N\"` on the update of `pos`."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 45,
|
||||||
|
"metadata": {
|
||||||
|
"collapsed": false
|
||||||
|
},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"4480"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 45,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"knothash(stream)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"**Part Two**:\n",
|
||||||
|
"\n",
|
||||||
|
"Now it gets *really* complicated: string processing, the suffix, hex string output, and dense hashing. But just take them one at a time:"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 46,
|
||||||
|
"metadata": {
|
||||||
|
"collapsed": false
|
||||||
|
},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"'c500ffe015c83b60fad2e4b7d59dabc4'"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 46,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"stream2 = '63,144,180,149,1,255,167,84,125,65,188,0,2,254,229,24'\n",
|
||||||
|
"\n",
|
||||||
|
"def knothash2(lengthstr, N=256, rounds=64, suffix=(17, 31, 73, 47, 23),\n",
|
||||||
|
" verbose=False):\n",
|
||||||
|
" \"Do a reversal for each length; repeat `rounds` times.\"\n",
|
||||||
|
" nums = list(range(N))\n",
|
||||||
|
" lengths = mapt(ord, lengthstr) + suffix\n",
|
||||||
|
" pos = skip = 0\n",
|
||||||
|
" for round in range(rounds):\n",
|
||||||
|
" for L in lengths:\n",
|
||||||
|
" nums = rev(nums, pos, L)\n",
|
||||||
|
" if verbose: print(nums)\n",
|
||||||
|
" pos = (pos + L + skip) % N\n",
|
||||||
|
" skip += 1\n",
|
||||||
|
" return hexstr(dense_hash(nums))\n",
|
||||||
|
"\n",
|
||||||
|
"def hexstr(nums): \n",
|
||||||
|
" \"Convert a sequence of (0 to 255) ints into a hex str.\"\n",
|
||||||
|
" return cat(map('{:02x}'.format, nums))\n",
|
||||||
|
" \n",
|
||||||
|
"def dense_hash(nums, blocksize=16): \n",
|
||||||
|
" \"XOR each block of nums, return the list of them.\"\n",
|
||||||
|
" return [XOR(block) for block in grouper(nums, blocksize)]\n",
|
||||||
|
"\n",
|
||||||
|
"def XOR(nums):\n",
|
||||||
|
" \"Exclusive-or all the numbers together.\"\n",
|
||||||
|
" result = 0\n",
|
||||||
|
" for n in nums:\n",
|
||||||
|
" result ^= n\n",
|
||||||
|
" return result\n",
|
||||||
|
" \n",
|
||||||
|
"assert XOR([65, 27, 9, 1, 4, 3, 40, 50, 91, 7, 6, 0, 2, 5, 68, 22]) == 64\n",
|
||||||
|
"\n",
|
||||||
|
"assert knothash2('') == 'a2582a3a0e66e6e86e3812dcb672a272'\n",
|
||||||
|
"\n",
|
||||||
|
"knothash2(stream2)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"I had a bug: originally I used `'{:x}'` as the format instead of `'{:02x}'`; the later correctly formats `0` as `'00'`, not `'0'`."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# [Day 11](https://adventofcode.com/2017/day/11): Hex Ed\n",
|
||||||
|
"\n",
|
||||||
|
"The first thing I did was search `[hex coordinates]`, and the #1 result (as I expected) was Amit Patel's \"[Hexagonal Grids](https://www.redblobgames.com/grids/hexagons/)\" page. I chose his \"odd-q vertical layout\" to define the six directions as (dx, dy) deltas:"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 47,
|
||||||
|
"metadata": {
|
||||||
|
"collapsed": true
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"directions6 = dict(n=(0, -1), ne=(1, 0), se=(1, 1), s=(0, 1), sw=(-1, 0), nw=(-1, -1))"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"Now I can read the path, follow it, and see where it ends up. If the end point is `(x, y)`, then it will take `max(abs(x), abs(y))` steps to get back to the origin, because each step can increment or decrement either `x` or `y` or both."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 48,
|
||||||
|
"metadata": {
|
||||||
|
"collapsed": false
|
||||||
|
},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"705"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 48,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"path = Input(11).read().strip().split(',')\n",
|
||||||
|
"\n",
|
||||||
|
"def follow(path):\n",
|
||||||
|
" \"Follow each step of the path; return final distance to origin.\"\n",
|
||||||
|
" x, y = (0, 0)\n",
|
||||||
|
" for (dx, dy) in map(directions6.get, path):\n",
|
||||||
|
" x += dx; y += dy\n",
|
||||||
|
" return max(abs(x), abs(y))\n",
|
||||||
|
"\n",
|
||||||
|
"follow(path)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"This one seemed so easy that I didn't bother testing it on the simple examples in the puzzle; all I did was confirm that the answer for my puzzle input was correct.\n",
|
||||||
|
"\n",
|
||||||
|
"**Part Two:**\n",
|
||||||
|
"\n",
|
||||||
|
"This looks pretty easy; repeat Part One, but keep track of the maximum number of steps we get from the origin at any point in the path:"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 49,
|
||||||
|
"metadata": {
|
||||||
|
"collapsed": false
|
||||||
|
},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"1469"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 49,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"def follow2(path):\n",
|
||||||
|
" \"Follow each step of the path; return max steps to origin.\"\n",
|
||||||
|
" x = y = maxsteps = 0\n",
|
||||||
|
" for (dx, dy) in map(directions6.get, path):\n",
|
||||||
|
" x += dx; y += dy\n",
|
||||||
|
" maxsteps = max(maxsteps, abs(x), abs(y))\n",
|
||||||
|
" return maxsteps\n",
|
||||||
|
"\n",
|
||||||
|
"follow2(path)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"Again, no tests, just the final answer.\n",
|
||||||
|
"\n",
|
||||||
|
"# [Day 12](https://adventofcode.com/2017/day/12): Digital Plumber\n",
|
||||||
|
"\n",
|
||||||
|
"First I'll parse the data, creating a dict of `{program: direct_group_of_proggrams}`:"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 50,
|
||||||
|
"metadata": {
|
||||||
|
"collapsed": false
|
||||||
|
},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"{0, 659, 737}"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 50,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"def groups(lines):\n",
|
||||||
|
" \"Dict of {i: {directly_connected_to_i}\"\n",
|
||||||
|
" return {lhs: {lhs} | set(rhs)\n",
|
||||||
|
" for (lhs, _, *rhs) in array(lines)}\n",
|
||||||
|
" \n",
|
||||||
|
"groups(Input(12))[0]"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"That looks good. I recognize this as a [Union-Find](https://en.wikipedia.org/wiki/Disjoint-set_data_structure) problem, for which there are efficient algorithms. But for this small example, I don't need efficiency, I need clarity and simplicity. So I'll write `merge` to take a dict and merge together the sets that are connected:"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 51,
|
||||||
|
"metadata": {
|
||||||
|
"collapsed": false
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"def merge(G):\n",
|
||||||
|
" \"Merge all indirectly connected groups together.\"\n",
|
||||||
|
" for i in G:\n",
|
||||||
|
" for j in list(G[i]):\n",
|
||||||
|
" if G[i] != G[j]:\n",
|
||||||
|
" G[i].update(G[j])\n",
|
||||||
|
" G[j] = G[i]\n",
|
||||||
|
" return G\n",
|
||||||
|
"\n",
|
||||||
|
"G = merge(groups(Input(12)))"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 52,
|
||||||
|
"metadata": {
|
||||||
|
"collapsed": false
|
||||||
|
},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"115"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 52,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"len(G[0])"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"That's the answer for Part One.\n",
|
||||||
|
"\n",
|
||||||
|
"**Part Two**\n",
|
||||||
|
"\n",
|
||||||
|
"I did almost all the work; I just need to count the unique groups. The minimum value in a group uniquely identifies it, so:"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 53,
|
||||||
|
"metadata": {
|
||||||
|
"collapsed": false
|
||||||
|
},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"221"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 53,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"len({min(G[i]) for i in G})"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
Loading…
Reference in New Issue
Block a user