Advent of Code 2015, Day 5 – In Ruby
This is day 5 of 2015.
The first half of the puzzle
Santa needs help figuring out which strings in his text file are naughty or nice.
A nice string is one with all of the following properties:
- It contains at least three vowels (aeiou only), like aei, xazegov, or aeiouaeiouaeiou.
- It contains at least one letter that appears twice in a row, like xx, abcdde (dd), or aabbccdd (aa, bb, cc, or dd).
- It does not contain the strings ab, cd, pq, or xy, even if they are part of one of the other requirements.
For example:
- ugknbfddgicrmopn is nice because it has at least three vowels (u…i…o…), a double letter (…dd…), and none of the disallowed substrings.
- aaa is nice because it has at least three vowels and a double letter, even though the letters used by different rules overlap.
- jchzalrnumimnmhp is naughty because it has no double letter.
- haegwjzuvuyypxyu is naughty because it contains the string xy.
- dvszwmarrgswjxmb is naughty because it contains only one vowel.
How many strings are nice?
The solution:
#!/usr/bin/env ruby
f = File.read(ARGV.first)
nice_strings = 0
f.each_line do |line|
next unless line =~ /[aeiou].*[aeiou].*[aeiou]/
next unless line =~ /(\w)\1/
next if line =~ /ab|cd|pq|xy/
nice_strings << line
end
puts "Number of nice strings: #{nice_strings}"
This puzzle calls for regular expressions. We’ll loop over the input line-by-line, check each of the three conditions in turn, and if either of the conditions isn’t met, we skip to the next input line. Once we find a line that satisfies every condition, we increase the nice_strings
counter. At the end, we print the result.
A bit about the regular expressions:
- the first condition is
It contains at least three vowels
– the regexp checks for a vowel (the first[aeiou]
part), then it allows any character (even none) before the next vowel (the.*
part), and repeats it two more times. - the second condition is
It contains at least one letter that appears twice in a row
– the regexp captures a word character, (the(\w)
part, which is equivalent to[a-zA-Z0-9_]
but a bit terser), then checks whether the very next character is the same as the character just captured (the\1
part). - the third condition is
It does not contain the strings ab, cd, pq, or xy, even if they are part of one of the other requirements
. The regexp checks whether the input contains any of the strings. Since every check is independent of the others, it will not and it can not take into consideration whether the matched string is part of one of the previous requirements, which is just what we need.
The second half of the puzzle
Realizing the error of his ways, Santa has switched to a better model of determining whether a string is naughty or nice. None of the old rules apply, as they are all clearly ridiculous.
Now, a nice string is one with all of the following properties:
- It contains a pair of any two letters that appears at least twice in the string without overlapping, like xyxy (xy) or aabcdefgaa (aa), but not like aaa (aa, but it overlaps).
- It contains at least one letter which repeats with exactly one letter between them, like xyx, abcdefeghi (efe), or even aaa.
For example:
- qjhvhtzxzqqjkmpb is nice because is has a pair that appears twice (qj) and a letter that repeats with exactly one letter between them (zxz).
- xxyxx is nice because it has a pair that appears twice and a letter that repeats with one between, even though the letters used by each rule overlap.
- uurcxstgmygtbstg is naughty because it has a pair (tg) but no repeat with a single letter between them.
- ieodomkazucvgmuy is naughty because it has a repeating letter with one between (odo), but no pair that appears twice.
How many strings are nice under these new rules?
The solution:
#!/usr/bin/env ruby
f = File.read(ARGV.first)
nice_strings = 0
f.each_line do |line|
next unless line =~ /(\w\w).*\1/
next unless line =~ /(\w).\1/
nice_strings += 1
end
puts "Number of nice strings: #{nice_strings}"
This part is also about regular expressions. For these new rules, we’ll use slight variations of the second regexp from the first part:
- the first condition is
It contains a pair of any two letters that appears at least twice in the string without overlapping
– we capture any two word characters ((\w\w)
), then we allow any number (even zero) of other characters (.*
), then require the same pair of characters again (\1
). - the second condition is
It contains at least one letter which repeats with exactly one letter between them
– for this, we capture a word character ((\w)
), require exactly one character (which could be anything) (.
), then the captured character again (\1
).
The code with my input text is available in the GitHub repo.
Thanks for reading! If you have any comments, additions, or corrections, feel free to reach me via e-mail.