Skip to content

Commit ed5fda8

Browse files
committed
2023.18
1 parent 21bc4c1 commit ed5fda8

File tree

7 files changed

+139
-12
lines changed

7 files changed

+139
-12
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# [Day 18: Lavaduct Lagoon](https://adventofcode.com/2023/day/18)
+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import sys
2+
import re
3+
from collections import namedtuple
4+
import gridutil.coord as cu
5+
import gridutil.grid as gu
6+
7+
8+
Instruction = namedtuple("Instruction", ["direction", "dist", "colour"])
9+
PARSE_RE = re.compile(r"([RDUL]) (\d+) \(#([a-f\d]{6})\)")
10+
DIRECTION_TRANSFORMATION = {
11+
"R": cu.Direction.Right,
12+
"L": cu.Direction.Left,
13+
"U": cu.Direction.Up,
14+
"D": cu.Direction.Down,
15+
"0": cu.Direction.Right,
16+
"1": cu.Direction.Down,
17+
"2": cu.Direction.Left,
18+
"3": cu.Direction.Up,
19+
}
20+
21+
22+
def parse(instr: str) -> list[Instruction]:
23+
res = []
24+
for line in instr.splitlines():
25+
m = PARSE_RE.match(line)
26+
assert m is not None
27+
28+
raw_dir, dist, colour = m.groups()
29+
parsed_dir = DIRECTION_TRANSFORMATION[raw_dir]
30+
assert parsed_dir is not None
31+
32+
res.append(Instruction(parsed_dir, int(dist), colour))
33+
return res
34+
35+
36+
def run(instructions: list[Instruction]) -> int:
37+
perimeter = 0
38+
vertices = [cu.Coordinate(0, 0)]
39+
for instruction in instructions:
40+
perimeter += instruction.dist
41+
vertices.append(
42+
cu.add(
43+
vertices[-1], cu.mult(instruction.direction.delta(), instruction.dist)
44+
)
45+
)
46+
47+
vertices = vertices[:-1]
48+
49+
area = cu.area(vertices)
50+
51+
# This is Pick's theorem.
52+
# Normally, we'd want to just get the internal area, which the Shoelace formula would do.
53+
# But since we want the area including walls that we assume are a single
54+
# unit thick, we apply Pick's theorem as this counts all coordinates that
55+
# the walls pass through, which in this case is effectively the same thing.
56+
return int(area + perimeter / 2) + 1
57+
58+
59+
def one(instr: str):
60+
instructions = parse(instr)
61+
return run(instructions)
62+
63+
64+
def two(instr: str):
65+
instructions = parse(instr)
66+
for i, instruction in enumerate(instructions):
67+
instructions[i] = Instruction(
68+
DIRECTION_TRANSFORMATION[instruction.colour[-1]],
69+
int(instruction.colour[:5], base=16),
70+
"",
71+
)
72+
return run(instructions)
73+
74+
75+
def _debug(*args, **kwargs):
76+
kwargs["file"] = sys.stderr
77+
print(*args, **kwargs)
78+
79+
80+
if __name__ == "__main__":
81+
if len(sys.argv) < 2 or sys.argv[1] not in ["1", "2"]:
82+
print("Missing day argument", file=sys.stderr)
83+
sys.exit(1)
84+
inp = sys.stdin.read().strip()
85+
if sys.argv[1] == "1":
86+
print(one(inp))
87+
else:
88+
print(two(inp))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"1": [
3+
{
4+
"is": "62",
5+
"input": "R 6 (#70c710)\nD 5 (#0dc571)\nL 2 (#5713f0)\nD 2 (#d2c081)\nR 2 (#59c680)\nD 2 (#411b91)\nL 5 (#8ceee2)\nU 2 (#caa173)\nL 1 (#1b58a2)\nU 2 (#caa171)\nR 2 (#7807d2)\nU 3 (#a77fa3)\nL 2 (#015232)\nU 2 (#7a21e3)\n\n"
6+
},
7+
{
8+
"is": "20",
9+
"input": "R 2 (#000000)\nD 1 (#000000)\nR 2 (#000000)\nU 1 (#000000)\nR 2 (#000000)\nD 2 (#000000)\nL 6 (#000000)\nU 2 (#000000)\n"
10+
}
11+
],
12+
"2": [
13+
{
14+
"is": "952408144115",
15+
"input": "R 6 (#70c710)\nD 5 (#0dc571)\nL 2 (#5713f0)\nD 2 (#d2c081)\nR 2 (#59c680)\nD 2 (#411b91)\nL 5 (#8ceee2)\nU 2 (#caa173)\nL 1 (#1b58a2)\nU 2 (#caa171)\nR 2 (#7807d2)\nU 3 (#a77fa3)\nL 2 (#015232)\nU 2 (#7a21e3)\n\n"
16+
}
17+
]
18+
}

challenges/2023/README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,5 @@ A day denoted with a star means it has a visualisation.
3030
| 14* - Parabolic Reflector Dish | ★ ★ | Python | Why do I always overcomplicate cycle detection?! |
3131
| 15 - Lens Library | ★ ★ | Go | Still took some brainpower but this time the brainpower was needed to work out what the problem was, *not* to work out how to solve the problem. |
3232
| 16 - The Floor Will Be Lava | ★ ★ | Python | Pathfinding, sort of, but also brute forceable?? |
33-
| 17 - Clumsy Crucible | ★ ★ | Python | This taught me quite a lot about how to meddle with Djikstra's |
33+
| 17 - Clumsy Crucible | ★ ★ | Python | This taught me quite a lot about how to meddle with Djikstra's |
34+
| 18 - Ladaduct Lagoon | ★ ★ | Python | Nothing quite like a problem that I thought I knew the solution to showing up my lack of mathematical knowledge. |

challenges/2023/benchmarks.jsonl

+2
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,5 @@
3131
{"day": 16, "part": 2, "runner": "py", "min": 2.7863943576812744, "max": 4.14529013633728, "avg": 3.1346225261688234, "n": 15}
3232
{"day": 17, "part": 1, "runner": "py", "min": 5.36311674118042, "max": 5.36311674118042, "avg": 5.36311674118042, "n": 1}
3333
{"day": 17, "part": 2, "runner": "py", "min": 26.201914072036743, "max": 26.201914072036743, "avg": 26.201914072036743, "n": 1}
34+
{"day": 18, "part": 1, "runner": "py", "min": 0.02330160140991211, "max": 0.03203868865966797, "avg": 0.024628419876098633, "n": 100}
35+
{"day": 18, "part": 2, "runner": "py", "min": 0.023529052734375, "max": 0.030207157135009766, "avg": 0.02483478546142578, "n": 100}

gridutil/coord.py

+25-8
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,43 @@
33
from numbers import Number
44

55

6-
Coordinate: tuple[Number, Number] = namedtuple("Coordinate", ["x", "y"])
6+
Coordinate = namedtuple("Coordinate", ["x", "y"])
77

88

99
def add(a: Coordinate, b: Coordinate) -> Coordinate:
10-
return Coordinate(a.x + b.x, a.y + b.y)
10+
xa, ya = a
11+
xb, yb = b
12+
return Coordinate(xa + xb, ya + yb)
1113

1214

1315
def sub(a: Coordinate, b: Coordinate) -> Coordinate:
14-
return Coordinate(a.x - b.x, a.y - b.y)
16+
xa, ya = a
17+
xb, yb = b
18+
return Coordinate(xa - xb, ya - yb)
1519

1620

1721
def mult(a: Coordinate, b: Number) -> Coordinate:
18-
return Coordinate(a.x * b, a.y * b)
22+
x, y = a
23+
return Coordinate(x * b, y * b)
1924

2025

2126
def manhattan_dist(a: Coordinate, b: Coordinate) -> Number:
2227
x, y = sub(b, a)
2328
return abs(x) + abs(y)
2429

2530

31+
def area(x: list[Coordinate]) -> Number:
32+
"""
33+
Finds the area of a closed polygon.
34+
35+
https://en.wikipedia.org/wiki/Shoelace_formula
36+
"""
37+
acc = 0
38+
for ((ax, ay), (bx, by)) in zip(x, x[1:] + [x[0]]):
39+
acc += (ax * by) - (bx * ay)
40+
return acc / 2
41+
42+
2643
class Direction(Enum):
2744
Up = auto()
2845
Down = auto()
@@ -32,13 +49,13 @@ class Direction(Enum):
3249
def delta(self) -> Coordinate:
3350
match self:
3451
case Direction.Up:
35-
return (0, -1)
52+
return Coordinate(0, -1)
3653
case Direction.Down:
37-
return (0, 1)
54+
return Coordinate(0, 1)
3855
case Direction.Left:
39-
return (-1, 0)
56+
return Coordinate(-1, 0)
4057
case Direction.Right:
41-
return (1, 0)
58+
return Coordinate(1, 0)
4259

4360
def opposite(self):
4461
match self:

gridutil/grid.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def parse(instr: str, filter_fn: Optional[Callable[[str], bool]] = None) -> Grid
1414
for y, line in enumerate(instr.splitlines()):
1515
for x, char in enumerate(line):
1616
if filter_fn(char):
17-
res[(x, y)] = char
17+
res[coord.Coordinate(x, y)] = char
1818

1919
return res
2020

@@ -37,8 +37,8 @@ def get_max_y(grid: Grid, filter_fn: Optional[Callable[[T], bool]] = None) -> in
3737

3838

3939
def print_grid(grid: Grid, **kwargs):
40-
for y in range(get_max_y(grid) + 1):
41-
for x in range(get_max_x(grid) + 1):
40+
for y in range(min(map(lambda x: x[1], grid)), get_max_y(grid) + 1):
41+
for x in range(min(map(lambda x: x[0], grid)), get_max_x(grid) + 1):
4242
v = grid.get((x, y), " ")
4343
print(v, end="", **kwargs)
4444
print(**kwargs)

0 commit comments

Comments
 (0)