From f374494d417c1047c553d695225ab4c80f088435 Mon Sep 17 00:00:00 2001 From: Peter Norvig Date: Mon, 6 Dec 2021 22:51:07 -0800 Subject: [PATCH] Delete Advent-2021.ipynb --- Advent-2021.ipynb | 914 ---------------------------------------------- 1 file changed, 914 deletions(-) delete mode 100644 Advent-2021.ipynb diff --git a/Advent-2021.ipynb b/Advent-2021.ipynb deleted file mode 100644 index 8fcd6dc..0000000 --- a/Advent-2021.ipynb +++ /dev/null @@ -1,914 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
Peter Norvig, 1–25 Dec 2021
\n", - "\n", - "# Advent of Code 2021\n", - "\n", - "I'm going to solve some [Advent of Code 2021](https://adventofcode.com/) (AoC) puzzles, but I won't be competing for points. \n", - "\n", - "I won't explain each puzzle here; you'll have to click on each day's link (e.g. [Day 1](https://adventofcode.com/2021/day/1)) to understand the puzzle. \n", - "\n", - "Part of the idea of AoC is that you have to make some design choices to solve part 1 before you get to see the description of part 2. So there is a tension of wanting the solution to part 1 to provide general components that might be re-used in part 2, without falling victim to [YAGNI](https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it). Except for errors, I will show the code as I developed it; I won't go back and refactor the code for part 1 when I see part 2.\n", - "\n", - "# Day 0: Imports and Utilities\n", - "\n", - "First, imports that I have used in past AoC years:" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "from __future__ import annotations\n", - "from collections import Counter, defaultdict, namedtuple, deque\n", - "from itertools import permutations, combinations, chain, count as count_from, product as cross_product\n", - "from typing import Dict, Tuple, Set, List, Iterator, Optional, Union\n", - "from statistics import mean\n", - "\n", - "import functools\n", - "import math\n", - "import re\n", - "\n", - "cat = ''.join\n", - "flatten = chain.from_iterable\n", - "cache = functools.lru_cache(None)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now two functions that I will use each day, `parse` and `answer`. I will start by writing something like this for part 1 of Day 1:\n", - "\n", - " in1 = parse(1, int)\n", - " def solve_it(numbers): return ...\n", - " solve(in1)\n", - " \n", - "That is, `parse(1, int)` will parse the data file for day 1 in the format of one integer per line; then some function (here `solve_it`) will (hopefully) compute the correct answer. I'll then submit the answer to AoC and verify it is correct. If it is, I'll move on to solve part 2. When I get them both done, I'll use the function `answers` to assert the correct answers. So it will look like this:\n", - "\n", - " in1 = parse(1, int)\n", - " \n", - " def solve_it(numbers): return ...\n", - " answer(1.1, solve_it(in1), 123)\n", - " \n", - " def solve_it2(numbers): return ...\n", - " answer(1.2, solve_it2(in1), 123456)\n", - " \n" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "def parse(day, parser=str, sep='\\n') -> tuple:\n", - " \"Split the day's input file into sections separated by `sep`, and apply `parser` to each.\"\n", - " sections = open(f'AOC2021/input{day}.txt').read().rstrip().split(sep)\n", - " return mapt(parser, sections)\n", - "\n", - "def answer(puzzle_number, got, expected) -> bool:\n", - " \"\"\"Verify the answer we got was expected.\"\"\"\n", - " assert got == expected, f'For {puzzle_number}, expected {expected} but got {got}.'\n", - " return True" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Two useful parsers are `ints` and `atoms`:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "def ints(text: str) -> Tuple[int]:\n", - " \"Return a tuple of all the integers in text, ignoring non-numbers.\"\n", - " return mapt(int, re.findall('-?[0-9]+', text))\n", - "\n", - "Atom = Union[float, int, str]\n", - "\n", - "def atoms(text: str, sep=None) -> Tuple[Atom]:\n", - " \"Parse text into atoms (numbers or strs).\"\n", - " return tuple(map(atom, text.split(sep)))\n", - "\n", - "def atom(text: str) -> Atom:\n", - " \"Parse text into a single float or int or str.\"\n", - " try:\n", - " val = float(text)\n", - " return round(val) if round(val) == val else val\n", - " except ValueError:\n", - " return text" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Some additional useful utility functions:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "def quantify(iterable, pred=bool) -> int:\n", - " \"Count the number of items in iterable for which pred is true.\"\n", - " return sum(1 for item in iterable if pred(item))\n", - "\n", - "def first(iterable, default=None) -> object:\n", - " \"Return first item in iterable, or default.\"\n", - " return next(iter(iterable), default)\n", - "\n", - "def rest(sequence) -> object: return sequence[1:]\n", - "\n", - "def multimap(items: Iterable[Tuple]) -> dict:\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 prod(numbers) -> float: # Will be math.prod in Python 3.8, but I'm in 3.7\n", - " \"The product of an iterable of numbers.\" \n", - " result = 1\n", - " for n in numbers:\n", - " result *= n\n", - " return result\n", - "\n", - "def sign(x) -> int:\n", - " \"\"\"The sign of a number: +1, 0, or -1.\"\"\"\n", - " return (0 if x == 0 else +1 if x > 0 else -1)\n", - "\n", - "def median(numbers) -> float: return sorted(numbers)[len(numbers) // 2]\n", - " \n", - "def dotproduct(A, B) -> float: return sum(a * b for a, b in zip(A, B))\n", - "\n", - "def mapt(fn, *args):\n", - " \"map(fn, *args) and return the result as a tuple.\"\n", - " return tuple(map(fn, *args))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# [Day 1](https://adventofcode.com/2021/day/1): Sonar Sweep\n", - "\n", - "The input is a list of integers.\n", - "\n", - "1. How many numbers increase from the previous number?\n", - "2. How many sliding windows of 3 numbers increase from the previous window?" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "in1 = parse(1, int)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "def increases(nums: List[int]) -> int:\n", - " \"\"\"How many numbers are bigger than the previous one?\"\"\"\n", - " return quantify(nums[i] > nums[i - 1] \n", - " for i in range(1, len(nums)))\n", - "\n", - "answer(1.1, increases(in1), 1400)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "def window_increases(nums: List[int], w=3) -> int:\n", - " \"\"\"How many sliding windows of w numbers have a sum bigger than the previous window?\"\"\"\n", - " return quantify(sum(nums[i:i+w]) > sum(nums[i-1:i-1+w])\n", - " for i in range(1, len(nums) + 1 - w))\n", - "\n", - "answer(1.2, window_increases(in1), 1429)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# [Day 2](https://adventofcode.com/2021/day/2): Dive! \n", - "\n", - "The input is a list of instructions, such as \"`forward 10`\".\n", - "\n", - "1. Follow instructions and report the product of your final horizontal position and depth.\n", - "1. Follow *revised* instructions and report the product of your final horizontal position and depth. (There is an \"aim\" which is increased by down and up instructions. Depth is changed not by down and up, but by going forward *n* units, which changes depth by aim × *n* units.)" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "in2 = parse(2, atoms)" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "def drive(instructions) -> int:\n", - " \"\"\"What is the product of position and depth after following instructions?\"\"\"\n", - " pos = depth = 0\n", - " for (op, n) in instructions:\n", - " if op == 'forward': pos += n\n", - " if op == 'down': depth += n\n", - " if op == 'up': depth -= n\n", - " return pos * depth\n", - "\n", - "answer(2.1, drive(in2), 1670340)" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "def drive2(instructions) -> int:\n", - " \"\"\"What is rthe product of position and depth after following instructions?\n", - " This time we have to keep track of `aim` as well.\"\"\"\n", - " pos = depth = aim = 0\n", - " for (op, n) in instructions:\n", - " if op == 'forward': pos += n; depth += aim * n\n", - " if op == 'down': aim += n\n", - " if op == 'up': aim -= n\n", - " return pos * depth\n", - "\n", - "answer(2.2, drive2(in2), 1954293920)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# [Day 3](https://adventofcode.com/2021/day/3): Binary Diagnostic\n", - "\n", - "The input is a list of bit strings.\n", - "\n", - "1. What is the power consumption (product of gamma and epsilon rates)?\n", - "2. What is the life support rating (product of oxygen and CO2)?" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [], - "source": [ - "in3 = parse(3, str) # Parse into bit strings, (e.g. '1110'), not binary ints (e.g. 14)" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "def common(strs, i) -> str: \n", - " \"\"\"The bit that is most common in position i.\"\"\"\n", - " bits = [s[i] for s in strs]\n", - " return '1' if bits.count('1') >= bits.count('0') else '0'\n", - "\n", - "def uncommon(strs, i) -> str: return '1' if common(strs, i) == '0' else '0'\n", - "\n", - "def epsilon(strs) -> str:\n", - " \"\"\"The bit string formed from most common bit at each position.\"\"\"\n", - " return cat(common(strs, i) for i in range(len(strs[0])))\n", - "\n", - "def gamma(strs) -> str:\n", - " \"\"\"The bit string formed from most uncommon bit at each position.\"\"\"\n", - " return cat(uncommon(strs, i) for i in range(len(strs[0])))\n", - "\n", - "def power(strs) -> int: \n", - " return int(epsilon(strs), 2) * int(gamma(strs), 2)\n", - " \n", - "answer(3.1, power(in3), 2261546)" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "def select(strs, common_fn, i=0) -> str:\n", - " \"\"\"Select a str from strs according to common_fn.\"\"\"\n", - " if len(strs) == 1:\n", - " return strs[0]\n", - " else:\n", - " bit = common_fn(strs, i)\n", - " selected = [s for s in strs if s[i] == bit]\n", - " return select(selected, common_fn, i + 1)\n", - "\n", - "def life(strs) -> int: \n", - " return int(select(strs, common), 2) * int(select(strs, uncommon), 2)\n", - " \n", - "answer(3.2, life(in3), 6775520)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# [Day 4](https://adventofcode.com/2021/day/4): Giant Squid\n", - "\n", - "The first line of the input is a permutation of the integers 0-99. That is followed by 5 × 5 bingo boards, each separated by two newlines.\n", - "\n", - "1. What will your final score be if you choose the first bingo board to win?\n", - "2. What will your final score be if you choose the last bingo board to win?\n", - "\n", - "I'll represent a board as a tuple of 25 ints; that makes `parse` easy: the permutation of integers and the bingo boards can both be parsed by `ints`. \n", - "\n", - "I'm worried about an ambiguity: what if two boards win at the same time? I'll have to assume Eric arranged it so that can't happen. I'll define `bingo_winners` to return a list of boards that win when a number has just been called, and I'll arbitrarily choose the first of them." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [], - "source": [ - "order, *boards = in4 = parse(4, ints, sep='\\n\\n')" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Board = Tuple[int]\n", - "Line = List[int]\n", - "B = 5\n", - "def sq(x, y) -> int: return x + B * y\n", - "\n", - "def lines(square) -> Tuple[Line, Line]:\n", - " \"\"\"The two lines through square number `square`.\"\"\"\n", - " x, y = square % B, square // B\n", - " return [sq(x, y) for x in range(B)], [sq(x, y) for y in range(B)]\n", - "\n", - "def bingo_winners(boards, drawn, just_called) -> List[Board]:\n", - " \"\"\"Are any boards winners due to the number just called (and previously drawn numbers)?\"\"\"\n", - " return [board for board in boards\n", - " if just_called in board\n", - " and any(all(board[n] in drawn for n in line)\n", - " for line in lines(board.index(just_called)))]\n", - "\n", - "def bingo_score(board, drawn, just_called) -> int:\n", - " \"\"\"Sum of unmarked numbers multiplied by the number just called.\"\"\"\n", - " return sum(n for n in board if n not in drawn) * just_called\n", - "\n", - "def bingo(boards, order) -> int: \n", - " \"\"\"What is the score of the first winning board?\"\"\"\n", - " drawn = set()\n", - " for num in order:\n", - " drawn.add(num)\n", - " winners = bingo_winners(boards, drawn, num)\n", - " if winners:\n", - " return bingo_score(winners[0], drawn, num)\n", - "\n", - "answer(4.1, bingo(boards, order), 39902)" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "def bingo_last(boards, order) -> int: \n", - " \"\"\"What is the score of the last winning board?\"\"\"\n", - " boards = set(boards)\n", - " drawn = set()\n", - " for num in order:\n", - " drawn.add(num)\n", - " winners = bingo_winners(boards, drawn, num)\n", - " boards -= set(winners)\n", - " if not boards:\n", - " return bingo_score(winners[-1], drawn, num)\n", - " \n", - "answer(4.2, bingo_last(boards, order), 26936)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# [Day 5](https://adventofcode.com/2021/day/5): Hydrothermal Venture\n", - "\n", - "The input is a list of \"lines\" denoted by start and end points, e.g. \"`0,9 -> 5,9`\". I'll represent that line as the tuple `(0, 9, 5, 9)`.\n", - "\n", - "1. Consider only horizontal and vertical lines. At how many points do at least two lines overlap?\n", - "2. Consider all of the lines (including diagonals). At how many points do at least two lines overlap?" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [], - "source": [ - "in5 = parse(5, ints)" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "def points(line) -> bool:\n", - " \"\"\"All the (integer) points on a line.\"\"\"\n", - " x1, y1, x2, y2 = line\n", - " if x1 == x2:\n", - " return [(x1, y) for y in cover(y1, y2)]\n", - " elif y1 == y2:\n", - " return [(x, y1) for x in cover(x1, x2)]\n", - " else: # non-orthogonal lines not allowed\n", - " return []\n", - " \n", - "def cover(x1, x2) -> range:\n", - " \"\"\"All the ints from x1 to x2, inclusive, with x1, x2 in either order.\"\"\"\n", - " return range(min(x1, x2), max(x1, x2) + 1)\n", - "\n", - "def overlaps(lines) -> int:\n", - " \"\"\"How many points overlap 2 or more lines?\"\"\"\n", - " counts = Counter(flatten(map(points, lines)))\n", - " return quantify(counts[p] >= 2 for p in counts)\n", - "\n", - "answer(5.1, overlaps(in5), 7436)" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "def overlaps(lines, diagonal=False) -> int:\n", - " \"\"\"How many points overlap 2 or more lines?\"\"\"\n", - " counts = Counter(flatten(points(line, diagonal) for line in lines))\n", - " return quantify(counts[p] >= 2 for p in counts)\n", - "\n", - "def points(line, diagonal=False) -> bool:\n", - " \"\"\"All the (integer) points on a line; optionally allow diagonal lines.\"\"\"\n", - " x1, y1, x2, y2 = line\n", - " if diagonal or x1 == x2 or y1 == y2:\n", - " dx, dy = sign(x2 - x1), sign(y2 - y1)\n", - " length = max(abs(x2 - x1), abs(y2 - y1))\n", - " return [(x1 + k * dx, y1 + k * dy) for k in range(length + 1)]\n", - " else: # non-orthogonal lines not allowed when diagonal is False\n", - " return []\n", - "\n", - "assert points((1, 1, 1, 3), False) == [(1, 1), (1, 2), (1, 3)]\n", - "assert points((1, 1, 3, 3), True) == [(1, 1), (2, 2), (3, 3)]\n", - "assert points((9, 7, 7, 9), True) == [(9, 7), (8, 8), (7, 9)]\n", - "\n", - "answer(5.2, overlaps(in5, True), 21104)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# [Day 6](https://adventofcode.com/2021/day/6): Lanternfish\n", - "\n", - "The input is a single line of ints, each describing the age of a lanternfish. Over time, they age and reproduce in a specified way.\n", - "\n", - "1. Find a way to simulate lanternfish. How many lanternfish would there be after 80 days?\n", - "2. How many lanternfish would there be after 256 days?" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [], - "source": [ - "in6 = parse(6, ints)[0]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Although the puzzle description treats each fish individually, I won't take the bait (pun intended). Instead, I'll use a Counter of fish, and treat all the fish of each age group together. I have a hunch that part 2 will involve a ton-o'-fish." - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Fish = Counter # Represent a school of fish as a Counter of their timers\n", - "\n", - "def simulate(fish, days=1) -> Tuple[Fish, int]:\n", - " \"\"\"Simulate the aging and birth of fish over `days`;\n", - " return the Counter of fish and the total number of fish.\"\"\"\n", - " for day in range(days):\n", - " fish = Fish({t - 1: fish[t] for t in fish})\n", - " if -1 in fish: # births\n", - " fish[6] += fish[-1]\n", - " fish[8] = fish[-1]\n", - " del fish[-1]\n", - " return fish, sum(fish.values())\n", - " \n", - "assert simulate(Fish((3, 4, 3, 1, 2))) == (Fish((2, 3, 2, 0, 1)), 5)\n", - "assert simulate(Fish((2, 3, 2, 0, 1))) == (Fish((1, 2, 1, 6, 0, 8)), 6)\n", - "\n", - "answer(6.1, simulate(Fish(in6), 80)[1], 350917)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "My hunch was right, so part 2 is simple:" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "answer(6.2, simulate(Fish(in6), 256)[1], 1592918715629)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# [Day 7](https://adventofcode.com/2021/day/7): The Treachery of Whales\n", - "\n", - "The input is a single line of integers given the horizontal positions of each member of a swarm of crabs.\n", - "\n", - "1. Determine the horizontal position that the crabs can align to using the least fuel possible. (Each unit of travel costs one unit of fuel.) How much fuel must they spend to align to that position?\n", - "2. Determine the horizontal position that the crabs can align to using the least fuel possible. (Now for each crab the first unit of travel costs 1, the second 2, and so on.) How much fuel must they spend to align to that position?" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": {}, - "outputs": [], - "source": [ - "in7 = parse(7, ints)[0]" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "def fuel_cost(positions) -> int:\n", - " \"\"\"How much fuel does it cost to get everyone to the best alignment point?\"\"\"\n", - " # I happen to know that the best alignment point is the median\n", - " align = median(positions)\n", - " return sum(abs(p - align) for p in positions)\n", - "\n", - "answer(7.1, fuel_cost(in7), 352707)" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "def fuel_cost2(positions) -> int:\n", - " \"\"\"How much fuel does it cost to get everyone to the best alignment point, with nonlinear fuel costs?\"\"\"\n", - " # I don't know the best alignment point, so I'll try all of them\n", - " return min(sum(travel_cost(p, align) for p in positions)\n", - " for align in range(min(positions), max(positions)))\n", - "\n", - "def travel_cost(p, align) -> int:\n", - " \"\"\"The first step costs 1, the second 2, etc: triangular numbers.\"\"\"\n", - " steps = abs(p - align)\n", - " return steps * (steps + 1) // 2\n", - "\n", - "answer(7.2, fuel_cost2(in7), 95519693)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now that I got the right answer and have some time to think about it, if the travel cost were exactly quadratic, we would be minimizing the sum of square distances, and Legendre and Gauss knew that the **mean**, not the **median**, is the alignment point that does that. However, what we really want to minimize is (steps2 + steps), so that does not quite apply. Still, let's see what happens when we align to the mean:" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "95519083.0" - ] - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "positions = in7\n", - "align = mean(positions)\n", - "sum(travel_cost(p, align) for p in positions)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Wait, that's even *better* than the correct answer; what's up with that? It must be that the mean is not an integer:" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "490.543" - ] - }, - "execution_count": 27, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "mean(positions)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We get the correct answer if we round the mean down (but not if we round up):" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "95519693" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "align = 490\n", - "sum(travel_cost(p, align) for p in positions)" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "95519725" - ] - }, - "execution_count": 29, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "align = 491\n", - "sum(travel_cost(p, align) for p in positions)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# [Day 8](https://adventofcode.com/2021/day/8): ???" - ] - } - ], - "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.6" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -}