def part_b(data, t=200): zgrid = ZGrid(data) del zgrid[2 + 2j] grids = {0: zgrid} for i in range(t): evolve_b(grids) n_bugs = 0 for depth, grid in sorted(grids.items()): # print(f"Depth {depth}:") # grid.draw(pretty=False, overlay={2+2j: "?"}) n_bugs += grid.n_on return n_bugs
def __getitem__(self, item): if isinstance(item, np.ndarray) and item.ndim == 2: if item.dtype == "U1": item = (item == "#").astype(int) item = autocrop(item) txt = array2txt(item).replace("0", ".").replace("1", "#") if txt in known: return known[txt] H, W = item.shape for k, v in glyphs.items(): h, w = v.shape if h == H and (item[:, :w] == v).all(): letter = known[k] return letter + self.__getitem__(item[:, w:]) item = txt # trigger fallthrough to __missing__ if isinstance(item, (dict, ZGrid)): grid = ZGrid(item) grid.translate({grid.off: 0, grid.on: 1}) full = np.array(grid) full = autocrop(full) return self.__getitem__(full) if isinstance(item, np.ndarray): self.__missing__(array2txt(item)) return super(DebugDict, self).__getitem__(item)
def __init__(self, data, part="a"): grid = ZGrid(data, on=".", off="#") pos = ("@", ) if part == "b": [z0] = [z for z, v in grid.items() if v == "@"] if grid.count_near(z0, val=".", n=8, default=".") == 8: pos = ("@0", "@1", "@2", "@3") for z in grid.near(z0, n=5): grid[z] = grid.off for p, dz in zip(pos, [-1 - 1j, 1 - 1j, 1 + 1j, -1 + 1j]): grid[z0 + dz] = p graph = grid.graph(extra=frozenset(string.ascii_letters).union(pos)) self.all_keys = {k for k in graph.extra if k in string.ascii_lowercase} # map of the other keys that must be acquired before each key is acquired self.obstructions = {k: set() for k in self.all_keys} # precached distances between points of interest self.kdist = {} for k0 in self.all_keys: for p in pos: try: path = nx.shortest_path(graph, graph.extra[p], graph.extra[k0]) except nx.NetworkXNoPath: pass else: break else: raise Exception("nothing possible to do") self.kdist[k0, p] = self.kdist[p, k0] = len(path) - 1 assert path[0] == graph.extra[p] assert path[-1] == graph.extra[k0] for p in path[1:-1]: if p in graph.extra.values(): k1 = grid[p].lower() self.obstructions[k0].add(k1) for k1, k2 in combinations(self.all_keys, 2): z1 = graph.extra[k1] z2 = graph.extra[k2] try: d = nx.shortest_path_length(graph, z1, z2) except nx.NetworkXNoPath: continue self.kdist[k1, k2] = self.kdist[k2, k1] = d state0 = pos, frozenset() AStar.__init__(self, state0, target=None)
def evolve(grid, part="a"): result = ZGrid(grid.d.copy()) max_occ = 4 if part == "a" else 5 for z0, c0 in grid.items(): if c0 == ".": continue # floor if part == "a": n_occ = grid.count_near(z0, "#", n=8) else: n_occ = 0 for dz in grid.near(0, n=8): z = z0 val = "." while val == ".": z += dz val = grid.get(z) n_occ += val == "#" if c0 == "L" and n_occ == 0: result[z0] = "#" if c0 == "#" and n_occ >= max_occ: result[z0] = "L" return result
pathABC = pathABC[len(A):].lstrip(",") compressed_pathABC += "A," if pathABC.startswith(B): pathABC = pathABC[len(B):].lstrip(",") compressed_pathABC += "B," if pathABC.startswith(C): pathABC = pathABC[len(C):].lstrip(",") compressed_pathABC += "C," if not pathABC: result = compressed_pathABC.rstrip(",") if len(result) <= mem: results.append("\n".join([result, A, B, C])) return results assert calibration(ZGrid(test_calibration)) == 76 uncompressed_test_path = get_path(ZGrid(test_path), compressed=False) assert uncompressed_test_path == "R,8,R,8,R,4,R,4,R,8,L,6,L,2,R,4,R,4,R,8,R,8,R,8,L,6,L,2" assert """\ A,B,C,B,A,C R,8,R,8 R,4,R,4,R,8 L,6,L,2""" in compress(uncompressed_test_path) grid = parsed(data) print("part a", calibration(grid)) comp = IntComputer(data) comp.reg[0] = 2 path = get_path(grid) comp.input_text(path) comp.run()
def parsed(data, part="a"): grid = ZGrid(data) A = (np.array(grid) == "#").astype(int) if part == "b": A[0, 0] = A[0, -1] = A[-1, 0] = A[-1, -1] = 1 return A
""" --- Day 11: Dumbo Octopus --- https://adventofcode.com/2021/day/11 """ from aocd import data from aoc_wim.zgrid import ZGrid g = ZGrid(data, transform=int) a = b = 0 while True: b += 1 n = 0 flash = [] for z in g: if g[z] == 9: g[z] = 0 flash.append(z) else: g[z] += 1 while flash: z0 = flash.pop() n += 1 for z in g.near(z0, n=8): if g.get(z): if g[z] == 9: g[z] = 0 flash.append(z) else: g[z] += 1 if b <= 100: a += n
def test_n_points_in_10x10(): grid = ZGrid(minibeam) count = 0 for z in zrange(0, 10 + 10j): count += grid.get(z) == "#" assert count == 27
class OutOfBeam(Exception): pass @functools.lru_cache(maxsize=100**2) def beam(z): comp = IntComputer(data, inputs=[int(z.imag), int(z.real)]) comp.run(until=IntComputer.op_output) [result] = comp.output return result print("populating 50x50 zgrid...") grid = ZGrid() x0 = 0 for y in range(50): on = False for x in range(x0, 50): z = x + y*1j val = grid[z] = beam(z) if not on and val: on = True x0 = x if x0: m = y / x0 if on and not val: break grid.draw() print("part a", sum(grid.values()))
n_occ = 0 for dz in grid.near(0, n=8): z = z0 val = "." while val == ".": z += dz val = grid.get(z) n_occ += val == "#" if c0 == "L" and n_occ == 0: result[z0] = "#" if c0 == "#" and n_occ >= max_occ: result[z0] = "L" return result for p in "ab": grid0 = ZGrid(data) while True: grid1 = evolve(grid0, part=p) grid1.draw(clear=True, pretty=True, transform={ ".": " ", "L": "💺", "#": "🧘🏽" }) if grid1.d == grid0.d: break grid0 = grid1 print("part", p, grid0.count("#"))
grid[row, col] = tile del tiles[tile.id] break full_rows = [ np.hstack([grid[r, c].trimmed() for c in range(n)]) for r in range(n) ] full_grid = np.vstack(full_rows) big_tile = Tile(0, full_grid) sea_monster_raw = """\ # # ## ## ### # # # # # # """ sea_monster = (np.array(ZGrid(sea_monster_raw)) == "#").astype(int) n = sea_monster.sum() h, w = sea_monster.shape H, W = big_tile.data.shape n_monsters = 0 while n_monsters <= 0: big_tile.transform() n_monsters = 0 for row in range(H - h): for col in range(W - w): section = big_tile.data[row:row + h, col:col + w] if (sea_monster * section).sum() == n: n_monsters += 1 print("part b:", big_tile.data.sum() - n_monsters * sea_monster.sum())
def check(y): z = left_edge_of_beam(y, gradient_estimate, beam) val = beam(z + d * ZGrid.NE) print(f"y={y}", "wide" if val else "narrow") return val bisect = Bisect(check, lo=d, hi=hi) print("bisecting...") y = bisect.run() + 1 z = left_edge_of_beam(y, gradient_estimate, beam) + d * ZGrid.N return z if __name__ == "__main__": print("populating 50x50 zgrid...") grid = ZGrid() x0 = 0 for y in range(50): on = False for x in range(x0, 50): z = x + y * 1j val = grid[z] = beam(z) if not on and val: on = True x0 = x if x0: m = y / x0 if on and not val: break print("part a", sum(grid.values()))
def test_calibrate(): assert q17.calibration(ZGrid(test_calibration)) == 76
def test_pathological_example(): grid = q17.parsed(test_pathological_intcode) assert ZGrid(test_pathological).d == grid.d compressed = q17.get_path(grid) assert compressed == test_pathological_path
""" --- Day 9: Smoke Basin --- https://adventofcode.com/2021/day/9 """ from aocd import data import networkx as nx from aoc_wim.zgrid import ZGrid g = ZGrid(data) bs = sorted(nx.connected_components(g.graph(extra="012345678")), key=len) print("part a:", sum(min([1 + int(g[z]) for z in b]) for b in bs)) print("part b:", len(bs[-1]) * len(bs[-2]) * len(bs[-3]))
""" --- Day 19: A Series of Tubes --- https://adventofcode.com/2017/day/19 """ from aocd import data from aoc_wim.zgrid import ZGrid grid = ZGrid(data) z = data.splitlines()[0].index("|") assert grid[z] == "|" dz = ZGrid.down letters = "" n_steps = 0 while True: z += dz n_steps += 1 a = grid.get(z, " ") if a == "+": dz *= ZGrid.turn_left if grid.get(z + dz, " ") == " ": dz *= ZGrid.turn_around elif a not in "-| ": letters += a elif a == " ": break print("part a:", letters) print("part b:", n_steps)
""" --- Day 3: Toboggan Trajectory --- https://adventofcode.com/2020/day/3 """ from math import prod from aocd import data from aoc_wim.zgrid import ZGrid grid = ZGrid(data) w, h = grid.width, grid.height dzs = {}.fromkeys([1 + 1j, 3 + 1j, 5 + 1j, 7 + 1j, 1 + 2j], 0) for dz in dzs: z = grid.top_left while z.imag < h: dzs[dz] += grid[complex(z.real % w, z.imag)] == "#" z += dz print("part a:", dzs[3 + 1j]) print("part b:", prod(dzs.values()))
from aocd import data class WallMap: def __init__(self, fav_number=None): if fav_number is None: fav_number = int(data) self.fav_number = fav_number def __call__(self, z): if z.real < 0 or z.imag < 0: return "#" x, y = z.real, z.imag fz = x * x + 3 * x + 2 * x * y + y + y * y popcount = bin(int(fz) + self.fav_number).count("1") result = "#" if popcount % 2 else "." return result z0 = 1 + 1j target = 31 + 39j if __name__ == "__main__": grid = ZGrid(WallMap(), on=".", off="#") depths = grid.bfs(target=target, z0=z0) print("part a:", depths[target]) depths = grid.bfs(z0=z0, max_depth=50) print("part b:", len(depths)) grid.draw_path(z=target, z0=z0)
#......CJ NM..#...# ###.#.# #.###.# RE....#.# #......RF ###.### X X L #.#.#.# #.....# F Q P #.#.#.# ###.###########.###.#######.#########.### #.....#...#.....#.......#...#.....#.#...# #####.#.###.#######.#######.###.###.#.#.# #.......#.......#.#.#.#.#...#...#...#.#.# #####.###.#####.#.#.#.#.###.###.#.###.### #.......#.....#.#...#...............#...# #############.#.#.###.################### A O F N A A D M """ grid = ZGrid(data, on=".", off="#") h, w = np.array(grid).shape dzs = [-1j, 1, 1j, -1] # parse the warps outside = {} inside = {} for z, glyph in grid.items(): if glyph in string.ascii_uppercase: for dz in dzs: if grid.get(z + dz) == ".": zp = z + dz # actual position of portal name = glyph + grid.get(z - dz) # add other letter if 3 < z.real < w - 3 and 3 < z.imag < h - 3: side = inside else:
def test_square(): grid = ZGrid(minibeam) expected = grid.z("O") grid.translate({".": 0, "O": 1, "#": 1}) z = q19.locate_square(beam=grid.get, width=10, hi=grid.height) assert z == 25 + 20j == expected
""" --- Day 24: Air Duct Spelunking --- https://adventofcode.com/2016/day/24 """ from aoc_wim.zgrid import ZGrid from itertools import combinations from itertools import permutations import networkx as nx from aocd import data grid = ZGrid(data, on=".", off="#") graph = grid.graph(extra="0123456789") distances = {} for a, b in combinations(graph.extra, 2): d = nx.shortest_path_length(graph, graph.extra[a], graph.extra[b]) distances[a, b] = distances[b, a] = d def shortest_path(part="a"): paths = {} for nodes in permutations(graph.extra.keys() - {"0"}): path = "0" + "".join(nodes) if part == "b": path += "0" paths[path] = sum(distances[a, b] for a, b in zip(path, path[1:])) return min(paths.values()) print("part a:", shortest_path(part="a")) print("part b:", shortest_path(part="b"))
def parsed(data): zs = ZGrid(data).z(val="#", first=False) xys = [(int(z.real), int(z.imag)) for z in zs] assert len(xys) == data.count("#") return xys
..#... .#.... #..... #..... ###### ##### #...# #...# #...# ##### """ glyphs = {g: np.array(ZGrid(g)) == "#" for g in glyphs.strip().split("\n\n\n")} known = dict(zip(glyphs, "AABBCCEEFFGGHHHIIJJKKLLNOPPRRSUXZZ□")) known[""] = "" def autocrop(A): if A.dtype != int: cropped = autocrop((A == "#").astype(int)) return np.where(cropped, "#", ".") on = np.argwhere(A) if not on.size: return A[:0, :0] r0, c0 = on.min(axis=0) r1, c1 = on.max(axis=0) + 1 return A[r0:r1, c0:c1]
def new_empty_grid(): grid = ZGrid(".....\n" * 5) del grid[2 + 2j] return grid
def test_animate(): A = np.full((3, 7), ".") for frame, line in zip(frames.split("\n\n"), data.splitlines()): A = q08.animate(A, line) expected = np.array(ZGrid(frame)) assert (A == expected).all()