Add files via upload

This commit is contained in:
Peter Norvig 2021-12-08 11:49:44 -08:00 committed by GitHub
parent 8fba543ca8
commit bc09d83366
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -8,11 +8,11 @@
"\n",
"# Advent of Code 2021\n",
"\n",
"I'm doing [Advent of Code 2021](https://adventofcode.com/) (AoC) this year, but I won't be competing for points. \n",
"I'm doing [Advent of Code 2021](https://adventofcode.com/) (AoC) this year. I won't be competing for points\\, just for fun.\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",
"You'll have to click on each day's link (e.g. [Day 1](https://adventofcode.com/2021/day/1)) to see the full description of each puzzle; I won't repeat them in this notebook. \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",
"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 (although I may edit the code for clarity, without changing the initial approach).\n",
"\n",
"# Day 0: Preparations\n",
"\n",
@ -44,7 +44,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"Now two functions that I will use each day, `parse` and `answer`, and two parser functions, `ints` and `atoms`:"
"Here are two functions that I will use each day, `parse` and `answer`, and two parser functions, `ints` and `atoms`:"
]
},
{
@ -54,7 +54,7 @@
"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",
" \"\"\"Split the day's input file into entries 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",
@ -118,6 +118,10 @@
" result *= n\n",
" return result\n",
"\n",
"def total(counts: Counter) -> int: \n",
" \"\"\"The sum of all the counts in a Counter\"\"\"\n",
" return sum(counts.values())\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))"
@ -129,10 +133,14 @@
"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",
"This year's theme involves Santa's Elves on a submarine. [Gary J Grady](https://twitter.com/GaryJGrady/) has some nice drawings to set the scene:\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?"
"<img src=\"https://pbs.twimg.com/media/FFiywKpWYAAm6B5?format=jpg&name=medium\" width=300>\n",
"\n",
"Each entry in the input is an integer depth measurement, such as \"`148`\".\n",
"\n",
"1. How many measurements are larger than the previous measurement?\n",
"2. Consider sums of a three-measurement sliding window. How many sums are larger than the previous sum?"
]
},
{
@ -198,19 +206,16 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"**Note**: each day will follow the same pattern of code as written above:\n",
"# Note: Daily Code Pattern\n",
"\n",
"1. I'll first parse the input file, in this case with `in1 = parse(1, int)`. The \"`1`\" means the file for day 1; `int` means each line of the input file should be parsed as an `int`; the collection of integers will be returned as a tuple. The `sep` keyword on `parse` can be used for inputs in which it is not the case that each line is a separate record.\n",
"Each day will follow a pattern similar to the code above for Day 1:\n",
"\n",
"1. I'll first parse the input file, in this case with `in1 = parse(1, int)`. The \"`1`\" means to read the file for day 1; `int` means each entry of the input file is parsed as an `int`. The resulting integers will be returned as a tuple. By default, each entry is a single line of input; the `sep` keyword for `parse` can be used for input files in which that is not true.\n",
"2. I'll write a function to solve the problem. In this case, the function call is `increases(in1)`.\n",
"\n",
"3. I'll then submit the answer to AoC and verify it is correct. \n",
"\n",
"4. If it is correct, I'll solve part 2 in a similar way. \n",
"\n",
"5. When both parts are correct, I'll use the function `answers` to assert that the correct answers are computed. \n",
" \n",
"6. 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."
"4. If it is correct, I'll moe on to part 2. \n",
"5. When both parts are correct, I'll use the function `answers` to record the correct answer, and assert that the computation matches the answer. \n",
"6. For more complex puzzles, I will include some `assert` statements to show that I am getting the right partial results on the small example given in the puzzle description."
]
},
{
@ -219,10 +224,10 @@
"source": [
"# [Day 2](https://adventofcode.com/2021/day/2): Dive! \n",
"\n",
"The input is a list of instructions, such as \"`forward 10`\".\n",
"Each entry in the input is a command, like \"`forward 1`\", \"`down 2`\", or \"`up 3`\".\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*.)"
"1. Calculate the horizontal position and depth you would have after following the planned course. What do you get if you multiply your final horizontal position by your final depth?\n",
"1. Using the new interpretation of the commands, calculate the horizontal position and depth you would have after following the planned course. What do you get if you multiply your final horizontal position by your final depth? (New interpretation: the \"down\" and \"up\" commands no longer change depth, rather they change *aim*, and going forward *n* units both increments position by *n* and depth by *aim* × *n*.)"
]
},
{
@ -251,10 +256,10 @@
}
],
"source": [
"def drive(instructions) -> int:\n",
" \"\"\"What is the product of position and depth after following instructions?\"\"\"\n",
"def drive(commands) -> int:\n",
" \"\"\"What is the product of position and depth after following commands?\"\"\"\n",
" pos = depth = 0\n",
" for (op, n) in instructions:\n",
" for (op, n) in commands:\n",
" if op == 'forward': pos += n\n",
" if op == 'down': depth += n\n",
" if op == 'up': depth -= n\n",
@ -280,11 +285,11 @@
}
],
"source": [
"def drive2(instructions) -> int:\n",
" \"\"\"What is rthe product of position and depth after following instructions?\n",
"def drive2(commands) -> int:\n",
" \"\"\"What is the product of position and depth after following commands?\n",
" This time we have to keep track of `aim` as well.\"\"\"\n",
" pos = depth = aim = 0\n",
" for (op, n) in instructions:\n",
" for (op, n) in commands:\n",
" if op == 'forward': pos += n; depth += aim * n\n",
" if op == 'down': aim += n\n",
" if op == 'up': aim -= n\n",
@ -299,10 +304,10 @@
"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",
"Each entry in the input is a bit string, 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)?"
"1. What is the power consumption of the submarine (product of gamma and epsilon rates)?\n",
"2. What is the life support rating of the submarine (product of oxygen and CO2)?"
]
},
{
@ -372,7 +377,7 @@
}
],
"source": [
"def select(strs, common_fn, i=0) -> str:\n",
"def select_str(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",
@ -380,11 +385,11 @@
" 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",
" return select_str(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",
" return int(select_str(strs, common), 2) * int(select_str(strs, uncommon), 2)\n",
" \n",
"answer(3.2, life_support(in3), 6775520)"
]
@ -395,10 +400,10 @@
"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",
"The first entry of the input is a permutation of the integers 0-99. Subsequent entries are bingo boards: 5 lines of 5 ints each. Entries are separated by *two* newlines. Bingo games will be played against a giant squid.\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",
"2. Figure out which board will win last. Once it wins, what would its final score be?\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",
@ -496,13 +501,20 @@
"answer(4.2, bingo_last(boards, order), 26936)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<img src=\"https://pbs.twimg.com/media/FFykNslXMAwe_xA?format=jpg&name=medium\" width=300 title=\"Drawing by Gary Grady @GaryJGrady\">"
]
},
{
"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",
"Each entry in the input is a \"line\" 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?"
@ -600,6 +612,7 @@
"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.1, overlaps(in5), 7436)\n",
"answer(5.2, overlaps(in5, True), 21104)"
]
},
@ -609,7 +622,7 @@
"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",
"The input is a single line of comma-separated integers, each one the age of a lanternfish. Over time, the lanternfish 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?"
@ -621,7 +634,7 @@
"metadata": {},
"outputs": [],
"source": [
"in6 = parse(6, ints)[0]"
"in6 = parse(6, int, sep=',')"
]
},
{
@ -650,21 +663,20 @@
"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",
"def simulate(fish, days=1) -> Fish:\n",
" \"\"\"Simulate the aging and birth of fish over `days`.\"\"\"\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",
" return fish\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",
"assert simulate(Fish((3, 4, 3, 1, 2))) == Fish((2, 3, 2, 0, 1))\n",
"assert simulate(Fish((2, 3, 2, 0, 1))) == Fish((1, 2, 1, 6, 0, 8))\n",
"\n",
"answer(6.1, simulate(Fish(in6), 80)[1], 350917)"
"answer(6.1, total(simulate(Fish(in6), 80)), 350917)"
]
},
{
@ -691,7 +703,14 @@
}
],
"source": [
"answer(6.2, simulate(Fish(in6), 256)[1], 1592918715629)"
"answer(6.2, total(simulate(Fish(in6), 256)), 1592918715629)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<img src=\"https://pbs.twimg.com/media/FF9GGGJXEAEQ0MD?format=jpg&name=medium\" width=300 title=\"Drawing by Gary Grady @GaryJGrady\">"
]
},
{
@ -700,10 +719,10 @@
"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",
"The input is a single line of comma-separated integers, each one the horizontal position of a crab (in its own submarine).\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?"
"1. Determine the horizontal position that the crabs can align to using the least fuel possible. How much fuel must they spend to align to that position? (Each unit of travel costs one unit of fuel.)\n",
"2. Determine the horizontal position that the crabs can align to using the least fuel possible so they can make you an escape route! How much fuel must they spend to align to that position? (Now for each crab the first unit of travel costs 1, the second 2, the third 3, and so on.) "
]
},
{
@ -712,7 +731,7 @@
"metadata": {},
"outputs": [],
"source": [
"in7 = parse(7, ints)[0]"
"in7 = parse(7, int, sep=',')"
]
},
{
@ -777,7 +796,7 @@
"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 of the positions?"
"**Note**: 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 of the positions?"
]
},
{
@ -805,7 +824,7 @@
"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:"
"That's not an integer, but I'll try it, along with the integers above and below it:"
]
},
{
@ -816,7 +835,7 @@
{
"data": {
"text/plain": [
"95519693"
"{490: 95519693, 491: 95519725, 490.543: 95519083.0}"
]
},
"execution_count": 26,
@ -825,27 +844,47 @@
}
],
"source": [
"sum(burn_rate2(p, 490) for p in positions)"
"{align: sum(burn_rate2(p, align) for p in positions)\n",
" for align in [490, 491, mean(positions)]}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We see that rounding down gives the right answer, rounding up does a bit worse, and using the exact mean gives a total fuel cost that is *better* than the correct answer (but is apparently not a legal alignment point)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# [Day 8](https://adventofcode.com/2021/day/8): Seven Segment Search\n",
"\n",
"Each entry in the input is 10 patterns followed by 4 output values, in the form:\n",
"\n",
" be cfbegad cbdgef fgaecd cgeb fdcge agebfd fecdb fabcd edb | edb cefdb eb gcbe\n",
" \n",
"Each pattern and output value represents a digit, with each letter representing one of the segments in a [7-segment display](https://en.wikipedia.org/wiki/Seven-segment_display). The mapping of letters to segments is unknown, but is consistent within each entry.\n",
"\n",
"1. In the output values, how many times do digits 1, 4, 7, or 8 appear?\n",
"2. For each entry, determine all of the wire/segment connections and decode the four-digit output values. What do you get if you add up all of the output values?\n",
"\n",
"The entry above would be parsed as:\n",
"\n",
" (('be', 'cfbegad', 'cbdgef', 'fgaecd', 'cgeb', 'fdcge', 'agebfd', 'fecdb', 'fabcd', 'edb'), \n",
" ('edb', 'cefdb', 'eb', 'gcbe'))\n",
" \n",
"I refer to the left- and right-hand sides of the line with \"lhs\" and \"rhs\". "
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"95519083.0"
]
},
"execution_count": 27,
"metadata": {},
"output_type": "execute_result"
}
],
"outputs": [],
"source": [
"sum(burn_rate2(p, 490.543) for p in positions)"
"in8 = parse(8, lambda line: mapt(atoms, line.split('|')))"
]
},
{
@ -856,7 +895,7 @@
{
"data": {
"text/plain": [
"95519725"
"True"
]
},
"execution_count": 28,
@ -865,21 +904,67 @@
}
],
"source": [
"sum(burn_rate2(p, 491) for p in positions)"
"def count1478(entries) -> int:\n",
" \"\"\"How many of the rhs digits in all the entries are a 1, 4, 7, or 8?\"\"\"\n",
" # (1, 4, 7, 8) have (2, 4, 3, 7) segments, respectively.\n",
" return sum(len(digit) in (2, 4, 3, 7) for (lhs, rhs) in entries for digit in rhs)\n",
"\n",
"answer(8.1, count1478(in8), 493)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We see that rounding down gives the right answer, keeping the mean as is (490.543) 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."
"Part 2 is **tricky**. The digits other than 1, 4, 7, 8 are ambiguous. To figure out which is which in each entry I could do some fancy constraint satisfaction, but that sounds hard. Or I could exhaustively try all permutations of the segments. That sounds easy! Here's my plan:\n",
"- Make a list of the 7! = 5,040 possible string translators that permute `'abcdefg'`.\n",
"- Decode an entry by trying all translators and keeping the one that maps all of the ten lhs patterns to a valid digit. `decode` then applies the translator to the rhs and forms it into an `int`.\n",
" - Note that `get_digit` applies the translator, but then has to sort the letters to get a key that can be looked up in the `segment_map` dict.\n",
"- Finally, sum up the decoding of each entry."
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 29,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"segments7 = 'abcdefg'\n",
"segment_map = {'abcefg': '0', 'cf': '1', 'acdeg': '2', 'acdfg': '3', 'bcdf': '4',\n",
" 'abdfg': '5', 'abdefg': '6', 'acf': '7', 'abcdefg': '8', 'abcdfg': '9'}\n",
"\n",
"translators = [str.maketrans(segments7, cat(p)) for p in permutations(segments7)]\n",
"\n",
"def get_digit(pattern, translator) -> Optional[Char]:\n",
" \"\"\"Translate the pattern, and return a digit '0' to '9' if valid.\"\"\"\n",
" return segment_map.get(cat(sorted(pattern.translate(translator))))\n",
"\n",
"def decode(entry) -> int:\n",
" \"\"\"Decode an entry into an integer representing the 4 output digits\"\"\"\n",
" lhs, rhs = entry\n",
" for t in translators:\n",
" if all(get_digit(pattern, t) for pattern in lhs):\n",
" return int(cat(get_digit(pattern, t) for pattern in rhs))\n",
"\n",
"answer(8.2, sum(map(decode, in8)), 1010460)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# [Day 8](https://adventofcode.com/2021/day/8): ???"
"# [Day 9](https://adventofcode.com/2021/day/9): ???"
]
}
],