Reference: https://adventofcode.com/2020/day/2
Data Preparation
As with day 1, I will be starting with getting my input into a good format before even taken a reat pass at the problem…
real_run = False
file_name = "day2-input.txt" if real_run else "day2-test.txt"
# create a list from the file, removing any '\n' characters
data = [line.rstrip('\n') for line in open(file_name)]
# print data to check it's what we want it to be
print(data)
['1-3 a: abcde', '1-3 b: cdefg', '2-9 c: ccccccccc']
Part One
Each line of our data is a NUMBER1-NUMBER2 CHAR: STRING and is a valid password when there are between NUMBER1 and NUMBER2 instances of CHAR in STRING. We the need to return the number of acceptable passwords so we’ll be keeping track of that.
First I want to make sure my line splitting function is up to scratch so I’ll be testing that out on just the first item in data and bearing in mind that NUMBER1 and NUMBER2 may be more than a single digit… re.split() is a great way to split on multiple characters at once and that is what we’ll be using. This post explains more: https://www.geeksforgeeks.org/python-split-multiple-characters-from-string/
import re
number_one, number_two, char, blank, string = re.split('-| |:', data[0])
print(number_one, number_two, char, blank, string)
1 3 a abcde
Now we have a line of code we are confident will split our string how we expect it to every time, we can use these values to verify whether or not it is a valid string. We can do this in a function too, returning true if the string is valid, and then we can use it in our actual loop!
An useful base string method for this is string count as seen here: https://www.tutorialspoint.com/python3/string_count.htm
def valid_string(to_validate):
number_one, number_two, char, blank, string = re.split('-| |:', to_validate)
# turn our numbers into int types, rather than strings, as they are now!
num_one = int(number_one)
num_two = int(number_two)
char_count = string.count(char)
if char_count >= num_one and char_count <= num_two:
return True
else:
return False
# Test one that we know works
print(valid_string(data[0]))
# Test one we know that doesn't
# We'd add some print lines around our variables if we don't get the answer we expect!
print(valid_string(data[1]))
True
False
Taking this to the final point of this puzzle we can use the logic we’ve just created in a loop and since we created it as a function, we can just call valid_string for every line in the data!
valid_count = 0
for line in data:
# Since python is extremely clever and True = 1 and False = 0,
# we can add to valid count with the boolean response from the verify_string function
valid_count += valid_string(line)
print(valid_count)
2
Sorted, some neat code and the answer we were hoping for.
A better solution?
With some list comprehension, we can represent the above cell, in just one line:
count = len([line for line in data if valid_string(line)])
print(count)
2
Run again but now with the real data
And then submit our solutions to Advent of Code and select our star!
Part Two
This part means that little of our part one code is useful! It changes the verification system entirely and so we can use what we’ve learnt and the same splitting system, but we’ll have to be checking different parameters.
The lines now mean that either (but not both) the character at NUMBER_ONE OR NUMBER_TWO are CHAR – but we need to remember that python uses a 0 index and this system starts at 1.
We’ll create a new function which implements this system and then test it.
def valid_string_pt2(to_validate):
number_one, number_two, char, blank, string = re.split('-| |:', to_validate)
# minus one from our ints so they now match python indexing
num_one = int(number_one) - 1
num_two = int(number_two) - 1
# find out chars by accessing the string in the same way we would a list
char_one = string[num_one]
char_two = string[num_two]
# an exclusive or is the same as this matching chars == 1, since if both matched, it would be 2
matching_chars = int(char_one == char) + int(char_two == char)
if matching_chars == 1:
return True
else:
return False
# Test one that we know works
print(valid_string_pt2(data[0]))
# Test one we know that doesn't
print(valid_string_pt2(data[1]))
True
False
Since we got what we want, we can move on and put our list comprehension strategy to work
count_pt2 = len([line for line in data if valid_string_pt2(line)])
print(count_pt2)
1
Mega!
We got what we were hoping for so we can now run with real_run being true and hope for the best when we submit!