from misc import init_day init_day(__file__, 15) def get_numbers(numbers_list): return [int(n) for n in numbers_list.split(',')] def solve(numbers_list, position): numbers = get_numbers(numbers_list) count = len(numbers) track = dict((number, index + 1) for index, number in enumerate(numbers)) while count <= position: last_number = numbers[count - 1] tracked = track.get(last_number) if tracked: number = count - tracked else: number = 0 track[last_number] = count numbers.append(number) count += 1 return last_number assert solve('0,3,6', 2020) == 436 assert solve('1,3,2', 2020) == 1 assert solve('2,1,3', 2020) == 10
import math from misc import init_day from input import get_file_lines_with_no_blanks init_day(__file__, 3) forest = get_file_lines_with_no_blanks('./input.txt') def get_tree_count(slope_horizontal, slope_vertical): tree_count = 0 pos = 0 for index, row in enumerate(forest): if index % slope_vertical == 0: if row[pos] == '#': tree_count += 1 pos = (pos + slope_horizontal) % len(row) return tree_count result_1 = get_tree_count(3, 1) result_2 = math.prod( get_tree_count(h, v) for (h, v) in [(1, 1), (3, 1), (5, 1), (7, 1), (1, 2)]) print(f'Part 1: {result_1}') print(f'Part 2: {result_2}')
from misc import init_day from input import get_file_lines_with_no_blanks init_day(__file__, 8) class Machine: def __init__(self): self.pc = 0 self.acc = 0 self.memory = [] self.looped = False def parse(self, mnemonics): self.memory = [] for line in mnemonics: operation, argument = line.split(' ') argument = int(argument) self.memory.append((operation, argument)) def run(self): self.pc = 0 self.acc = 0 self.looped = False pc_already_executed = {} memory_size = len(self.memory) while self.pc < memory_size: already_executed = pc_already_executed.get(self.pc, False) if already_executed: self.looped = True return
import re from functools import reduce from collections import Counter from misc import init_day from input import get_file_lines_with_no_blanks init_day(__file__, 24) MOVEMENT_DELTAS = { 'e': (1, 0), 'w': (-1, 0), 'ne': (0, 1), 'sw': (0, -1), 'se': (1, -1), 'nw': (-1, 1), } def sum_coords(coord_1, coord_2): return (coord_1[0] + coord_2[0], coord_1[1] + coord_2[1]) def sum_all_coords(coords_list): return reduce(sum_coords, coords_list, (0, 0)) def get_blacks_for_movements(input): movements = [re.findall(r'se|sw|nw|ne|e|w', line) for line in input] flips = Counter( sum_all_coords([MOVEMENT_DELTAS[direction] for direction in movement])
from functools import cache from misc import init_day from input import get_file_numbers init_day(__file__, 10) def solve_part_1(part1_input): adaptors = sorted(part1_input[:]) adaptors.append(max(adaptors) + 3) joltage = 0 previous_joltage = 0 diff_count = {1: 0, 3: 0} while len(adaptors) > 0: next_adaptor = next(adaptor for adaptor in adaptors if (adaptor - joltage) in [1, 3]) adaptors.remove(next_adaptor) joltage = next_adaptor diff_count[joltage - previous_joltage] += 1 previous_joltage = joltage return diff_count[1] * diff_count[3] def solve_part_2(part2_input): @cache def get_combinations(joltage, candidate_adaptors, ignore_adaptor=-1): next_adaptors = tuple([ adaptor for adaptor in candidate_adaptors if (1 <= adaptor - joltage <= 3) and (adaptor != ignore_adaptor) ])
from math import prod from misc import init_day from input import get_file_lines_with_no_blanks init_day(__file__, 13) def solve_part_1(timestamp, ids_text): ids = [int(id) for id in ids_text.split(',') if id != 'x'] ids_with_remainders = [(id, id - (timestamp % id)) for id in ids] good_id, good_wait_time = min(ids_with_remainders, key=lambda item: item[1]) return good_id * good_wait_time def solve_part_2(ids_text): def chinese_remainder(divisors, remainders): # Chinese remainder will give a number that when divided by some divisors leaves certain remainders. product = prod(divisors) return sum(r * pow(product // d, -1, d) * (product // d) for d, r in zip(divisors, remainders)) % product ids = [0 if id == 'x' else int(id) for id in ids_text.split(',')] buses = [(index, id) for index, id in enumerate(ids) if id != 0] actual_ids = [id for id in ids if id != 0] bus_offsets = [timestamp - index for index, timestamp in buses] return chinese_remainder(actual_ids, bus_offsets) test_input = get_file_lines_with_no_blanks('test.txt') test_timestamp, test_ids_text = int(test_input[0]), test_input[1]
import re from math import prod from misc import init_day from input import get_file_lines_with_no_blanks init_day(__file__, 16) def parse_input(problem_input): def parse_int_list(number_list): return [int(number) for number in number_list.split(',')] tickets = [] fields = [] for line in problem_input: if re.match(r'^[\d,]+$', line): tickets.append(line) else: field_matches = re.match(r'^(.+)\s*:', line) if field_matches: field = field_matches.group(1) ranges = [(int(range_min), int(range_max)) for range_min, range_max in re.findall( r'(\d+)\-(\d+)', line)] fields.append((field, ranges)) my_ticket = parse_int_list(tickets[0]) nearby_tickets = [parse_int_list(ticket) for ticket in tickets[1:]] return my_ticket, nearby_tickets, fields
from misc import init_day from input import get_file_numbers from indexes import unique_index_pairs, unique_index_triplets init_day(__file__, 1) numbers = get_file_numbers('./input.txt') number_count = len(numbers) for i, j in unique_index_pairs(number_count): if numbers[i] + numbers[j] == 2020: print(f'Part 1: {numbers[i] * numbers[j]}') break for i, j, k in unique_index_triplets(number_count): if numbers[i] + numbers[j] + numbers[k] == 2020: print(f'Part 2: {numbers[i] * numbers[j] * numbers[k]}') break
from misc import init_day init_day(__file__, 25) def transform_subject_number(subject_number, loop_size): value = 1 for _ in range(loop_size): value *= subject_number value = value % 20201227 return value def find_loop_size(public_key, subject_number): loop_size = 1 calculated_publick_key = 1 while True: calculated_publick_key *= subject_number calculated_publick_key = calculated_publick_key % 20201227 if calculated_publick_key == public_key: return loop_size loop_size += 1 def solve_part_1(card_public_key, door_public_key, check_key_match): card_loop_size = find_loop_size(card_public_key, 7) door_loop_size = find_loop_size(door_public_key, 7) encryption_key = transform_subject_number(card_public_key, door_loop_size) if check_key_match: encryption_key_2 = transform_subject_number(door_public_key, card_loop_size)
import re from collections import defaultdict from misc import init_day from input import get_file_lines_with_no_blanks init_day(__file__, 14) def transform_mask(mask, transform_map): return ''.join([transform_map.get(char, char) for char in mask]) def solve_part_1(problem_input): mem = defaultdict(int) for line in problem_input: mask_matches = re.match(r'^mask\s*=\s*([X01]{36})$', line) if mask_matches: mask = mask_matches.group(1) and_mask = int(transform_mask(mask, {'X': '1', '1': '0', '0': '0'}), 2) or_mask = int(transform_mask(mask, {'X': '0', '1': '1', '0': '0'}), 2) else: assign_matches = re.match(r'^mem\[(\d+)\]\s*=\s*(\d+)$', line) if assign_matches: mem_pos = int(assign_matches.group(1)) mem_value = int(assign_matches.group(2)) mem[mem_pos] = (mem_value & and_mask) | or_mask return sum(mem.values()) def solve_part_2(problem_input):
import re from misc import init_day from input import get_file_lines_with_no_blanks init_day(__file__, 7) def contains_bag(definitions, container, bag): if container == bag: return True container_definition = definitions[container] for number, contained_bag in container_definition: if contains_bag(definitions, contained_bag, bag): return True return False def bag_contents_count(definitions, bag): def bag_contents_count_rec(definitions, bag): container_definition = definitions[bag] return 1 + sum( number * bag_contents_count_rec(definitions, contained_bag) for number, contained_bag in container_definition) return bag_contents_count_rec(definitions, bag) - 1 def get_bags_definition(input): result = {} for line in input:
import re from misc import init_day from input import get_file_lines_with_no_blanks init_day(__file__, 21) def parse_input(input): all_ingredients = [] allergen_reference = dict() index = 0 for line in input: matches = re.match(r'^([a-z\s]+) \(contains ([a-z,\s]+)\)$', line) ingredients_list = matches.group(1).split() allergens = matches.group(2).split(', ') all_ingredients.append(ingredients_list) for allergen in allergens: allergen_reference[allergen] = allergen_reference.get(allergen, []) allergen_reference[allergen].append(index) index += 1 allergen_candidates = { key: set.intersection( *[set(all_ingredients[k]) for k in allergen_reference[key]]) for key, value in allergen_reference.items() } no_ingredient = set(ingredient for ingredients_list in all_ingredients for ingredient in ingredients_list) no_ingredient = no_ingredient.difference( set.union(*(allergen_candidates.values()))) return all_ingredients, no_ingredient, allergen_candidates
from misc import init_day from input import get_file_numbers init_day(__file__, 9) def solve_part_1(part1_input, preamble_length): def any_pair_sums(collection, target_sum): collection_length = len(collection) for i in range(0, collection_length - 1): for j in range(i + 1, collection_length): if collection[i] + collection[j] == target_sum: return True return False for block_start in range(0, len(part1_input) - preamble_length): block = part1_input[block_start:block_start + preamble_length] number = part1_input[block_start + preamble_length] if not any_pair_sums(block, number): return number raise Exception('Part 1 is unsolvable!') def solve_part_2(part1_input, target): input_length = len(part1_input) for block_size in range(2, input_length): for block_start in range(0, input_length - block_size + 1): block = part1_input[block_start:block_start + block_size] if sum(block) == target: return min(block) + max(block) raise Exception('Part 2 is unsolvable!')
from collections import defaultdict from misc import init_day from input import get_file_lines_with_no_blanks init_day(__file__, 17) def parse_input(problem_input, dimensions): def complete_coords(coords): return coords + [0] * abs((len(coords) - dimensions)) world = defaultdict(bool) for y, line in enumerate(problem_input): for x, char in enumerate(line): coords_list = complete_coords([x, y, 0]) coord = tuple(coords_list) world[coord] = char == '#' return world def get_world_limits_1(world): coords = world.keys() xs = [x for x, _, _ in coords] ys = [y for _, y, _ in coords] zs = [z for _, _, z in coords] min_x = min(xs) max_x = max(xs) min_y = min(ys) max_y = max(ys) min_z = min(zs)
import re from misc import init_day from lists import list_difference from input import get_file_lines_with_no_blanks init_day(__file__, 5) def get_boarding_pass_id(boarding_pass): return int(re.sub(r'[FL]', '0', re.sub(r'[BR]', '1', boarding_pass)), 2) def solve_part_1(boarding_passes): return max( get_boarding_pass_id(boarding_pass) for boarding_pass in boarding_passes) def solve_part_2(boarding_passes): boarding_pass_ids = [ get_boarding_pass_id(boarding_pass) for boarding_pass in boarding_passes ] full_plane_ids = list(range(0, 2**len(boarding_passes[0]))) missing_ids = list_difference(full_plane_ids, boarding_pass_ids) for boarding_pass_id in missing_ids: if (boarding_pass_id - 1) in boarding_pass_ids and ( boarding_pass_id + 1) in boarding_pass_ids: return boarding_pass_id
from misc import init_day init_day(__file__, 23) def cups_clockwise_map(input, num_cups, move_count): cups = [int(cup) for cup in list(str(input))] if num_cups >= 10: cups += [cup for cup in range(10, num_cups + 1)] cup_clockwise_from = {} for index in range(num_cups): if index == num_cups - 1: cup_clockwise_from[cups[index]] = cups[0] else: cup_clockwise_from[cups[index]] = cups[index + 1] current = cups[0] for _ in range(move_count): pick_1 = cup_clockwise_from[current] pick_2 = cup_clockwise_from[pick_1] pick_3 = cup_clockwise_from[pick_2] cup_clockwise_from[current] = cup_clockwise_from[pick_3] destination = current - 1 while destination in [pick_1, pick_2, pick_3] or destination < 1: destination -= 1 if destination < 1: destination = num_cups cup_clockwise_from[pick_3] = cup_clockwise_from[destination] cup_clockwise_from[destination] = pick_1 current = cup_clockwise_from[current] return cup_clockwise_from
import sys import re from misc import init_day from input import get_file_lines_with_no_blanks init_day(__file__, 2) test_pattern = re.compile(r'^(\d+)-(\d+)\s+(\w)\s*:\s*(\w+)$') lines = get_file_lines_with_no_blanks('./input.txt') parsed_list = [] for line in lines: matches = test_pattern.match(line) if matches is None: print(f'Wrong test line: {line}') sys.exit(1) (num1, num2, letter, sequence) = matches.groups() parsed_list.append((int(num1), int(num2), letter, sequence)) valid_count_part1 = 0 for (num1, num2, letter, sequence) in parsed_list: count_letter = len([char for char in sequence if char == letter]) if num1 <= count_letter <= num2: valid_count_part1 += 1 print(f'Part 1: {valid_count_part1}') valid_count_part2 = 0 for (num1, num2, letter, sequence) in parsed_list: count_letter = len([char for char in sequence if char == letter]) if (sequence[num1 - 1] == letter) != (sequence[num2 - 1] == letter):
from misc import init_day from lists import list_intersection from input import get_file_lines init_day(__file__, 6) def solve_part_1(part_answers): def get_group_answers(answer_list): answer_list.append('') result = [] chars = '' for line in answer_list: if line == '': result.append(''.join(set(chars))) chars = '' else: chars = chars + line return result group_answers = get_group_answers(part_answers) return sum(len(group) for group in group_answers) def solve_part_2(part_answers): def get_group_answers(answer_list): answer_list.append('') result = [] chars = '' for line in answer_list: if line == '':
import re from misc import init_day from input import get_file_lines_with_no_blanks init_day(__file__, 18) class Stack: def __init__(self): self.items = [] def is_empty(self): return self.count() == 0 def not_empty(self): return not self.is_empty() def push(self, item): self.items.append(item) def pop(self): return self.items.pop() def peek(self): return self.items[-1] def count(self): return len(self.items) def __repr__(self):
import math import re from collections import defaultdict from misc import init_day from input import get_file_contents init_day(__file__, 20) def parse_input(s): tiles = {} for block in s.split('\n\n'): lines = block.splitlines() matches = re.fullmatch(r'Tile (\d+):', lines[0]) tile_id = int(matches.group(1)) tiles[tile_id] = [list(line) for line in lines[1:]] return tiles def tile_edges(tile): top_edge = tile[0] right_edge = ''.join(row[-1] for row in tile) bottom_edge = tile[-1] left_edge = ''.join(row[0] for row in tile) return (top_edge, right_edge, bottom_edge, left_edge) def tile_variants(tile): result = [] for item in sum(
import re from misc import init_day from input import get_file_lines from lists import list_intersection init_day(__file__, 4) passport_parts = re.compile(r'(\w{3}):(\S+)') def no_validation(value, regexp_matches): return True PASSPORT_VALIDATIONS = { 'byr': (r'\d{4}', lambda v, m: 1920 <= int(v) <= 2002), 'iyr': (r'\d{4}', lambda v, m: 2010 <= int(v) <= 2020), 'eyr': (r'\d{4}', lambda v, m: 2020 <= int(v) <= 2030), 'hgt': (r'(\d+)(cm|in)', lambda v, m: (150 <= int(m[0]) <= 193) if m[1] == 'cm' else (59 <= int(m[0]) <= 76)), 'hcl': (r'#[a-f0-9]{6}', no_validation), 'ecl': (r'(amb|blu|brn|gry|grn|hzl|oth)', no_validation), 'pid': (r'\d{9}', no_validation), } REQUIRED_PASSPORT_KEYS = PASSPORT_VALIDATIONS.keys() REQUIRED_PASSPORT_KEY_COUNT = len(REQUIRED_PASSPORT_KEYS) def parse_passports(passport_input_lines): passport_input_lines.append('')
from misc import init_day from input import get_file_lines_with_no_blanks init_day(__file__, 19) def parse_input(input): rules = {} messages = [] for line in input: if ':' in line: rule_id, rule_content = line.split(': ') rule_id = int(rule_id) if '"' in rule_content: rules[rule_id] = rule_content[1] else: elements = [part.strip() for part in rule_content.split('|')] rules[rule_id] = [[int(item) for item in element.split(' ')] for element in elements] else: messages.append(line) return rules, messages def message_matches(message, rules, rules_list): if message == '' or rules_list == []: return message == '' and rules_list == [] rule = rules[rules_list[0]] if isinstance(rule, str): if message[0] in rule: return message_matches(message[1:], rules, rules_list[1:])