Skip to content

Commit 1dd1c0f

Browse files
authored
Add 2023/22 py
1 parent fc2d5ac commit 1dd1c0f

File tree

10 files changed

+336
-1
lines changed

10 files changed

+336
-1
lines changed

2023/17/part1_naive.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def dir_count(pos, prev, dir):
9595
# heat_map[B].
9696
# https://en.wikipedia.org/wiki/Constrained_Shortest_Path_First
9797
def constrained_dijkstra(heat_map, rows, cols, pos_start, pos_end):
98-
que = PriorityQueue() # TODO make a heapq version for comparison
98+
que = PriorityQueue()
9999
dist = defaultdict(lambda: float('inf')) # pos -> dist
100100
prev = defaultdict(lambda: (None, None)) # pos -> (prev_pos, direction_prev_pos_to_pos)
101101
visited = set()

2023/22/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 22
2+
Here are my solutions to this puzzle.
3+
4+
* Problem instructions: [adventofcode.com/2023/day/22](https://adventofcode.com/2023/day/22)
5+
* Input: [adventofcode.com/2023/day/22/input](https://adventofcode.com/2023/day/22/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/22/input
10+
```
11+
12+
or run `fetch_input.sh` in this directory.

2023/22/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/22/input"
5+
test "$?" -eq 0 && echo "Fetched input" && exit 0 || echo "Failed to fetch input" && exit 1

2023/22/input1.0

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
1,0,1~1,2,1
2+
0,0,2~2,0,2
3+
0,2,3~2,2,3
4+
0,0,4~0,2,4
5+
2,0,5~2,2,5
6+
0,1,6~2,1,6
7+
1,1,8~1,1,9

2023/22/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/22

2023/22/output1.0

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

2023/22/output2.0

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

2023/22/part1.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
#!/usr/bin/env python3
2+
import fileinput
3+
from pprint import pprint
4+
from collections import defaultdict
5+
6+
SYM_EMPTY = "."
7+
SYM_HIDDEN = "?"
8+
9+
def read_bricks():
10+
bricks = []
11+
for i, line in enumerate(fileinput.input()):
12+
brick = tuple(list(map(int, p.split(","))) for p in line.rstrip("\n").split("~"))
13+
brick = [list(map(int, p.split(","))) for p in line.rstrip("\n").split("~")]
14+
brick.append(i)
15+
bricks.append(brick)
16+
return bricks
17+
18+
19+
def group_bricks_by_z(bricks):
20+
bricks_by_z = defaultdict(list)
21+
for brick in bricks:
22+
p1, p2, _id = brick
23+
z_min, z_max = min(p1[2], p2[2]), max(p1[2], p2[2])
24+
for z in range(z_min, z_max + 1):
25+
bricks_by_z[z].append(brick)
26+
return bricks_by_z
27+
28+
29+
def extract_coordinate(bricks, axis):
30+
az = [a for t in map(lambda b: (b[0][axis], b[1][axis]), bricks) for a in t]
31+
a_min, a_max = min(az), max(az)
32+
a_len = max(az) - min(az) + 1
33+
return az, a_min, a_max, a_len
34+
35+
36+
def id2name(id):
37+
return chr(id + ord('A'))
38+
39+
def brick_name(brick):
40+
return id2name(brick[2])
41+
42+
def print_bricks_side(bricks, axis):
43+
_ss, s_min, s_max, s_len = extract_coordinate(bricks, axis)
44+
_zs, z_min, z_max, z_len = extract_coordinate(bricks, 2)
45+
bricks_by_z = group_bricks_by_z(bricks)
46+
47+
# Print view S-axis
48+
print(" " * (s_len//2) + ("x" if axis == 0 else "y") + " " * (s_len//2))
49+
s_axis = []
50+
for s in range(s_min, s_max + 1):
51+
s_axis.append(str(s))
52+
print("".join(s_axis))
53+
54+
for z in range(z_max, z_min - 1, -1):
55+
line = []
56+
for s in range(s_min, s_max + 1):
57+
bricks_present = []
58+
for brick in bricks_by_z[z]:
59+
bs_min = min(brick[0][axis], brick[1][axis])
60+
bs_max = max(brick[0][axis], brick[1][axis])
61+
if bs_min <= s <= bs_max:
62+
bricks_present.append(brick[2])
63+
match len(bricks_present):
64+
case 0:
65+
line.append(SYM_EMPTY)
66+
case 1:
67+
line.append(id2name(bricks_present[0]))
68+
case _:
69+
line.append(SYM_HIDDEN)
70+
line.append(f" {z}")
71+
if z == z_len//2 + 1:
72+
line.append(f" z")
73+
print("".join(line))
74+
print("-" * s_len + " 0")
75+
76+
77+
def print_bricks(bricks):
78+
print_bricks_side(bricks, 0)
79+
print("")
80+
print_bricks_side(bricks, 1)
81+
82+
def overlaps_xy(brick_a, brick_b):
83+
(a_x1, a_y1, _a_z1), (a_x2, a_y2, _a_z2), _a_id = brick_a
84+
(b_x1, b_y1, _b_z1), (b_x2, b_y2, _b_z2), _b_id = brick_b
85+
86+
overlap_x = a_x1 <= b_x1 <= a_x2 or b_x1 <= a_x1 <= b_x2
87+
overlap_y = a_y1 <= b_y1 <= a_y2 or b_y1 <= a_y1 <= b_y2
88+
89+
return overlap_x and overlap_y
90+
91+
def fall(bricks_by_z, brick):
92+
(_x1, _y1, z1), (_x2, _y2, z2), _id = brick
93+
z_min = min(z1, z2)
94+
if z_min == 1:
95+
return False
96+
97+
for brick_below in bricks_by_z[z_min - 1]:
98+
if overlaps_xy(brick, brick_below):
99+
return False
100+
101+
brick[0][2] -= 1
102+
brick[1][2] -= 1
103+
return True
104+
105+
def settle_bricks(bricks):
106+
for brick in sorted(bricks, key=lambda b: min(b[0][2], b[1][2])):
107+
bricks_by_z = group_bricks_by_z(bricks)
108+
while fall(bricks_by_z, brick):
109+
pass
110+
111+
def calc_brick_supported_by(bricks, bricks_by_z):
112+
brick_supported_by = defaultdict(int) # brick_id -> int(nbr bricks it is supported by)
113+
for brick in bricks:
114+
(x1, y1, z1), (x2, y2, z2), id = brick
115+
z_min = min(z1, z2)
116+
for brick_below in bricks_by_z[z_min - 1]:
117+
if overlaps_xy(brick, brick_below):
118+
brick_supported_by[id] += 1
119+
return brick_supported_by
120+
121+
122+
def disintegration_safe(bricks):
123+
bricks_by_z = group_bricks_by_z(bricks)
124+
brick_supported_by = calc_brick_supported_by(bricks, bricks_by_z)
125+
126+
safe_bricks = []
127+
for brick in bricks:
128+
(x1, y1, z1), (x2, y2, z2), id = brick
129+
z_max = max(z1, z2)
130+
safe = True
131+
for brick_above in bricks_by_z[z_max + 1]:
132+
if overlaps_xy(brick, brick_above) and brick_supported_by[brick_above[2]] == 1:
133+
safe = False
134+
break
135+
if safe:
136+
safe_bricks.append(brick)
137+
return safe_bricks
138+
139+
140+
def main():
141+
bricks = read_bricks()
142+
# print_bricks(bricks)
143+
144+
settle_bricks(bricks)
145+
# print("\n==== After settling:")
146+
# print_bricks(bricks)
147+
148+
bricks_safe = disintegration_safe(bricks)
149+
print(len(bricks_safe))
150+
151+
if __name__ == '__main__':
152+
main()

2023/22/part2.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
#!/usr/bin/env python3
2+
import fileinput
3+
from pprint import pprint
4+
from collections import defaultdict
5+
from copy import deepcopy
6+
import heapq
7+
8+
SYM_EMPTY = "."
9+
SYM_HIDDEN = "?"
10+
11+
def read_bricks():
12+
bricks = []
13+
for i, line in enumerate(fileinput.input()):
14+
brick = tuple(list(map(int, p.split(","))) for p in line.rstrip("\n").split("~"))
15+
brick = [list(map(int, p.split(","))) for p in line.rstrip("\n").split("~")]
16+
brick.append(i)
17+
bricks.append(brick)
18+
return bricks
19+
20+
21+
def group_bricks_by_z(bricks):
22+
bricks_by_z = defaultdict(list)
23+
for brick in bricks:
24+
p1, p2, _id = brick
25+
z_min, z_max = min(p1[2], p2[2]), max(p1[2], p2[2])
26+
for z in range(z_min, z_max + 1):
27+
bricks_by_z[z].append(brick)
28+
return bricks_by_z
29+
30+
31+
def extract_coordinate(bricks, axis):
32+
az = [a for t in map(lambda b: (b[0][axis], b[1][axis]), bricks) for a in t]
33+
a_min, a_max = min(az), max(az)
34+
a_len = max(az) - min(az) + 1
35+
return az, a_min, a_max, a_len
36+
37+
38+
def id2name(id):
39+
return chr(id + ord('A'))
40+
41+
def brick_name(brick):
42+
return id2name(brick[2])
43+
44+
def print_bricks_side(bricks, axis):
45+
_ss, s_min, s_max, s_len = extract_coordinate(bricks, axis)
46+
_zs, z_min, z_max, z_len = extract_coordinate(bricks, 2)
47+
bricks_by_z = group_bricks_by_z(bricks)
48+
49+
# Print view S-axis
50+
print(" " * (s_len//2) + ("x" if axis == 0 else "y") + " " * (s_len//2))
51+
s_axis = []
52+
for s in range(s_min, s_max + 1):
53+
s_axis.append(str(s))
54+
print("".join(s_axis))
55+
56+
for z in range(z_max, z_min - 1, -1):
57+
line = []
58+
for s in range(s_min, s_max + 1):
59+
bricks_present = []
60+
for brick in bricks_by_z[z]:
61+
bs_min = min(brick[0][axis], brick[1][axis])
62+
bs_max = max(brick[0][axis], brick[1][axis])
63+
if bs_min <= s <= bs_max:
64+
bricks_present.append(brick[2])
65+
match len(bricks_present):
66+
case 0:
67+
line.append(SYM_EMPTY)
68+
case 1:
69+
line.append(id2name(bricks_present[0]))
70+
case _:
71+
line.append(SYM_HIDDEN)
72+
line.append(f" {z}")
73+
if z == z_len//2 + 1:
74+
line.append(f" z")
75+
print("".join(line))
76+
print("-" * s_len + " 0")
77+
78+
79+
def print_bricks(bricks):
80+
print_bricks_side(bricks, 0)
81+
print("")
82+
print_bricks_side(bricks, 1)
83+
84+
def overlaps_xy(brick_a, brick_b):
85+
(a_x1, a_y1, _a_z1), (a_x2, a_y2, _a_z2), _a_id = brick_a
86+
(b_x1, b_y1, _b_z1), (b_x2, b_y2, _b_z2), _b_id = brick_b
87+
88+
overlap_x = a_x1 <= b_x1 <= a_x2 or b_x1 <= a_x1 <= b_x2
89+
overlap_y = a_y1 <= b_y1 <= a_y2 or b_y1 <= a_y1 <= b_y2
90+
91+
return overlap_x and overlap_y
92+
93+
def fall(bricks_by_z, brick):
94+
(_x1, _y1, z1), (_x2, _y2, z2), _id = brick
95+
z_min = min(z1, z2)
96+
if z_min == 1:
97+
return False
98+
99+
for brick_below in bricks_by_z[z_min - 1]:
100+
if overlaps_xy(brick, brick_below):
101+
return False
102+
103+
brick[0][2] -= 1
104+
brick[1][2] -= 1
105+
return True
106+
107+
def settle_bricks(bricks):
108+
for brick in sorted(bricks, key=lambda b: min(b[0][2], b[1][2])):
109+
bricks_by_z = group_bricks_by_z(bricks)
110+
while fall(bricks_by_z, brick):
111+
pass
112+
113+
def brick_supported_by(brick, fallen_bricks, bricks_by_z):
114+
supported_by = 0
115+
(x1, y1, z1), (x2, y2, z2), id = brick
116+
z_min = min(z1, z2)
117+
for brick_below in bricks_by_z[z_min - 1]:
118+
if brick_below[2] not in fallen_bricks and overlaps_xy(brick, brick_below):
119+
supported_by += 1
120+
121+
return supported_by
122+
123+
def disintegration_falls(bricks_by_z, brick):
124+
b_z_min = min(brick[0][2], brick[1][2])
125+
fallen_bricks = set([brick[2]]) # brick IDs
126+
falling = [(b_z_min, brick)]
127+
while falling:
128+
falling_brick = heapq.heappop(falling)[1]
129+
(x1, y1, z1), (x2, y2, z2), id = falling_brick
130+
fb_z_max = max(z1, z2)
131+
132+
for brick_above in bricks_by_z[fb_z_max + 1]:
133+
if overlaps_xy(falling_brick, brick_above) and brick_supported_by(brick_above, fallen_bricks, bricks_by_z) == 0:
134+
if brick_above[2] not in fallen_bricks:
135+
fallen_bricks.add(brick_above[2])
136+
ba_z_min = min(brick_above[0][2], brick_above[1][2])
137+
heapq.heappush(falling, (ba_z_min, brick_above))
138+
139+
return len(fallen_bricks) - 1
140+
141+
142+
def main():
143+
bricks = read_bricks()
144+
settle_bricks(bricks)
145+
bricks_by_z = group_bricks_by_z(bricks)
146+
147+
falls = 0
148+
for brick in bricks:
149+
falls += disintegration_falls(bricks_by_z, brick)
150+
print(falls)
151+
152+
if __name__ == '__main__':
153+
main()

tricks.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,8 @@ to get side-by-side comparison between expected (column 1) and actual (column 2)
199199

200200
## Misc
201201
* It's a good idea during development to output the state of the program in the same format as the puzzle description. This could be for example single lines of state, or to print every snapshot of the progress of some algorithm on a grid system. Having the same format as the puzzle description makes comparision with given examples easy!
202+
* Examples: [2023/22](2023/22/part1.py), [2023/17](2023/17/part1.py)
203+
202204
* Constraint solver
203205
* Is the problem too hard to solve? Let someone else do it: [Z3](https://github.com/Z3Prover/Z3).
204206
* Examples: [2018/10](2018/23/part2.rb)

0 commit comments

Comments
 (0)