diff --git a/ipynb/Advent-2022.ipynb b/ipynb/Advent-2022.ipynb index 3bba6cf..a61d66d 100644 --- a/ipynb/Advent-2022.ipynb +++ b/ipynb/Advent-2022.ipynb @@ -329,7 +329,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.000 seconds for correct answer: 8,401\n" + "0.001 seconds for correct answer: 8,401\n" ] } ], @@ -878,7 +878,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.049 seconds for correct answer: 1,829\n" + "0.050 seconds for correct answer: 1,829\n" ] } ], @@ -920,7 +920,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.054 seconds for correct answer: 291,840\n" + "0.055 seconds for correct answer: 291,840\n" ] } ], @@ -1043,7 +1043,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.019 seconds for correct answer: 6,236\n" + "0.021 seconds for correct answer: 6,236\n" ] } ], @@ -1091,8 +1091,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.025 seconds for correct answer: 6,236\n", - "0.109 seconds for correct answer: 2,449\n" + "0.023 seconds for correct answer: 6,236\n", + "0.108 seconds for correct answer: 2,449\n" ] } ], @@ -1491,7 +1491,7 @@ "output_type": "stream", "text": [ "0.001 seconds for correct answer: 54,036\n", - "0.285 seconds for correct answer: 13,237,873,355\n" + "0.282 seconds for correct answer: 13,237,873,355\n" ] } ], @@ -1593,7 +1593,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.035 seconds for correct answer: 394\n" + "0.036 seconds for correct answer: 394\n" ] } ], @@ -1650,7 +1650,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Almost a thousand locations; so the search would take over 30 seconds. [Who's got that kind of time?](https://www.gocomics.com/calvinandhobbes/1995/08/17) Instead I'll invent yet another problem subclass, `HillClimbProblem2`, which starts at a dummy state that is off the grid, and can transition with zero cost from that state to any location with height 1:" + "Almost a thousand locations; so the search would take about 30 seconds. [Who's got that kind of time?](https://www.gocomics.com/calvinandhobbes/1995/08/17) Instead I'll invent yet another problem subclass, `HillClimbProblem2`, which starts at a dummy state that is off the grid, and can transition with zero cost from that state to any location with height 1:" ] }, { @@ -1662,7 +1662,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.038 seconds for correct answer: 388\n" + "0.039 seconds for correct answer: 388\n" ] } ], @@ -1674,7 +1674,7 @@ " return A_star_search(HillClimbProblem2(off_grid, goal, grid=grid))\n", "\n", "class HillClimbProblem2(GridProblem):\n", - " \"\"\"A GridProblem where heights are letters, and you can't climb upward more than one letter.\"\"\"\n", + " \"\"\"Like HillClimbProblem, but with a free pass to any location with height 1.\"\"\"\n", " \n", " def action_cost(self, s1, a, s2): return 0 if s1 == off_grid else 1\n", " \n", @@ -1692,6 +1692,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "\n", + "\n", "#### Part 3: Exploration\n", "\n", "I'm interested in seeing what the landscape looks like. We can repurpose the code used to show the forest:" @@ -1723,6 +1725,165 @@ " cmap=plt.get_cmap('YlGn')))" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# [Day 13](https://adventofcode.com/2022/day/13): Distress Signal\n", + "\n", + "\n", + "The input is divided into paragraphs, where each paragraph has two nested list structures, with integer values." + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "Puzzle input ➜ 449 lines:\n", + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "[[[[],[7,10,6,5],[],[8]],0,1,[[8,10]],4],[[[4,0,3,2,0]],7],[[],3,[[0,2],8,5],[],[[2,10,4,6]]],[3 ...\n", + "[[0,2,7],[],[10,[[0,7,3,6]]],[2,[8],3]]\n", + "\n", + "[[],[[0,8,[],[0,8,6,7,10]],4,10,[[],9,[1],4,10],3],[[7,1,0],10,[[1,10,7,8],[3,7],[3,6],5],1,0],[ ...\n", + "[[[],0,[[4,5,10],8]],[0,5],[[[1,8,8,8],2],0,[7,9],3]]\n", + "\n", + "...\n", + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "Parsed representation ➜ 150 tuples:\n", + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "([[[[], [7, 10, 6, 5], [], [8]], 0, 1, [[8, 10]], 4], [[[4, 0, 3, 2, 0]], 7], [[], 3, [[0, 2], 8 ...\n", + "([[], [[0, 8, [], [0, 8, 6, 7, 10]], 4, 10, [[], 9, [1], 4, 10], 3], [[7, 1, 0], 10, [[1, 10, 7, ...\n", + "([[3, [[10, 2, 8], 3, 0, [2, 1], 7]], [[[1, 9, 5, 5, 8], 1, 9, [9, 2, 4, 5]], 2, 1, [[], [4, 3], ...\n", + "([[4, 0], [[[5, 7, 10, 9], [1, 8, 0, 3], [10]], 9, 10, []], [5, [[1, 3], 5, 3, [], [4, 8, 0, 2, ...\n", + "([[], [2, [[3, 0, 5]]], [1, [7], [0, [10, 3, 9], 10, [8, 2, 4, 2, 2], [5]], [[0, 2, 3, 1], 3, [8 ...\n", + "([[7], [0, [2], 3], [1, 0], [[], 2], [1, [[6], [4, 3, 10, 2, 5], [6, 9, 3, 3, 0], [8, 5], 10], [ ...\n", + "...\n" + ] + } + ], + "source": [ + "in13 = parse(13, (lambda text: mapt(literal_eval, text.splitlines())), paragraphs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Part 1: Determine which pairs of packets are already in the right order. What is the sum of the indices of those pairs?\n", + "\n", + "The notion of \"right order\" is almost the same as \"less than\", except that an integer is equal to a singleton list. I was confused for a bit because the rules don't say whether two identical lists are in the right order or not. I decided that `right_order` should return `True` or `False` for inputs that are different, and `None` for inputs that are equal. " + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": {}, + "outputs": [], + "source": [ + "def right_order(left, right) -> Union[bool, None]:\n", + " \"\"\"Are the two packets in the right order? Return `None` if equal.\"\"\"\n", + " types = type(left), type(right)\n", + " if types == (int, int):\n", + " return (None if left == right else left <= right)\n", + " elif types == (int, list):\n", + " return right_order([left], right)\n", + " elif types == (list, int):\n", + " return right_order(left, [right])\n", + " else:\n", + " for L, R in zip(left, right):\n", + " result = right_order(L, R)\n", + " if result != None:\n", + " return result\n", + " return (None if len(left) == len(right) else len(left) < len(right))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "That makes things pretty straightforward, but I threw in some test cases just to make sure: " + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": {}, + "outputs": [], + "source": [ + "assert right_order([1,1,3,1,1], [1,1,5,1,1])\n", + "assert right_order([[1],[2,3,4]], [[1],4])\n", + "assert not right_order([7,7,7,7], [7,7,7]) \n", + "assert not right_order([1,[2,[3,[4,[5,6,7]]]],8,9], [1,[2,[3,[4,[5,6,0]]]],8,9])\n", + "assert right_order([1,2,3], [1,2,3]) == None" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "At this point I'm ready to compute the answer, and was happy to see it worked the first time:" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.000 seconds for correct answer: 5,882\n" + ] + } + ], + "source": [ + "answer(13.1, 5882, lambda: sum(i for i, (L, R) in enumerate(in13, 1) if right_order(L, R)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Part 2: Organize all of the packets into the correct order. What is the decoder key for the distress signal?\n", + "\n", + "This should be easy; all I have to do is sort using `right_order`, then look up the resulting indices of the two divider packets (taking care to use 1-based rather than 0-based indexing). I tried to sort using `key=right_order` and got an error message reminding me how foolish I am: `right_order` is not a key function (of one argument), it is a comparison function (of two arguments).\n", + "\n", + "In Python 2, the `sorted` function did accept a two-argument `cmp` function, but in Python 3 that functionality is gone. I had to look it up to find that the `functools.cmp_to_key` function converts a comparison function to a key function, and that a `cmp_to_key` comparison function returns -1/0/+1, not True/None/False like my `right_order` does. Once I correct for that, it works fine." + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.007 seconds for correct answer: 24,948\n" + ] + } + ], + "source": [ + "def decode(packets, dividers=[[[2]],[[6]]]):\n", + " \"\"\"Sort the packets and the dividers, and return their product of the indices of the dividers.\"\"\"\n", + " ordered = sorted(append(packets) + dividers, key=functools.cmp_to_key(cmp_order))\n", + " return prod(ordered.index(d) + 1 for d in dividers)\n", + "\n", + "def cmp_order(left, right) -> int:\n", + " \"\"\"Call `right_order` and convert True/None/False to -1/0/+1.\"\"\"\n", + " return {True: -1, None: 0, False: +1}[right_order(left, right)]\n", + " \n", + "answer(13.2, 24948, lambda: decode(in13))" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -1734,7 +1895,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 50, "metadata": {}, "outputs": [ { @@ -1744,7 +1905,7 @@ " 1.2: '0.000 seconds for correct answer: 206,582',\n", " 2.1: '0.001 seconds for correct answer: 13,268',\n", " 2.2: '0.001 seconds for correct answer: 15,508',\n", - " 3.1: '0.000 seconds for correct answer: 8,401',\n", + " 3.1: '0.001 seconds for correct answer: 8,401',\n", " 3.2: '0.000 seconds for correct answer: 2,641',\n", " 4.1: '0.000 seconds for correct answer: 477',\n", " 4.2: '0.000 seconds for correct answer: 830',\n", @@ -1754,19 +1915,21 @@ " 6.2: '0.002 seconds for correct answer: 3,059',\n", " 7.1: '0.001 seconds for correct answer: 1,232,307',\n", " 7.2: '0.001 seconds for correct answer: 7,268,994',\n", - " 8.1: '0.049 seconds for correct answer: 1,829',\n", - " 8.2: '0.054 seconds for correct answer: 291,840',\n", - " 9.1: '0.025 seconds for correct answer: 6,236',\n", - " 9.2: '0.109 seconds for correct answer: 2,449',\n", + " 8.1: '0.050 seconds for correct answer: 1,829',\n", + " 8.2: '0.055 seconds for correct answer: 291,840',\n", + " 9.1: '0.023 seconds for correct answer: 6,236',\n", + " 9.2: '0.108 seconds for correct answer: 2,449',\n", " 10.1: '0.000 seconds for correct answer: 12,560',\n", " 10.2: '0.012 seconds for correct answer: PLPAFBCL',\n", " 11.1: '0.001 seconds for correct answer: 54,036',\n", - " 11.2: '0.285 seconds for correct answer: 13,237,873,355',\n", - " 12.1: '0.035 seconds for correct answer: 394',\n", - " 12.2: '0.038 seconds for correct answer: 388'}" + " 11.2: '0.282 seconds for correct answer: 13,237,873,355',\n", + " 12.1: '0.036 seconds for correct answer: 394',\n", + " 12.2: '0.039 seconds for correct answer: 388',\n", + " 13.1: '0.000 seconds for correct answer: 5,882',\n", + " 13.2: '0.007 seconds for correct answer: 24,948'}" ] }, - "execution_count": 45, + "execution_count": 50, "metadata": {}, "output_type": "execute_result" }