day 16
what. a. day.
the input has a few sections: the rules for each field, a blank line, 'your ticket:', the values on your ticket, another blank line, 'nearby tickets:', and then the remaining lines are all one ticket each with a list of integers. the following code reads in the file from 16.txt as per usual, and sorts it as follows:
- rules is a list where each element is a list [field name (str), min of range 1, max of range 1, min of range 2, max of range 2]
- line = 'departure location: 31-538 or 546-960'
- rule = ['departure location', 31, 538, 546, 960]
- my_ticket is a list of numbers in the order in the input (more cheeky list comprehension)
- tickets is a list of lists of numbers, where each of these internal lines is one ticket
# input
with open('16.txt', 'r') as file:
input = file.read()
# reading in tickets
input_list = list(input.split('\n'))
# start iterating over lines
line_no = 0
line = input_list[line_no]
# get the rules as a list of dicts
rules = []
while len(line) > 0: # before the blank line
# departure location: 31-538 or 546-960
line1 = line.split(': ')
# ['departure location', '31-538 or 546-960']
line2 = [line1[0],] + line1[1].split(' or ')
# ['departure location', '31-538', '546-960']
line3 = [line2[0],] + line2[1].split('-') + line2[2].split('-')
# ['departure location', '31', '538', '546', '960']
line4 = [line3[0],] + [int(num) for num in line3[1:]]
# ['departure location', 31, 538, 546, 960]
rules.append(line4)
# next line
line_no += 1
line = input_list[line_no]
# current line is empty
# skip 'your ticket:'
line_no += 2
line = input_list[line_no]
my_ticket = [int(num) for num in line.split(',')]
# skip empty line, 'nearby tickets:'
line_no += 3
line = input_list[line_no]
tickets = []
while line_no < len(input_list):
line = input_list[line_no]
tickets.append([int(num) for num in line.split(',')])
line_no += 1
now that we have these set up, for part a we need to take the sum of all values across all tickets that are invalid for any field, and output this sum as the error rate (stored in error_rate). we can just flatten the entire list (i.e. get a list of the values in the sublists) since it doesn't matter which ticket the invalid field is in:
all_tickets = [num for ticket in tickets for num in ticket]
we set up two lists - confirmed_valid for numbers that we've already come across that are valid, and confirmed_invalid for ones that we already know are invalid - and set error_rate = 0. the lists aren't really necessary, but they speed up the checking process since we can easily rule out any values that have already been checked.
confirmed_valid = [] confirmed_invalid = [] error_rate = 0
we then go through all_tickets, skipping the value if we already know it's valid, and adding it onto error_rate if we already know it's invalid. if this is a number we haven't encountered before, it checks if it satisfies any of the rules, and if so we confirm it as valid and this for loop breaks; otherwise, it isn't valid for any field, so we hit the else clause and confirm it as invalid, adding the value to the error_rate. once this loop terminates, we have the answer for part a.
for num in all_tickets:
if num in confirmed_valid:
continue
elif num in confirmed_invalid:
error_rate += num # add error
continue
for rule in rules:
# rule = ['departure location', 31, 538, 546, 960]
# check if this value is valid for this field
if (rule[1] <= num <= rule[2]) or (rule[3] <= num <= rule[4]):
# value is valid!
confirmed_valid.append(num)
break
else:
# did not break
confirmed_invalid.append(num)
error_rate += num
print(error_rate)
now for part b, we have to determine which value corresponds to which field. i appended this code to part a, so we already have the list confirmed_invalid for values that are invalid and appear on at least one ticket. we remove all tickets that have a value that's invalid for all fields:
invalid_tickets = []
for ticket in tickets:
for num in ticket:
if num in confirmed_invalid:
invalid_tickets.append(ticket)
break
for ticket in invalid_tickets:
tickets.remove(ticket)
we get the length of a ticket (it's 20 fields as per the input - i'm just getting myself out of the habit of hardcoding values)
ticket_len = len(my_ticket)
and then we set up a dictionary, where the keys are numbers corresponding to each location on the ticket (0 to 19, where 0 is the first number in the ticket) and the values are a list of fields (again 0 to 19, where 0 is the first field appearing in the input list - 'departure location' in mine at least). since we haven't checked anything yet, we say that any location can correspond to any field.
loc_to_field = {}
for i in range(0,ticket_len):
loc_to_field[i] = [j for j in range(0,ticket_len)]
we also get a list unknown_locs for locations where there's more than one possible field - this will be used so that we only bother trying to find corresponding fields for locations where we don't know the field yet:
unknown_locs = [i for i in range(0,ticket_len)]
now we have a few nested for loops coming up. we look at each ticket, and set known_locs = [] - this list will contain any locations where we know for sure which field is there immediately after checking the current ticket.
within this ticket, we go through the unknown_locs. for unknown loc i, we get num (the number at position i on the current ticket), and two lists: can_be_field, which has the fields that position i can be, and cant_be_field, which has the fields we know it can't be - this starts off as the list of fields corresponding to locations that can only be one field.
we iterate over the fields j in can_be_field, and check if num satisfies the needed rule; if not, j is added to cant_be_field. after this, we iterate over the fields j in cant_be_field, and remove these from can_be_field if needed. we then update the dict loc_to_field to reflect the new possible lists of fields for each loc. if this new list only has one value, we add it to known_locs.
after iterating through all unknown_locs, we remove any newly-found known locs from unknown_locs, printing to the console that we know which field corresponds to that position.
# this list of tickets definitely has no invalid tickets (checked)
for ticket in tickets: # get a list of numbers (ticket)
known_locs = [] # new locations where we know the field
for i in unknown_locs: # get one of the locs
num = ticket[i] # value of ticket in loc i
can_be_field = loc_to_field[i] # possible fields for loc i
cant_be_field = [loc_to_field[j][0] for j in known_locs]
for j in can_be_field:
rule = rules[j] # get the rule for this field
if not ((rule[1] <= num <= rule[2]) or (rule[3] <= num <= rule[4])):
# does not satisfy rule for this field
cant_be_field.append(j)
# we now have a list of fields that it can't be, based on this value
for j in cant_be_field:
if j in can_be_field:
can_be_field.remove(j)
# update dict
loc_to_field[i] = can_be_field
if len(can_be_field) == 1:
known_locs.append(i)
# remove from unknown_locs if it's a known loc
for i in known_locs:
unknown_locs.remove(i)
print('determined field ' + str(loc_to_field[i][0]) + ' for number in loc ' + str(i))
we've now reduced the list down by going through all tickets - however, this doesn't guarantee that we have exactly one field for each position. we can reduce the list further. i cleared known_locs on every loop, and instead of going back and not clearing this but adding checks (only try to remove from a list if the value is in it, don't add a value to the list if it's already in it).. it's a lot easier to just recreate it by making a list of every integer from 0 to 19 that isn't in unknown_locs.
print('gone through all tickets. reducing list of possible values...')
known_locs = [i for i in range(0,ticket_len) if not i in unknown_locs]we now start a while loop that continues so long as the list unknown_locs is not empty (this will only terminate if a matching is found, but luckily there is a matching because the ticket list is made to only have one!). it keeps going through unknown_locs, and checks if this value should actually be in known_locs (there's only one field it could be) - if so, it appends it to known_locs. otherwise, it goes through the known locs, gets the field corresponding to that known loc, and removes this field as a possibility for the unknown loc. once it's gone through all unknown_locs, it removes anything added to known_locs from unknown_locs.
for example, if we have only positions 0 and 1 and we know position 0 has to be field 2, and position 1 can be fields 2 or 3, this will remove field 2 from the list for position 1. then, on the next iteration, position 1 will be marked as a known loc since it can only be field 3, and removed from unknown_locs, terminating the while loop.
# knock down the possible list even further
while unknown_locs: # while there is at least one unknown loc
for i in unknown_locs: # go through unknown locs
if len(loc_to_field[i]) == 1: # if this should actually be a known loc
known_locs.append(i) # add to known locs
print('determined field ' + str(loc_to_field[i][0]) + ' for number in loc ' + str(i))
else: # still an unknown loc
for j in known_locs: # go through known locs
k = loc_to_field[j][0] # get the field for the known loc
if i != j and k in loc_to_field[i]:
print('removing ' + str(k) + ' from possibilities for loc ' + str(j))
# if this is a different loc and has this field
# remove the field
tmp = loc_to_field[i]
tmp.remove(k)
loc_to_field[i] = tmp
# remove known locs from unknown_locs
for i in known_locs:
if i in unknown_locs:
unknown_locs.remove(i)we now need to consider the values on my_ticket (now that your own ticket is finally relevant), and output the product of all fields where the field name (first element of a rule in rules) starts with 'departure':
output = 1
for i in range(0,ticket_len):
rule_num = loc_to_field[i][0]
rule = rules[rule_num]
if rule[0][:9] == 'departure':
output *= my_ticket[i]
print('departure product (part b):' + str(output))honestly? this one was a really fun problem, even though i kept having to debug my code every two seconds. it's easily a record for the most time i've spent just reading in the input - i don't know if it's the most lines i've written for aoc though
Files
Get aoc 2020
aoc 2020
not a mod, just some code
| Status | Released |
| Category | Other |
| Author | riv |
| Tags | advent-of-code, advent-of-code-2020, python |
Leave a comment
Log in with itch.io to leave a comment.