Example #1
0
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))))
Example #2
0
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]))
Example #3
0
def test_manhattan():
    a = [5, -4]
    b = [2, 3]

    d = manhattan(a, b)
    expected = 10

    assert (expected == d)
Example #4
0
def test_manhattan_3d():
    a = [5, -4, 7]
    b = [2, 3, 11]

    d = manhattan(a, b)
    expected = 14

    assert (expected == d)
Example #5
0
def test_manhattan_same():
    a = [5, -4]
    b = [5, -4]

    d = manhattan(a, b)
    expected = 0

    assert (expected == d)
Example #6
0
def test_manhattan_flipped():
    b = [5, -4]
    a = [2, 3]

    d = manhattan(a, b)
    expected = 10

    assert (expected == d)
Example #7
0
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))
Example #8
0
    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
Example #9
0
    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
Example #10
0
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))
Example #11
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))
Example #12
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)))
Example #13
0
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]))