Skip to content

Commit 413df9e

Browse files
authored
Add 2023/18 py
1 parent 4f1eae3 commit 413df9e

File tree

9 files changed

+283
-0
lines changed

9 files changed

+283
-0
lines changed

2023/18/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Advent of Code - 2023 Day 18
2+
Here are my solutions to this puzzle.
3+
4+
* Problem instructions: [adventofcode.com/2023/day/18](https://adventofcode.com/2023/day/18)
5+
* Input: [adventofcode.com/2023/day/18/input](https://adventofcode.com/2023/day/18/input)
6+
7+
Fetch input by exporting `$AOC_SESSION` in your shell and then:
8+
```bash
9+
curl -OJLsb session=$AOC_SESSION adventofcode.com/2023/day/18/input
10+
```
11+
12+
or run `fetch_input.sh` in this directory.

2023/18/fetch_input.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env sh
2+
# Make sure $AOC_SESSION is exported before running this script.
3+
4+
curl --remote-name --remote-header-name --silent --fail -A 'https://erikw.me/contact' --cookie "session=$AOC_SESSION" "https://adventofcode.com/2023/day/18/input"
5+
test "$?" -eq 0 && echo "Fetched input" && exit 0 || echo "Failed to fetch input" && exit 1

2023/18/input1.0

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
R 6 (#70c710)
2+
D 5 (#0dc571)
3+
L 2 (#5713f0)
4+
D 2 (#d2c081)
5+
R 2 (#59c680)
6+
D 2 (#411b91)
7+
L 5 (#8ceee2)
8+
U 2 (#caa173)
9+
L 1 (#1b58a2)
10+
U 2 (#caa171)
11+
R 2 (#7807d2)
12+
U 3 (#a77fa3)
13+
L 2 (#015232)
14+
U 2 (#7a21e3)

2023/18/instructions.url

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[InternetShortcut]
2+
URL = https://adventofcode.com/2023/day/18

2023/18/output1.0

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
62

2023/18/output2.0

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
952408144115

2023/18/part1.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#!/usr/bin/env python3
2+
import fileinput
3+
from pprint import pprint
4+
5+
SYM_TRENCH = "#"
6+
SYM_TERRAIN = "."
7+
8+
DIR_DELTA = {
9+
"R": 1j,
10+
"L": -1j,
11+
"U": -1,
12+
"D": 1,
13+
}
14+
15+
16+
def find_trench_loop():
17+
pos = (
18+
0 # Let's call the starting point origin, and make it at (0,0) for simplicity.
19+
)
20+
trench_loop: complex = {pos}
21+
row_min, col_min = 0, 0
22+
row_max, col_max = 0, 0
23+
for line in fileinput.input():
24+
dir, meters, rgb = line.rstrip("\n").split()
25+
meters = int(meters)
26+
rgb = rgb.strip("(#)")
27+
28+
delta = DIR_DELTA[dir]
29+
for _ in range(meters):
30+
pos += delta
31+
trench_loop.add(pos)
32+
row_min, row_max = min(row_min, pos.real), max(row_max, pos.real)
33+
col_min, col_max = min(col_min, pos.imag), max(col_max, pos.imag)
34+
return (
35+
trench_loop,
36+
(int(row_min), int(row_max) + 1),
37+
(int(col_min), int(col_max) + 1),
38+
)
39+
40+
41+
def raytrace_count(trench_loop, col_dim, pos):
42+
count = 0
43+
in_lagoon = False
44+
found_trench_up = False
45+
found_trench_down = False
46+
47+
# Save quite some CPU time by shooting ray in closest direction left or right.
48+
if pos.imag < col_dim[0] + (col_dim[1] - col_dim[0]) / 2:
49+
steps = range(int(pos.imag), col_dim[0] - 1, -1)
50+
dir = DIR_DELTA["L"]
51+
else:
52+
steps = range(int(pos.imag), col_dim[1])
53+
dir = DIR_DELTA["R"]
54+
55+
for _ in steps:
56+
pos += dir
57+
58+
# It counts as crossing the shape (lagoon loop) only if the shape goes up and down from this line, otherwise it's not really crossing the edge like a "^" shape.
59+
pos_up = pos + DIR_DELTA["U"]
60+
pos_down = pos + DIR_DELTA["D"]
61+
if pos_up in trench_loop:
62+
found_trench_up = True
63+
if pos_down in trench_loop:
64+
found_trench_down = True
65+
66+
if pos in trench_loop:
67+
in_lagoon = True
68+
elif in_lagoon:
69+
in_lagoon = False
70+
if found_trench_up and found_trench_down:
71+
count += 1
72+
found_trench_up = False
73+
found_trench_down = False
74+
return count
75+
76+
77+
def dig_out_interior(trench_loop, row_dim, col_dim):
78+
lagoon_map = trench_loop.copy()
79+
for row in range(*row_dim):
80+
for col in range(*col_dim):
81+
pos = complex(row, col)
82+
if (
83+
pos not in trench_loop
84+
and raytrace_count(trench_loop, col_dim, pos) % 2 == 1
85+
):
86+
lagoon_map.add(pos)
87+
return lagoon_map
88+
89+
90+
def print_map(dig_map, row_dim, col_dim):
91+
for row in range(*row_dim):
92+
for col in range(*col_dim):
93+
pos = complex(row, col)
94+
print(SYM_TRENCH if pos in dig_map else SYM_TERRAIN, end="")
95+
print()
96+
97+
98+
def main():
99+
trench_loop, row_dim, col_dim = find_trench_loop()
100+
# print_map(trench_loop, row_dim, col_dim)
101+
102+
lagoon_map = dig_out_interior(trench_loop, row_dim, col_dim)
103+
# print("\n\n==== After dig out")
104+
# print_map(lagoon_map, row_dim, col_dim)
105+
106+
print(len(lagoon_map))
107+
108+
109+
if __name__ == "__main__":
110+
main()

2023/18/part2.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#!/usr/bin/env python3
2+
import fileinput
3+
4+
DIR_DELTA = {
5+
0: (0, 1),
6+
1: (1, 0),
7+
2: (0, -1),
8+
3: (-1, 0),
9+
}
10+
11+
12+
def find_trench_loop():
13+
trench_loop: list[complex] = []
14+
15+
x, y = 0, 0
16+
for line in fileinput.input():
17+
rgb = line.rstrip("\n").split()[-1].strip("(#)")
18+
meters, dir = (int(x, 16) for x in (rgb[:-1], rgb[-1]))
19+
delta = DIR_DELTA[dir]
20+
21+
x, y = x + delta[0] * meters, y + delta[1] * meters
22+
trench_loop.append((x, y))
23+
24+
return trench_loop
25+
26+
27+
def loop_length(loop):
28+
len = 0
29+
pairs = tuple(zip(loop, loop[1:] + [loop[0]]))
30+
for (x1, y1), (x2, y2) in pairs:
31+
len += abs(x2 - x1) + abs(y2 - y1) # Manhattan distance
32+
return len
33+
34+
35+
def polygon_area(loop):
36+
# https://en.wikipedia.org/wiki/Shoelace_formula
37+
pairs = tuple(zip(loop, loop[1:] + [loop[0]]))
38+
# abs() because of loop orientation might be backwards.
39+
return 0.5 * abs(sum((y1 + y2) * (x1 - x2) for (x1, y1), (x2, y2) in pairs))
40+
41+
42+
def cubic_meters_contained(loop):
43+
"""
44+
We can use https://en.wikipedia.org/wiki/Pick%27s_theorem.
45+
A = i + b/2 - 1 (1)
46+
Note that the result we look for is actually not the area A but the total number of points i.e.
47+
i + b (2)
48+
which we can get i with (1) as
49+
i = A - b/2 + 1 (3)
50+
Plugging (3) in to (2):
51+
i + b = A - b/2 + 1 + b = A + b/2 + 1
52+
We can use the shoelace area to find A, and b is just the loop length.
53+
"""
54+
b = loop_length(loop)
55+
A = polygon_area(loop)
56+
return int(A + b / 2 + 1)
57+
58+
59+
def main():
60+
trench_loop = find_trench_loop()
61+
area = cubic_meters_contained(trench_loop)
62+
print(area)
63+
64+
65+
if __name__ == "__main__":
66+
main()

2023/18/part2_complex.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#!/usr/bin/env python3
2+
# Version using complex coordinates.
3+
import fileinput
4+
5+
DIR_RIGHT = 0
6+
DIR_DOWN = 1
7+
DIR_LEFT = 2
8+
DIR_UP = 3
9+
10+
DIR_DELTA = {
11+
DIR_RIGHT: 1j,
12+
DIR_DOWN: 1,
13+
DIR_LEFT: -1j,
14+
DIR_UP: -1,
15+
}
16+
17+
18+
def find_trench_loop():
19+
trench_loop: list[complex] = []
20+
21+
pos = 0
22+
for line in fileinput.input():
23+
rgb = line.rstrip("\n").split()[-1].strip("(#)")
24+
meters, dir = (int(x, 16) for x in (rgb[:-1], rgb[-1]))
25+
delta = DIR_DELTA[dir]
26+
27+
pos += delta * meters
28+
trench_loop.append(pos)
29+
30+
return trench_loop
31+
32+
33+
def loop_length(loop):
34+
len = 0
35+
pairs = tuple(zip(loop, loop[1:] + [loop[0]]))
36+
for p1, p2 in pairs:
37+
len += abs(p2.real - p1.real) + abs(p2.imag - p1.imag) # Manhattan distance
38+
return len
39+
40+
41+
def polygon_area(loop):
42+
# https://en.wikipedia.org/wiki/Shoelace_formula
43+
pairs = tuple(zip(loop, loop[1:] + [loop[0]]))
44+
# abs() because of loop orientation might be backwards.
45+
return 0.5 * abs(sum((p1.imag + p2.imag) * (p1.real - p2.real) for p1, p2 in pairs))
46+
47+
48+
def cubic_meters_contained(loop):
49+
"""
50+
We can use https://en.wikipedia.org/wiki/Pick%27s_theorem.
51+
A = i + b/2 - 1 (1)
52+
Note that the result we look for is actually not the area A but the total number of points i.e.
53+
i + b (2)
54+
which we can get i with (1) as
55+
i = A - b/2 + 1 (3)
56+
Plugging (3) in to (2):
57+
i + b = A - b/2 + 1 + b = A + b/2 + 1
58+
We can use the shoelace area to find A, and b is just the loop length.
59+
"""
60+
b = loop_length(loop)
61+
A = polygon_area(loop)
62+
return int(A + b / 2 + 1)
63+
64+
65+
def main():
66+
trench_loop = find_trench_loop()
67+
area = cubic_meters_contained(trench_loop)
68+
print(area)
69+
70+
71+
if __name__ == "__main__":
72+
main()

0 commit comments

Comments
 (0)