Add files via upload

This commit is contained in:
Peter Norvig 2024-12-12 11:00:50 -08:00 committed by GitHub
parent d25fbb0957
commit 20fdde239f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -262,7 +262,7 @@
{
"data": {
"text/plain": [
"Puzzle 2.1: .0011 seconds, answer 257 ok"
"Puzzle 2.1: .0012 seconds, answer 257 ok"
]
},
"execution_count": 7,
@ -312,7 +312,7 @@
{
"data": {
"text/plain": [
"Puzzle 2.2: .0077 seconds, answer 328 ok"
"Puzzle 2.2: .0069 seconds, answer 328 ok"
]
},
"execution_count": 9,
@ -366,6 +366,9 @@
"id": "5327e631-502f-4687-83d5-84a4e4012ccf",
"metadata": {},
"source": [
"<a href=\"https://x.com/garyjgrady\"><img src=\"https://pbs.twimg.com/media/Gd5ZSI9XEAAAzQh?format=jpg&name=small\" width=400 alt=\"GaryJGrady cartoon\"></a>\n",
"\n",
"\n",
"### Part 1: What do you get if you add up all of the results of the multiplications?\n",
"\n",
"For Part 1, just look for instructions of the form \"mul(*digits*,*digits*)\", using a regular expression and `re.findall`. Perform each of these multiplications and add them up, and ignore all other characters/instructions:"
@ -400,7 +403,7 @@
{
"data": {
"text/plain": [
"Puzzle 3.1: .0014 seconds, answer 156388521 ok"
"Puzzle 3.1: .0013 seconds, answer 156388521 ok"
]
},
"execution_count": 12,
@ -539,7 +542,7 @@
{
"data": {
"text/plain": [
"Puzzle 4.1: .0742 seconds, answer 2401 ok"
"Puzzle 4.1: .0737 seconds, answer 2401 ok"
]
},
"execution_count": 17,
@ -577,7 +580,7 @@
{
"data": {
"text/plain": [
"Puzzle 4.2: .0605 seconds, answer 1822 ok"
"Puzzle 4.2: .0602 seconds, answer 1822 ok"
]
},
"execution_count": 18,
@ -708,7 +711,7 @@
{
"data": {
"text/plain": [
"Puzzle 5.1: .0011 seconds, answer 5762 ok"
"Puzzle 5.1: .0010 seconds, answer 5762 ok"
]
},
"execution_count": 21,
@ -793,7 +796,7 @@
{
"data": {
"text/plain": [
"Puzzle 5.2: .0016 seconds, answer 4130 ok"
"Puzzle 5.2: .0015 seconds, answer 4130 ok"
]
},
"execution_count": 24,
@ -875,7 +878,7 @@
{
"data": {
"text/plain": [
"Puzzle 6.1: .0043 seconds, answer 5329 ok"
"Puzzle 6.1: .0044 seconds, answer 5329 ok"
]
},
"execution_count": 26,
@ -958,7 +961,7 @@
{
"data": {
"text/plain": [
"Puzzle 6.2: 5.3153 seconds, answer 2162 ok"
"Puzzle 6.2: 5.2239 seconds, answer 2162 ok"
]
},
"execution_count": 28,
@ -1104,7 +1107,7 @@
{
"data": {
"text/plain": [
"Puzzle 7.1: .0406 seconds, answer 1985268524462 ok"
"Puzzle 7.1: .0395 seconds, answer 1985268524462 ok"
]
},
"execution_count": 32,
@ -1136,7 +1139,7 @@
{
"data": {
"text/plain": [
"Puzzle 7.2: 2.6449 seconds, answer 150077710195188 ok"
"Puzzle 7.2: 2.5654 seconds, answer 150077710195188 ok"
]
},
"execution_count": 33,
@ -1205,43 +1208,44 @@
{
"cell_type": "code",
"execution_count": 35,
"id": "33199a01-6e69-4d7d-aef2-e59b3b804398",
"id": "22180ce8-5d03-4aee-8c73-62f2afbddf71",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Puzzle 8.1: .0006 seconds, answer 220 ok"
]
},
"execution_count": 35,
"metadata": {},
"output_type": "execute_result"
}
],
"outputs": [],
"source": [
"def antinodes(antennas: Grid) -> Set[Point]:\n",
" \"\"\"The set of all antinodal points in the grid.\n",
" (That is, points that are of distance d and 2d from same frequency antennas.)\"\"\"\n",
" groups = [antennas.findall(f) for f in set(antennas.values()) if f != '.']\n",
" return union(antinodes2(A, B, antennas)\n",
" for points in group_all(antennas).values()\n",
" for points in groups\n",
" for A, B in combinations(points, 2))\n",
"\n",
"def antinodes2(A: Point, B: Point, antennas: Grid) -> Set[Point]:\n",
" \"\"\"The set of antinodal points for two antenna points, A and B.\"\"\"\n",
" return {P for P in {sub(mul(A, 2), B), sub(mul(B, 2), A)}\n",
" if P in antennas}\n",
"\n",
"def group_all(antennas: Grid) -> Dict[Char, List[Point]]:\n",
" \"\"\"A dict of {frequency: [all_points_with_an_antenna_of_that_frequency]}.\"\"\"\n",
" frequencies = defaultdict(list)\n",
" for point, f in antennas.items():\n",
" if f != '.':\n",
" frequencies[f].append(point)\n",
" return frequencies\n",
"\n",
"answer(8.1, 220, \n",
" lambda: len(antinodes(antennas)))"
" if P in antennas}"
]
},
{
"cell_type": "code",
"execution_count": 36,
"id": "dd173ce9-cbbb-4282-b43f-c7cff662bd90",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Puzzle 8.1: .0066 seconds, answer 220 ok"
]
},
"execution_count": 36,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"answer(8.1, 220, lambda:\n",
" len(antinodes(antennas)))"
]
},
{
@ -1251,21 +1255,24 @@
"source": [
"### Part 2: How many unique locations within the bounds of the map contain an updated antinode?\n",
"\n",
"For Part 2, the updated definition of antinodes means that they can now occur at any point that is exactly on line with two antennas of the same frequency, regardless of distance. So if the two antennas are *A* and *B* then the antinbodal points are of the form *A* + *k*(*A* - *B*) for any scalar value *k* (positive or negative) that makes the resulting point fall within the grid."
"For Part 2, the updated definition of antinodes means that they can now occur at *any* point that is exactly on line with two antennas of the same frequency, regardless of distance. So if the two antennas are *A* and *B* then the antinbodal points are of the form *A* + *k*(*A* - *B*) for any scalar value *k* (positive or negative) that makes the resulting point fall within the grid.\n",
"\n",
"I'll parametrize `updated_antinodes` so it can handle both parts:"
]
},
{
"cell_type": "code",
"execution_count": 36,
"execution_count": 37,
"id": "d30f8ce9-f186-46a0-a2e7-f74eceae6905",
"metadata": {},
"outputs": [],
"source": [
"def updated_antinodes(antennas: Grid) -> Set[Point]:\n",
"def antinodes(antennas: Grid, antinodes2=antinodes2) -> Set[Point]:\n",
" \"\"\"The set of all updated antinodal points in the grid.\n",
" (That is, points that are on a line with two same frequency antennas.)\"\"\"\n",
" return union(updated_antinodes2(A, B, antennas)\n",
" for points in group_all(antennas).values()\n",
" groups = [antennas.findall(f) for f in set(antennas.values()) if f != '.']\n",
" return union(antinodes2(A, B, antennas)\n",
" for points in groups\n",
" for A, B in combinations(points, 2))\n",
"\n",
"def updated_antinodes2(A: Point, B: Point, antennas: Grid) -> Set[Point]:\n",
@ -1279,30 +1286,51 @@
" antinodes.add(P)\n",
" else:\n",
" break\n",
" return antinodes\n",
" "
" return antinodes"
]
},
{
"cell_type": "code",
"execution_count": 37,
"execution_count": 38,
"id": "6bf85b57-8b8f-4196-9903-6d5fe082f404",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Puzzle 8.1: .0063 seconds, answer 220 ok"
]
},
"execution_count": 38,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"answer(8.1, 220, lambda:\n",
" len(antinodes(antennas)))"
]
},
{
"cell_type": "code",
"execution_count": 39,
"id": "f232952c-5fc6-4696-a8b1-d0b54137ac02",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Puzzle 8.2: .0021 seconds, answer 813 ok"
"Puzzle 8.2: .0074 seconds, answer 813 ok"
]
},
"execution_count": 37,
"execution_count": 39,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"answer(8.2, 813, lambda:\n",
" len(updated_antinodes(antennas)))"
" len(antinodes(antennas, updated_antinodes2)))"
]
},
{
@ -1310,7 +1338,7 @@
"id": "9696a986-b6a2-4530-b55b-9db959ef7485",
"metadata": {},
"source": [
"I got both of these right the first time (except for some simple typos like mismatched parens and typing `grid` when I meant the grid called `antennas`), but maybe I should have parameterized the first version of `antinodes` to allow for a different `antinodes2` function."
"I got both of these right the first time (except for some simple typos: a mismatched paren and typing `grid` when I meant the grid called `antennas`)."
]
},
{
@ -1325,7 +1353,7 @@
},
{
"cell_type": "code",
"execution_count": 38,
"execution_count": 40,
"id": "0e944f9e-5c16-440c-b12e-178058a87048",
"metadata": {},
"outputs": [
@ -1366,7 +1394,7 @@
},
{
"cell_type": "code",
"execution_count": 39,
"execution_count": 41,
"id": "76e8454d-a2f3-4b6b-92df-182116cf46e0",
"metadata": {},
"outputs": [],
@ -1401,17 +1429,17 @@
},
{
"cell_type": "code",
"execution_count": 40,
"execution_count": 42,
"id": "2aa7e2b9-844e-49ed-b41b-4a4cecff86b7",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Puzzle 9.1: .0433 seconds, answer 6332189866718 ok"
"Puzzle 9.1: .0415 seconds, answer 6332189866718 ok"
]
},
"execution_count": 40,
"execution_count": 42,
"metadata": {},
"output_type": "execute_result"
}
@ -1439,7 +1467,7 @@
},
{
"cell_type": "code",
"execution_count": 41,
"execution_count": 43,
"id": "fcf4d832-3d7d-4987-aa57-e6e0f1df16bf",
"metadata": {},
"outputs": [],
@ -1482,17 +1510,17 @@
},
{
"cell_type": "code",
"execution_count": 42,
"execution_count": 44,
"id": "e3036875-88d0-496e-9d2f-facd0e80a5b2",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Puzzle 9.2: 6.2098 seconds, answer 6353648390778 ok"
"Puzzle 9.2: 6.1664 seconds, answer 6353648390778 ok"
]
},
"execution_count": 42,
"execution_count": 44,
"metadata": {},
"output_type": "execute_result"
}
@ -1522,7 +1550,7 @@
},
{
"cell_type": "code",
"execution_count": 43,
"execution_count": 45,
"id": "5804fb03-05f3-402f-b6cc-6804c5d22512",
"metadata": {},
"outputs": [
@ -1575,7 +1603,7 @@
},
{
"cell_type": "code",
"execution_count": 44,
"execution_count": 46,
"id": "76b5379e-ee19-4607-91b8-88ec7b38023f",
"metadata": {},
"outputs": [],
@ -1591,17 +1619,17 @@
},
{
"cell_type": "code",
"execution_count": 45,
"execution_count": 47,
"id": "97cf05f7-fa56-4a90-b2d8-2cd4d9b81f95",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Puzzle 10.1: .0141 seconds, answer 744 ok"
"Puzzle 10.1: .0143 seconds, answer 744 ok"
]
},
"execution_count": 45,
"execution_count": 47,
"metadata": {},
"output_type": "execute_result"
}
@ -1625,7 +1653,7 @@
},
{
"cell_type": "code",
"execution_count": 46,
"execution_count": 48,
"id": "b763450f-a565-4936-bee4-e531c2eeebdb",
"metadata": {},
"outputs": [],
@ -1642,17 +1670,17 @@
},
{
"cell_type": "code",
"execution_count": 47,
"execution_count": 49,
"id": "f8a87032-6556-4fc9-9bb8-573611aee8dc",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Puzzle 10.2: .0161 seconds, answer 1651 ok"
"Puzzle 10.2: .0222 seconds, answer 1651 ok"
]
},
"execution_count": 47,
"execution_count": 49,
"metadata": {},
"output_type": "execute_result"
}
@ -1677,12 +1705,12 @@
"source": [
"# [Day 11](https://adventofcode.com/2024/day/11): Plutonian Pebbles\n",
"\n",
"Today's input is a single line consisting of a list of integers, representing numbers enscribed on some stones, which are arranged in a straight line."
"Today's narrative involves a straight line of stones, each of which has a number enscribed on it. The input is a single line of these numbers:"
]
},
{
"cell_type": "code",
"execution_count": 48,
"execution_count": 50,
"id": "76b68cef-d8de-4145-b65c-b254fedf1671",
"metadata": {},
"outputs": [
@ -1706,6 +1734,7 @@
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "a7302dc5-5163-4f0b-bdcc-8c00e367391c",
"metadata": {},
@ -1717,12 +1746,15 @@
"- Otherwise, a stone with an even number of digits splits into two stones, with the first and second halves of those digits.\n",
"- Otherwise, the stone's number is multiplied by 2024.\n",
"\n",
"<img src=\"https://pbs.twimg.com/media/GejUQBgWIAAUtDf?format=jpg&name=medium\" width=400>\n",
"\n",
"\n",
"I'll define `blink` to simulate the effect of a given number of blinks, and `change_stone` to change a single stone, returning a list of wither one or two stones (the two stones computed by `split_stone`):"
]
},
{
"cell_type": "code",
"execution_count": 49,
"execution_count": 51,
"id": "1513df56-3d6f-42cf-8aec-1bdbeb991d90",
"metadata": {},
"outputs": [],
@ -1748,17 +1780,17 @@
},
{
"cell_type": "code",
"execution_count": 50,
"execution_count": 52,
"id": "eff17cd0-a2c7-4d69-bc55-c0ef97917915",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Puzzle 11.1: .1570 seconds, answer 194482 ok"
"Puzzle 11.1: .1567 seconds, answer 194482 ok"
]
},
"execution_count": 50,
"execution_count": 52,
"metadata": {},
"output_type": "execute_result"
}
@ -1775,20 +1807,21 @@
"source": [
"### Part 2: How many stones would you have after blinking a total of 75 times?\n",
"\n",
"It looks like the number of stones is roughly doubling every 2 blinks, so for 75 blinks we could have trillions of stones. I'd like something more efficient. I note that:\n",
"It looks like the number of stones is roughly doubling every 1 or 2 blinks, so for 75 blinks we could have trillions of stones. I'd like something more efficient. I note that:\n",
"- Although the puzzle makes it clear that the stones are in a line, it turns out their position in the line is irrelevant.\n",
"- Because all the even-digit numbers get split in half, it seems like many small numbers will appear multiple times. In the example, after 6 blinks the number 2 appears 4 times.\n",
"- Because all the even-digit numbers get split in half, it seems like many small numbers will appear multiple times.\n",
"- (In the example, after 6 blinks the number 2 appears 4 times.)\n",
"- Therefore, I'll keep a `Counter` of stones rather than a list of stones."
]
},
{
"cell_type": "code",
"execution_count": 51,
"execution_count": 53,
"id": "707b5a97-0296-48df-bdab-e34064cc67c2",
"metadata": {},
"outputs": [],
"source": [
"def blink2(stones: Ints], blinks=25) -> Counter:\n",
"def blink2(stones: Ints, blinks=25) -> Counter:\n",
" \"\"\"Simulate the changes after blinking `blinks` times and return a Counter of stones.\"\"\"\n",
" counts = Counter(stones)\n",
" for _ in range(blinks):\n",
@ -1808,17 +1841,17 @@
},
{
"cell_type": "code",
"execution_count": 52,
"execution_count": 54,
"id": "efdcdbf8-e8ec-4a85-9d09-90a20e08c66a",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Puzzle 11.1: .0037 seconds, answer 194482 ok"
"Puzzle 11.1: .0035 seconds, answer 194482 ok"
]
},
"execution_count": 52,
"execution_count": 54,
"metadata": {},
"output_type": "execute_result"
}
@ -1830,17 +1863,17 @@
},
{
"cell_type": "code",
"execution_count": 53,
"execution_count": 55,
"id": "657b1f13-ffcc-44c6-84f1-398fa2fcdac7",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Puzzle 11.2: .1285 seconds, answer 232454623677743 ok"
"Puzzle 11.2: .1301 seconds, answer 232454623677743 ok"
]
},
"execution_count": 53,
"execution_count": 55,
"metadata": {},
"output_type": "execute_result"
}
@ -1855,7 +1888,201 @@
"id": "ce377749-b3e2-4ca4-b50d-e7c3d2e7201a",
"metadata": {},
"source": [
"Again, I did pretty well, with no errors, and moving at what I thought was a good pace, but I didn't even crack the top 2000 on the leaderboard. I guess I spent too much time writing docstrings and type hints, and refactoring as I go."
"Again, I did pretty well, with no errors, and moving at what I thought was a good pace, but I didn't even crack the top 2000 on the leaderboard. I guess I spent too much time writing docstrings and type hints, and refactoring as I went."
]
},
{
"cell_type": "markdown",
"id": "391cec1e-32fe-4e6e-81c2-4e38469b15e3",
"metadata": {},
"source": [
"# [Day 12](https://adventofcode.com/2024/day/12): Garden Groups\n",
"\n",
"Today's input is yet another 2D map. This one depicts different garden plots on a farm, each plot planted with a crop, indicated by a letter (maybe \"I\" is iceberg lettuce and \"U\" is udo, and so on):"
]
},
{
"cell_type": "code",
"execution_count": 56,
"id": "8161ee7e-76e3-499a-abf8-a607991c9602",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"────────────────────────────────────────────────────────────────────────────────────────────────────\n",
"Puzzle input ➜ 140 strs:\n",
"────────────────────────────────────────────────────────────────────────────────────────────────────\n",
"IIIIIIIIIIIIIIIIIIIIIUUUUUUUUJLLLLAAAAAAMMMAUUUUPPXPZZZZZZZZZZZXXXXXXXXXXXXXXXXXXXXXFFFFFFFFFFZZ ...\n",
"IIIIIIIIIIIIIIIIIIIIUUUUUUUUUJALEAAAAAAAAAAAAUUUUPXPPZZZZZHHHHHHXHXXXXXXXXXXXXXXXXXXFXFFFFFFFFZZ ...\n",
"IIIIIIIIIIIIIIIIIIIIUUUUUUUUUJAAAAAAAAAAAAAAUUPPPPPPPZZZZZZZHHHHHHHXXXXXXXXXXXXXXXXXXXFFFFFFFFFZ ...\n",
"IIIIIIIIIIIAAIIIIIIIIIUUUUUUUJJAAAAAAAAAAAAAAVVPPPPPPPPZHHHHHHHHHHHXXXXXXXXXXXXXXXXXFFFFFFFFFFFZ ...\n",
"IOOIIIIIIIIAAIIIIIIIIIIUUUQVUJJAAAAAAAAAAAAAEVVZPPPPPPPHHHHHHHHHHHHXXXXXXXXXXXXXXXXXXXXFFFFFFLLL ...\n",
"OOOOOOOOIIAAAAAIIIIIIIIUQQQQVQJAAAAAAAAAAAAAAVVPPPPPIIHHHHHHHHHHHHHXXXXXUXXXXXXUUXXBBBSFFFLLLLLL ...\n",
"OOOOOOOOIAAAAAAIIIIIIQQQQQQQQQQQEADDAAAAAAAAHHVVPPPIIIIHHHHHHHHHHHHHXXXUUUUUXXUUUXXBBBSFFFLLELLL ...\n",
"OOOOOOOOIIIAAIIIIIQQQQQQQQQQTQJQEEDDDAAAAAAHHVVVVPIIIIIHHHHHHHHHHHHHXUUUUUUUUUUUMMXBBBSSSFFFLLLL ...\n",
"...\n"
]
}
],
"source": [
"farm = Grid(parse(12))"
]
},
{
"cell_type": "markdown",
"id": "bb95a10b-1f83-4940-a68b-b94696c3aab3",
"metadata": {},
"source": [
"### Part 1: What is the total price of fencing all regions on your map?\n",
"\n",
"We are asked to calculate the cost of putting fences around each **region** (a region is a set of plots with the same crop that abut each other horizontally or vertically). The price of the fence for a region is defined as the product of the region's area and its perimeter. If we represent a region as a set of (x, y) points, then the area is easy: it is just the number of points. The perimeter length can be computed by, for each plot point in the region, looking at each of the four directions and counting cases where the adjacent plot in that direction is *not* in the region. (Initially I had a **bug** in that I looked at the `farm.neighbors` of each plot. That doesn't work because a plot on the edge of the grid should count as part of the perimeter.)"
]
},
{
"cell_type": "code",
"execution_count": 57,
"id": "79f91f38-e325-44f2-9e53-b64ce12d9d35",
"metadata": {},
"outputs": [],
"source": [
"Region = Set[Point]\n",
"\n",
"def fence_price(farm: Grid) -> int:\n",
" \"\"\"Total price of fences for all the regions in the farm.\"\"\"\n",
" area = len\n",
" return sum(area(region) * perimeter_length(region)\n",
" for region in regions(farm))\n",
"\n",
"def perimeter_length(region: Region) -> int:\n",
" \"\"\"The number of sides on the perimeter of the region.\"\"\"\n",
" return quantify(add2(plot, d) not in region for plot in region for d in directions4)"
]
},
{
"cell_type": "markdown",
"id": "9524ee15-c378-4cd4-a79b-14fb99b17cb3",
"metadata": {},
"source": [
"To find all the regions I'll start at a point and do a [flood fill](https://en.wikipedia.org/wiki/Flood_fill) to neighboring points with the same region letter, keeping track of points that have already been found so as to not duplicate them. The function `regions` iterates over all points to make sure it finds every region, and `flood_fill` recursively expands to all points that neighbor `p` and have the same crop (letter). This function mutates the set `already_found` as it goes (and also mutates the `region` it is building up)."
]
},
{
"cell_type": "code",
"execution_count": 58,
"id": "1fbabbfb-50c8-4197-8517-e7cee9582765",
"metadata": {},
"outputs": [],
"source": [
"def regions(farm: Grid) -> List[Region]:\n",
" \"\"\"Find all the regions in the farm.\"\"\"\n",
" already_found = set() # Set of plots already accounted for\n",
" return [flood_fill(p, farm, set(), already_found) \n",
" for p in farm if p not in already_found]\n",
"\n",
"def flood_fill(p: Point, grid: Grid, region: set, already_found: set) -> set:\n",
" \"\"\"Starting at point p, recursively add all neighboring points to `region`, keeping track of `already_found`.\"\"\"\n",
" if p not in already_found:\n",
" region.add(p)\n",
" already_found.add(p)\n",
" for p2 in grid.neighbors(p):\n",
" if farm[p2] == farm[p]:\n",
" flood_fill(p2, grid, region, already_found)\n",
" return region"
]
},
{
"cell_type": "code",
"execution_count": 59,
"id": "cdaf655b-d12c-4973-b19b-3132e5e691c6",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Puzzle 12.1: .0773 seconds, answer 1402544 ok"
]
},
"execution_count": 59,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"answer(12.1, 1402544, lambda:\n",
" fence_price(farm))"
]
},
{
"cell_type": "markdown",
"id": "b3383560-2bbc-4dfc-b643-feb103876823",
"metadata": {},
"source": [
"### Part 2: What is the new total price of fencing all regions on your map, with the bulk discount?\n",
"\n",
"In Part 2 we get a **bulk discount** on the fencing; we only need to pay for the number of straight line sides on the perimeter, not the total length of the perimeter. For example, a 10 x 10 square has perimeter 40, but has only 4 sides; that's a 90% discount!\n",
"\n",
"It took me a while to figure out a good approach for this. At first I was reminded of the Convex Hull problem, for which I have [a notebook](https://github.com/norvig/pytudes/blob/main/ipynb/Convex%20Hull.ipynb). But that's not really appropriate here; our regions could be non-convex, and the set of points in a region are not the same as the vertexes of a polygon (e.g., a region with one point has 4 sides, not 0).\n",
"\n",
"A better idea is to start with the perimeter length and subtract one for every case in which a points has an edge in one direction (e.g., an edge to the North) and also has a neighbor with the same edge. To be precise, I'll look for four cases:\n",
"- A point with an edge to the North whose neighbor to the East also has an edge to the North\n",
"- A point with an edge to the East whose neighbor to the South also has an edge to the East\n",
"- A point with an edge to the South whose neighbor to the West also has an edge to the South\n",
"- A point with an edge to the West whose neighbor to the North also has an edge to the West\n",
"\n",
"Here's a diagram of a region of \"`X`\" crops with a \"`-`\" marking each place where a perimeter piece would be subtracted.\n",
"\n",
" .X-...\n",
" -X-.X-\n",
" -XXXX-\n",
" ..XXX.\n",
" ...--.\n"
]
},
{
"cell_type": "code",
"execution_count": 60,
"id": "38c30e15-3a33-40c2-b734-163a15af7a8a",
"metadata": {},
"outputs": [],
"source": [
"def region_sides(region):\n",
" \"\"\"How many straight-line sides does this region have?\"\"\"\n",
" def has_edge(p: Point, d: Vector): return p in region and add2(p, d) not in region\n",
" def neighbor(p: Point, d: Vector): return add2(p, make_turn(d, 'R'))\n",
" subtract = quantify(has_edge(p, d) and has_edge(neighbor(p, d), d)\n",
" for p in region\n",
" for d in directions4)\n",
" return perimeter_length(region) - subtract\n",
"\n",
"def discount_fence_price(farm: Grid) -> int:\n",
" \"\"\"Total price of fences for all the regions in the farm, with the bulk discount applied.\"\"\"\n",
" area = len\n",
" return sum(area(region) * region_sides(region)\n",
" for region in regions(farm))"
]
},
{
"cell_type": "code",
"execution_count": 61,
"id": "9defcd35-91bc-41d4-a16f-bb7a4ede75e7",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Puzzle 12.2: .1090 seconds, answer 862486 ok"
]
},
"execution_count": 61,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"answer(12.2, 862486, lambda: \n",
" discount_fence_price(farm))"
]
},
{
@ -1870,7 +2097,7 @@
},
{
"cell_type": "code",
"execution_count": 54,
"execution_count": 62,
"id": "34813fc9-a000-4cd8-88ae-692851b3242c",
"metadata": {},
"outputs": [
@ -1880,26 +2107,28 @@
"text": [
"Puzzle 1.1: .0002 seconds, answer 1830467 ok\n",
"Puzzle 1.2: .0002 seconds, answer 26674158 ok\n",
"Puzzle 2.1: .0011 seconds, answer 257 ok\n",
"Puzzle 2.2: .0077 seconds, answer 328 ok\n",
"Puzzle 3.1: .0014 seconds, answer 156388521 ok\n",
"Puzzle 2.1: .0012 seconds, answer 257 ok\n",
"Puzzle 2.2: .0069 seconds, answer 328 ok\n",
"Puzzle 3.1: .0013 seconds, answer 156388521 ok\n",
"Puzzle 3.2: .0009 seconds, answer 75920122 ok\n",
"Puzzle 4.1: .0742 seconds, answer 2401 ok\n",
"Puzzle 4.2: .0605 seconds, answer 1822 ok\n",
"Puzzle 5.1: .0011 seconds, answer 5762 ok\n",
"Puzzle 5.2: .0016 seconds, answer 4130 ok\n",
"Puzzle 6.1: .0043 seconds, answer 5329 ok\n",
"Puzzle 6.2: 5.3153 seconds, answer 2162 ok\n",
"Puzzle 7.1: .0406 seconds, answer 1985268524462 ok\n",
"Puzzle 7.2: 2.6449 seconds, answer 150077710195188 ok\n",
"Puzzle 8.1: .0006 seconds, answer 220 ok\n",
"Puzzle 8.2: .0021 seconds, answer 813 ok\n",
"Puzzle 9.1: .0433 seconds, answer 6332189866718 ok\n",
"Puzzle 9.2: 6.2098 seconds, answer 6353648390778 ok\n",
"Puzzle 10.1: .0141 seconds, answer 744 ok\n",
"Puzzle 10.2: .0161 seconds, answer 1651 ok\n",
"Puzzle 11.1: .0037 seconds, answer 194482 ok\n",
"Puzzle 11.2: .1285 seconds, answer 232454623677743 ok\n"
"Puzzle 4.1: .0737 seconds, answer 2401 ok\n",
"Puzzle 4.2: .0602 seconds, answer 1822 ok\n",
"Puzzle 5.1: .0010 seconds, answer 5762 ok\n",
"Puzzle 5.2: .0015 seconds, answer 4130 ok\n",
"Puzzle 6.1: .0044 seconds, answer 5329 ok\n",
"Puzzle 6.2: 5.2239 seconds, answer 2162 ok\n",
"Puzzle 7.1: .0395 seconds, answer 1985268524462 ok\n",
"Puzzle 7.2: 2.5654 seconds, answer 150077710195188 ok\n",
"Puzzle 8.1: .0063 seconds, answer 220 ok\n",
"Puzzle 8.2: .0074 seconds, answer 813 ok\n",
"Puzzle 9.1: .0415 seconds, answer 6332189866718 ok\n",
"Puzzle 9.2: 6.1664 seconds, answer 6353648390778 ok\n",
"Puzzle 10.1: .0143 seconds, answer 744 ok\n",
"Puzzle 10.2: .0222 seconds, answer 1651 ok\n",
"Puzzle 11.1: .0035 seconds, answer 194482 ok\n",
"Puzzle 11.2: .1301 seconds, answer 232454623677743 ok\n",
"Puzzle 12.1: .0773 seconds, answer 1402544 ok\n",
"Puzzle 12.2: .1090 seconds, answer 862486 ok\n"
]
}
],