visited.append(person) # If all of the people are in the list, add the total change in happiness to the possibilities if len(visited) == len(happiness): total_so_far += (happiness[first_person][person] + happiness[person][first_person]) possibilities.append(total_so_far) # For each person the person can sit beside for neighbor in happiness[person]: # If they're already in the list, skip them if neighbor in visited: continue # Get all the possibilities of the next person's neighbor calc_possibilities( first_person, neighbor, visited, total_so_far + happiness[neighbor][person] + happiness[person][neighbor], ) # Start with each person and go around the table, trying every combination for p in happiness: for n in happiness[p]: calc_possibilities(p, n, [p], happiness[p][n] + happiness[n][p]) aoc.p2(max(possibilities))
finished_cards = list(filter(is_bingo, bingo_cards)) if finished_cards: last_drawn = n bingo = finished_cards[0] break unmarked = sum(filter(None, flatten(bingo))) aoc.p1(unmarked * last_drawn) # Part 2 bingo_cards = chunk(5, list(filter(None, data.numbers_by_line()[1:]))) for n in numbers_drawn: bingo_cards = [ [[b[r][c] if b[r][c] != n else None for c in range(5)] for r in range(5)] for b in bingo_cards ] unfinished_cards = list(filter(lambda x: not is_bingo(x), bingo_cards)) if len(bingo_cards) == 1 and len(unfinished_cards) == 0: last_drawn = n bingo = bingo_cards[0] break bingo_cards = unfinished_cards unmarked = sum(filter(None, flatten(bingo))) aoc.p2(unmarked * last_drawn)
fish = data.nums() for _ in range(80): nfish = [] for f in fish: if f == 0: nfish.append(6) nfish.append(8) else: nfish.append(f - 1) fish = nfish aoc.p1(len(fish)) # Part 2 fish = [0] * 9 for f in data.nums(): fish[f] += 1 for _ in range(256): nfish = [0] * 9 for i, f in enumerate(fish[1:]): nfish[i] = f nfish[6] += fish[0] nfish[8] = fish[0] fish = nfish aoc.p2(sum(fish))
previous_deck_orders = set() while first_hand and second_hand: tuple_first = tuple(first_hand) tuple_second = tuple(second_hand) if (tuple_first, tuple_second) in previous_deck_orders: return 1 previous_deck_orders.add((tuple_first, tuple_second)) p1 = first_hand.pop(0) p2 = second_hand.pop(0) if len(first_hand) >= p1 and len(second_hand) >= p2: winner = play_game(first_hand[:p1], second_hand[:p2]) else: winner = 1 if p1 > p2 else 2 if winner == 1: first_hand.append(p1) first_hand.append(p2) else: second_hand.append(p2) second_hand.append(p1) return 1 if first_hand else 2 while first_hand and second_hand: play_game(first_hand, second_hand) aoc.p2(calculate_score(first_hand if first_hand else second_hand))
return { "header": header, "length": node_length, "children": children, "metadata": metadata, } root_node = build_node(data.numbers_by_line()[0]) def value_of_node(node): if not node["children"]: return sum(node["metadata"]) meta_cache = {} value = 0 for metadata in node["metadata"]: index = metadata - 1 if index == -1: continue if index < node["header"]["children"]: if index not in meta_cache: meta_cache[index] = value_of_node(node["children"][index]) value += meta_cache[index] return value aoc.p2(value_of_node(root_node))
] # Initial position (number 5) start = (2, 0) code = [] keypad_height = len(layout) keypad_width = len(layout[0]) def is_valid(y, x): # Returns true if a position is valid on the keypad return (x in range(0, keypad_width) and y in range(0, keypad_height) and layout[y][x] != -1) for line in data.lines(): for char in line: if char == "L" and is_valid(start[0], start[1] - 1): start = (start[0], start[1] - 1) if char == "U" and is_valid(start[0] - 1, start[1]): start = (start[0] - 1, start[1]) if char == "R" and is_valid(start[0], start[1] + 1): start = (start[0], start[1] + 1) if char == "D" and is_valid(start[0] + 1, start[1]): start = (start[0] + 1, start[1]) code.append(layout[start[0]][start[1]]) aoc.p2("".join([str(c) for c in code]))
from math import prod from aoc import AOC, mins aoc = AOC(year=2015, day=2) data = aoc.load() ## Part 1 total_square_feet = 0 for sides in data.numbers_by_line(): first = sides[0] * sides[1] second = sides[1] * sides[2] third = sides[2] * sides[0] total_square_feet += 2 * (first + second + third) + min( first, second, third) aoc.p1(total_square_feet) ## Part 2 # Initialize to 0 feet of ribbon total_length = 0 for sides in data.numbers_by_line(): total_length += sides[0] * sides[1] * sides[2] + (2 * sum(mins(sides, 2))) aoc.p2(total_length)
aoc = AOC(year=2015, day=5) data = aoc.load() ## Part 1 # Create regex to match the rules # 1. Contains at least one pair of two letters that appears twice (non-overlapping) # 2. At least one letter that repeats, with one letter between them nicestring_regex = re.compile( r"^(?=\w*(\w)\w\1\w*)(\w*(\w\w)\w*\3\w*)$", flags=re.MULTILINE ) total_nicestrings = len(re.findall(nicestring_regex, data.contents())) # Print the total number of nice strings aoc.p1(total_nicestrings) ## Part 2 # Create regex to match the rules # 1. Contains at least 3 values # 2. At least one letter that appears twice in a row # 3. Does not contain 'ab', 'cd', 'pq', or 'xy' nicestring_regex = re.compile( r"^(?=\w*(\w)\1)(?!\w*(ab|cd|pq|xy))((\w*[aeiou]\w*){3,})$", flags=re.MULTILINE ) total_nicestrings = len(re.findall(nicestring_regex, data.contents())) # Print the total number of nice strings aoc.p2(total_nicestrings)
def resolve_infinite_rule(r): @lru_cache def manual_resolution(r): if r == 8: return "(" + resolver(42) + ")" + "+" elif r == 11: return ("(" + "|".join( [resolver(42) * i + resolver(31) * i for i in range(1, 5)]) + ")") return "" @lru_cache def resolver(r): if type(rules[r][0]) is str: return rules[r][0] else: return ("(" + "|".join([ "".join([ manual_resolution(y) if y in [8, 11] else resolver(y) for y in x ]) for x in rules[r] ]) + ")") return "^" + resolver(r) + "$" rule_zero = resolve_infinite_rule(0) aoc.p2(len([1 for x in chunks[1] if re.match(rule_zero, x)]))
gamma = int(gamma, 2) epsilon = int(epsilon, 2) aoc.p1(gamma * epsilon) # Part 2 def least_common_bit(bitstrings, position): bits = {0: 0, 1: 0} for b in bitstrings: bits[int(b[position])] += 1 return 1 if bits[1] < bits[0] else 0 def reduce(bitstrings, position, bit_extractor): reduced = [] comparator = str(bit_extractor(bitstrings, position)) reduced = [b for b in bitstrings if b[position] == comparator] if len(reduced) == 1: return int(reduced[0], 2) else: return reduce(reduced, position + 1, bit_extractor) oxygen = reduce(data.lines(), 0, most_common_bit) co = reduce(data.lines(), 0, least_common_bit) aoc.p2(oxygen * co)
aoc = AOC(year=2021, day=2) data = aoc.load() # Part 1 x, y = 0, 0 for command, value in data.parse(r"(\w+) (\d+)"): if command == "forward": x += value if command == "down": y += value if command == "up": y -= value aoc.p1(x * y) # Part 2 x, y, aim = 0, 0, 0 for command, value in data.parse(r"(\w+) (\d+)"): if command == "forward": x += value y += aim * value if command == "down": aim += value if command == "up": aim -= value aoc.p2(x * y)
# Part 1 cups = get_cups(False) lowest = min(cups.keys()) highest = max(cups.keys()) current = next(iter(cups.keys())) for _ in range(100): step() current = cups[1]["n"] labels = [] while current != 1: labels.append(str(cups[current]["v"])) current = cups[current]["n"] aoc.p1("".join(labels)) # Part 2 cups = get_cups(True) lowest = min(cups.keys()) highest = max(cups.keys()) current = next(iter(cups.keys())) for _ in range(10_000_000): step() aoc.p2(cups[1]["n"] * cups[cups[1]["n"]]["n"])
## Part 2 # Door ID which prefixes hashed value door_id = "reyedfim" # Starting value to hash hashed_integer = -1 values_found = 0 code = [None, None, None, None, None, None, None, None] while values_found < 8: hashed_integer += 1 hashed = md5() hashed.update((door_id + str(hashed_integer)).encode()) digest = hashed.hexdigest() # While the first 5 characters are not all 0s, increment the counter and generate a new hash while digest[:5] != "00000": hashed_integer += 1 hashed = md5() hashed.update((door_id + str(hashed_integer)).encode()) digest = hashed.hexdigest() if digest[5].isdigit() and int(digest[5]) < 8 and code[int(digest[5])] is None: values_found += 1 code[int(digest[5])] = digest[6] # Print the password aoc.p2("".join(code))
def in_target_range(position): return position.x in target_x and position.y in target_y [minX, maxX, minY, maxY] = data.nums() target_x = range(minX, maxX + 1) target_y = range(minY, maxY + 1) total_max_height = 0 total_valid_velocities = 0 for dx in range(max(target_x) + 1): for dy in range(min(target_y) - 1, 200): position = Position(0, 0) velocity = Position(dx, dy) max_height = 0 while not overshot(position) and not in_target_range(position): step(position, velocity) max_height = max(position.y, max_height) if velocity.x == 0 and position.x < min(target_x): break if in_target_range(position): total_max_height = max(total_max_height, max_height) total_valid_velocities += 1 aoc.p1(total_max_height) aoc.p2(total_valid_velocities)
def count_trees(slope): position = Position(0, 0) trees = 0 while position.y < len(hill): if hill[position.y][position.x % len(hill[0])] == "#": trees += 1 position = Position(position.x + slope.x, position.y + slope.y) return trees aoc.p1(count_trees(Position(3, 1))) # Part 2 aoc.p2( math.prod( [ count_trees(slope) for slope in [ Position(1, 1), Position(3, 1), Position(5, 1), Position(7, 1), Position(1, 2), ] ] ) )
from aoc import AOC import math from itertools import chain aoc = AOC(year=2020, day=1) data = aoc.load() # Part 1 expenses = set(data.numbers()) product = math.prod([e for e in expenses if (2020 - e) in expenses]) aoc.p1(product) # Part 2 product = math.prod( set( chain(*[[e for e in expenses if (2020 - f - e) in expenses] for f in expenses]))) aoc.p2(product)
from aoc import AOC, chinese_remainder import itertools import re aoc = AOC(year=2020, day=13) contents = aoc.load().lines() # Part 1 start_time = int(contents[0]) buses = [int(v) for v in re.findall(r"\d+", contents[1])] for i in itertools.count(start_time): departing_bus = next((bid for bid in buses if (start_time + i) % bid == 0), False) if departing_bus: aoc.p1(departing_bus * i) break # Part 2 buses = [int(b) if b != "x" else b for b in contents[1].split(",")] n = [bid for bid in buses if bid != "x"] a = [0] + [bid - (idx + 1) for idx, bid in enumerate(buses[1:]) if bid != "x"] aoc.p2(chinese_remainder(n, a))
# Part 1 comp = Computer(data) def run_until_finished(comp): seen = set() while comp.position not in seen and not comp.is_finished(): seen.add(comp.position) comp.step() return comp.is_finished() run_until_finished(comp) aoc.p1(comp.accumulator) # Part 2 for idx, ins in enumerate(comp.instructions()): modified_comp = Computer(data) if ins[0] == "jmp": modified_comp.replace(idx, "nop") elif ins[0] == "nop": modified_comp.replace(idx, "jmp") if run_until_finished(modified_comp): break aoc.p2(modified_comp.accumulator)
aoc.p1(abs(ship.x) + abs(ship.y)) # Part 2 def rotate_waypoint(waypoint, ins, val): while val >= 90: if ins == "R": waypoint = Position(-waypoint.y, waypoint.x) if ins == "L": waypoint = Position(waypoint.y, -waypoint.x) val -= 90 return waypoint def command_waypoint(ship, waypoint, ins, val): if ins in ["L", "R"]: return ship, rotate_waypoint(waypoint, ins, val) elif ins == "F": return move_ship(ship, waypoint, val), waypoint else: return ship, move_ship(waypoint, Direction[ins].position, val) ship, waypoint = Position(0, 0), Position(10, -1) for instruction in data.parse_lines(r"(\w)(\d+)"): ship, waypoint = command_waypoint(ship, waypoint, instruction[0], int(instruction[1])) aoc.p2(abs(ship.x) + abs(ship.y))
count = 10 seconds = 0 while True: if should_print: print_lights(points) updated_points = {} for point in points: x, y = point for velocity in points[point]: xx, yy = velocity new_point = (x + xx, y + yy) if new_point in updated_points: updated_points[new_point].append((xx, yy)) else: updated_points[new_point] = [(xx, yy)] points = updated_points seconds += 1 count -= 1 if count == 0: count = 10 ys = [x[0] for x in points.keys()] top, height = min(ys), max(ys) did_print = should_print should_print = abs(top - height) < 100 if not should_print and did_print != should_print: break aoc.p2(10081)
if pos == end: return risk for adj in pos.adjacent(diagonal=False): if adj not in visited: new_risk = risk + grid[adj.y][adj.x] visited.add(adj) heappush(q, (new_risk, adj)) base_grid = data.digits_by_line() aoc.p1(least_risk_path(base_grid)) full_grid = [[0 for _ in range(len(base_grid[0]) * 5)] for _ in range(len(base_grid) * 5)] # Generate first row for full_grid for y, row in enumerate(base_grid): for i in range(5): for x, value in enumerate(row): full_grid[y][x + i * len(row)] = (value + i - 1) % 9 + 1 # Copy first X rows to rest of full_grid for i in range(5): for y, row in enumerate(full_grid[:len(base_grid)]): for x, value in enumerate(row): full_grid[y + i * len(base_grid)][x] = (value + i - 1) % 9 + 1 aoc.p2(least_risk_path(full_grid))
hashed.update(("ckczppom" + str(lowest_positive_int)).encode()) digest = hashed.hexdigest() # While the first 5 characters are not all 0s, increment the counter and generate a new hash while digest[:5] != "00000": lowest_positive_int += 1 hashed = md5() hashed.update((PUZZLE_INPUT + str(lowest_positive_int)).encode()) digest = hashed.hexdigest() # Print the lowest valid positive integer aoc.p1(lowest_positive_int) ## Part 2 # Start with checking 1 lowest_positive_int = 1 hashed = md5() hashed.update(("ckczppom" + str(lowest_positive_int)).encode()) digest = hashed.hexdigest() # While the first 6 characters are not all 0s, increment the counter and generate a new hash while digest[:6] != "000000": lowest_positive_int += 1 hashed = md5() hashed.update((PUZZLE_INPUT + str(lowest_positive_int)).encode()) digest = hashed.hexdigest() # Print the lowest valid positive integer aoc.p2(lowest_positive_int)
flashed.add((x, y)) for adj in Position(x, y).adjacent(): if adj.tuple not in flashed: # Octo's can only flash once in a step, so skip if they've already flashed needs_increment.append(adj.tuple) clear_flashed(octos) return octos, len(flashed) # Part 1 total = 0 for _ in range(100): octos, flash_count = step(octos) total += flash_count aoc.p1(total) # Part 2 octos = data.digits_by_line() flash_count = 0 step_count = 0 while flash_count != 100: octos, flash_count = step(octos) step_count += 1 aoc.p2(step_count)
# Part 1 def passes_basic_validation(passport): return (set(passport.keys()) - set(["cid"])) == passport_properties aoc.p1(len([p for p in passports if passes_basic_validation(p)])) # Part 2 validations = [ lambda p: passes_basic_validation(p), lambda p: 1920 <= int(p["byr"]) <= 2002, lambda p: 2010 <= int(p["iyr"]) <= 2020, lambda p: 2020 <= int(p["eyr"]) <= 2030, lambda p: (p["hgt"][3:] == "cm" and 150 <= int(p["hgt"][0:3]) <= 193) or (p["hgt"][2:] == "in" and 59 <= int(p["hgt"][0:2]) <= 76), lambda p: re.search(r"^#[0-9a-fA-F]{6}$", p["hcl"]), lambda p: p["ecl"] in ["amb", "blu", "brn", "gry", "grn", "hzl", "oth"], lambda p: re.search(r"^[0-9]{9}$", p["pid"]), ] def passes_validation(passport): return all(v(passport) for v in validations) aoc.p2(len([p for p in passports if passes_validation(p)]))
if in_range(water): touched.add(water) for x in range(left_edge[0], right_edge[0] + 1): cell = (x, water[1]) if left_enclosed and right_enclosed: settled_water.add(cell) if in_range(cell): touched.add(cell) under_left = under(left_edge) if not left_enclosed: if in_range(under_left): moving_water.append(under_left) else: spilled_water.add(under_left) under_right = under(right_edge) if not right_enclosed: if in_range(under_right) and (not moving_water or moving_water[-1] != under_right): moving_water.append(under_right) else: spilled_water.add(under_right) if left_enclosed and right_enclosed: moving_water.append(above(water)) aoc.p2(settled_water)
if battle.check_game_over(): if battle.boss.hp <= 0: minimum_mana = ( min(minimum_mana, battle.total_cost) if minimum_mana != -1 else battle.total_cost ) continue next_battles = [battle.duplicate() for x in spells] for index, next_battle in enumerate(next_battles): if not next_battle.can_cast(spell_names[index]): continue next_battle.player_turn(spell_names[index]) if next_battle.check_game_over(): if next_battle.boss.hp <= 0: minimum_mana = ( min(minimum_mana, next_battle.total_cost) if minimum_mana != -1 else next_battle.total_cost ) continue next_battle.boss_turn() battles.append(next_battle) aoc.p2(minimum_mana)
field_indices = {r: set(range(len(tickets[0]))) for r in rules.keys()} confirmed_fields = set() for t in tickets: for i, v in enumerate(t): for r in rules: if not any(v in x for x in rules[r]): field_indices[r].remove(i) while True: nf = next( (f for f in field_indices if f not in confirmed_fields and len(field_indices[f]) == 1), None, ) if not nf: break idx, next_field = next(iter(field_indices[nf])), nf for f in field_indices: if f == next_field or idx not in field_indices[f]: continue field_indices[f].remove(idx) confirmed_fields.add(next_field) aoc.p2( prod([ your_ticket[next(iter(field_indices[x]))] for x in field_indices if "departure " in x ]))
# Only update the score if the calorie count is 500 if calorie_score == 500: # Return the product of all scores, replacing the score with 0 if it is less than 0 return (max(capacity_score, 0) * max(durability_score, 0) * max(flavor_score, 0) * max(texture_score, 0)) return 0 # Find the next unused ingredient and try all the combination of ingredients to get the best score for ingredient in ingredients: if not ingredient in amounts: best = 0 for c in range(101 - total_used): updated_amounts = amounts.copy() updated_amounts[ingredient] = c best = max(get_best_score(updated_amounts, total_used + c), best) return best return 0 # Pick an ingredient to start and recursively get the best score best_score = 0 for item in ingredients: for count in range(101): score = get_best_score({item: count}, count) if score > best_score: best_score = score break aoc.p2(best_score)
if len(remaining_containers) == 0: return # Get the total size of all the containers containers_total = sum(containers_used) # Iterate over each of the remaining containers for i in range(len(remaining_containers) - 1, -1, -1): # If the target eggnog amount is met, then check if the number of containers is the minimum if containers_total + remaining_containers[i] == target_eggnog: if (minimum_containers == -1 or len(containers_used) + 1 < minimum_containers): minimum_containers = len(containers_used) + 1 minimum_arrangements = 1 elif len(containers_used) + 1 == minimum_containers: minimum_arrangements += 1 # If the total is too large, then only larger containers are remaining elif containers_total + remaining_containers[i] > target_eggnog: break # If the total is too small, keep iterating over the remaining containers elif i > 0: updated_containers_used = containers_used[:] updated_containers_used.append(remaining_containers[i]) get_arrangements(updated_containers_used, remaining_containers[:i]) # Gets the number of total container arrangements get_arrangements([], PUZZLE_INPUT) aoc.p2(minimum_arrangements)
from aoc import AOC aoc = AOC(year=2021, day=7) data = aoc.load() crabs = data.nums() # Part 1 aoc.p1(min(sum(abs(c - i) for c in crabs) for i in range(max(crabs)))) # Part 2 aoc.p2( min( sum((abs(c - i) * (abs(c - i) + 1)) // 2 for c in crabs) for i in range(max(crabs)) ) )