DEV Community 👩‍💻👨‍💻

Cover image for Advent of Code 2015 - Day 5
Jules
Jules

Posted on • Updated on

Advent of Code 2015 - Day 5

Day 5 is one of those puzzles that has you processing strings as efficiently as you can manage. I love Python's ability to slice strings, it's a real strength of the language. Here's Part 1 of the puzzle:

Screenshot of Advent of Code puzzle

So there are three things we need to do to decide whether a string is 'nice':

  1. Check whether it has three or more vowels
  2. Check whether it has a doubled letter
  3. Ensure it doesn't contain the 'forbidden' strings

1. Summing vowels

In Python, a string is essentially a list of characters, and lists are iterable. That's why we can write code like for c in 'hello':. Iterables can be used in the map() function, with each item having a function applied to it, for example str.count() as in my example below:

def count_vowels(s):
    return sum(map(s.count, "aeiou"))
Enter fullscreen mode Exit fullscreen mode

The return value of map() is also an iterable, which means it can be sent to the sum() function. This gives us a Pythonic (finally!) way of counting the vowels in a string.

2. Doubled letters

I wrote this part as a simple look through the string, with a two character sliding window. I saw lots of solutions in the megathread that used regular expressions to find doubled letters, such as re.search(r'([a-z])\1', s) and re.search(r'(.)\1', s). All this confirmed for me is that regular expressions are another topic to go on my 'learn more about Python' list.

def check_double(s):
    for i in range(len(s)-1):
        if s[i] == s[i+1]:
            return True
    return False
Enter fullscreen mode Exit fullscreen mode

3. Forbidden pairs

I eventually found regular expression approaches to this test too (e.g. re.search(r'ab|cd|pq|xy', s)), but used a loop to code this test:

def check_forbidden(s):
    for forbidden in ['ab', 'cd', 'pq', 'xy']:
        if forbidden in s:
            return False
    return True
Enter fullscreen mode Exit fullscreen mode

Of course, in hindsight I could have written the code in exactly the same way as I checked for vowels:

def check_forbidden(s):
    return sum(map(s.count, ['ab', 'cd', 'pq', 'xy'])) == 0
Enter fullscreen mode Exit fullscreen mode

Either way works, and combining the three tests I was able to move on to Part 2:

Screenshot of Advent of Code puzzle

It's unusual for the puzzles to change so abruptly, but here we are. Now strings only have to pass two tests, but they are slightly more complicated than those in Part 1:

  1. Check for letter pairs that appear twice without overlapping
  2. Check for triplets where the first and last letter is the same (regardless of what the middle character is)

1. Letter pairs that appear twice

Really wish I was more familiar with regular expressions! I could have used something like re.search(r'(.)(.).*\1\2', s) as many cleverer Pythonistas did, but ended up writing the following:

def check_duplicates(s):
    for i in range(len(s)-1):
        check = s[i:i+2]
        if check in s[i+2:]:
            return True
    return False
Enter fullscreen mode Exit fullscreen mode

This uses a sliding window to consider two characters at a time, and then look in the rest of the string to see if it appears again. So for the string mqzxvvskslbxvyjt we look for:

 [mq] in zxvvskslbxvyjt
m [qz] in xvvskslbxvyjt
mq [zx] in vvskslbxvyjt
mqz [xv] in vskslbxvyjt, which is a hit!
Enter fullscreen mode Exit fullscreen mode

2. Symmetrical triplets

Another sliding window, this time with three characters in the window:

def check_triples(s):
    for i in range(len(s)-2):
        if s[i] == s[i+2]:
            return True
    return False
Enter fullscreen mode Exit fullscreen mode

Golfing

Inevitably there are some one-liners in the megathread. Here's both parts solved in a line of code each, in a comment by Reddit user Stactic:

print len([w for w in open('Day5Words.txt') if (re.search(r'([aeiou].*){3,}',w) and re.search(r'(.)\1', w) and not re.search(r'ab|cd|pq|xy', w))])

print len([w for w in open('Day5Words.txt') if (re.search(r'(..).*\1', w) and re.search(r'(.).\1', w))])
Enter fullscreen mode Exit fullscreen mode

Unlike a lot of golfing code, this remains pretty readable, even to a regex novice like me!

On to Day 6, and some flashing lights!

Top comments (0)

🌚 Friends don't let friends browse without dark mode.

Sorry, it's true.