def main(file): print("RUNNING", file) sections = list(aocutils.readsections(file)) rules = {} for line in sections[0]: key, match = line.split(": ") key = int(key) if '"' in match: rules[key] = Literal(match.strip('"')) elif "|" in match: r1, r2 = match.split(" | ") refs1 = [int(r) for r in r1.split(" ")] refs2 = [int(r) for r in r2.split(" ")] rules[key] = Opt(rules, refs1, refs2) else: refs = [int(r) for r in match.split(" ")] rules[key] = Ref(rules, refs) start_rule = rules[0] matches = 0 for line in sections[1]: for match_length in start_rule.matches(line, 0): if match_length == len(line): matches += 1 break print(matches)
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) sections = list(aocutils.readsections(file)) currentp = sections[0][0] subs = {} for line in sections[1]: a, b = line.split(' -> ') subs[a] = b print(currentp) nextp = [] for step in range(10): print(step) for i in range(len(currentp) - 1): l = currentp[i:i + 2] if l in subs: nextp.append(l[0]) nextp.append(subs[l]) else: nextp.append(l[0]) nextp.append(l[1]) currentp = ''.join(nextp) nextp = [] counts = defaultdict(int) for c in currentp: counts[c] += 1 print(counts) values = sorted([(v, k) for k, v in counts.items()]) print(values) print(values[-1][0] - values[0][0])
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) 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 main(file, rounds): print("RUNNING", file) sections = list(aocutils.readsections(file)) algo = ''.join(sections[0]) image = set() image_inverted = False minx = 0 miny = 0 maxx = 0 maxy = 0 for y, line in enumerate(sections[1]): for x, char in enumerate(line): if char == '#': image.add((x, y)) maxy = max(maxy, y) maxx = max(maxx, x) invert_mode = False if algo[0] == '#': assert algo[0b111_111_111] == '.' invert_mode = True for round in range(rounds): new_image = set() new_image_inverted = invert_mode and not image_inverted outputx = list(range(minx - 1, maxx + 2)) outputy = list(range(miny - 1, maxy + 2)) for y in outputy: for x in outputx: bitstr = [] for yi in range(y - 1, y + 2): for xi in range(x - 1, x + 2): if (xi, yi) in image: if image_inverted: bitstr.append('0') else: bitstr.append('1') else: if image_inverted: bitstr.append('1') else: bitstr.append('0') val = int(''.join(bitstr), 2) if new_image_inverted: is_filled = algo[val] == '.' else: is_filled = algo[val] == '#' if is_filled: new_image.add((x, y)) minx = min(minx, x) miny = min(miny, y) maxx = max(maxx, x) maxy = max(maxy, y) image = new_image image_inverted = new_image_inverted print(len(image))
def main(file): print("RUNNING", file) sections = list(aocutils.readsections(file)) deck1 = deque(int(x) for x in sections[0][1:]) deck2 = deque(int(x) for x in sections[1][1:]) _, winning_deck = recursive_combat(deck1, deck2) winning_deck = list(winning_deck) print(winning_deck) print(sum([x * (i + 1) for i, x in enumerate(reversed(winning_deck))]))
def main(file): print("RUNNING", file) sections = list(aocutils.readsections(file)) numbers = [int(x) for x in sections[0][0].split(',')] boards = [] for b in sections[1:]: rows = [] for row in b: rows.append([int(x) for x in row.split(' ') if x]) boards.append(rows) play_bingo(boards, numbers)
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): check_rotations() print("RUNNING", file) scanners = [] for sec_lines in aocutils.readsections(file): scanner = [] for line in sec_lines[1:]: scanner.append(tuple(int(x) for x in line.split(','))) scanners.append(scanner) # Scanner ID is their index in 'scanners' searched = set() offsets = { 0: (0, 0, 0) } to_search = [0] while to_search: progress = (len(searched) + len(offsets)) * 50 / len(scanners) print(f'{round(progress)}%') if len(offsets) == len(scanners): break s1_id = to_search.pop() assert s1_id not in searched searched.add(s1_id) s1 = scanners[s1_id] s1_set = set(s1) for s2_id, s2 in enumerate(scanners): if s1_id == s2_id: continue if s2_id in offsets: continue for rotate in rotations: rotated_s2 = [rotate(x) for x in s2] if offset := scanners_overlaps(s1_set, rotated_s2): scanners[s2_id] = rotated_s2 offsets[s2_id] = ( offsets[s1_id][0] + offset[0], offsets[s1_id][1] + offset[1], offsets[s1_id][2] + offset[2], ) to_search.append(s2_id) break
def main(file): print("RUNNING", file) sections = list(aocutils.readsections(file)) deck1 = deque(int(x) for x in sections[0][1:]) deck2 = deque(int(x) for x in sections[1][1:]) while deck1 and deck2: c1 = deck1.popleft() c2 = deck2.popleft() if c1 > c2: deck1.append(c1) deck1.append(c2) elif c1 < c2: deck2.append(c2) deck2.append(c1) else: assert False winning_deck = list(deck1 or deck2) print(winning_deck) print(sum([x * (i + 1) for i, x in enumerate(reversed(winning_deck))]))
def main(file): print("RUNNING", file) sections = list(aocutils.readsections(file)) poly = sections[0][0] subs = {} for line in sections[1]: a, b = line.split(' -> ') subs[a] = b cache = dict() def expand_and_count(pair, num_expands): if num_expands == 0: result = defaultdict(int) result[pair[0]] += 1 result[pair[1]] += 1 return result elif (pair, num_expands) in cache: return cache[(pair, num_expands)] else: new_char = subs[pair] r1 = expand_and_count(pair[0] + new_char, num_expands - 1) r2 = expand_and_count(new_char + pair[1], num_expands - 1) result = r1.copy() for k, v in r2.items(): result[k] += v result[new_char] -= 1 cache[(pair, num_expands)] = result return result total_counts = defaultdict(int) for i in range(len(poly) - 1): for k, v in expand_and_count(poly[i:i + 2], 40).items(): total_counts[k] += v for i in range(1, len(poly) - 1): total_counts[poly[i]] -= 1 values = sorted([(v, k) for k, v in total_counts.items()]) print(len(cache)) print(values) print(values[-1][0] - values[0][0])
def main(file): print("RUNNING", file) sections = list(aocutils.readsections(file)) currentp = sections[0][0] subs = {} subs20 = {} for line in sections[1]: a, b = line.split(' -> ') subs[a] = b # Pre calculate result of doing 20 expansion for all pairs for a in subs.keys(): print(a) subs20[a] = expand20x(a, subs) # Expand x20 nextp = [] for i in range(len(currentp)-1): pair = currentp[i:i + 2] nextp.append(subs20[pair][:-1]) nextp.append(pair[1]) currentp = ''.join(nextp) # Expand x20 again, but only count characters this time counts20 = {} total_count = defaultdict(int) for i in range(len(currentp) - 1): pair = currentp[i:i + 2] if pair not in counts20: counts20[pair] = count_chars(subs20[pair]) for k, v in counts20[pair].items(): total_count[k] += v total_count[pair[-1]] += 1 values = sorted([(v, k) for k, v in total_count.items()]) print(values) print(values[-1][0]-values[0][0])
def main(file): print("RUNNING", file) sections = list(aocutils.readsections(file)) maxx = 0 maxy = 0 for line in sections[0]: x, y = [int(a) for a in line.split(',')] maxx = max(maxx, x) maxy = max(maxy, y) grid = [] for _ in range(maxy + 1): grid.append([False for _ in range(maxx + 1)]) for line in sections[0]: x, y = [int(x) for x in line.split(',')] grid[y][x] = True folds = sections[1] for fold in folds: instr, v = fold.split('=') v = int(v) if instr == "fold along x": part1 = [r[:v] for r in grid] part2 = [r[v + 1:] for r in grid] part1 = vflip(part1) result = merge(part1, part2) result = vflip(result) grid = result elif instr == "fold along y": part1 = grid[:v] part2 = grid[v + 1:] part1 = hflip(part1) result = merge(part1, part2) result = hflip(result) grid = result print_grid(grid)