def puzzle2(data): # The input data is a dense L1-sphere with a bunch of outliers # It's much easier to process if these outliers are gone, so we identify them here # For each nanobot, count the number of other nanobots whose range overlaps with ours counts = np.array([0 for _ in range(data.shape[0])]) for i, node in enumerate(data): x1, y1, z1, r1 = node # Don't compare against ourself for x2, y2, z2, r2 in np.append(data[:i], data[i + 1:]): dist = manhattan((x1, y1, z1), (x2, y2, z2)) totalRadius = r1 + r2 if dist <= totalRadius: counts[i] += 1 # Remove the outliers, and just leave the core L1-sphere of nanobots # 0.95 is a magic number, but for this input overlapping with 95% of other bots is a good test # Ideally this would be improved. newData = np.delete(data, np.argwhere(counts < data.shape[0] * 0.95).flatten()) # Use 3 orthogonal pairs of planes to constrain the single overlap point into a cube # Each plane pair takes the form (minimum value, maximum value) planes = [[None for _ in range(2)] for _ in range(3)] for x, y, z, r in newData: for i, v in enumerate([(1, 1, -1), (1, -1, 1), (-1, 1, 1)]): modX, modY, modZ = v mid = modX * x + modY * y + modZ * z low = mid - r high = mid + r if planes[i][0] is None: planes[i][0] = low else: planes[i][0] = max(low, planes[i][0]) if planes[i][1] is None: planes[i][1] = high else: planes[i][1] = min(high, planes[i][1]) # The three planes we used give us: # a <= x + y - z <= A # b <= x - y + z <= B # c <= -x + y + z <= C # Which we can resolve to: # a + b <= 2x <= A + B # a + c <= 2y <= A + C # b + c <= 2z <= B + C # Since we know we are only looking for one point, these must all have only one value, so we get: x = int((planes[0][0] + planes[1][0]) / 2) # x = (a + b) / 2 y = int((planes[0][0] + planes[2][0]) / 2) # y = (a + c) / 2 z = int((planes[1][0] + planes[2][0]) / 2) # z = (b + c) / 2 print('Answer: {}'.format(manhattan((0, 0, 0), (x, y, z))))
def puzzle1(data): constellations = np.array([-1 for _ in range(len(data))]) nextConst = 0 for i, star in enumerate(data): nearby = [] # No need to check stars we've previously done for j, otherStar in enumerate(data[i:]): if manhattan(star, otherStar) <= 3: # Because j starts from 0, add the "start" index nearby.append(i + j) if len(nearby) > 0: constToModify = np.unique(constellations[nearby]) for j in nearby: constellations[j] = nextConst for j, const in enumerate(constellations): if const != -1 and const in constToModify: constellations[j] = nextConst nextConst += 1 print('Answer: {}'.format(np.unique(constellations).shape[0]))
def test_manhattan(): a = [5, -4] b = [2, 3] d = manhattan(a, b) expected = 10 assert (expected == d)
def test_manhattan_3d(): a = [5, -4, 7] b = [2, 3, 11] d = manhattan(a, b) expected = 14 assert (expected == d)
def test_manhattan_same(): a = [5, -4] b = [5, -4] d = manhattan(a, b) expected = 0 assert (expected == d)
def test_manhattan_flipped(): b = [5, -4] a = [2, 3] d = manhattan(a, b) expected = 10 assert (expected == d)
def puzzle1(data): # Get the strongest nanobot tx, ty, tz, tr = np.sort(data, order=['r'])[-1] # Count the number of nanobots in its range count = 0 for x, y, z, _ in data: if manhattan((tx, ty, tz), (x, y, z)) <= tr: count += 1 print('Answer: {}'.format(count))
def move(self, xmod, ymod, zmod): self.me_obj.loc_x += xmod self.me_obj.loc_y += ymod self.me_obj.loc_z += zmod self.me_obj.set_modified() from helpers import manhattan ourx, oury = self.me_obj.loc_x, self.me_obj.loc_y for o in Object.get_objects(physical=True): # Is the distance between that object and the character less than 3? if manhattan(o.loc_x, o.loc_y, ourx, oury) < 1: # We collided against something, so return now and don't # save the location changes into the database. return False else: # We didn't collide with any objects. self.me_obj.save() return True
def solve(lines): wayy = 1 wayx = 10 y = 0 x = 0 for instruction in lines: command = instruction[0] val = int(instruction[1:]) if command == 'N': wayy += val if command == 'S': wayy -= val if command == 'E': wayx += val if command == 'W': wayx -= val if command == 'L': if val == 90: wayy, wayx = wayx, wayy * -1 if val == 180: wayy, wayx = wayy * -1, wayx * -1 if val == 270: wayy, wayx = wayx * -1, wayy if command == 'R': if val == 90: wayy, wayx = wayx * -1, wayy if val == 180: wayy, wayx = wayy * -1, wayx * -1 if val == 270: wayy, wayx = wayx, wayy * -1 if command == 'F': y += val * wayy x += val * wayx return manhattan((y, x), (0, 0))
def solve(lines): facing = [0, 1] y = 0 x = 0 for instruction in lines: command = instruction[0] val = int(instruction[1:]) if command == 'N': y += val if command == 'S': y -= val if command == 'E': x += val if command == 'W': x -= val if command == 'L': if val == 90: facing = rotate(facing, -1) if val == 180: facing = rotate(facing, -2) if val == 270: facing = rotate(facing, -3) if command == 'R': if val == 90: facing = rotate(facing, 1) if val == 180: facing = rotate(facing, 2) if val == 270: facing = rotate(facing, 3) if command == 'F': y += val * facing[0] x += val * facing[1] return manhattan((y, x), (0, 0))
dd = 'ESWN' d = 0 x = y = 0 for l in lines: op = l[0] offs = int(l[1:]) if op in dd: x, y = helpers.add_delta((x, y), helpers.COMPASS[op], offs) if l[0] in 'R': d = (d + offs // 90) % 4 if l[0] in 'L': d = (d - offs // 90) % 4 if l[0] in 'F': x, y = helpers.add_delta((x, y), helpers.COMPASS[dd[d]], offs) print("Puzzle 12.1: ", helpers.manhattan((0, 0), (x, y))) x = y = 0 wp = 10, 1 for l in lines: dd = l[0] ofs = int(l[1:]) if dd in 'NSWE': wp = helpers.add_delta(wp, helpers.COMPASS_INV[dd], ofs) if dd in 'RL': wp = rotate(wp, l) if dd in 'F': x, y = helpers.add_delta((x, y), wp, ofs) print("Puzzle 12.2: ", helpers.manhattan((0, 0), (x, y)))
def puzzle2(grid, depth, target): tx, ty = target maxX = tx + 10 maxY = ty + 10 grid = expandGrid(grid, depth, tx, ty, maxX, maxY) minTravelTime = np.array([[[0 for _ in range(3)] for _ in range(maxX + 1)] for _ in range(maxY + 1)], dtype=np.uint16) # Using a priority queue means a .get() will always pop off the node with the lowest f(n) openSet = PriorityQueue() # (f(n), x, y, tool); 0 = neither, 1 = torch, 2 = climbing gear # This mapping of equipment means that grid[y, x] % 3 can't be used at (x, y) openSet.put((tx + ty, 0, 0, 1)) closedSet = set() while not openSet.empty(): node = openSet.get() fn, x, y, curEquipped = node if x == tx and y == ty and curEquipped == 1: while not openSet.empty(): # Empty the queue, we're done openSet.get() else: closedSet.add((x, y, curEquipped)) for dx, dy in [(0, -1), (0, 1), (-1, 0), (1, 0)]: newX = x + dx newY = y + dy curTime = minTravelTime[y, x, curEquipped] if newX >= 0 and newY >= 0 and (newX, newY, curEquipped) not in closedSet: # Check if we want to leave the current calculated grid, if so then expand it a bit more if newX > maxX or newY > maxY: maxX += 10 maxY += 10 grid = expandGrid(grid, depth, tx, ty, maxX, maxY) newMTT = np.array([[[0 for _ in range(3)] for _ in range(maxX + 1)] for _ in range(maxY + 1)], dtype=np.uint16) newMTT[:maxY - 9, :maxX - 9, :] = minTravelTime[:, :, :] minTravelTime = newMTT thisType = grid[y, x] % 3 otherType = grid[newY, newX] % 3 newTime = curTime + 1 newEquipped = curEquipped # If they're the same type, we can just move there # If they're different, check whether our tool is valid there # If it is we can move, if not we'll have to swap if not (thisType == otherType or otherType != curEquipped): newTime += 7 newEquipped = ((thisType + otherType) * 2) % 3 # We must always switch to the torch at the target, so force this if newX == tx and newY == ty and newEquipped != 1: newTime += 7 newEquipped = 1 # A value of 0 means this node hasn't been visited yet if minTravelTime[newY, newX, newEquipped] == 0 or newTime < minTravelTime[newY, newX, newEquipped]: minTravelTime[newY, newX, newEquipped] = newTime # Our f(n) heuristic is g(n) + manhattan distance, since this is an admissable heuristic for this problem openSet.put((newTime + manhattan((newX, newY), (tx, ty)), newX, newY, newEquipped)) # The target is always reached with the torch in hand print('Answer: {}'.format(minTravelTime[ty, tx, 1]))