Skip to content

Commit 34b4885

Browse files
committed
feat: 2023 days 21 and 22
1 parent 9e7a269 commit 34b4885

File tree

14 files changed

+2206
-83
lines changed

14 files changed

+2206
-83
lines changed

2023/21.txt

Lines changed: 131 additions & 0 deletions
Large diffs are not rendered by default.

2023/21_expa.txt

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

2023/21_expb.txt

Whitespace-only changes.

2023/21_test.txt

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

2023/21a.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from aoc import *
2+
from functools import cache
3+
4+
5+
S = [(-1, 0), (1, 0), (0, -1), (0, 1)]
6+
7+
8+
def main(input_file: str):
9+
m = load_map_dd(input_file)
10+
11+
@cache
12+
def step(x, y):
13+
return {(x + dx, y + dy) for dx, dy in S if m[(x + dx, y + dy)] in ['.', 'S']}
14+
15+
places = {(x, y) for (x, y), c in m.items() if c == 'S'}
16+
for i in range(64):
17+
new_places = set()
18+
for x, y in places:
19+
new_places.update(step(x, y))
20+
places = new_places
21+
22+
# maxx = max(x for x, y in m)
23+
# maxy = max(y for x, y in m)
24+
# for ri in range(maxx + 1):
25+
# for ci in range(maxy + 1):
26+
# if (ri, ci) in places:
27+
# print('O', end='')
28+
# else:
29+
# print(m[(ri, ci)], end='')
30+
# print()
31+
32+
return len(places)
33+
34+
DAY = 21
35+
FILE = f"{DAY}.txt"
36+
print(main(FILE))

2023/21b.py

Lines changed: 216 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,236 @@
11
from aoc import *
2-
from statistics import median, mode
3-
from more_itertools import windowed, take, peekable, chunked, first_true, split_when, first_true
4-
from heapq import *
5-
# from functools import reduce, cache, cmp_to_key
62
from functools import cache
7-
from math import ceil
8-
import re
9-
from blessed import BlessedList
10-
from copy import deepcopy
11-
import operator
12-
from string import ascii_letters, ascii_lowercase, ascii_uppercase, digits, hexdigits, whitespace
13-
from pprint import pprint as pp
14-
import bisect
15-
import math
16-
from itertools import pairwise, combinations
17-
import sys
18-
from collections import defaultdict, OrderedDict, deque, Counter
19-
# from ordered_set import OrderedSet
3+
from collections import Counter
4+
205

216
S = [(-1, 0), (1, 0), (0, -1), (0, 1)]
227

238

249
def main(input_file: str):
2510
m = load_map_dd(input_file)
2611
maxx = max(x for x, y in m)
27-
sizex = maxx + 1
28-
sizey = maxy + 1
12+
sizex = maxx + 1 # 131
2913
maxy = max(y for x, y in m)
30-
# pp(m)
14+
sizey = maxy + 1 # 131
15+
3116
@cache
3217
def step(x, y):
33-
return {((x + dx),
34-
(y + dy)) for dx, dy in S
35-
if m[((x + dx) % (maxx + 1),
36-
(y + dy) % (maxy + 1))] in ['.', 'S']}
18+
return {
19+
((x + dx), (y + dy))
20+
for dx, dy in S
21+
if m[((x + dx) % (maxx + 1), (y + dy) % (maxy + 1))] in ['.', 'S']
22+
}
3723

3824
places = {(x, y) for (x, y), c in m.items() if c == 'S'}
3925
c = Counter()
4026
c[next(iter(places))] = 1
27+
start = next(iter(places))
28+
m[start] = '.'
4129
steps = 26501365
42-
size = steps * 2 + 1
4330
suma = 0
44-
for i in range(size):
45-
if i <= size // 2:
46-
# we are above or in the middle of the rhombus, the width is
47-
# increasing
48-
row = i % sizex
49-
width = i * 2 + 1
50-
if width <= sizey:
51-
# take inner size of the rhomboid
52-
for col in range(sizey // 2 - i, width, 2):
53-
if m[i, col] == '.':
54-
suma += 1
55-
else:
56-
# take outer size of the rhomboid
57-
full_widths = width // sizey
58-
remainder = width % sizey
59-
if remainder <= sizey // 2:
60-
# Count the edges
61-
# XXX.........XXX
62-
pass
63-
64-
else:
65-
# Count the inside
66-
# XXXXXX...
67-
# ...XXXXXX
68-
# ^^^......counting just these
69-
pass
70-
pass
71-
else:
72-
# we are below rhombus, the width is decreasing
73-
pass
74-
for i in range(50):
75-
new_places = set()
76-
new_counter = Counter()
77-
for x, y in places:
78-
# steps_number = c[x, y]
79-
# c[x, y] = 0
80-
new_steps = step(x, y)
81-
new_places.update(new_steps)
82-
# for nx, ny in new_steps:
83-
# new_counter[nx, ny] = steps_number
84-
places = new_places
85-
c = new_counter
86-
# pp(m)
87-
# for ri in range(maxx + 1):
88-
# for ci in range(maxy + 1):
89-
# if (ri, ci) in places:
90-
# print('O', end='')
91-
# else:
92-
# print(m[(ri, ci)], end='')
93-
# print()
94-
# pp(c)
95-
return sum(c.values())
31+
32+
# The best solution is finding the quadratic recurence. Since I can't find
33+
# it on my own, I have painstakingly split the whole farm into 14 types of
34+
# squares (each square represents visited places in one map):
35+
#
36+
# ^ - UP pentagon
37+
# V - DOWN pentagon
38+
# < - LEFT pentagon
39+
# > - RIGHT pentagon
40+
# / - RIGHT-DOWN pentagon
41+
# \ - LEFT-DOWN pentagon
42+
# ( - LEFT-UP pentagon
43+
# ) - RIGHT-UP pentagon
44+
# , - RIGHT-DOWN triangle
45+
# . - LEFT-DOWN triangle
46+
# ` - RIGHT-UP triangle
47+
# ' - LEFT-UP triangle
48+
# e - even parity square
49+
# o - odd parity square
50+
# S - starting square (the same as even parity square)
51+
#
52+
# ,^.
53+
# ,/e\.
54+
# ,/eoe\.
55+
# ,/eoeoe\.
56+
# ,/eoeoeoe\.
57+
# <eoeoSoeoe>
58+
# `(eoeoeoe)'
59+
# `(eoeoe)'
60+
# `(eoe)'
61+
# `(e)'
62+
# `V'
63+
#
64+
# Pentagons have odd parity an triangles have even parity.
65+
#
66+
# Parity can be computed by adding two coordinates together and computing
67+
# modulo two. (0, 0) has even parity, (1, 2) has odd parity.
68+
#
69+
# The input is constructed so that the walking stops right at the edges of
70+
# squares (0, 65) for the up pentagon, (130, 65) for the down pentagon, (65,
71+
# 0) for the left pentagon, (65, 130) for the right pentagon. It's because
72+
# the middle row and middle columns are garden plots. There's also garden
73+
# plots inside the square creating a 45-degree rotated square of garden
74+
# plots. Since there are no rocks, this creates a perfect sharp line of
75+
# visited garden plots in pentagons and triangles, not requiring to manually
76+
# do flood fill or search.
77+
#
78+
# Thus the problem reduces to just counting the number of each square in the
79+
# whole farm and then counting the garden plots with the correct parity.
80+
#
81+
# And one special thing with this approach -- there are some garden plots
82+
# not visitable by moving in four directions. I have manually detected these
83+
# using my eyes and a bucket tool in Gimp. The adjusted input file is
84+
# attached in the repository.
85+
86+
# Compute all types of squares
87+
up_pentagon = 0
88+
for i in range(sizex // 2):
89+
for col in range(sizey // 2 - i, sizey // 2 + i + 1):
90+
if (i + col) % 2 and m[i, col] == '.':
91+
up_pentagon += 1
92+
for i in range(sizex // 2, sizex):
93+
for col in range(sizey):
94+
if (i + col) % 2 and m[i, col] == '.':
95+
up_pentagon += 1
96+
97+
down_pentagon = 0
98+
for i in range(sizex // 2):
99+
for col in range(sizey // 2 - i, sizey // 2 + i + 1):
100+
if (maxx - i + col) % 2 and m[maxx - i, col] == '.':
101+
down_pentagon += 1
102+
for i in range(sizex // 2, sizex):
103+
for col in range(sizey):
104+
if (maxx - i + col) % 2 and m[maxx - i, col] == '.':
105+
down_pentagon += 1
106+
107+
right_pentagon = 0
108+
for i in range(sizex // 2):
109+
for col in range(sizey // 2 - i, sizey // 2 + i + 1):
110+
if (maxx - i + col) % 2 and m[col, maxx - i] == '.':
111+
right_pentagon += 1
112+
for i in range(sizex // 2, sizex):
113+
for col in range(sizey):
114+
if (maxx - i + col) % 2 and m[col, maxx - i] == '.':
115+
right_pentagon += 1
116+
117+
left_pentagon = 0
118+
for i in range(sizex // 2):
119+
for col in range(sizey // 2 - i, sizey // 2 + i + 1):
120+
if (i + col) % 2 and m[col, i] == '.':
121+
left_pentagon += 1
122+
for i in range(sizex // 2, sizex):
123+
for col in range(sizey):
124+
if (i + col) % 2 and m[col, i] == '.':
125+
left_pentagon += 1
126+
127+
right_down_pentagon = 0
128+
for i in range(sizex // 2):
129+
for col in range(sizey // 2 - i, sizex):
130+
if (i + col) % 2 and m[i, col] == '.':
131+
right_down_pentagon += 1
132+
for i in range(sizex // 2, sizex):
133+
for col in range(sizey):
134+
if (i + col) % 2 and m[i, col] == '.':
135+
right_down_pentagon += 1
136+
137+
left_down_pentagon = 0
138+
for i in range(sizex // 2):
139+
for col in range(sizey // 2 + i + 1):
140+
if (i + col) % 2 and m[i, col] == '.':
141+
left_down_pentagon += 1
142+
for i in range(sizex // 2, sizex):
143+
for col in range(sizey):
144+
if (i + col) % 2 and m[i, col] == '.':
145+
left_down_pentagon += 1
146+
147+
right_up_pentagon = 0
148+
for i in range(sizex // 2):
149+
for col in range(sizey // 2 - i, sizex):
150+
if (maxx - i + col) % 2 and m[maxx - i, col] == '.':
151+
right_up_pentagon += 1
152+
for i in range(sizex // 2, sizex):
153+
for col in range(sizey):
154+
if (maxx - i + col) % 2 and m[maxx - i, col] == '.':
155+
right_up_pentagon += 1
156+
157+
left_up_pentagon = 0
158+
for i in range(sizex // 2):
159+
for col in range(sizey // 2 + i + 1):
160+
if (maxx - i + col) % 2 and m[maxx - i, col] == '.':
161+
left_up_pentagon += 1
162+
for i in range(sizex // 2, sizex):
163+
for col in range(sizey):
164+
if (maxx - i + col) % 2 and m[maxx - i, col] == '.':
165+
left_up_pentagon += 1
166+
167+
left_up_triangle = 0
168+
for i in range(sizex // 2):
169+
for col in range(sizey // 2 - i):
170+
# The last one should be (64, 0)
171+
if (i + col) % 2 == 0 and m[i, col] == '.':
172+
left_up_triangle += 1
173+
174+
left_down_triangle = 0
175+
for i in range(sizex // 2):
176+
for col in range(sizey // 2 - i):
177+
if (maxx - i + col) % 2 == 0 and m[maxx - i, col] == '.':
178+
left_down_triangle += 1
179+
180+
right_up_triangle = 0
181+
for i in range(sizex // 2):
182+
for col in range(sizey // 2 + 1 + i, sizey):
183+
# The last one should be (64, 0)
184+
if (i + col) % 2 == 0 and m[i, col] == '.':
185+
right_up_triangle += 1
186+
187+
right_down_triangle = 0
188+
for i in range(sizex // 2):
189+
for col in range(sizey // 2 + 1 + i, sizey):
190+
if (maxx - i + col) % 2 == 0 and m[maxx - i, col] == '.':
191+
right_down_triangle += 1
192+
193+
even_square = 0
194+
for i in range(sizex):
195+
for col in range(sizey):
196+
if (i + col) % 2 == 0 and m[i, col] == '.':
197+
even_square += 1
198+
199+
odd_square = 0
200+
for i in range(sizex):
201+
for col in range(sizey):
202+
if (i + col) % 2 and m[i, col] == '.':
203+
odd_square += 1
204+
205+
steps = (steps - sizex // 2) // sizex
206+
suma += (
207+
up_pentagon
208+
+ down_pentagon
209+
+ left_pentagon
210+
+ right_pentagon
211+
+ (steps - 1)
212+
* (
213+
right_down_pentagon
214+
+ right_up_pentagon
215+
+ left_down_pentagon
216+
+ left_up_pentagon
217+
+ right_down_triangle
218+
+ right_up_triangle
219+
+ left_down_triangle
220+
+ left_up_triangle
221+
)
222+
+ right_down_triangle
223+
+ right_up_triangle
224+
+ left_down_triangle
225+
+ left_up_triangle
226+
+ steps * (steps + 1) // 2 * even_square
227+
+ steps * (steps - 1) // 2 * odd_square
228+
+ steps * (steps - 1) // 2 * even_square
229+
+ (steps - 1) * (steps - 2) // 2 * odd_square
230+
)
231+
return suma
232+
96233

97234
DAY = 21
98-
FILE_TEST = f"{DAY}_test.txt"
99-
FILE_EXP = f"{DAY}_expa.txt"
100-
FILE = f"{DAY}.txt"
101-
# test_and_submit(main, FILE_TEST, FILE_EXP, FILE, DAY)
102-
print(main(FILE_TEST))
103-
# print(main(FILE))
235+
FILE = f"{DAY}mod.txt"
236+
print(main(FILE))

0 commit comments

Comments
 (0)