#!/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))