Advent of Code 2020 - Day 2

By Eric Burden | December 3, 2020

In the spirit of the holidays (and programming), I’ll be posting my solutions to the Advent of Code 2020 puzzles here, at least one day after they’re posted (no spoilers!). I’ll be implementing the solutions in R because, well, that’s what I like! What I won’t be doing is posting any of the actual answers, just the reasoning behind them.

Also, as a general convention, whenever the puzzle has downloadable input, I’m saving it in a file named input.txt.

Day 2 - Password Philosophy

Find the problem description HERE.

Part One - Count the Letters

This problem gives you a list (in a text file) of passwords and their (apparently) individualized password policy. Joy! Each line consists of a string in the format 1-2 a: password, with the policy to the left of the colon, and the password to the right. You could certainly break these lines down by splitting multiple times on different characters:

  • Split on ': ' - ['1-2 a', 'password']
  • Split on ' ' - ['1-2', 'a', 'password']
  • Split on '-' - ['1', '2', 'a', 'password']

Or, you could really enjoy regular expressions:

library(stringr)

# Helper function to count the number of matching letters in a string
count_letters <- function(string, letter) {
  letters <- str_split(string, '', simplify = T)
  length(letters[letters == letter])
}

letters <- str_extract(input, '[a-z](?=:)')                  # The letter
min_letters <- as.integer(str_extract(input, '^\\d+'))       # Minimum number
max_letters <- as.integer(str_extract(input, '(?<=-)\\d+'))  # Maximum number
passwords <- str_extract(input, '(?<=: )\\w+')               # The password

# For each password, the count of required letters in the password
letter_counts <- mapply(count_letters, passwords, letters)

# From the password list, the passwords where the count of letters is in the
# range from `min_letters` to `max_letters`
valid_passwords <- passwords[letter_counts >= min_letters & letter_counts <= max_letters]

answer <- length(valid_passwords)

The tricky expressions (at least for me) are always the look-aheads ((?=...) and look-behinds ((?<=...)), probably because I don’t use them often enough. I could have (and almost did) mostly avoid them here, too. Like:

  • [a-z]{1} instead of [a-z](?=:)
  • \\w+$ instead of (?<=: )\\w+

Turns out, I really needed either (?<=-)\\d+ or \\d+(?=\\s) to get that second number, though, so it was a good excuse to practice.

Part Two - Policy Police

After all that work, part two tells us we did it wrong! Well, OK, what we did we did right, we just did the wrong thing. sigh It turns out that the correct policy interpretation (of which there is still a [random?] policy per password…) involves having the indicated letter at either the position indicated by the first number OR the position indicated by the second number, but definitely not both. Personally, this seems like a recipe for bad passwords, but what do I know? At any rate, this part is solved in almost entirely the same manner as part one:

library(stringr)

# Helper function to confirm the presence of a particular letter at a 
# particular position in a string
is_letter_at_position <- function(string, pos, letter) {
  str_split(string, '', simplify = T)[pos] == letter
}

letters <- str_extract(input, '[a-z](?=:)')                      # The letter
first_position <- as.integer(str_extract(input, '^\\d+'))        # The first number
second_position <- as.integer(str_extract(input, '(?<=-)\\d+'))  # The second number
passwords <- str_extract(input, '(?<=: )\\w+$')                  # The password

# Is the indicated letter present at the first indicated position?
# How about the second?
letter_at_first_pos <- mapply(is_letter_at_position, passwords, first_position, letters)
letter_at_second_pos <- mapply(is_letter_at_position, passwords, second_position, letters)

# If the letter was found in the first position, was it also at the second?
valid <- ifelse(letter_at_first_pos, !letter_at_second_pos, letter_at_second_pos)
valid_passwords <- passwords[valid]

answer <- length(valid_passwords)

Wrap-Up

Day 2 was a nice reprieve from the brain-teaser on Day 1, with a fairly straightforward strategy for finding both answers. The tricky bit was most likely to come from regular expressions (if you went that route), especially if you forgot to account for the fact that sometimes numbers have more than one character and used ^\\d instead of ^\\d+ somewhere… If you found a different solution (or spotted a mistake in one of mine), please drop me a line, I’d love to hear about it!

comments powered by Disqus