Skip to content

Commit eff324a

Browse files
committed
Added day 2018-23
1 parent c21d7eb commit eff324a

File tree

1 file changed

+221
-0
lines changed

1 file changed

+221
-0
lines changed
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
# -------------------------------- Input data ---------------------------------------- #
2+
import os, heapq
3+
4+
test_data = {}
5+
6+
test = 1
7+
test_data[test] = {
8+
"input": """pos=<0,0,0>, r=4
9+
pos=<1,0,0>, r=1
10+
pos=<4,0,0>, r=3
11+
pos=<0,2,0>, r=1
12+
pos=<0,5,0>, r=3
13+
pos=<0,0,3>, r=1
14+
pos=<1,1,1>, r=1
15+
pos=<1,1,2>, r=1
16+
pos=<1,3,1>, r=1""",
17+
"expected": ["7", "Unknown"],
18+
}
19+
test += 1
20+
test_data[test] = {
21+
"input": """pos=<10,12,12>, r=2
22+
pos=<12,14,12>, r=2
23+
pos=<16,12,12>, r=4
24+
pos=<14,14,14>, r=6
25+
pos=<50,50,50>, r=200
26+
pos=<10,10,10>, r=5""",
27+
"expected": ["Unknown", "Position 12, 12, 12 => 36"],
28+
}
29+
test += 1
30+
test_data[test] = {
31+
"input": """pos=<20,0,0>, r=15
32+
pos=<0,0,0>, r=6""",
33+
"expected": ["Unknown", "5"],
34+
}
35+
36+
test = "real"
37+
input_file = os.path.join(
38+
os.path.dirname(__file__),
39+
"Inputs",
40+
os.path.basename(__file__).replace(".py", ".txt"),
41+
)
42+
test_data[test] = {
43+
"input": open(input_file, "r+").read().strip(),
44+
"expected": ["761", "89915526"],
45+
}
46+
47+
# -------------------------------- Control program execution ------------------------- #
48+
49+
case_to_test = "real"
50+
part_to_test = 2
51+
52+
# -------------------------------- Initialize some variables ------------------------- #
53+
54+
puzzle_input = test_data[case_to_test]["input"]
55+
puzzle_expected_result = test_data[case_to_test]["expected"][part_to_test - 1]
56+
puzzle_actual_result = "Unknown"
57+
58+
59+
# -------------------------------- Various functions ----------------------------- #
60+
61+
62+
def manhattan_distance(source, target):
63+
dist = 0
64+
for i in range(len(source)):
65+
dist += abs(target[i] - source[i])
66+
return dist
67+
68+
69+
def in_range_cube(corners):
70+
nb = 0
71+
for bot in bots:
72+
xb, yb, zb = bot
73+
radius = bots[bot]
74+
75+
# bot is outside the cube extended by radius in a cubic manner
76+
# said differently: bot is outside cube of size initial_size+radius*2
77+
if xb < corners[0][0] - radius or xb > corners[1][0] + radius:
78+
continue
79+
if yb < corners[0][1] - radius or yb > corners[1][1] + radius:
80+
continue
81+
if zb < corners[0][2] - radius or zb > corners[1][2] + radius:
82+
continue
83+
84+
# bot is inside the cube
85+
if xb >= corners[0][0] and xb <= corners[1][0]:
86+
if yb >= corners[0][1] and yb <= corners[1][1]:
87+
if zb >= corners[0][2] and zb <= corners[1][2]:
88+
nb += 1
89+
continue
90+
91+
# bot is too far from the cube's center
92+
cube_size = (
93+
corners[1][0] - corners[0][0] + 4
94+
) # 4 added for margin of error & rounding
95+
center = [(corners[0][i] + corners[1][i]) // 2 for i in (0, 1, 2)]
96+
# The center is at cube_size // 2 * 3 distance from each corner
97+
max_distance = cube_size // 2 * 3 + radius
98+
if manhattan_distance(center, bot) <= max_distance:
99+
nb += 1
100+
101+
return nb
102+
103+
104+
def all_corners(cube):
105+
coords = list(zip(*cube))
106+
corners = [[x, y, z] for x in coords[0] for y in coords[1] for z in coords[2]]
107+
return corners
108+
109+
110+
def in_range_spot(spot):
111+
nb = 0
112+
for bot in bots:
113+
if manhattan_distance(spot, bot) <= bots[bot]:
114+
nb += 1
115+
116+
return nb
117+
118+
119+
def add_each(a, b):
120+
cpy = a.copy()
121+
for i in range(len(cpy)):
122+
cpy[i] += b[i]
123+
return cpy
124+
125+
126+
# -------------------------------- Actual code execution ----------------------------- #
127+
128+
129+
bots = {}
130+
for string in puzzle_input.split("\n"):
131+
if string == "":
132+
continue
133+
pos, rad = string.split(", ")
134+
pos = tuple(map(int, pos[5:-1].split(",")))
135+
bots[pos] = int(rad[2:])
136+
137+
max_strength = max(bots.values())
138+
max_strength_bots = [x for x in bots if bots[x] == max(bots.values())]
139+
140+
141+
if part_to_test == 1:
142+
in_range = {}
143+
for bot in max_strength_bots:
144+
in_range[bot] = 0
145+
for target in bots:
146+
if manhattan_distance(bot, target) <= max_strength:
147+
in_range[bot] += 1
148+
puzzle_actual_result = max(in_range.values())
149+
150+
else:
151+
x, y, z = zip(*bots)
152+
corners = [[min(x), min(y), min(z)], [max(x), max(y), max(z)]]
153+
cube_size = max(max(x) - min(x), max(y) - min(y), max(z) - min(z))
154+
count_bots = in_range_cube(corners)
155+
156+
cubes = [(-count_bots, cube_size, corners)]
157+
heapq.heapify(cubes)
158+
159+
all_cubes = [(count_bots, cube_size, corners)]
160+
161+
# First, octree algorithm: the best candidates are split in 8 and analyzed
162+
min_bots = 1
163+
best_dot = [10 ** 9, 10 ** 9, 10 ** 9]
164+
while cubes:
165+
nb, cube_size, cube = heapq.heappop(cubes)
166+
167+
if -nb < min_bots:
168+
# Not enough bots in range
169+
continue
170+
if -nb == min_bots:
171+
if manhattan_distance((0, 0, 0), cube[0]) > sum(map(abs, best_dot)):
172+
# Cube is too far away from source
173+
continue
174+
175+
# print (-nb, len(cubes), min_bots, cube_size, cube, best_dot, sum(map(abs, best_dot)))
176+
177+
# Analyze all corners in all cases, it helps reduce the volume in the end
178+
corners = all_corners(cube)
179+
for dot in corners:
180+
nb_spot = in_range_spot(dot)
181+
if nb_spot > min_bots:
182+
min_bots = nb_spot
183+
best_dot = dot
184+
print("Min bots updated to ", nb_spot, "for dot", dot)
185+
elif nb_spot == min_bots:
186+
if manhattan_distance((0, 0, 0), best_dot) > manhattan_distance(
187+
(0, 0, 0), dot
188+
):
189+
best_dot = dot
190+
print("Best dot set to ", dot)
191+
192+
if cube_size == 1:
193+
# We can't divide it any further
194+
continue
195+
196+
cube_size = (cube_size // 2) if cube_size % 2 == 0 else (cube_size // 2 + 1)
197+
198+
new_cubes = [
199+
[
200+
add_each(cube[0], [x, y, z]),
201+
add_each(cube[0], [x + cube_size, y + cube_size, z + cube_size]),
202+
]
203+
for x in (0, cube_size)
204+
for y in (0, cube_size)
205+
for z in (0, cube_size)
206+
]
207+
208+
for new_cube in new_cubes:
209+
count_bots = in_range_cube(new_cube)
210+
if count_bots >= min_bots:
211+
heapq.heappush(cubes, (-count_bots, cube_size, new_cube))
212+
all_cubes.append((count_bots, cube_size, new_cube))
213+
214+
print("max power", min_bots)
215+
puzzle_actual_result = manhattan_distance((0, 0, 0), best_dot)
216+
217+
218+
# -------------------------------- Outputs / results --------------------------------- #
219+
220+
print("Expected result : " + str(puzzle_expected_result))
221+
print("Actual result : " + str(puzzle_actual_result))

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy