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
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)