def solve(steps, file): data = load(file) grid = [[int(c) for c in x] for x in data] n, m = len(grid), len(grid[0]) flashes = 0 def flash(q, x, y): grid[y][x] = (grid[y][x] + 1) % 10 if not grid[y][x]: for dx, dy in DIRS_8: if 0 <= y + dy < n and 0 <= x + dx < m: q += (x + dx, y + dy), return not grid[y][x] for round in range(1, (steps or 999) + 1): q = [] for x in range(m): for y in range(n): flashes += flash(q, x, y) while q: x, y = q.pop() if grid[y][x]: flashes += flash(q, x, y) if set(x for row in grid for x in row) == {0}: return round return flashes
def solve(part, file): ingredients = {} for line in load(file): name, spec = line.split(':') ingredients[name] = Ingredient(*parse_nums(spec)) return max(scores(ingredients, 500 * (part - 1)))
def solve(part, file): data = load(file) graph = defaultdict(list) for line in data: a, b = line.split('-') graph[a] += b, graph[b] += a, def dfs(node, visited, mulligans): added = 0 if node == 'end': return 1 elif node.islower(): if node in visited and (node == 'start' or not mulligans): return 0 elif node in visited: mulligans -= 1 else: added = 1 visited.add(node) try: return sum(dfs(g, visited, mulligans) for g in graph[node]) finally: if added: visited.remove(node) return dfs('start', set(), part - 1)
def solve(part, file): input = load(file)[0] g = hashgen(input, part == 2) code = [next(g) for _ in range(8)] if part == 2: code = sorted((x[::-1] for x in code), key=itemgetter(1)) return ''.join(c[0] for c in code)
def solve(part, file): data = load(file)[0] bits = iter(bin(int(data, 16))[2:].zfill(len(data)*4)) versions = pos = 0 def read(x): nonlocal pos pos += x return int(''.join(next(bits) for _ in range(x)), 2) def parts(type): if type == 4: while read(1): yield read(4) yield read(4) elif read(1): for _ in range(read(11)): yield parse() else: size = read(15) end = pos + size while pos < end: yield parse() def parse(): nonlocal versions versions += read(3) type = read(3) return reduce(OPS[type], parts(type)) root = int(parse()) return (versions, root)[part == 2]
def solve(file, compares=None): data = load(file) instructions = [list(g) for _, g in groupby(sorted(data), key=itemgetter(0))] splits = {} for line in instructions[0]: _, bot, _, _, _, is_out_lo, lo, _, _, _, is_out_hi, hi = find_tokens(line) bot, lo, hi = map(int, (bot, lo, hi)) is_out_lo, is_out_hi = is_out_lo[0] == 'o', is_out_hi[0] == 'o' assert bot not in splits splits[bot] = ((is_out_lo, lo), (is_out_hi, hi)) bots = defaultdict(list) for line in instructions[1]: value, bot = parse_nums(line) assert len(bots[bot]) < 2 bots[bot].append(value) def run_bot(): for b in bots: if len(bots[b]) == 2: (is_out_lo, lo_id), (is_out_hi, hi_id) = splits[b] if (is_out_lo or len(bots[lo_id]) < 2) and (is_out_hi or len(bots[hi_id]) < 2): return b outs = defaultdict(list) while ((b := run_bot()) is not None): lo, hi = sorted(bots[b]) if compares and (lo, hi) == compares: return b bots[b] = [] (is_out_lo, lo_id), (is_out_hi, hi_id) = splits[b] (outs if is_out_lo else bots)[lo_id] += lo, (outs if is_out_hi else bots)[hi_id] += hi,
def load_floors(file): locations = defaultdict(lambda: [0, 0]) for floor_num, line in enumerate(load(file), 1): for material, type in re.findall( r'a (\w+)( generator|-compatible microchip)', line): locations[material][type[0] == '-'] = floor_num return tuple(map(tuple, sorted(locations.values())))
def solve(part, file): data = [*map(parse_nums, load(file))][0] if part == 1: return compute(data, lambda x: x, sorted(data)[len(data) // 2]) else: avg = sum(data) / len(data) return compute(data, lambda x: x * (x + 1) // 2, *{floor(avg), ceil(avg)})
def solve(part, file, lo=0, hi=0): ranges = sorted(parse_nums(line) for line in load(file)) res = 0 for x, y in ranges: if lo + 1 <= x - 1: if part == 1: return lo + 1 res += x - lo - 1 lo = max(lo, y) return res + hi - lo
def solve(part, file): lines = [*map(parse_nums, load(file))] grid = defaultdict(int) for x1, y1, x2, y2 in lines: dx, dy = sign(x2 - x1), sign(y2 - y1) if part == 2 or not dx or not dy: for i in range(abs(x2 - x1 or y2 - y1) + 1): grid[x1 + i * dx, y1 + i * dy] += 1 return sum(c > 1 for c in grid.values())
def load_amphibians(part, file): hall = [-1] * 11 rooms = [[], [], [], []] for line in load(file)[3:1:-1]: for i, c in enumerate(line[3:11:2]): rooms[i].append(ord(c) - ord('A')) if part == 2: for room, fold in zip(rooms, [[3, 3], [1, 2], [0, 1], [2, 0]]): room[:] = [room[0], *fold, room[1]] return tuple(hall), tuples(rooms)
def solve(part, file): data = load(file) total = 0 for line in data: seqs = re.split(r'\[|\]', line) outs, ins = seqs[::2], seqs[1::2] if part == 1: total += not any(map(has_abba, ins)) and any(map(has_abba, outs)) else: total += any(bab in s for bab, s in product(find_babs(outs), ins)) return total
def solve(part, file): data = load(file) sues = {} for line in data: m = re.split(r'[:, ]+', line) sues[int(m[1])] = { item: int(count) for item, count in zip(*[iter(m[2:])] * 2) } ops = (ops_part_2 if part == 2 else {}) for num, info in sues.items(): if all( ops.get(item, eq)(count, my_sue[item]) for item, count in info.items()): yield num
def solve(part, file): cube_input = [(line[:2] == 'on', parse_nums(line)) for line in load(file)] cubes = {} for on, cube in cube_input: if part == 2 or is_within(cube, 50): cube = tuple(cube) for other_cube in list(cubes): if is_overlapping(cube, other_cube): del cubes[other_cube] for piece in cut_cube(other_cube, cube): cubes[piece] = area(piece) if on: cubes[cube] = area(cube) return sum(cubes.values())
def solve(part, file): boss_hp, boss_damage = (parse_nums(line)[0] for line in load(file)) start = Fight(mana=500, player_hp=50, boss_hp=boss_hp) def is_goal(fight): return fight.boss_hp <= 0 def expand(fight): for spell, cost in spell_costs.items(): result = fight.copy() if result.round(boss_damage, spell, part == 2) != False: yield cost, result return djyk(start, expand, is_goal)
def solve(part, file): boss = parse_character(load(file)) shop = parse_shop() players = [] for weapon in shop['Weapons']: for armor in chain(shop['Armor'], [None]): for num_rings in range(3): for rings in permutations(shop['Rings'], num_rings): stats = combine(weapon, armor, *rings) player = Character(100, stats.damage, stats.armor) players.append((stats.cost, player)) players.sort() if part == 1: return next(cost for cost, player in players if fight(player, boss)) else: return next(cost for cost, player in reversed(players) if not fight(player, boss))
def solve(file): data = load(file) n, m = len(data), len(data[0]) grid = {(x, y): data[y][x] for y in range(n) for x in range(m) if data[y][x] != '.'} for r in count(1): moved = 0 for dx, dy, c in ((1, 0, '>'), (0, 1, 'v')): moves = [(x, y) for x, y in grid if grid[x, y] == c and ((x + dx) % m, (y + dy) % n) not in grid] moved = moved or len(moves) for x, y in moves: del grid[x, y] grid[(x + dx) % m, (y + dy) % n] = c if not moved: return r
def solve(part, file): x1, x2, y1, y2 = parse_nums(load(file)[0]) if part == 1: return y1 * (y1 - 1) // 2 def tryit(vx, vy): x = y = 0 while y > -y1: x += vx y += vy if vx > 0: vx -= 1 vy -= 1 if x1 <= x <= x2 and -y1 <= y <= -y2: return 1 return 0 return sum( tryit(vx, vy) for vx in range(1, x2 + 1) for vy in range(-y1, y1))
def solve(part, file): data = load(file) grid = {} for line in data[2:]: pattern = r'/dev/grid/node-x(\d+)-y(\d+) +(\d+)T +(\d+)T +(\d+)T +(\d+)%' x, y, size, used, avail, percent = map( int, re.fullmatch(pattern, line).groups()) grid[x, y] = (size, used, avail, percent) space_x, space_y = next((x, y) for x, y in grid if grid[x, y][1] == 0) too_big = set(grid) - {(space_x, space_y)} \ - {(x, y) for x, y in grid if 0 < grid[x, y][1] <= grid[space_x, space_y][2]} barrier_x = {x for x, _ in too_big} opening_x = next(x for x in range(space_x, -1, -1) if x not in barrier_x) goal_x = max(x for x, _ in grid) if part == 1: return sum(0 < a[1] <= b[2] for a, b in permutations(grid.values(), 2)) else: return 2*(space_x-opening_x) + space_y + (goal_x - space_x) + 5*(goal_x-1)
def solve(part, file): data = load(file) grid = defaultdict(lambda: 9) for y, line in enumerate(data): for x, c in enumerate(line): grid[x, y] = int(c) res = [] if part == 1: for x, y in list(grid.keys()): if all(grid[x, y] < grid[x+dx, y+dy] for dx, dy in DIRS): res.append(1 + grid[x, y]) return sum(res) else: def dfs(x, y): return grid.pop((x, y), 9) < 9 and 1 + sum(dfs(x+dx, y+dy) for dx, dy in DIRS) for x, y in list(grid.keys()): if (size := dfs(x, y)): res.append(size) return reduce(int.__mul__, nlargest(3, res))
def solve(part, file): data = load(file) res = [] keypad = keypads[part].splitlines() x, y = starts[part] for line in data: for c in line: if c == 'U': if keypad[y-1][x] != ' ': y -= 1 elif c == 'D': if keypad[y+1][x] != ' ': y += 1 elif c == 'L': if keypad[y][x-1] != ' ': x -= 1 elif c == 'R': if keypad[y][x+1] != ' ': x += 1 res += keypad[y][x], return ''.join(res)
def solve(part, file): grid = [[int(x) for x in line] for line in load(file)] seen = defaultdict(lambda: inf, {(0, 0): 0}) n, m = len(grid), len(grid[0]) goal = (m - 1, n - 1) if part == 1 else (m * 5 - 1, n * 5 - 1) q = [(0, 0, 0)] while q: score, x, y = heappop(q) if (x, y) == goal: return score for dx, dy in DIRS_4: if 0 <= (x1 := x + dx) <= goal[0] and 0 <= (y1 := y + dy) <= goal[1]: if part == 1: val = score + grid[y1][x1] else: yd, ym = divmod(y1, n) xd, xm = divmod(x1, m) val = score + (grid[ym][xm] + xd + yd - 1) % 9 + 1 if seen[x1, y1] > val: seen[x1, y1] = val heappush(q, (val, x1, y1))
def solve(part, input, file): s = list(input) lines = load(file) if part == 2: lines = lines[::-1] for line in lines: tokens = line.split() nums = parse_nums(line) if tokens[0] == 'swap' and tokens[1] == 'position': s = swap_pos(s, *nums) elif tokens[0] == 'swap' and tokens[1] == 'letter': s = swap_letter(s, tokens[2], tokens[5]) elif tokens[0] == 'move': if part == 1: s = move(s, *nums) else: s = move(s, *nums[::-1]) elif tokens[0] == 'reverse': s = reverse(s, *nums) elif tokens[0] == 'rotate' and tokens[1] == 'left': if part == 1: s = rotate_left(s, *nums) else: s = rotate_right(s, *nums) elif tokens[0] == 'rotate' and tokens[1] == 'right': if part == 1: s = rotate_right(s, *nums) else: s = rotate_left(s, *nums) elif tokens[0] == 'rotate' and tokens[1] == 'based': if part == 1: s = rotate_by_letter(s, tokens[-1]) else: s = rotate_by_letter_inverse(s, tokens[-1]) else: raise Exception('no match', line) return ''.join(s)
def solve(part, file): lines = load(file) blocks = [ block.split('\n') for block in '\n'.join(lines).split('inp w\n')[1:] ] model = [0] * 14 stack = [] for i, block in enumerate(blocks): if block[3] == 'div z 1': x = int(block[14].split(' ')[-1]) stack.append((i, x)) elif block[3] == 'div z 26': y = int(block[4].split(' ')[-1]) j, x = stack.pop() diff = x + y if diff < 0: i, j, diff = j, i, -diff if part == 1: model[i] = 9 model[j] = 9 - diff else: model[i] = 1 + diff model[j] = 1 return int(''.join(map(str, model)))
def solve(part, file): lines = load(file) optimize(lines) instructions = parse_instructions(lines) return run(instructions, {'c': part - 1})
def solve(file): row, col = parse_nums(load(file)[0]) return find_code(row, col)
def solve(part, file): data = load(file) res = filter(None, (get_score(line)[part-1] for line in data)) return (sum, median)[part-1](res)
def load_instructions(file): return [line.replace(',', '').split() for line in load(file)]
def load_discs(file): return [parse_nums(line)[3::-2] for line in load(file)]
def solve(part, file): salt = load(file)[0] gen = keys(salt, (1, 2017)[part - 1]) return next(islice(gen, 63, None))