import aoclib DAY = 16 TEST_SOLUTION_PART1 = 71 TEST_SOLUTION_PART2 = 7 def parse_input(to_parse): restraints = {} my_ticket = [] other_tickets = [] for i, line in enumerate(to_parse): if not line: my_ticket = list(map(int, to_parse[i+2].split(","))) other_tickets = [list(map(int, ticket.split(","))) for ticket in to_parse[i+5:]] break field, limits = line.split(": ") limit_low, limit_hi = limits.split(" or ") min_low, max_low = limit_low.split("-") min_hi, max_hi = limit_hi.split("-") restraints[field] = [(int(min_low), int(max_low)), (int(min_hi), int(max_hi))] return restraints, my_ticket, other_tickets def is_valid_number(number, restraints): for restraint in restraints.values(): if restraint[0][0] <= number <= restraint[0][1] or restraint[1][0] <= number <= restraint[1][1]: return True return False def part1(test_mode=False): my_input = aoclib.getInputAsArray(day=DAY, test=test_mode) restraints, _, other_tickets = parse_input(my_input) invalid_numbers = [] for ticket in other_tickets: for number in ticket: if not is_valid_number(number, restraints): invalid_numbers.append(number) return sum(invalid_numbers) def get_single_possible_positions(possible_positions): single_possibilities = [] while 1: all_done = True for field, possibilities in possible_positions.items(): if len(possibilities) == 1: possible_position = possibilities[0] if possible_position not in single_possibilities: all_done = False single_possibilities.append(possible_position) for check_field, check_positions in possible_positions.items(): if possible_position in check_positions and len(check_positions) > 1: possible_positions[check_field].remove(possible_position) if all_done: break return possible_positions def part2(test_mode=False): my_input = aoclib.getInputAsArray(day=DAY, test=test_mode) restraints, my_ticket, other_tickets = parse_input(my_input) possible_positions = {} num_fields = len(my_ticket) for field in restraints: possible_positions[field] = list(range(num_fields)) for ticket in other_tickets: invalid = False for number in ticket: if not is_valid_number(number, restraints): invalid = True break if invalid: continue for position, number in enumerate(ticket): for field, restraint in restraints.items(): if not restraint[0][0] <= number <= restraint[0][1] and not restraint[1][0] <= number <= restraint[1][1]: possible_positions[field].remove(position) possible_positions = get_single_possible_positions(possible_positions) answer = 1 for field in restraints: if field.startswith('departure'): answer *= my_ticket[possible_positions[field][0]] return answer