\n",
"\n",
"# Advent of Code 2021\n",
"\n",
"I'm going to do [Advent of Code 2021](https://adventofcode.com/) (AoC), 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). In this notebook I won't refactor the code for part 1 based on what I see in part 2.\n",
"\n",
"# Day 0: Preparations\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 *\n",
"from statistics import mean, median\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) -> int: return ...\n",
" solve(in1)\n",
" \n",
"That is, `parse(1, int)` will parse the data file for day 1 in the format of one `int` per line and return those ints as a tuple. Then a new function I define for the day (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 that the correct answers are computed. So it will look like this:\n",
"\n",
" in1 = parse(1, int)\n",
" \n",
" def solve_it(numbers) -> int: return ...\n",
" answer(1.1, solve_it(in1), 123)\n",
" \n",
" def solve_it2(numbers) -> int: return ...\n",
" answer(1.2, solve_it2(in1), 123456)\n",
" \n",
"For more complex puzzles, I will include some `assert` statements to show that I am getting the right partial results on the small example in the puzzle description.\n",
"\n",
"Here's `parse` and `answer`:"
]
},
{
"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-number characters.\"\"\"\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",
" x = float(text)\n",
" return round(x) if round(x) == x else x\n",
" except ValueError:\n",
" return text\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": [
"A few additional 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 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\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: return (0 if x == 0 else +1 if x > 0 else -1)\n",
" \n",
"def dotproduct(A, B) -> float: return sum(a * b for a, b in zip(A, B))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# [Day 1](https://adventofcode.com/2021/day/1): Sonar Sweep\n",
"\n",
"The input is a list of integers, such as \"`148`\", one per line.\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. (The \"down\" and \"up\" instructions no longer change depth, rather they change *aim*, and going forward *n* units both increments position by *n* and depth by *aim* × *n*.)"
]
},
{
"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, such as \"`101000111100`\".\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: \n",
" \"\"\"The bit that is least common in position i.\"\"\"\n",
" 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",
" \"\"\"Product of epsilon and gamma rates.\"\"\"\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",
" Going left-to-right, repeatedly select just the strs that have the right i-th bit.\"\"\"\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_support(strs) -> int: \n",
" \"\"\"The product of oxygen (most common select) and CO2 (least common select) rates.\"\"\"\n",
" return int(select(strs, common), 2) * int(select(strs, uncommon), 2)\n",
" \n",
"answer(3.2, life_support(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 bingo boards (5 lines of 5 ints each), 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: \"The index number of the square at (x, y)\"; return x + B * y\n",
"\n",
"def lines(square) -> Tuple[Line, Line]:\n",
" \"\"\"The two lines through square number `square`.\"\"\"\n",
" return ([sq(x, square // B) for x in range(B)], \n",
" [sq(square % B, y) for y in range(B)])\n",
"\n",
"def bingo_winners(boards, drawn, just_called) -> List[Board]:\n",
" \"\"\"Boards that win due to the number just called.\"\"\"\n",
" def filled(board, line) -> bool: return all(board[n] in drawn for n in line)\n",
" return [board for board in boards\n",
" if just_called in board\n",
" and any(filled(board, line) 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",
" unmarked = sum(n for n in board if n not in drawn)\n",
" return unmarked * just_called\n",
"\n",
"def bingo(boards, order) -> int: \n",
" \"\"\"What is the final 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 final 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 x,y points, e.g. \"`0,9 -> 5,9`\". I'll represent a line as a 4-tuple of integers, e.g. `(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, which are all at ±45°). 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": "markdown",
"metadata": {},
"source": [
"For part 2 I'll redefine `points` and `overlaps` in a way that doesn't break part 1:"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"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",
"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",
"assert points((1, 1, 1, 3), False) == [(1, 1), (1, 2), (1, 3)]\n",
"assert points((1, 1, 3, 3), False) == []\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 one 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 timer-ages\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, \n",
" with nonlinear fuel costs?\"\"\"\n",
" # I don't know the best alignment point, so I'll try all of them\n",
" return min(sum(burn_rate2(p, align) for p in positions)\n",
" for align in range(min(positions), max(positions)))\n",
"\n",
"def burn_rate2(p, align) -> int:\n",
" \"\"\"The first step costs 1, the second 2, etc. (i.e. 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. What's the mean?"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"490.543"
]
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"positions = in7\n",
"mean(positions)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's sum the burn rates for this alignment point in three ways: rounded down, as is, and rounded up:"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"95519693"
]
},
"execution_count": 27,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sum(burn_rate2(p, 490) for p in positions)"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"95519083.0"
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sum(burn_rate2(p, 490.543) 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": [
"sum(burn_rate2(p, 491) for p in positions)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We see that rounding down gives the right answer, keeping the mean as is gives a total fuel cost that is *better* than the correct answer (but is apparently not a legal alignment point), and rounding up does a bit worse."
]
},
{
"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
}