def main(file): print("RUNNING", file) sections = list(aocutils.readsections(file)) fields = [] for line in sections[0]: name, a, b, c, d = aocutils.multisplit(line, [": ", "-", " or ", "-"]) fields.append([ name, (int(a), int(b)), (int(c), int(d)), ]) invalid = [] for line in sections[2][1:]: values = [int(x) for x in line.split(",")] for v in values: valid = False for _, (a, b), (c, d) in fields: if a <= v <= b or c <= v <= d: valid = True break if not valid: invalid.append(v) print(invalid) print(sum(invalid))
def main(file): print("RUNNING", file) on_actions = defaultdict(int) off_actions = defaultdict(int) total = 0 for line in aocutils.readlines(file): parts = aocutils.multisplit(line, [' x=', '..', ',y=', '..', ',z=', '..']) action_type = parts[0] x0, x1, y0, y1, z0, z1 = [int(a) for a in parts[1:]] assert x0 <= x1 assert y0 <= y1 assert z0 <= z1 cuboid = Cuboid(x0, x1, y0, y1, z0, z1) # Turn off all cubes inside the cuboid by doing # the inverse of all actions so far, but only in the # intersection with the cuboid prev_actions = [] for k, v in on_actions.items(): for _ in range(v): prev_actions.append(('on', k)) for k, v in off_actions.items(): for _ in range(v): prev_actions.append(('off', k)) for prev_action_type, c in prev_actions: if prev_action_type == 'on': if overlap_cuboid := overlap(c, cuboid): if not try_remove_cuboid(on_actions, overlap_cuboid): off_actions[overlap_cuboid] += 1 total -= size(overlap_cuboid) elif prev_action_type == 'off': if overlap_cuboid := overlap(c, cuboid): if not try_remove_cuboid(off_actions, overlap_cuboid): on_actions[overlap_cuboid] += 1 total += size(overlap_cuboid)
def main(file): print("RUNNING", file) sections = list(aocutils.readsections(file)) tile_by_pattern = defaultdict(list) for section in sections: tile_id = int(aocutils.multisplit(section[0], [" ", ":"])[1]) top = section[1] bot = section[-1] left = [] right = [] for line in section[1:]: left.append(line[0]) right.append(line[-1]) top = min(pattern_id(top), pattern_id(reversed(top))) bot = min(pattern_id(bot), pattern_id(reversed(bot))) left = min(pattern_id(left), pattern_id(reversed(left))) right = min(pattern_id(right), pattern_id(reversed(right))) tile_by_pattern[top].append(tile_id) tile_by_pattern[bot].append(tile_id) tile_by_pattern[left].append(tile_id) tile_by_pattern[right].append(tile_id) counts = defaultdict(int) tile_ids_for_unmatched_edges = [ tiles[0] for tiles in tile_by_pattern.values() if len(tiles) == 1 ] for tile_id in tile_ids_for_unmatched_edges: counts[tile_id] = counts[tile_id] + 1 corners = [tile_id for tile_id, cnt in counts.items() if cnt == 2] print(corners) print(corners[0] * corners[1] * corners[2] * corners[3])
def main(file): print("RUNNING", file) grid = set() for line in aocutils.readlines(file): parts = aocutils.multisplit(line, [' x=', '..', ',y=', '..', ',z=', '..']) action = parts[0] x0, x1, y0, y1, z0, z1 = [int(a) for a in parts[1:]] assert x0 <= x1 assert y0 <= y1 assert z0 <= z1 x0 = max(x0, -50) y0 = max(y0, -50) z0 = max(z0, -50) x1 = min(x1, 50) y1 = min(y1, 50) z1 = min(z1, 50) for x in range(x0, x1 + 1): for y in range(y0, y1 + 1): for z in range(z0, z1 + 1): if action == 'on': grid.add((x, y, z)) else: grid.discard((x, y, z)) print(len(grid))
def main(file): print("RUNNING", file) actions = [] total = 0 for line in aocutils.readlines(file): parts = aocutils.multisplit(line, [' x=', '..', ',y=', '..', ',z=', '..']) action_type = parts[0] x0, x1, y0, y1, z0, z1 = [int(a) for a in parts[1:]] assert x0 <= x1 assert y0 <= y1 assert z0 <= z1 cuboid = Cuboid(x0, x1, y0, y1, z0, z1) # Turn off all cubes inside the cuboid by doing # the inverse of all actions so far, but only in the # intersection with the cuboid new_actions = [] for prev_action_type, c in actions: if prev_action_type == 'on': if overlap_cuboid := overlap(c, cuboid): new_actions.append(('off', overlap_cuboid)) total -= size(overlap_cuboid) elif prev_action_type == 'off': if overlap_cuboid := overlap(c, cuboid): new_actions.append(('on', overlap_cuboid)) total += size(overlap_cuboid)
def main(file): print("RUNNING", file) memory = {} masks = [] for line in aocutils.readlines(file): if line.startswith("mask"): pattern = line.split(" = ")[1] masks = [Mask()] for c in pattern: if c == "X": new_masks = [] for mask in masks: new_masks.append(mask.both()) masks.extend(new_masks) elif c == "1": for mask in masks: mask.push_force1() elif c == "0": for mask in masks: mask.push_unchanged() else: assert False else: parts = aocutils.multisplit(line, ["[", "]", " = "]) address = int(parts[1]) value = int(parts[-1]) for mask in masks: a = mask.apply(address) memory[a] = value print(sum(memory.values()))
def main(file): print("RUNNING", file) # 10X # (0b111 & 0b101) | 0b100 = 0x101 # (0b000 & 0b101) | 0b100 = 0x100 memory = {} and_mask = 0 or_mask = 0 for line in aocutils.readlines(file): if line.startswith("mask"): pattern = line.split(" = ")[1] and_mask = 0 or_mask = 0 for c in pattern: and_mask = and_mask << 1 or_mask = or_mask << 1 if c == "X": and_mask |= 1 elif c == "1": and_mask |= 1 or_mask |= 1 elif c == "0": pass else: assert False else: parts = aocutils.multisplit(line, ["[", "]", " = "]) address = int(parts[1]) value = int(parts[-1]) write_value = (value & and_mask) | or_mask memory[address] = write_value print(sum(memory.values()))
def main(file): print("RUNNING", file) sections = list(aocutils.readsections(file)) fields = [] for line in sections[0]: field_name, a, b, c, d = aocutils.multisplit(line, [": ", "-", " or ", "-"]) fields.append(( field_name, (int(a), int(b)), (int(c), int(d)), )) valid_tickets = [] for line in sections[2][1:]: values = [int(x) for x in line.split(",")] ticket_valid = True for v in values: if not any(is_valid(f, v) for f in fields): ticket_valid = False break if ticket_valid: valid_tickets.append(values) possible_fields = [] for v in valid_tickets[0]: possible_fields.append({f[0] for f in fields if is_valid(f, v)}) for i in range(len(possible_fields)): p = possible_fields[i] for t in valid_tickets[1:]: v = t[i] valids = {f[0] for f in fields if is_valid(f, v)} p.intersection_update(valids) print(possible_fields) while True: solved = [] not_solved = [] for p in possible_fields: if len(p) == 1: solved.append(list(p)[0]) else: not_solved.append(p) if not not_solved: break for p in not_solved: p.difference_update(solved) print(possible_fields) own_values = [int(x) for x in sections[1][1].split(",")] result = 1 for i in range(len(possible_fields)): field_name = list(possible_fields[i])[0] if field_name.startswith("departure "): result *= own_values[i] print(result)
def build_tiles_and_find_corners(file): sections = list(aocutils.readsections(file)) tiles_by_id = {} tile_by_pattern = defaultdict(list) for section in sections: tile_id = int(aocutils.multisplit(section[0], [" ", ":"])[1]) top = section[1] bot = section[-1] left = [] right = [] for line in section[1:]: left.append(line[0]) right.append(line[-1]) inner = [] for line in section[2:-1]: inner.append(line[1:-1]) assert len(inner) == len(inner[0]) # Edges are represents by an int with a bit pattern describing the shape # Since the reverse pattern is the same we use the smaller of the two possible representation (when seen as an int) # min(pattern_id("##..#"), pattern_id(reversed("##..#"))) # = min(pattern_id("##..#"), pattern_id( "#..##")) # = min( 0b_11001, 0b_10011) # = min(25, 19) # = 19 top = min(pattern_id(top), pattern_id(reversed(top))) bot = min(pattern_id(bot), pattern_id(reversed(bot))) left = min(pattern_id(left), pattern_id(reversed(left))) right = min(pattern_id(right), pattern_id(reversed(right))) tile = Tile(tile_id, top, bot, left, right, inner) tiles_by_id[tile_id] = tile tile_by_pattern[top].append(tile) tile_by_pattern[bot].append(tile) tile_by_pattern[left].append(tile) tile_by_pattern[right].append(tile) counts = defaultdict(int) for tile_id in [tiles[0].tile_id for tiles in tile_by_pattern.values() if len(tiles) == 1]: counts[tile_id] = counts[tile_id] + 1 corners = [tiles_by_id[tile_id] for tile_id, cnt in counts.items() if cnt == 2] assert len(corners) == 4 for pattern, tiles in tile_by_pattern.items(): if len(tiles) == 2: tiles[0].neighbors.append((pattern, tiles[1])) tiles[1].neighbors.append((pattern, tiles[0])) return corners
def main(file): print("RUNNING", file) line = list(aocutils.readlines(file))[0] _, tx0, tx1, ty0, ty1 = aocutils.multisplit( line, ['target area: x=', '..', ', y=', '..']) tx0 = int(tx0) tx1 = int(tx1) ty0 = int(ty0) ty1 = int(ty1) maxy = None for y in range(1, 1000): if simulate(0, y, tx0, tx1, ty0, ty1, ignore_x=True): maxy = y success = set() while maxy > -1000: for x in range(1, tx1 + 1): if simulate(x, maxy, tx0, tx1, ty0, ty1): success.add((x, maxy)) maxy -= 1 print(len(success))
def main(file): print("RUNNING", file) possible_allergen = defaultdict(set) recipies = [] ingredients_by_allergen = dict() for line in aocutils.readlines(file): parts = aocutils.multisplit(line, [" (contains ", ")"]) ingredients = parts[0].split(" ") recipies.append(ingredients) allergen = parts[1].split(", ") for allergen in allergen: if allergen not in ingredients_by_allergen: ingredients_by_allergen[allergen] = set(ingredients) else: ingredients_by_allergen[allergen].intersection_update(ingredients) for i in ingredients: possible_allergen[i].add(i) ingredients_with_known_allergen = set() for _ in range(10): # arbitrary limit which was enough for ingredient_set in ingredients_by_allergen.values(): if len(ingredient_set) == 1: ingredients_with_known_allergen.update(ingredient_set) else: ingredient_set.difference_update(ingredients_with_known_allergen) safe = 0 for recipe in recipies: for i in recipe: if i not in ingredients_with_known_allergen: safe += 1 print("part1", safe) d = [] for allergen, ingredient_set in ingredients_by_allergen.items(): assert len(ingredient_set) == 1 i = list(ingredient_set)[0] d.append((i, allergen)) d.sort(key=lambda x: x[1]) print("part2", ",".join(x[0] for x in d))
def main(file): print("RUNNING", file) counts = defaultdict(int) for line in aocutils.readlines(file): x0, y0, x1, y1 = [int(i) for i in aocutils.multisplit(line, [',', ' -> ', ','])] if x0 == x1: for i in range(min(y0, y1), max(y0, y1) + 1): counts[(x0, i)] += 1 elif y0 == y1: for i in range(min(x0, x1), max(x0, x1) + 1): counts[(i, y0)] += 1 else: xdir = -1 if x0 > x1 else 1 ydir = -1 if y0 > y1 else 1 for i in range(0, max(x0, x1) - min(x0, x1) + 1): counts[(x0 + (i * xdir), y0 + (i * ydir))] += 1 n = 0 for x in counts.values(): if x >= 2: n += 1 print(n)
def main(file): print("RUNNING", file) line = list(aocutils.readlines(file))[0] _, tx0, tx1, ty0, ty1 = aocutils.multisplit( line, ['target area: x=', '..', ', y=', '..']) tx0 = int(tx0) tx1 = int(tx1) ty0 = int(ty0) ty1 = int(ty1) maxy = None for y in range(1, 1000): ok, reached = simulate(0, y, tx0, tx1, ty0, ty1, ignore_x=True) if ok: maxy = y while maxy > 0: for x in range(1, tx1 + 1): ok, reached = simulate(x, maxy, tx0, tx1, ty0, ty1) if ok: print(x, maxy) print(reached) return maxy -= 1
def main(file): print("RUNNING", file) counts = defaultdict(int) for line in aocutils.readlines(file): x0, y0, x1, y1 = [ int(i) for i in aocutils.multisplit(line, [',', ' -> ', ',']) ] if x0 != x1 and y0 != y1: continue if x0 == x1: for i in range(min(y0, y1), max(y0, y1) + 1): counts[(x0, i)] += 1 elif y0 == y1: for i in range(min(x0, x1), max(x0, x1) + 1): counts[(i, y0)] += 1 else: assert False n = 0 for x in counts.values(): if x >= 2: n += 1 print(n)
def test_multisplit(): assert aocutils.multisplit("1-3 b: cdefg", ["-", " ", ": "]) == ["1", "3", "b", "cdefg"]