From 744dbb4ffc890ad63c67dca4d7689fe0c6f0564c Mon Sep 17 00:00:00 2001 From: felixm Date: Wed, 18 Dec 2024 22:09:38 -0500 Subject: [PATCH] Solve 2024 till day 18 --- 2024/d14.py | 48 +++++---------------------- 2024/d15.py | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2024/d16.py | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2024/d17.py | 61 ++++++++++++++++++++++++++++++++++ 2024/d18.py | 67 +++++++++++++++++++++++++++++++++++++ README.md | 5 +++ 6 files changed, 329 insertions(+), 40 deletions(-) create mode 100644 2024/d15.py create mode 100644 2024/d16.py create mode 100644 2024/d17.py create mode 100644 2024/d18.py diff --git a/2024/d14.py b/2024/d14.py index afff48f..d322c63 100644 --- a/2024/d14.py +++ b/2024/d14.py @@ -11,45 +11,19 @@ def score(robots): quadrants = defaultdict(int) for _, r, c in robots: if r < rows // 2 and c < cols // 2: - quadrant = 1 # Top-left - quadrants[quadrant] += 1 + quadrants[1] += 1 elif r < rows // 2 and c > cols // 2: - quadrant = 2 # Top-right - quadrants[quadrant] += 1 + quadrants[2] += 1 elif r > rows // 2 and c < cols // 2: - quadrant = 3 # Bottom-left - quadrants[quadrant] += 1 + quadrants[3] += 1 elif r > rows // 2 and c > cols // 2: - quadrant = 4 # Bottom-right - quadrants[quadrant] += 1 - else: - pass - + quadrants[4] += 1 t = 1 for q in quadrants.values(): t *= q return t -def get_easter_egg_coords(rows, cols): - coords = set() - center_y, center_x = rows // 2, cols // 2 - height = rows * 0.8 - width = cols * 0.6 - - for i in range(rows): - for j in range(cols): - squeeze = 1 - 0.3 * ((i - center_y + height / 4) / height) - egg_eq = (j - center_x) ** 2 / (width / 2) ** 2 + (i - center_y) ** 2 / ( - (height / 2 * squeeze) ** 2 - ) - - if egg_eq <= 1: - coords.add((i, j)) - - return coords - - def print_from_xy(xs): x_min = min(v[1] for v in xs) x_max = max(v[1] for v in xs) @@ -72,25 +46,19 @@ for i, line in enumerate(data.splitlines()): robots_speed[i] = (vr, vc) robots.append((i, r, c)) -egg_coords = get_easter_egg_coords(rows, cols) - -max_count = 0 - -for j in range(10_000_000_000): +for j in range(rows * cols): nrobots = list() count = 0 for i, r, c in robots: vr, vc = robots_speed[i] nr = (r + vr) % rows nc = (c + vc) % cols - if (nr, nc) in egg_coords: - count += 1 nrobots.append((i, nr, nc)) robots = nrobots - max_count = max(count, max_count) if j == 99: print(score(robots)) - if count > 380: + robots_coords = [(r, c) for _, r, c in robots] + if len(robots_coords) == len(set(robots_coords)): print(j + 1) - # print_from_xy([(r, c) for _, r, c in robots]) + # print_from_xy(robots_coords) break diff --git a/2024/d15.py b/2024/d15.py new file mode 100644 index 0000000..b54d899 --- /dev/null +++ b/2024/d15.py @@ -0,0 +1,92 @@ +from lib import get_data +from lib import Grid2D + +DIRS = { + "^": (-1, 0), + ">": (0, 1), + "v": (1, 0), + "<": (0, -1), +} + +data = get_data(__file__) +data, moves = data.split("\n\n") + +g = Grid2D(data) + + +def simulate(grid: Grid2D, moves: str): + (p,) = g.find("@") + for c in moves: + if c == "\n": + continue + d = DIRS[c] + + to_move = set([p]) + row = to_move + blocked = False + while True: + nrow = set() + for ( + r, + c, + ) in row: + np = r + d[0], c + d[1] + if g[np] == "#": + blocked = True + elif g[np] == "O": + nrow.add(np) + elif g[np] == "[": + nrow.add(np) + if d == (1, 0) or d == (-1, 0): + nrow.add((np[0], np[1] + 1)) + elif g[np] == "]": + if d == (1, 0) or d == (-1, 0): + nrow.add((np[0], np[1] - 1)) + nrow.add(np) + to_move |= row + row = nrow + if len(row) == 0 or blocked: + break + + if not blocked: + to_place = {} + p = p[0] + d[0], p[1] + d[1] + + for tm in to_move: + np = tm[0] + d[0], tm[1] + d[1] + to_place[np] = g[tm] + g[tm] = "." + + for pos, c in to_place.items(): + g[pos] = c + + +def score(g: Grid2D) -> int: + os = g.find("O") + if os: + return sum(r * 100 + c for r, c in os) + return sum(r * 100 + c for r, c in g.find("[")) + + +simulate(g, moves) +print(score(g)) + +ndata = "" +for c in data: + if c == "#": + ndata += "##" + elif c == "O": + ndata += "[]" + elif c == ".": + ndata += ".." + elif c == "@": + ndata += "@." + else: + ndata += c + +g = Grid2D(ndata) +(p,) = g.find("@") + +simulate(g, moves) +print(score(g)) +exit() diff --git a/2024/d16.py b/2024/d16.py new file mode 100644 index 0000000..8a6b2a9 --- /dev/null +++ b/2024/d16.py @@ -0,0 +1,96 @@ +import heapq +from collections import defaultdict +from lib import get_data +from lib import Grid2D + + +data = get_data(__file__) + +g = Grid2D(data) + + +def search(start, end, dir): + paths = set() + + def reconstruct(parents, current): + seen = set() + to_visit = [current] + while to_visit: + current = to_visit.pop() + if current in seen: + continue + seen.add(current) + paths.add(current[0]) + for p in parents[current]: + to_visit.append(p) + + def dist(n1, n2): + """cost from node to node""" + if n1 == 0 or n1 == n2: + return 0 + p1, d1 = n1 + p2, d2 = n2 + if p1 != p2: + return 1 + if d1 != d2: + return 1000 + assert False + + def h(node): + """heuristic function (never overestimate)""" + pos = node[0] + return abs(pos[0] - end[0]) + abs(pos[1] - end[1]) + + def is_goal(node): + return node[0] == end + + def neighbors(node): + pos, dir = node + npos = pos[0] + dir[0], pos[1] + dir[1] + nbs = [] + if g.contains(npos) and g[npos] != "#": + nbs.append((npos, dir)) + ndir = dir[1], -dir[0] + nbs.append((pos, ndir)) + ndir = -dir[1], dir[0] + nbs.append((pos, ndir)) + return nbs + + starts = [(start, dir)] + open_set = [] + g_score = {} + cost = None + + for start in starts: + heapq.heappush(open_set, (h(start), start)) + g_score[start] = dist(0, start) + + parents = defaultdict(list) + while open_set: + current_f_score, current = heapq.heappop(open_set) + if is_goal(current): + # assert current_f_score == g_score[current] + gs = g_score[current] + if cost is None or gs <= cost: + cost = gs + reconstruct(parents, current) + else: + break + + for neighbor in neighbors(current): + tentative_g_score = g_score[current] + dist(current, neighbor) + if tentative_g_score <= g_score.get(neighbor, 10**12): + parents[neighbor].append(current) + g_score[neighbor] = tentative_g_score + f_score = g_score[neighbor] + h(neighbor) + heapq.heappush(open_set, (f_score, neighbor)) + return cost, paths + + +(start,) = g.find("S") +(end,) = g.find("E") +dir = (0, 1) + +cost, paths = search(start, end, dir) +print(cost) +print(len(paths)) diff --git a/2024/d17.py b/2024/d17.py new file mode 100644 index 0000000..5e3b2e5 --- /dev/null +++ b/2024/d17.py @@ -0,0 +1,61 @@ +from lib import get_data +from lib import ints + + +data = get_data(__file__) +nums, prog = data.split("\n\n") +regs = ints(nums) +prog = ints(prog) + +i = 0 +out = [] +while i < len(prog) - 1: + inst, op = prog[i], prog[i + 1] + assert op != 7 + combo = op if op not in [4, 5, 6] else regs[op - 4] + literal = op + match inst: + case 0: + regs[0] = regs[0] >> combo + case 1: + regs[1] = regs[1] ^ literal + case 2: + regs[1] = combo % 8 + case 3: + if regs[0] != 0: + i = literal - 2 + case 4: + regs[1] = regs[1] ^ regs[2] + case 5: + out.append(combo % 8) + case 6: + regs[1] = regs[0] // (2**combo) + case 7: + regs[2] = regs[0] // (2**combo) + case _: + assert False + i += 2 + +print(",".join(map(str, out))) + + +def get_a(a_current, xs): + if len(xs) == 0: + return [a_current // 8] + x = xs[0] + aan = [] + for a_new in range(8): + a = a_current + a_new + b = a % 8 + assert b == a_new + b = b ^ 1 + c = a >> b + b = b ^ c + b = b ^ 5 + if (b % 8) == x: + aan += get_a(a << 3, xs[1:]) + return aan + + +aa = get_a(0, list(reversed(prog))) +print(min(aa)) diff --git a/2024/d18.py b/2024/d18.py new file mode 100644 index 0000000..2b59f8e --- /dev/null +++ b/2024/d18.py @@ -0,0 +1,67 @@ +import heapq +from lib import get_data +from lib import ints +from lib import Grid2D + +data = get_data(__file__) + +size = 71 +grid = "\n".join(["." * size for _ in range(size)]) + +g = Grid2D(grid) +for i, line in enumerate(data.splitlines()): + x, y = ints(line) + g[(y, x)] = "x" + + start = (0, 0) + end = (size - 1, size - 1) + + def dist(n1, n2): + """cost from node to node""" + if n1 == 0 or n1 == n2: + return 0 + return 1 + + def h(node): + """heuristic function (never overestimate)""" + return abs(node[0] - end[0]) + abs(node[1] - end[1]) + + def is_goal(node): + return node == end + + def neighbors(node): + nbs = [] + for nb in g.neighbors_ort(node): + if g[nb] != "x": + nbs.append(nb) + return nbs + + starts = [start] + open_set = [] + g_score = {} + cost = None + + for start in starts: + heapq.heappush(open_set, (h(start), start)) + g_score[start] = dist(0, start) + + while open_set: + current_f_score, current = heapq.heappop(open_set) + if is_goal(current): + assert current_f_score == g_score[current] + cost = g_score[current] + break + + for neighbor in neighbors(current): + tentative_g_score = g_score[current] + dist(current, neighbor) + if neighbor not in g_score or tentative_g_score < g_score[neighbor]: + g_score[neighbor] = tentative_g_score + f_score = g_score[neighbor] + h(neighbor) + heapq.heappush(open_set, (f_score, neighbor)) + + if i == 1023: + print(cost) + + if cost is None: + print(line) + break diff --git a/README.md b/README.md index 17a2e86..08d48ab 100644 --- a/README.md +++ b/README.md @@ -305,4 +305,9 @@ and focus. Of course, learning more algorithms and techniques helps. - Day 12: `00:30:30 3540 0 11:41:33 16212 0` - Day 13: `00:05:35 225 0 00:55:48 2544 0` - Day 14: `00:20:41 2136 0 00:35:14 1048 0` +- Day 15: ` >24h 32248 0 >24h 23671 0` +- Day 16: ` >24h 24941 0 >24h 20575 0` +- Day 17: `17:34:16 23722 0 >24h 17778 0` +- Day 18: `14:35:01 20398 0 14:37:18 19550 0` +- Day 19: