AoC update

This commit is contained in:
Peter Norvig 2022-12-11 00:45:43 -08:00 committed by GitHub
parent 55eb6ed407
commit 1968a9b8c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 304 additions and 176 deletions

View File

@ -100,14 +100,11 @@
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'0.000 seconds for correct answer: 70,116'"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
"name": "stdout",
"output_type": "stream",
"text": [
"0.000 seconds for correct answer: 70,116\n"
]
}
],
"source": [
@ -129,14 +126,11 @@
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'0.000 seconds for correct answer: 206,582'"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
"name": "stdout",
"output_type": "stream",
"text": [
"0.000 seconds for correct answer: 206,582\n"
]
}
],
"source": [
@ -224,14 +218,11 @@
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'0.001 seconds for correct answer: 13,268'"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
"name": "stdout",
"output_type": "stream",
"text": [
"0.001 seconds for correct answer: 13,268\n"
]
}
],
"source": [
@ -260,14 +251,11 @@
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'0.001 seconds for correct answer: 15,508'"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
"name": "stdout",
"output_type": "stream",
"text": [
"0.001 seconds for correct answer: 15,508\n"
]
}
],
"source": [
@ -331,14 +319,11 @@
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'0.001 seconds for correct answer: 8,401'"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
"name": "stdout",
"output_type": "stream",
"text": [
"0.001 seconds for correct answer: 8,401\n"
]
}
],
"source": [
@ -367,14 +352,11 @@
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'0.000 seconds for correct answer: 2,641'"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
"name": "stdout",
"output_type": "stream",
"text": [
"0.000 seconds for correct answer: 2,641\n"
]
}
],
"source": [
@ -448,14 +430,11 @@
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'0.000 seconds for correct answer: 477'"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
"name": "stdout",
"output_type": "stream",
"text": [
"0.000 seconds for correct answer: 477\n"
]
}
],
"source": [
@ -479,14 +458,11 @@
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'0.000 seconds for correct answer: 830'"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
"name": "stdout",
"output_type": "stream",
"text": [
"0.000 seconds for correct answer: 830\n"
]
}
],
"source": [
@ -560,14 +536,11 @@
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'0.001 seconds for correct answer: SHQWSRBDL'"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
"name": "stdout",
"output_type": "stream",
"text": [
"0.001 seconds for correct answer: SHQWSRBDL\n"
]
}
],
"source": [
@ -598,14 +571,12 @@
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'0.000 seconds for correct answer: CDTQZHBRS'"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
"name": "stdout",
"output_type": "stream",
"text": [
"0.000 seconds for correct answer: SHQWSRBDL\n",
"0.000 seconds for correct answer: CDTQZHBRS\n"
]
}
],
"source": [
@ -665,14 +636,11 @@
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'0.001 seconds for correct answer: 1,987'"
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
"name": "stdout",
"output_type": "stream",
"text": [
"0.001 seconds for correct answer: 1,987\n"
]
}
],
"source": [
@ -698,14 +666,11 @@
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'0.002 seconds for correct answer: 3,059'"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
"name": "stdout",
"output_type": "stream",
"text": [
"0.002 seconds for correct answer: 3,059\n"
]
}
],
"source": [
@ -779,14 +744,11 @@
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'0.001 seconds for correct answer: 1,232,307'"
]
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
"name": "stdout",
"output_type": "stream",
"text": [
"0.001 seconds for correct answer: 1,232,307\n"
]
}
],
"source": [
@ -824,14 +786,11 @@
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'0.001 seconds for correct answer: 7,268,994'"
]
},
"execution_count": 22,
"metadata": {},
"output_type": "execute_result"
"name": "stdout",
"output_type": "stream",
"text": [
"0.001 seconds for correct answer: 7,268,994\n"
]
}
],
"source": [
@ -904,14 +863,11 @@
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'0.047 seconds for correct answer: 1,829'"
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
"name": "stdout",
"output_type": "stream",
"text": [
"0.048 seconds for correct answer: 1,829\n"
]
}
],
"source": [
@ -949,14 +905,11 @@
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'0.053 seconds for correct answer: 291,840'"
]
},
"execution_count": 25,
"metadata": {},
"output_type": "execute_result"
"name": "stdout",
"output_type": "stream",
"text": [
"0.054 seconds for correct answer: 291,840\n"
]
}
],
"source": [
@ -1077,14 +1030,11 @@
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'0.020 seconds for correct answer: 6,236'"
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
"name": "stdout",
"output_type": "stream",
"text": [
"0.021 seconds for correct answer: 6,236\n"
]
}
],
"source": [
@ -1128,14 +1078,12 @@
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'0.112 seconds for correct answer: 2,449'"
]
},
"execution_count": 29,
"metadata": {},
"output_type": "execute_result"
"name": "stdout",
"output_type": "stream",
"text": [
"0.025 seconds for correct answer: 6,236\n",
"0.111 seconds for correct answer: 2,449\n"
]
}
],
"source": [
@ -1295,14 +1243,11 @@
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'0.000 seconds for correct answer: 12,560'"
]
},
"execution_count": 34,
"metadata": {},
"output_type": "execute_result"
"name": "stdout",
"output_type": "stream",
"text": [
"0.000 seconds for correct answer: 12,560\n"
]
}
],
"source": [
@ -1337,14 +1282,11 @@
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'0.011 seconds for correct answer: PLPAFBCL'"
]
},
"execution_count": 35,
"metadata": {},
"output_type": "execute_result"
"name": "stdout",
"output_type": "stream",
"text": [
"0.013 seconds for correct answer: PLPAFBCL\n"
]
},
{
"data": {
@ -1373,6 +1315,191 @@
"answer(10.2, \"PLPAFBCL\", lambda: render(in10) or \"PLPAFBCL\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# [Day 11](https://adventofcode.com/2022/day/11): Monkey in the Middle\n",
"\n",
"The input is separated into paragraphs but each one has a complex structure describing a monkey. I'll define the function `parse_monkey` to parse the paragraph into an instance of the class `Monkey`. I'll use a regular expression, but I'll keep it as simple as possible, so I don't have [two problems](http://regex.info/blog/2006-09-15/247). The `Monkey` class has seven fields; the regular expression has seven groups, each of which is either digits `(\\d+)` or any characters `(.+)`."
]
},
{
"cell_type": "code",
"execution_count": 36,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"────────────────────────────────────────────────────────────────────────────────────────────────────\n",
"Puzzle input ➜ 55 lines:\n",
"────────────────────────────────────────────────────────────────────────────────────────────────────\n",
"Monkey 0:\n",
" Starting items: 98, 97, 98, 55, 56, 72\n",
" Operation: new = old * 13\n",
" Test: divisible by 11\n",
" If true: throw to monkey 4\n",
" If false: throw to monkey 7\n",
"\n",
"Monkey 1:\n",
"...\n",
"────────────────────────────────────────────────────────────────────────────────────────────────────\n",
"Parsed representation ➜ 8 Monkeys:\n",
"────────────────────────────────────────────────────────────────────────────────────────────────────\n",
"Monkey(num=0, items=(98, 97, 98, 55, 56, 72), op='*', arg=13, test=11, true=4, false=7)\n",
"Monkey(num=1, items=(73, 99, 55, 54, 88, 50, 55), op='+', arg=4, test=17, true=2, false=6)\n",
"Monkey(num=2, items=(67, 98), op='*', arg=11, test=5, true=6, false=5)\n",
"Monkey(num=3, items=(82, 91, 92, 53, 99), op='+', arg=8, test=13, true=1, false=2)\n",
"Monkey(num=4, items=(52, 62, 94, 96, 52, 87, 53, 60), op='*', arg='old', test=19, true=3, false=1)\n",
"Monkey(num=5, items=(94, 80, 84, 79), op='+', arg=5, test=2, true=7, false=0)\n",
"Monkey(num=6, items=(89,), op='+', arg=1, test=3, true=0, false=5)\n",
"Monkey(num=7, items=(70, 59, 63), op='+', arg=3, test=7, true=4, false=3)\n"
]
}
],
"source": [
"Monkey = namedtuple('Monkey', 'num, items, op, arg, test, true, false')\n",
"\n",
"monkey_regex = \"\"\"Monkey (\\d+):\n",
" Starting items: (.+)\n",
" Operation: new = old (.) (.+)\n",
" Test: divisible by (\\d+)\n",
" If true: throw to monkey (\\d+)\n",
" If false: throw to monkey (\\d+)\"\"\"\n",
"\n",
"def parse_monkey(text) -> Monkey:\n",
" \"\"\"Parse a paragraph of text into a `Monkey`.\"\"\"\n",
" match = re.search(monkey_regex, text)\n",
" num, items, op, arg, test, true, false = match.groups()\n",
" return Monkey(int(num), ints(items), op, atom(arg), int(test), int(true), int(false))\n",
"\n",
"in11 = parse(11, parse_monkey, paragraphs, show=8)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Part 1: What is the level of monkey business after 20 rounds of stuff-slinging simian shenanigans?\n",
"\n",
"Following the instructions for inspecting and throwing items requires careful attention, but is only a dozen lines or so. One thing to note: my `Monkey` class is immutable, That's a good thing: I don't want to modify the monkeys, because I want to be able to re-run calculations without having to re-parse the monkeys. So instead, my `inspect` function will keep track of which monkey has which items by using a dict, `items`. The whole point of `inspect` is to keep track of the number of items each monkey inspects in the `inspected` Counter, and return the Counter at the end. Then the function `monkey_business` can pick out the two busiest monkeys to compute the puzzle's final answer."
]
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0.001 seconds for correct answer: 54,036\n"
]
}
],
"source": [
"def inspect(monkeys, rounds=1) -> Counter:\n",
" \"\"\"Simulate the monkeys doing inspections for `rounds`; return the Counter of items inspected.\"\"\"\n",
" inspected = Counter()\n",
" items = {monkey.num: list(monkey.items) for monkey in monkeys}\n",
" ops = {'+': operator.add, '*': operator.mul, '-': operator.sub}\n",
" for round in range(rounds):\n",
" for monkey in monkeys:\n",
" inspected[monkey.num] += len(items[monkey.num])\n",
" for old in items[monkey.num]:\n",
" new = ops[monkey.op](old, (old if monkey.arg == 'old' else monkey.arg)) // 3\n",
" throw = (monkey.true if (new % monkey.test == 0) else monkey.false)\n",
" items[throw].append(new)\n",
" items[monkey.num] = [] \n",
" return inspected\n",
"\n",
"def monkey_business(inspected) -> int:\n",
" \"\"\"The product of the number of inspections by the two busiest monkeys.\"\"\"\n",
" return prod(sorted(inspected.values())[-2:])\n",
"\n",
"answer(10.1, 54036, lambda: monkey_business(inspect(in11, 20)))"
]
},
{
"cell_type": "code",
"execution_count": 38,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 1.37 ms, sys: 7 µs, total: 1.37 ms\n",
"Wall time: 1.37 ms\n"
]
},
{
"data": {
"text/plain": [
"11140"
]
},
"execution_count": 38,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%time len(str(13**10000))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Part 2: Worry levels are no longer divided by three after each item is inspected; you'll need to find another way to keep your worry levels manageable. Starting again from the initial state in your puzzle input, what is the level of monkey business after 10000 rounds?\n",
"\n",
"The puzzle instructions are warning me that worry levels will get quite high. Certainly too high for a 64-bit int, but probably much higher. For example, monkey 0 multiplies the worry level by 13, and 13<sup>10,000</sup> is a number with more than 10,000 digits. \n",
"\n",
"\"No problem!,\" I thought to myself; since each monkey only uses the worry level to see if it is divisible by their test number, we can just use the worry level modulo the test number. I tried that, and was pleased that 10,000 rounds executed very quickly. But I was disappointed AoC told me my answer was wrong. \n",
"\n",
"So I thought to myself again. I figured out that the issue is that worry levels get passed around, and the next monkey will have a different test number. So I want to be working with worry levels modulo the product of *all* the monkey's test numbers. (It could be the least common multiple, but product works fine). I tried that and it works. I re-wrote `inspect` to specify the boredom level; by default it is 3, to do the calculation for Part 1, but if you specify `boredom=None`, then the calculation works for Part 2."
]
},
{
"cell_type": "code",
"execution_count": 39,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0.001 seconds for correct answer: 54,036\n",
"0.282 seconds for correct answer: 13,237,873,355\n"
]
}
],
"source": [
"def inspect(monkeys, rounds=1, boredom=3) -> Counter:\n",
" \"\"\"Simulate the monkeys doing inspections for `rounds`; return the Counter of items inspected.\"\"\"\n",
" inspected = Counter()\n",
" items = {monkey.num: list(monkey.items) for monkey in monkeys}\n",
" ops = {'+': operator.add, '*': operator.mul, '-': operator.sub}\n",
" m = prod(monkey.test for monkey in monkeys)\n",
" for round in range(rounds):\n",
" for monkey in monkeys:\n",
" inspected[monkey.num] += len(items[monkey.num])\n",
" for old in items[monkey.num]:\n",
" new = ops[monkey.op](old, (old if monkey.arg == 'old' else monkey.arg)) % m\n",
" if boredom: new = new // boredom\n",
" throw = (monkey.true if (new % monkey.test == 0) else monkey.false)\n",
" items[throw].append(new)\n",
" items[monkey.num] = [] \n",
" return inspected\n",
"\n",
"answer(10.1, 54036, lambda: monkey_business(inspect(in11, 20)))\n",
"answer(10.2, 13237873355, lambda: monkey_business(inspect(in11, 10_000, boredom=None)))"
]
},
{
"cell_type": "markdown",
"metadata": {},
@ -1384,7 +1511,7 @@
},
{
"cell_type": "code",
"execution_count": 36,
"execution_count": 40,
"metadata": {},
"outputs": [
{
@ -1404,15 +1531,15 @@
" 6.2: '0.002 seconds for correct answer: 3,059',\n",
" 7.1: '0.001 seconds for correct answer: 1,232,307',\n",
" 7.2: '0.001 seconds for correct answer: 7,268,994',\n",
" 8.1: '0.047 seconds for correct answer: 1,829',\n",
" 8.2: '0.053 seconds for correct answer: 291,840',\n",
" 9.1: '0.024 seconds for correct answer: 6,236',\n",
" 9.2: '0.112 seconds for correct answer: 2,449',\n",
" 10.1: '0.000 seconds for correct answer: 12,560',\n",
" 10.2: '0.011 seconds for correct answer: PLPAFBCL'}"
" 8.1: '0.048 seconds for correct answer: 1,829',\n",
" 8.2: '0.054 seconds for correct answer: 291,840',\n",
" 9.1: '0.025 seconds for correct answer: 6,236',\n",
" 9.2: '0.111 seconds for correct answer: 2,449',\n",
" 10.1: '0.001 seconds for correct answer: 54,036',\n",
" 10.2: '0.282 seconds for correct answer: 13,237,873,355'}"
]
},
"execution_count": 36,
"execution_count": 40,
"metadata": {},
"output_type": "execute_result"
}

View File

@ -13,7 +13,7 @@
},
{
"cell_type": "code",
"execution_count": 1,
"execution_count": 20,
"metadata": {},
"outputs": [],
"source": [
@ -31,6 +31,7 @@
"import heapq\n",
"import string\n",
"import functools\n",
"import operator\n",
"import pathlib\n",
"import re"
]
@ -59,7 +60,7 @@
},
{
"cell_type": "code",
"execution_count": 2,
"execution_count": 19,
"metadata": {},
"outputs": [],
"source": [
@ -67,13 +68,13 @@
"lines = '\\n' # For inputs where each record is a line\n",
"paragraphs = '\\n\\n' # For inputs where each record is a paragraph \n",
"\n",
"def parse(day_or_text:Union[int, str], parser:Callable=str, sep:Callable=lines, show=6) -> tuple:\n",
"def parse(day_or_text:Union[int, str], parser:Callable=str, sep:str=lines, show=6) -> tuple:\n",
" \"\"\"Split the input text into items separated by `sep`, and apply `parser` to each.\n",
" The first argument is either the text itself, or the day number of a text file.\"\"\"\n",
" text = get_text(day_or_text)\n",
" print_parse_items('Puzzle input', text.splitlines(), show, 'line')\n",
" records = mapt(parser, text.rstrip().split(sep))\n",
" if parser != str:\n",
" if parser != str or sep != lines:\n",
" print_parse_items('Parsed representation', records, show, f'{type(records[0]).__name__}')\n",
" return records\n",
"\n",
@ -109,7 +110,7 @@
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": 21,
"metadata": {},
"outputs": [],
"source": [
@ -144,7 +145,7 @@
" x = float(text)\n",
" return round(x) if x.is_integer() else x\n",
" except ValueError:\n",
" return text\n",
" return text.strip()\n",
" \n",
"def mapt(function: Callable, sequence) -> tuple:\n",
" \"\"\"`map`, with the result as a tuple.\"\"\"\n",
@ -189,7 +190,7 @@
"# `answers` is a dict of {puzzle_number_id: message_about_results}\n",
"answers = {} \n",
"\n",
"def answer(puzzle, correct, code: callable) -> str:\n",
"def answer(puzzle, correct, code: callable):\n",
" \"\"\"Verify that calling `code` computes the `correct` answer for `puzzle`. \n",
" Record results in the dict `answers`. Prints execution time.\"\"\"\n",
" def pretty(x): return f'{x:,d}' if is_int(x) else truncate(x)\n",
@ -201,7 +202,7 @@
" f'correct answer: {ans}' if (got == correct) else\n",
" f'WRONG!! ANSWER: {ans}; EXPECTED {pretty(correct)}')\n",
" answers[puzzle] = msg\n",
" return msg"
" print(msg)"
]
},
{