diff --git a/ipynb/Advent-2018.ipynb b/ipynb/Advent-2018.ipynb new file mode 100644 index 0000000..4e34d37 --- /dev/null +++ b/ipynb/Advent-2018.ipynb @@ -0,0 +1,1498 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Advent of Code 2018\n", + "\n", + "
Peter Norvig
December 2018
\n", + "\n", + "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" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "#### IMPORTS\n", + "\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", + "\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", + "\n", + "#### FILE INPUT AND PARSING\n", + "\n", + "def Input(day, line_parser=str.strip, file_template='data/advent2018/input{}.txt'):\n", + " \"For this day's input file, return a tuple of each line parsed by `line_parser`.\"\n", + " return mapt(line_parser, open(file_template.format(day)))\n", + "\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))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# [Day 1: Chronal Calibration](https://adventofcode.com/2018/day/1)\n", + "\n", + "Part 1 was just a wordy way of saying \"add up the input numbers.\" Part 2 requires cycling through the numbers multiple times, finding the partial sum (frequency) that first appears twice (that is, was seen before)." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "466" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "input1 = Input(1, int)\n", + "\n", + "sum(input1)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "assert _ == 466, 'Day 1.1'" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "750" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Part 2\n", + "\n", + "def partial_sums(nums):\n", + " \"The sums of each prefix of nums.\"\n", + " total = 0\n", + " for n in nums:\n", + " total += n\n", + " yield total\n", + " \n", + "def seen_before(items):\n", + " \"The first item that appears twice.\"\n", + " seen = set()\n", + " for item in items:\n", + " if item in seen:\n", + " return item\n", + " seen.add(item)\n", + " \n", + "seen_before(partial_sums(cycle(input1)))" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "assert _ == 750, 'Day 1.2'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# [Day 2: Inventory Management System](https://adventofcode.com/2018/day/7)\n", + "\n", + "How many ids have a letter that appears exactly 2 times? 3 times?" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "8118" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "input2 = Input(2)\n", + "\n", + "def numwith(ids, n): return quantify(ids, lambda s: n in Counter(s).values())\n", + "\n", + "numwith(input2, 2) * numwith(input2, 3)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "assert _ == 8118, 'Day 2.1'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, what letters are in common among the ids that differ in exactly one position?" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'jbbenqtlaxhivmwyscjukztdp'" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Part 2\n", + "\n", + "def diff(A, B): return sum(a != b for a, b in zip(A, B))\n", + "\n", + "def common(A, B): return cat(a for a,b in zip(A, B) if a == b)\n", + "\n", + "common(*[i for i in input2 if any(diff(i, x) == 1 for x in input2)])" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "assert _ == 'jbbenqtlaxhivmwyscjukztdp', 'Day 2.2'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# [Day 3: No Matter How You Slice It](https://adventofcode.com/2018/day/7)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "103806" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "input3 = Input(3, integers)\n", + "\n", + "def claimed(claims):\n", + " \"For each square inch position (x, y), how many claims claimed it?\"\n", + " C = Counter()\n", + " for (id, x, y, w, h) in claims:\n", + " C += Counter(product(range(x, x+w), range(y, y+h)))\n", + " return C\n", + "\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", + "\n", + "value_ge(claimed(input3), 2)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "assert _ == 103806, 'Day 3.1'" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "625" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Part 2\n", + "\n", + "def process2(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)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "assert _ == 625, 'Day 3.2'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# [Day 4: Repose Record ](https://adventofcode.com/2018/day/7)\n", + "\n", + "Find the guard that has the most minutes asleep, and the minute of the day that the guard spends asleep the most.\n", + "\n", + "First make sure we can parse the log correctly:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(('1518-11-01', 0, 'Guard', '10'),\n", + " ('1518-11-01', 5, 'falls', 'asleep'),\n", + " ('1518-11-01', 25, 'wakes', 'up'),\n", + " ('1518-11-01', 30, 'falls', 'asleep'),\n", + " ('1518-11-01', 55, 'wakes', 'up'),\n", + " ('1518-11-01', 58, 'Guard', '99'),\n", + " ('1518-11-02', 40, 'falls', 'asleep'),\n", + " ('1518-11-02', 50, 'wakes', 'up'),\n", + " ('1518-11-03', 5, 'Guard', '10'),\n", + " ('1518-11-03', 24, 'falls', 'asleep'),\n", + " ('1518-11-03', 29, 'wakes', 'up'),\n", + " ('1518-11-04', 2, 'Guard', '99'),\n", + " ('1518-11-04', 36, 'falls', 'asleep'),\n", + " ('1518-11-04', 46, 'wakes', 'up'),\n", + " ('1518-11-05', 3, 'Guard', '99'),\n", + " ('1518-11-05', 45, 'falls', 'asleep'),\n", + " ('1518-11-05', 55, 'wakes', 'up'))" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "example4 = '''[1518-11-01 00:00] Guard #10 begins shift\n", + "[1518-11-01 00:05] falls asleep\n", + "[1518-11-01 00:25] wakes up\n", + "[1518-11-01 00:30] falls asleep\n", + "[1518-11-01 00:55] wakes up\n", + "[1518-11-01 23:58] Guard #99 begins shift\n", + "[1518-11-02 00:40] falls asleep\n", + "[1518-11-02 00:50] wakes up\n", + "[1518-11-03 00:05] Guard #10 begins shift\n", + "[1518-11-03 00:24] falls asleep\n", + "[1518-11-03 00:29] wakes up\n", + "[1518-11-04 00:02] Guard #99 begins shift\n", + "[1518-11-04 00:36] falls asleep\n", + "[1518-11-04 00:46] wakes up\n", + "[1518-11-05 00:03] Guard #99 begins shift\n", + "[1518-11-05 00:45] falls asleep\n", + "[1518-11-05 00:55] wakes up'''.splitlines()\n", + "\n", + "def parse_record(text):\n", + " \"Return date, minutes, opcode action, and argument.\"\n", + " text = re.sub('[][#:]', ' ', text)\n", + " day, hour, mins, opcode, arg, *rest = text.split()\n", + " return day, int(mins), opcode, arg\n", + "\n", + "mapt(parse_record, example4)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "defaultdict(list,\n", + " {'1518-11-02': [40, 41, 42, 43, 44, 45, 46, 47, 48, 49],\n", + " '1518-11-04': [36, 37, 38, 39, 40, 41, 42, 43, 44, 45],\n", + " '1518-11-05': [45, 46, 47, 48, 49, 50, 51, 52, 53, 54]})" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def timecard(log):\n", + " \"Return {guard: {day: [asleep_range, ...]}}.\"\n", + " records = sorted(map(parse_record, log))\n", + " sleep = defaultdict(lambda: defaultdict(list)) # sleep[guard][day] = [minute, ...]\n", + " for day, mins, opcode, arg in records:\n", + " if opcode == 'Guard':\n", + " guard = int(arg)\n", + " elif opcode == 'falls':\n", + " falls = mins\n", + " elif opcode == 'wakes':\n", + " wakes = mins\n", + " sleep[guard][day].extend(range(falls, wakes))\n", + " else:\n", + " error()\n", + " return sleep\n", + " \n", + "timecard(example4)[99] # Guard 99's sleep record" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "143415" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def sleepiest_guard(sleep):\n", + " \"Which guard sleeps the most minutes?\"\n", + " return max(sleep, key=lambda guard: sum(map(len, sleep[guard].values())))\n", + "\n", + "def sleepiest_minute(guard, sleep):\n", + " \"In which minute is this guard asleep the most?\"\n", + " def times_asleep(m): return sum(m in mins for mins in sleep[guard].values())\n", + " return max(range(60), key=times_asleep)\n", + "\n", + "def repose(lines):\n", + " \"The ID of the sleepiest guard times that guard's sleepiest minute.\"\n", + " sleep = timecard(lines)\n", + " guard = sleepiest_guard(sleep)\n", + " minute = sleepiest_minute(guard, sleep)\n", + " return guard * minute\n", + "\n", + "repose(Input(4))" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "assert _ == 143415, 'Day 4.1'" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "49944" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Part 2\n", + "\n", + "def repose2(lines):\n", + " \"\"\"Of all guards, which guard is most frequently asleep on the same minute?\n", + " What is the ID of the guard you chose multiplied by the minute you chose?\"\"\"\n", + " sleep = timecard(lines)\n", + " c = Counter((guard, minute) for guard in sleep\n", + " for minute in chain(*sleep[guard].values()))\n", + " [((guard, minute), _)] = c.most_common(1)\n", + " return guard * minute\n", + "\n", + "repose2(Input(4))" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "assert _ == 49944, 'Day 4.2'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# [Day 5: Alchemical Reduction](https://adventofcode.com/2018/day/5)\n", + "\n", + "How many units remain after fully reacting the polymer you scanned? Reacting means removing `Cc` and `cC`, etc." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'dabCBAcaDA'" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "input5 = cat(Input(5))\n", + "\n", + "OR = '|'.join\n", + "reaction = OR(OR([c + C, C + c]) for c, C in zip(alphabet, ALPHABET))\n", + "\n", + "def reduction(text):\n", + " \"Remove all cC or Cc pairs, and repeat.\"\n", + " while True:\n", + " text, n = re.subn(reaction, '', text)\n", + " if n == 0:\n", + " return text\n", + " \n", + "reduction('dabAcCaCBAcCcaDA')" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "10180" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(reduction(input5))" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [], + "source": [ + "assert _ == 10180, 'Day 5.1'" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5668" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Part 2\n", + "\n", + "def shortest(items): return min(items, key=len)\n", + "\n", + "def reduction2(text):\n", + " \"\"\"What is the length of the shortest polymer you can produce by removing \n", + " all units of exactly one type and fully reacting the result?\"\"\"\n", + " return shortest(reduction(re.sub(c, '', text, flags=re.I))\n", + " for c in alphabet)\n", + "\n", + "len(reduction2(input5))" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [], + "source": [ + "assert _ == 5668, 'Day 5.2'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# [Day 6: Chronal Coordinates](https://adventofcode.com/2018/day/6)\n", + "\n", + "Given a set of pointson a grid (all positive integer (x, y) coordinates), each point defines an *area* of grid points that are closer to it than any other point in the set (i.e. the Voronoi diagram). What is the size of the largest area (measured in number of grid points) that isn't infinite? To answer, use a `Counter` over grid points in the bounding box that covers all the input points. \n", + "\n", + "Originally, I thought that if a point on the perimeter of the box was closest to point `p`, then `p` has an infinite area. That's true for `p` that are in the corners, but need not be true for all `p` (consider a set of 5 points, 4 in the corners of a box, and one near the center; the center point will have a diamond-shaped area, and unless it is at the exact center, it will \"leak\" outside one of the edges). So I expand the bounding box by a margin. I make a guess at the necessary margin size, so my answer might not always be correct, but hey, it worked for the input I was given." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Counter({(3, 4): 9, (5, 5): 17})" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "input6 = Input(6, integers)\n", + "\n", + "def closest_to(point, points):\n", + " \"Which of `points` is closest to `point`? Return None if tie.\"\n", + " p1, p2 = sorted(points, key=lambda p: distance(p, point))[:2]\n", + " return p1 if distance(p1, point) < distance(p2, point) else None\n", + "\n", + "def X(point): return point[0]\n", + "def Y(point): return point[1]\n", + "def distance(p, q): return abs(X(p) - X(q)) + abs(Y(p) - Y(q))\n", + "def maxval(dic): return max(dic.values())\n", + "\n", + "def closest_counts(points, margin=100):\n", + " \"What is the size of the largest area of closest points that isn't infinite?\"\n", + " assert len(points) > 1\n", + " xside = range(-margin, max(map(X, points)) + margin)\n", + " yside = range(-margin, max(map(Y, points)) + margin)\n", + " box = product(xside, yside)\n", + " # counts[point] = number of grid points in box that are closest to point\n", + " counts = Counter(closest_to(p, points) for p in box)\n", + " # Now delete the counts that are suspected to be infinite:\n", + " for p in perimeter(xside, yside):\n", + " c = closest_to(p, points)\n", + " if c in counts:\n", + " del counts[c]\n", + " return counts\n", + "\n", + "def perimeter(xside, yside):\n", + " \"The perimeter of a box with these sides.\"\n", + " return chain(((xside[0], y) for y in yside),\n", + " ((xside[-1], y) for y in yside),\n", + " ((x, yside[0]) for x in xside),\n", + " ((x, yside[-1]) for x in xside))\n", + " \n", + "closest_counts([(1, 1), (1, 6), (8, 3), (3, 4), (5, 5), (8, 9)]) " + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3010" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "maxval(closest_counts(input6))" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [], + "source": [ + "assert _ == 3010, 'Day 6.1'" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [], + "source": [ + "# Part 2\n", + "\n", + "def total_distance(point, points):\n", + " \"Total distance of this point to all the `points`.\"\n", + " return sum(distance(point, c) for c in points)\n", + "\n", + "def neighborhood(points, dist=10000):\n", + " \"\"\"What is the size of the region containing all locations which have a \n", + " total distance to all given points of less than `dist`?\"\"\"\n", + " maxx = max(x for x,y in points)\n", + " maxy = max(y for x,y in points)\n", + " box = product(range(maxx + 1), range(maxy + 1))\n", + " return quantify(box, lambda p: total_distance(p, points) < dist)" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "48034" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "neighborhood(Input(6, integers))" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [], + "source": [ + "assert _ == 48034, 'Day 6.2'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# [Day 7: The Sum of Its Parts](https://adventofcode.com/2018/day/7)\n", + "\n", + "Given instructions like `Step B must be finished before step A can begin`, in what order should the steps be completed? When several steps are possible, do the alphabetically first (`min`) first.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['B', 'A']" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def parse_instruction(line): return re.findall(' ([A-Z]) ', line)\n", + "\n", + "parse_instruction('Step B must be finished before step A can begin.')" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'ABGKCMVWYDEHFOPQUILSTNZRJX'" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "input7 = Input(7, parse_instruction)\n", + "\n", + "def order(pairs):\n", + " \"Yield steps in order, respecting (before, after) pairs; break ties lexically.\"\n", + " steps = {step for pair in pairs for step in pair} # Steps remaining to be done\n", + " prereqs = multimap((A, B) for [B, A] in pairs) # prereqs[A] = [B, ...]\n", + " def ready(step): return all(pre not in steps for pre in prereqs[step])\n", + " while steps:\n", + " step = min(filter(ready, steps)) \n", + " steps.remove(step)\n", + " yield step\n", + " \n", + "cat(order(input7))" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [], + "source": [ + "assert _ == 'ABGKCMVWYDEHFOPQUILSTNZRJX', 'Day 7.1'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In part 2 I need to schedule each step; I'll keep track of `endtime[step]` as a time step (initially infinitely far in the future). I'll also keep track of the number of available workers, decrementing the count when I assign a step to a worker, and incrementing the count when a step is completed." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [], + "source": [ + "# Part 2\n", + "\n", + "def schedule(pairs, workers=5):\n", + " \"Return a {step:endtime} map for best schedule, given a number of `workers`.\"\n", + " steps = {step for pair in pairs for step in pair} # Steps remaining to be done\n", + " prereqs = multimap((A, B) for (B, A) in pairs) # prereqs[A] = [B, ...]\n", + " endtime = {step: infinity for step in steps} # endtime[step] = time it will be completed\n", + " for t in count_from(0): \n", + " # Assign available steps to free workers\n", + " def ready(step): return all(endtime[p] < t for p in prereqs[step])\n", + " available = filter(ready, steps)\n", + " for step in sorted(available)[:workers]:\n", + " endtime[step] = t + 60 + ord(step) - ord('A')\n", + " steps.remove(step)\n", + " workers -= 1\n", + " # Discover if any workers become free this time step\n", + " workers += quantify(endtime[step] == t for step in endtime)\n", + " # Return answer once all steps have been scheduled\n", + " if not steps:\n", + " return endtime" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'C': 195,\n", + " 'H': 266,\n", + " 'Y': 84,\n", + " 'P': 408,\n", + " 'V': 215,\n", + " 'L': 426,\n", + " 'T': 503,\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", + " 'G': 66,\n", + " 'E': 198}" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "schedule(input7)" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "898" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "maxval(schedule(input7)) + 1" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [], + "source": [ + "assert _ == 898, 'Day 7.2'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# [Day 8: Memory Maneuver](https://adventofcode.com/2018/day/8)\n", + "\n", + "I'll handle the list of numbers as a `deque` (so I can efficiently pop from the left), and I'll create a `Tree` data structure with `metadata` and `kids` (children) fields." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [], + "source": [ + "input8 = integers(cat(Input(8)))\n", + " \n", + "Tree = namedtuple('Tree', 'kids, metadata')\n", + "\n", + "def tree(nums):\n", + " \"\"\"A node consists of the numbers: [k, n, (k child nodes)..., (n metadata values)...].\"\"\"\n", + " k, n = nums.popleft(), nums.popleft()\n", + " return Tree([tree(nums) for _ in range(k)], \n", + " [nums.popleft() for _ in range(n)])\n", + "\n", + "example8 = tree(deque((2, 3, 0, 3, 10, 11, 12, 1, 1, 0, 1, 99, 2, 1, 1, 2)))\n", + "tree8 = tree(deque(input8))" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "138" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def sum_metadata(tree):\n", + " \"What is the sum of all metadata entries in a tree?\"\n", + " return sum(tree.metadata) + sum(map(sum_metadata, tree.kids))\n", + "\n", + "sum_metadata(example8)" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "48443" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sum_metadata(tree8)" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [], + "source": [ + "assert _ == 48443, 'Day 8.1'" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [], + "source": [ + "def value(tree):\n", + " \"\"\"What is the value of the root node? \n", + " Value of tree with no kids is sum of metadata; \n", + " Otherwise metadata are 1-based indexes into kids; sum up their values.\"\"\"\n", + " if not tree.kids:\n", + " return sum(tree.metadata)\n", + " else:\n", + " return sum(value(tree.kids[i - 1]) \n", + " for i in tree.metadata \n", + " if i <= len(tree.kids)) " + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "66" + ] + }, + "execution_count": 43, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "value(example8)" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "30063" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "value(tree8)" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": {}, + "outputs": [], + "source": [ + "assert _ == 30063, 'Day 8.2'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# [Day 9: Marble Mania](https://adventofcode.com/2018/day/9)\n", + "\n", + "I'll represent the circle as a `deque` (so I can `rotate`). I had a couple of off-by-one errors, due in part to the problem being 1-based not 0-based, so I put in the `verbose` option; in my verbose output (unlike the problem description), the circle always starts with the position after the \"current\" position, and the newly-inserted marble is always last." + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1 [0, 1]\n", + "2 [1, 0, 2]\n", + "3 [0, 2, 1, 3]\n", + "4 [2, 1, 3, 0, 4]\n", + "5 [1, 3, 0, 4, 2, 5]\n", + "6 [3, 0, 4, 2, 5, 1, 6]\n", + "7 [0, 4, 2, 5, 1, 6, 3, 7]\n", + "8 [4, 2, 5, 1, 6, 3, 7, 0, 8]\n", + "9 [2, 5, 1, 6, 3, 7, 0, 8, 4, 9]\n", + "1 [5, 1, 6, 3, 7, 0, 8, 4, 9, 2, 10]\n", + "2 [1, 6, 3, 7, 0, 8, 4, 9, 2, 10, 5, 11]\n", + "3 [6, 3, 7, 0, 8, 4, 9, 2, 10, 5, 11, 1, 12]\n", + "4 [3, 7, 0, 8, 4, 9, 2, 10, 5, 11, 1, 12, 6, 13]\n", + "5 [7, 0, 8, 4, 9, 2, 10, 5, 11, 1, 12, 6, 13, 3, 14]\n", + "6 [0, 8, 4, 9, 2, 10, 5, 11, 1, 12, 6, 13, 3, 14, 7, 15]\n", + "7 [8, 4, 9, 2, 10, 5, 11, 1, 12, 6, 13, 3, 14, 7, 15, 0, 16]\n", + "8 [4, 9, 2, 10, 5, 11, 1, 12, 6, 13, 3, 14, 7, 15, 0, 16, 8, 17]\n", + "9 [9, 2, 10, 5, 11, 1, 12, 6, 13, 3, 14, 7, 15, 0, 16, 8, 17, 4, 18]\n", + "1 [2, 10, 5, 11, 1, 12, 6, 13, 3, 14, 7, 15, 0, 16, 8, 17, 4, 18, 9, 19]\n", + "2 [10, 5, 11, 1, 12, 6, 13, 3, 14, 7, 15, 0, 16, 8, 17, 4, 18, 9, 19, 2, 20]\n", + "3 [5, 11, 1, 12, 6, 13, 3, 14, 7, 15, 0, 16, 8, 17, 4, 18, 9, 19, 2, 20, 10, 21]\n", + "4 [11, 1, 12, 6, 13, 3, 14, 7, 15, 0, 16, 8, 17, 4, 18, 9, 19, 2, 20, 10, 21, 5, 22]\n", + "5 [2, 20, 10, 21, 5, 22, 11, 1, 12, 6, 13, 3, 14, 7, 15, 0, 16, 8, 17, 4, 18, 19]\n", + "6 [20, 10, 21, 5, 22, 11, 1, 12, 6, 13, 3, 14, 7, 15, 0, 16, 8, 17, 4, 18, 19, 2, 24]\n", + "7 [10, 21, 5, 22, 11, 1, 12, 6, 13, 3, 14, 7, 15, 0, 16, 8, 17, 4, 18, 19, 2, 24, 20, 25]\n" + ] + }, + { + "data": { + "text/plain": [ + "Counter({5: 32})" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "players, marbles = integers('448 players; last marble is worth 71628 points')\n", + "\n", + "def play(players, marbles, verbose=False):\n", + " \"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", + " player = (m - 1) % players + 1\n", + " if m % 23:\n", + " circle.rotate(-1)\n", + " circle.append(m)\n", + " else:\n", + " circle.rotate(+7)\n", + " scores[player] += circle.pop() + m\n", + " circle.rotate(-1)\n", + " if verbose: print(player, list(circle))\n", + " return scores\n", + "\n", + "play(9, 25, True)" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "394486" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "maxval(play(players, marbles))" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": {}, + "outputs": [], + "source": [ + "assert _ == 394486, 'Day 9.1'" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3276488008" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "maxval(play(players, marbles * 100))" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": {}, + "outputs": [], + "source": [ + "assert _ == 3276488008, 'Day 9.2'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# [Day 10: The Stars Align](https://adventofcode.com/2018/day/10)\n", + "\n", + "\n", + "The basic idea of moving objects into a new configuration is easy: increment their position by their velocity to get a set of points. The hard part is figuring out when they spell a message. My assumption is that the lights are initially spread far afield; over time they will come together into a central location; then they will overshoot and go off in the other direction. My even bigger (but still resonable) assumption is that the message is spelled when the area of the bounding box of the points is a minimum. When that happens, I'll just print the configuration, and read what is there. (I won't write code to do OCR.)" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "metadata": {}, + "outputs": [], + "source": [ + "Light = namedtuple('Light', 'x, y, dx, dy')\n", + "\n", + "input10 = Input(10, lambda line: Light(*integers(line)))\n", + "\n", + "def configuration(t, lights=input10):\n", + " \"The positions of the lights at time t.\"\n", + " return {(L.x + t * L.dx, L.y + t * L.dy) for L in lights}\n", + " \n", + "def bounds(points):\n", + " \"Return xmin, xmax, ymin, ymax of the bounding box.\"\n", + " return (min(map(X, points)), max(map(X, points)),\n", + " min(map(Y, points)), max(map(Y, points)))\n", + "\n", + "def extent(points):\n", + " \"Area of bounding box.\"\n", + " xmin, xmax, ymin, ymax = bounds(points)\n", + " return (xmax - xmin) * (ymax - ymin)\n", + "\n", + "def localmin(sequence, key):\n", + " \"Iterate through sequence, when key(item) starts going up, return previous item.\"\n", + " prevscore = infinity\n", + " for item in sequence:\n", + " score = key(item)\n", + " if prevscore < score:\n", + " return prev\n", + " prev, prevscore = item, score\n", + " \n", + "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)))" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "#####...#....#.....###..#....#.....###....##....######..#....#\n", + "#....#..#....#......#...#....#......#....#..#...#.......#....#\n", + "#....#...#..#.......#....#..#.......#...#....#..#........#..#.\n", + "#....#...#..#.......#....#..#.......#...#....#..#........#..#.\n", + "#####.....##........#.....##........#...#....#..#####.....##..\n", + "#....#....##........#.....##........#...######..#.........##..\n", + "#....#...#..#.......#....#..#.......#...#....#..#........#..#.\n", + "#....#...#..#...#...#....#..#...#...#...#....#..#........#..#.\n", + "#....#..#....#..#...#...#....#..#...#...#....#..#.......#....#\n", + "#####...#....#...###....#....#...###....#....#..######..#....#\n" + ] + } + ], + "source": [ + "configurations = map(configuration, count_from(0))\n", + "lights = localmin(configurations, key=extent)\n", + "lightshow(lights)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "That should be \"`BXJXJAEX`\".\n", + "\n", + "In Part 2 I need to get the time `t` at which we generate the configuration `lights`:" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "10605" + ] + }, + "execution_count": 53, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Part 2\n", + "\n", + "next(t for t in count_from(1) if configuration(t) == lights)" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": {}, + "outputs": [], + "source": [ + "assert _ == 10605" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# [Day 11: Chronal Charge](https://adventofcode.com/2018/day/11)" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(30, (243, 27), 3)" + ] + }, + "execution_count": 55, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "serial = input11 = 6303\n", + "\n", + "def power_level(point):\n", + " \"Follow the rules for power level.\"\n", + " id = X(point) + 10\n", + " level = (id * Y(point) + serial) * id\n", + " return (level // 100) % 10 - 5\n", + "\n", + "def total_power(topleft, width=3):\n", + " \"Total power in the square with given topleft corner and width.\"\n", + " x, y = topleft\n", + " square = product(range(x, x + width), range(y, y + width))\n", + " return sum(map(power_level, square))\n", + "\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", + " return max((total_power(topleft, width), topleft, width)\n", + " for topleft in toplefts)\n", + "\n", + "maxpower()" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "metadata": {}, + "outputs": [], + "source": [ + "assert _[1] == (243, 27), 'Day 11.1'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To enumerate all the squares of any width is O(n4); too much when *n* = 300. So I'll refactor `total_power` to cache results in a summed-area table (a standard approach from computer graphics)." + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": {}, + "outputs": [], + "source": [ + "# Part 2\n", + "\n", + "def summed_area(side, key):\n", + " \"A summed-area table.\"\n", + " I = defaultdict(int)\n", + " for x, y in product(side, side):\n", + " 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", + " \"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", + " return I[xmin, ymin] + I[xmax, ymax] - I[xmax, ymin] - I[xmin, ymax]\n", + "\n", + "# Make sure we get the same answer for the Part 1 problem\n", + "assert maxpower() == (30, (243, 27), 3)" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(88, (284, 172), 12)" + ] + }, + "execution_count": 58, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "max(maxpower(width=w) for w in range(300))" + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "metadata": {}, + "outputs": [], + "source": [ + "assert _[1] + _[2:] == (284, 172, 12), 'Day 11.2'" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.0" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}