advent-of-code/2020-python/solutions/day_16.py

85 lines
3.2 KiB
Python
Raw Normal View History

2021-11-01 16:40:46 +01:00
import re
from collections import defaultdict
from solutions import BaseSolution
class Solution(BaseSolution):
input_file = "16.txt"
def __str__(self):
return "Day 16: Ticket Translation"
def parse_input(self, data):
def get_ranges(line):
label = line.split(":")[0]
r1a, r1b, r2a, r2b = re.findall(rxranges, line.strip())
return (label, range(int(r1a), int(r1b) + 1), range(int(r2a), int(r2b) + 1))
rxranges = r"([0-9]+)"
ranges, golden_ticket, other_tickets = data.split("\n\n")
ranges = [*map(get_ranges, ranges.splitlines())]
golden_ticket = [*map(int, golden_ticket.splitlines()[1].split(","))]
other_tickets = [
[*map(int, ticket.split(","))] for ticket in other_tickets.splitlines()[1:]
]
return (ranges, golden_ticket, other_tickets)
def get_invalid_values(self, ranges, ticket):
def not_in_any_range(x):
return all(x not in r1 and x not in r2 for _, r1, r2 in ranges)
return [*filter(not_in_any_range, ticket)]
def solve(self, puzzle_input):
ranges, _, tickets = puzzle_input
error_rate = 0
valid_tickets = []
for ticket in tickets:
error_rate += sum(self.get_invalid_values(ranges, ticket))
return error_rate
def solve_again(self, puzzle_input):
ranges, golden_ticket, tickets = puzzle_input
valid_tickets = []
for ticket in tickets:
if len(self.get_invalid_values(ranges, ticket)) == 0:
valid_tickets.append(ticket)
sorted_ranges = self.get_positions(ranges, valid_tickets)
factors = [i for i, r in enumerate(sorted_ranges) if "departure" in r]
m = 1
for f in factors:
m *= golden_ticket[f]
return m
def get_positions(self, ranges, valid_tickets):
columns = [*zip(*valid_tickets)]
matches = defaultdict(lambda: set())
# Step 1: find all possible range matches for columns
# Example: {'row': {0, 1, 2}, 'class': {1, 2}, 'seat': {2}}
for e, column in enumerate(columns):
for column_name, r1, r2 in ranges:
if all(x in r1 or x in r2 for x in column):
matches[column_name].add(e)
# Step 2: collect all columns with only one matching range, and subtract
# these ranges from the columns having multiple ranges. Repeat until all
# columns only have one matching range.
# Example:
# #1: {'row': {0, 1, 2}, 'class': {1, 2}, 'seat': {2}}
# #2: {'row': {0, 1}, 'class': {1}, 'seat': {2}}
# #3: {'row': {0}, 'class': {1}, 'seat': {2}}
while any(len(v) > 1 for _k, v in matches.items()):
singles = set({}).union(*[v for _k, v in matches.items() if len(v) == 1])
for m in matches:
if len(matches[m]) > 1:
matches[m] = matches[m] - singles
# step 3: Sort the matches list and return a list only containing the
# column names.
final = sorted([(k, v.pop()) for k, v in matches.items()], key=lambda kv: kv[1])
return [k for k, _v in final]
if __name__ == "__main__":
solution = Solution()
solution.show_results()