From db46e8df33e55fad9eb362c8bcc228976bebcee8 Mon Sep 17 00:00:00 2001 From: Peter Norvig Date: Wed, 23 Jan 2019 01:56:30 -0800 Subject: [PATCH] Add files via upload --- ipynb/Advent-2018.ipynb | 724 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 665 insertions(+), 59 deletions(-) diff --git a/ipynb/Advent-2018.ipynb b/ipynb/Advent-2018.ipynb index 4e34d37..3e81991 100644 --- a/ipynb/Advent-2018.ipynb +++ b/ipynb/Advent-2018.ipynb @@ -11,7 +11,7 @@ "Here are my solutions to [Advent of Code 2018](https://adventofcode.com/2018), following up on my solutions for [2017](Advent%202017.ipynb) and [2016](Advent%20of%20Code.ipynb). In order to understand each day's two-part puzzle, you'll need to click on the header links below (e.g. **[Day 1: Chronal Calibration](https://adventofcode.com/2018/day/1)**) and read the description. This year I didn't find time to race in real time when the puzzles are released; instead I'm doing batch processing, doing a couple of puzzles every few days. You can see I'm currently behind.\n", "\n", "\n", - "# Utility Functions" + "# Imports and Utility Functions" ] }, { @@ -22,39 +22,18 @@ "source": [ "#### IMPORTS\n", "\n", + "%matplotlib inline\n", + "import matplotlib.pyplot as plt\n", "import re\n", "from collections import Counter, defaultdict, namedtuple, deque\n", - "from itertools import chain, cycle, product, count as count_from\n", - "from functools import lru_cache\n", + "from itertools import chain, cycle, product, islice, count as count_from\n", + "from functools import lru_cache, total_ordering\n", + "from dataclasses import dataclass\n", "\n", "#### CONSTANTS\n", "\n", - "alphabet = 'abcdefghijklmnopqrstuvwxyz'\n", - "ALPHABET = alphabet.upper()\n", "infinity = float('inf')\n", - "\n", - "#### SIMPLE UTILITY FUNCTIONS\n", - "\n", - "cat = ''.join\n", - "\n", - "def ints(start, end, step=1):\n", - " \"The integers from start to end, inclusive: range(start, end+1)\"\n", - " return range(start, end + 1, step)\n", - "\n", - "def quantify(iterable, pred=bool):\n", - " \"Count how many times the predicate is true of an item in iterable.\"\n", - " return sum(map(pred, iterable))\n", - "\n", - "def multimap(items):\n", - " \"Given (key, val) pairs, return {key: [val, ....], ...}.\"\n", - " result = defaultdict(list)\n", - " for (key, val) in items:\n", - " result[key].append(val)\n", - " return result\n", - "\n", - "def mapt(fn, *args): \n", - " \"Do a map, and make the results into a tuple.\"\n", - " return tuple(map(fn, *args))\n", + "bignum = 10 ** 100\n", "\n", "#### FILE INPUT AND PARSING\n", "\n", @@ -64,7 +43,36 @@ "\n", "def integers(text): \n", " \"A tuple of all integers in a string (ignore other characters).\"\n", - " return mapt(int, re.findall(r'-?\\d+', text))" + " return mapt(int, re.findall(r'-?\\d+', text))\n", + "\n", + "#### UTILITY FUNCTIONS\n", + "\n", + "def mapt(fn, *args): \n", + " \"Do a map, and make the results into a tuple.\"\n", + " return tuple(map(fn, *args))\n", + "\n", + "def first(iterable, default=None):\n", + " \"Return first item in iterable, or default.\"\n", + " return next(iter(iterable), default)\n", + "\n", + "def nth(iterable, n): return next(islice(iter(iterable), n, n+1))\n", + "\n", + "cat = ''.join\n", + "\n", + "def rangei(start, end, step=1):\n", + " \"\"\"Inclusive, range from start to end: rangei(a, b) = range(a, b+1).\"\"\"\n", + " return range(start, end + 1, step) \n", + "\n", + "def quantify(iterable, pred=bool):\n", + " \"Count how many items in iterable have pred(item) true.\"\n", + " return sum(map(pred, iterable))\n", + "\n", + "def multimap(items):\n", + " \"Given (key, val) pairs, return {key: [val, ....], ...}.\"\n", + " result = defaultdict(list)\n", + " for (key, val) in items:\n", + " result[key].append(val)\n", + " return result" ] }, { @@ -272,7 +280,7 @@ "\n", "def value_ge(dic, threshold):\n", " \"How many items in dic have a value >= threshold?\"\n", - " return sum(dic[x] >= threshold for x in dic)\n", + " return quantify(dic[x] >= threshold for x in dic)\n", "\n", "value_ge(claimed(input3), 2)" ] @@ -305,14 +313,14 @@ "source": [ "# Part 2\n", "\n", - "def process2(claims):\n", + "def claimed2(claims):\n", " \"Which claim has only positions that are claimed by nobody else?\"\n", " C = claimed(claims)\n", " for (id, x, y, w, h) in claims:\n", " if all(C[pos] == 1 for pos in product(range(x, x+w), range(y, y+h))):\n", " return id\n", "\n", - "process2(input3)" + "claimed2(input3)" ] }, { @@ -547,6 +555,9 @@ "source": [ "input5 = cat(Input(5))\n", "\n", + "alphabet = 'abcdefghijklmnopqrstuvwxyz'\n", + "ALPHABET = alphabet.upper()\n", + "\n", "OR = '|'.join\n", "reaction = OR(OR([c + C, C + c]) for c, C in zip(alphabet, ALPHABET))\n", "\n", @@ -887,32 +898,32 @@ { "data": { "text/plain": [ - "{'C': 195,\n", - " 'H': 266,\n", - " 'Y': 84,\n", - " 'P': 408,\n", - " 'V': 215,\n", - " 'L': 426,\n", - " 'T': 503,\n", + "{'U': 354,\n", " 'I': 423,\n", - " 'F': 332,\n", - " 'D': 259,\n", - " 'Z': 665,\n", - " 'B': 61,\n", - " 'O': 273,\n", - " 'R': 743,\n", - " 'A': 60,\n", - " 'N': 579,\n", " 'M': 133,\n", - " 'U': 354,\n", - " 'Q': 350,\n", - " 'W': 144,\n", - " 'S': 505,\n", - " 'K': 132,\n", - " 'J': 813,\n", " 'X': 897,\n", + " 'K': 132,\n", + " 'W': 144,\n", + " 'J': 813,\n", + " 'T': 503,\n", + " 'D': 259,\n", + " 'B': 61,\n", + " 'P': 408,\n", + " 'L': 426,\n", + " 'E': 198,\n", + " 'O': 273,\n", + " 'A': 60,\n", + " 'F': 332,\n", + " 'Y': 84,\n", + " 'C': 195,\n", + " 'V': 215,\n", + " 'Z': 665,\n", + " 'S': 505,\n", " 'G': 66,\n", - " 'E': 198}" + " 'H': 266,\n", + " 'N': 579,\n", + " 'Q': 350,\n", + " 'R': 743}" ] }, "execution_count": 35, @@ -1165,7 +1176,7 @@ " \"Add `marbles` to `circle`, rotating according to rules and scoring every 23 marbles.\"\n", " circle = deque([0])\n", " scores = Counter()\n", - " for m in ints(1, marbles):\n", + " for m in rangei(1, marbles):\n", " player = (m - 1) % players + 1\n", " if m % 23:\n", " circle.rotate(-1)\n", @@ -1284,8 +1295,8 @@ "def lightshow(points, chars='.#'):\n", " \"Print out the locations of the points.\"\n", " xmin, xmax, ymin, ymax = bounds(points)\n", - " for y in ints(ymin, ymax):\n", - " print(cat(chars[(x, y) in points] for x in ints(xmin, xmax)))" + " for y in rangei(ymin, ymax):\n", + " print(cat(chars[(x, y) in points] for x in rangei(xmin, xmax)))" ] }, { @@ -1396,7 +1407,7 @@ "\n", "def maxpower(bounds=300, width=3):\n", " \"Return (power, topleft, width) of square within `bounds` of maximum power.\"\n", - " toplefts = product(ints(1, bounds - width), repeat=2)\n", + " toplefts = product(rangei(1, bounds - width), repeat=2)\n", " return max((total_power(topleft, width), topleft, width)\n", " for topleft in toplefts)\n", "\n", @@ -1434,7 +1445,7 @@ " I[x, y] = key((x, y)) + I[x, y - 1] + I[x - 1, y] - I[x - 1, y - 1]\n", " return I\n", "\n", - "def total_power(topleft, width=3, I=summed_area(ints(1, 300), power_level)):\n", + "def total_power(topleft, width=3, I=summed_area(rangei(1, 300), power_level)):\n", " \"Total power in square with given topleft corner and width (from `I`).\"\n", " x, y = topleft\n", " xmin, ymin, xmax, ymax = x - 1, y - 1, x + width - 1, y + width - 1\n", @@ -1472,6 +1483,601 @@ "source": [ "assert _[1] + _[2:] == (284, 172, 12), 'Day 11.2'" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# [Day 12: Subterranean Sustainability](https://adventofcode.com/2018/day/12)\n", + "\n", + "One-dimensional cellular automata. Fun! AT first I thought I would represent a row of plants as a string, but I realized I'd also need to track the start position of the string, so I decided it was easier to represent a row of plants as a set of index numbers of pots that are live. I'll make `grow()` yield all future generations:" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1447" + ] + }, + "execution_count": 60, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def Plants(row):\n", + " \"Convert '#..#.#' => {0, 3, 5}\"\n", + " return {i for i, c in enumerate(row) if c == '#'}\n", + "\n", + "input12 = Input(12, str.split)\n", + "plants = Plants(input12[0][-1])\n", + "rules = {rule[0]: rule[-1] for rule in input12 if '=>' in rule}\n", + "\n", + "def grow(plants=plants, rules=rules):\n", + " \"Grow plants using rules, yielding each generation (including 0th).\"\n", + " while True:\n", + " yield plants\n", + " extent = rangei(min(plants) - 3, max(plants) + 3)\n", + " plants = {i for i in extent if lives(plants, i, rules)}\n", + "\n", + "def lives(plants, i, rules):\n", + " \"Does pot i live in next generation, according to rules?\"\n", + " neighborhood = cat('.#'[j in plants] for j in rangei(i - 2, i + 2))\n", + " return rules[neighborhood] == '#'\n", + "\n", + "sum(nth(grow(), 20))" + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "metadata": {}, + "outputs": [], + "source": [ + "assert _ == 1447, 'Day 12.1' " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Cool, my answer is leet-er than `1337`. But Part 2, asks for 50 billion generations. Maybe the pattern reaches a fixed point, or cycles, like the blinker in Conway's Life game. Let's plot the number of plants, and their sums, over the first couple hundred generations and see if there is a pattern:" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# Part 2\n", + "\n", + "gens = list(islice(grow(), 200))\n", + "plt.plot(mapt(len, gens), label='x');" + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(mapt(sum, gens));" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "OK, no problem: after a while the sum settles down and grows at a constant rate. I can figure out what that rate is:" + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[2580, 2601, 2622]" + ] + }, + "execution_count": 64, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "[sum(plants) for plants in gens[100:103]]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It is growing by 21 each time. So my answer should be 2580 + 21 × (50 billion - 100). More generally:" + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1050000000480" + ] + }, + "execution_count": 65, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def nth_plant_sum(n, k=100):\n", + " \"The sum of plants in the nth generation, assuming linear growth after k generations.\"\n", + " a, b = map(sum, islice(grow(), k, k + 2))\n", + " return a + (n - k) * (b - a)\n", + "\n", + "nth_plant_sum(50 * 10 ** 9)" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "metadata": {}, + "outputs": [], + "source": [ + "assert _ == 1050000000480, 'Day 12.2'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, *why* does it grow by 21 each time? Well, we saw in the plot of lengths above that the length eventually remains constant; we can see the last few:" + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[21, 21, 21]" + ] + }, + "execution_count": 67, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "[len(g) for g in gens[-3:]]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are 21 plants in each generation; here is one generation:" + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{59,\n", + " 60,\n", + " 61,\n", + " 71,\n", + " 72,\n", + " 73,\n", + " 84,\n", + " 85,\n", + " 86,\n", + " 137,\n", + " 138,\n", + " 139,\n", + " 149,\n", + " 150,\n", + " 151,\n", + " 159,\n", + " 160,\n", + " 161,\n", + " 194,\n", + " 195,\n", + " 196}" + ] + }, + "execution_count": 68, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gens[100]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We see that the plants are bunched into groups of three. What happens to a group of three?" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{0: {1, 2, 3},\n", + " 1: {2, 3, 4},\n", + " 2: {3, 4, 5},\n", + " 3: {4, 5, 6},\n", + " 4: {5, 6, 7},\n", + " 5: {6, 7, 8}}" + ] + }, + "execution_count": 69, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dict(zip(range(6), grow(plants={1, 2, 3})))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "They move one to the right each generation; moving 7 groups of 3 one to the right results in adding 21 to the sum." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# [Day 13: Mine Cart Madness](https://adventofcode.com/2018/day/13)\n", + "\n", + "I need to do the following:\n", + "\n", + "- Parse the picture of the world into a data structure.\n", + "- Keep track of the carts, and have them make the right moves. \n", + "- Continue until there is a collision.\n", + "\n", + "Since this has to do with headings and turns, I'm going to use complex numbers to represent points, headings, and turns; multiplying a complex heading by a complex turn results in a new heading. Then adding the heading to a point results in a new point. I'll redefine the `X` and `Y` accessors to handle either the complex points used here or the tuple points used previously:" + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "metadata": {}, + "outputs": [], + "source": [ + "Point = complex\n", + "\n", + "def X(point): return point.real if isinstance(point, complex) else point[0]\n", + "def Y(point): return point.imag if isinstance(point, complex) else point[1]\n", + "\n", + "# Headings: HU, HD, HR, HL = Head up, down, right, left\n", + "# Turns: TL, TR, TS, TB = Turn left, right, straight, backwards\n", + "HU = TL = Point(0, -1)\n", + "HD = TR = -TL\n", + "HR = TS = Point(1, 0)\n", + "HL = TB = -TS\n", + "\n", + "headings = {'>': HR, '<': HL, '^': HU, 'v': HD}\n", + "inverse_headings = dict((headings[ch], ch) for ch in headings)\n", + "\n", + "assert HU * TR == HR and TR ** 4 == TL ** 4 == TS and HR * TB == HL and HU == -HD and TR == -TL" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we need to define a `Cart`, and the `World` they will live in. We use the [`dataclass`](https://docs.python.org/3/library/dataclasses.html) facility from Python 3." + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "metadata": {}, + "outputs": [], + "source": [ + "@dataclass\n", + "class Cart:\n", + " name: str\n", + " pos: Point\n", + " heading: Point\n", + " turns: object\n", + " \n", + "@dataclass\n", + "class World:\n", + " grid: tuple\n", + " carts: list\n", + "\n", + " def __init__(self, lines):\n", + " table = str.maketrans('<>^v', '--||')\n", + " names = cycle(alphabet)\n", + " self.grid = [line.translate(table) for line in lines]\n", + " self.carts = [Cart(next(names), Point(x, y), headings[h], cycle((TL, TS, TR)))\n", + " for (y, line) in enumerate(lines)\n", + " for (x, h) in enumerate(line)\n", + " if h in headings]\n", + " \n", + " def __getitem__(self, point): return self.grid[int(Y(point))][int(X(point))]\n", + " \n", + " def show(self):\n", + " \"Print a representation of the world.\"\n", + " for y, line in enumerate(self.grid):\n", + " for x, ch in enumerate(line):\n", + " cart = first(c for c in self.carts if c.pos == Point(x, y))\n", + " ch = inverse_headings[cart.heading] if cart else ch\n", + " print(ch, end='')\n", + " print()\n", + " print()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now the function `madmad(world)`, which simulates moving the carts until a collision." + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "metadata": {}, + "outputs": [], + "source": [ + "def madmad(world, show=False):\n", + " \"Simulate the mine cart madness until a collision.\"\n", + " while True:\n", + " if show: world.show()\n", + " for cart in sorted(world.carts, key=lambda c: (Y(c.pos), X(c.pos))):\n", + " cart.heading *= turn(cart, world)\n", + " cart.pos += cart.heading\n", + " if collision(cart, world):\n", + " if show: world.show()\n", + " return cart.pos\n", + "\n", + "def turn(cart, world):\n", + " \"Which way should the cart turn (depends on character it is on).\"\n", + " ch = world[cart.pos]\n", + " if ch == '+':\n", + " return next(cart.turns) \n", + " if ch == '/':\n", + " return (TR if cart.heading in (HU, HD) else TL)\n", + " if ch == '\\\\': \n", + " return (TR if cart.heading in (HR, HL) else TL)\n", + " else:\n", + " return TS\n", + " \n", + "def collision(cart, world):\n", + " \"Has this cart collided with any other cart?\"\n", + " return any(cart.pos == other.pos for other in world.carts if other is not cart)" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/->-\\ \n", + "| | /----\\ \n", + "| /-+--+-\\ | \n", + "| | | | v | \n", + "\\-+-/ \\-+--/ \n", + " \\------/\n", + "\n", + "/-->\\ \n", + "| | /----\\ \n", + "| /-+--+-\\ | \n", + "| | | | | | \n", + "\\-+-/ \\-v--/ \n", + " \\------/\n", + "\n", + "/---> \n", + "| | /----\\ \n", + "| /-+--+-\\ | \n", + "| | | | | | \n", + "\\-+-/ \\-+>-/ \n", + " \\------/\n", + "\n", + "/---\\ \n", + "| v /----\\ \n", + "| /-+--+-\\ | \n", + "| | | | | | \n", + "\\-+-/ \\-+->/ \n", + " \\------/\n", + "\n", + "/---\\ \n", + "| | /----\\ \n", + "| /-v--+-\\ | \n", + "| | | | | | \n", + "\\-+-/ \\-+--> \n", + " \\------/\n", + "\n", + "/---\\ \n", + "| | /----\\ \n", + "| /-+>-+-\\ | \n", + "| | | | | ^ \n", + "\\-+-/ \\-+--/ \n", + " \\------/\n", + "\n", + "/---\\ \n", + "| | /----\\ \n", + "| /-+->+-\\ ^ \n", + "| | | | | | \n", + "\\-+-/ \\-+--/ \n", + " \\------/\n", + "\n", + "/---\\ \n", + "| | /----^ \n", + "| /-+-->-\\ | \n", + "| | | | | | \n", + "\\-+-/ \\-+--/ \n", + " \\------/\n", + "\n", + "/---\\ \n", + "| | /---<\\ \n", + "| /-+--+>\\ | \n", + "| | | | | | \n", + "\\-+-/ \\-+--/ \n", + " \\------/\n", + "\n", + "/---\\ \n", + "| | /--<-\\ \n", + "| /-+--+-> | \n", + "| | | | | | \n", + "\\-+-/ \\-+--/ \n", + " \\------/\n", + "\n", + "/---\\ \n", + "| | /-<--\\ \n", + "| /-+--+-\\ | \n", + "| | | | v | \n", + "\\-+-/ \\-+--/ \n", + " \\------/\n", + "\n", + "/---\\ \n", + "| | /<---\\ \n", + "| /-+--+-\\ | \n", + "| | | | | | \n", + "\\-+-/ \\-v--/ \n", + " \\------/\n", + "\n", + "/---\\ \n", + "| | <----\\ \n", + "| /-+--+-\\ | \n", + "| | | | | | \n", + "\\-+-/ \\<+--/ \n", + " \\------/\n", + "\n", + "/---\\ \n", + "| | /----\\ \n", + "| /-+--v-\\ | \n", + "| | | | | | \n", + "\\-+-/ <-+--/ \n", + " \\------/\n", + "\n", + "/---\\ \n", + "| | /----\\ \n", + "| /-+--+-\\ | \n", + "| | | ^ | | \n", + "\\-+-/ \\-+--/ \n", + " \\------/\n", + "\n" + ] + }, + { + "data": { + "text/plain": [ + "(7+3j)" + ] + }, + "execution_count": 73, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "example = r'''\n", + "/->-\\ \n", + "| | /----\\ \n", + "| /-+--+-\\ | \n", + "| | | | v | \n", + "\\-+-/ \\-+--/ \n", + " \\------/ \n", + "'''.strip().splitlines()\n", + "\n", + "madmad(World(example), True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The answer, `7, 3`, is correct, and everything looks good!\n", + "\n", + "Unfortunately, I got an error when trying it on the actual (larger) input.\n", + "\n", + "Even more unfortunately, I ran out of spare time to debug this problem and to move on to the remaining days. Maybe next year!" + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "metadata": {}, + "outputs": [ + { + "ename": "IndexError", + "evalue": "string index out of range", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mIndexError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mmadmad\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mWorld\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mInput\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m13\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m\u001b[0m in \u001b[0;36mmadmad\u001b[0;34m(world, show)\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mshow\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mworld\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshow\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mcart\u001b[0m \u001b[0;32min\u001b[0m \u001b[0msorted\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mworld\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcarts\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkey\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mlambda\u001b[0m \u001b[0mc\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mY\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mc\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpos\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mX\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mc\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpos\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 6\u001b[0;31m \u001b[0mcart\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mheading\u001b[0m \u001b[0;34m*=\u001b[0m \u001b[0mturn\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcart\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mworld\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 7\u001b[0m \u001b[0mcart\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpos\u001b[0m \u001b[0;34m+=\u001b[0m \u001b[0mcart\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mheading\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 8\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mcollision\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcart\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mworld\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m\u001b[0m in \u001b[0;36mturn\u001b[0;34m(cart, world)\u001b[0m\n\u001b[1;32m 12\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mturn\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcart\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mworld\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 13\u001b[0m \u001b[0;34m\"Which way should the cart turn (depends on character it is on).\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 14\u001b[0;31m \u001b[0mch\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mworld\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mcart\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpos\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 15\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mch\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m'+'\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 16\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcart\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mturns\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m\u001b[0m in \u001b[0;36m__getitem__\u001b[0;34m(self, point)\u001b[0m\n\u001b[1;32m 20\u001b[0m if h in headings]\n\u001b[1;32m 21\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 22\u001b[0;31m \u001b[0;32mdef\u001b[0m \u001b[0m__getitem__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mpoint\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgrid\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mY\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpoint\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpoint\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 23\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 24\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mshow\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mIndexError\u001b[0m: string index out of range" + ] + } + ], + "source": [ + "madmad(World(Input(13)))" + ] } ], "metadata": {