adventofcode/2018/day23/day23.py

82 lines
3.1 KiB
Python

#!/usr/bin/python
from collections import OrderedDict, defaultdict, namedtuple
from functools import total_ordering
from heapq import heappop, heappush
from itertools import chain
import re
Bot = namedtuple('Bot', 'x y z r')
with open('day23.txt') as puzzle_file:
pattern = re.compile(r'pos=<(-?\d+),(-?\d+),(-?\d+)>, r=(\d+)')
bots = [Bot(*map(int, pattern.match(line).groups())) for line in puzzle_file]
max_r = max(bot.r for bot in bots)
print(
max(
sum(abs(b.x - a.x) + abs(b.y - a.y) + abs(b.z - a.z) <= a.r for b in bots) for a in bots
if a.r == max_r))
@total_ordering
class Octa_ordering(object):
def __lt__(self, other):
return self.min < other.min or self.min == other.min and other.max < self.max
class Octa(Octa_ordering, namedtuple('Octa', ('min', 'max'))):
def __new__(cls, *args, **kwargs):
if 'bot' in kwargs:
x, y, z, r = kwargs['bot']
t, u, v, w = x + y + z, x + y - z, x - y - z, x - y + z
return super(Octa, cls).__new__(cls, (t - r, u - r, v - r, w - r),
(t + r, u + r, v + r, w + r))
return super(Octa, cls).__new__(cls, *args, **kwargs)
def intersect(self, other):
c, d, e, f = self.min
g, h, i, j = other.min
k, l, m, n = self.max
o, p, q, r = other.max
s, t, u, v = max(c, g), max(d, h), max(e, i), max(f, j)
w, x, y, z = min(k, o), min(l, p), min(m, q), min(n, r)
return None if s > w or t > x or u > y or v > z else Octa((s, t, u, v), (w, x, y, z))
def distance_to_origin(self):
o, p, q, r = self.min
s, t, u, v = self.max
if o < s and p < t and q < u and r < v:
w = min(abs(o), abs(s)) if o * s >= 0 else 0
x = min(abs(p), abs(t)) if p * t >= 0 else 0
y = min(abs(q), abs(u)) if q * u >= 0 else 0
z = min(abs(r), abs(v)) if r * v >= 0 else 0
return max(w, x, y, z)
return min(
abs((x + z) // 2) + abs((y - z) // 2) + abs((x - y) // 2)
for x in range(o, s + 1) for y in range(p + ((p ^ x) & 1), t + 1, 2)
for z in range(q + ((q ^ x) & 1), u + 1, 2) if r <= x - y + z <= v)
best_count = 0
octs = defaultdict(set)
for i, bot in enumerate(bots):
octs[Octa(bot=bot)].add(i)
queue = [(0, (), OrderedDict((k, octs[k]) for k in sorted(octs)))]
while queue:
n, _, rest = heappop(queue)
if -n < best_count:
break
octa, n = rest.popitem()
sub = defaultdict(set)
for octa2, m in rest.items():
octa3 = octa.intersect(octa2)
if octa3 is not None:
(n if octa == octa3 else sub[octa3]).update(m)
if len(n) > best_count:
best_count, best_distance = len(n), [octa]
elif len(n) == best_count:
best_distance.append(octa)
m = frozenset(chain.from_iterable(rest.values()))
heappush(queue, (-len(m), m, rest))
rest = OrderedDict((k, sub[k].union(n)) for k in sorted(sub))
m = frozenset(chain.from_iterable(rest.values()))
heappush(queue, (-len(m), m, rest))
print(min(octa.distance_to_origin() for octa in best_distance))