AoC 2015 Puzzle 5

2015 Puzzle 5: Doesn't He Have Intern-Elves For This?

Full source code for the solution can be found in the repository.

Part 1

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.

Solution

The problem seems quite straightforward to find a solution too. The String that is passed to our validation function will need to be checked on 3 criteria.

First we need to start a for loop over the String. Instead of using the chars() function to get all individual chars, a counter i will be used to loop over the length of the String.

boolean isNiceString(String entry) {
    int vowels = 0;
    int doubles = 0;
    boolean offensive = false;

    for (int i = 0; i < entry.length(); ++i) {

The first condition is to see if there are at least 3 vowels used. Using charAt(i), to get the char from the String, a case statement can be constructed to count if any of the required characters are part of the String.

switch (entry.charAt(i)) {
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
    vowels++;
}

The check to see if there are doubles is a little more complex. First thing to do is to check of we are within bounds, which would be length() - 1 of size. Next the character at position i is compared to position i+1, if they are the same, a double is found.

if (i < entry.length() - 1 &&
    entry.charAt(i) == entry.charAt(i + 1)) {
    doubles++;
}

The last part of the criteria is that several combinations of characters are not allowed. This can be done using the contains() function of String.)

    for (String o : new String[] { "ab", "cd", "pq", "xy" }) {
        if (entry.contains(o)) {
            offensive = true;
        }
    }
}

Finally, after all checks are done, all the results are combined to determine if at least 3 vowels are present, more than 0 doubles are within the String and no offensive combinations are found.

    if (vowels >= 3 && doubles > 0 && !offensive) {
        return true;
    }

    return false;
}

The part1() function receives a List of String objects. Using a stream() each of these strings can be passed to a filter which calls the isNiceString() function. Only if the function returns true the String will be returned. A count of the resulting stream will actually yield the answer to the puzzle.

@Override
public String part1(List<String> input) {
    return String.valueOf(input.stream().filter(s -> isNiceString(s)).count());
}

Part 2

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.

Solution

Of course, these conditions are totally not ridiculous at all! Silly santa.

To solve the 2nd part the same approach can be used as before. It seems highly unlikely that we can combine both parts in the same function, so a new function is required.

boolean isNiceStringNotRidiculous(String entry) {
    int pair = 0;
    int repeat = 0;

    for (int i = 0; i < entry.length(); ++i) {

To find the pair of characters the substring of the current position + 2, which will take the current character and the one following it, can be searched in the current string using indexOf. The indexOf takes substring to search for and a starting position, which will be the current index +2, as with the substring funtion. If it returns an index larger than the current position, a pair is found that does not overlap.

if (i < entry.length() - 2) {
    String currentPair = entry.substring(i, i + 2);
    if (entry.indexOf(currentPair, i + 2) > i) {
        ++pair;
    }
}

In order to satisfy the 2nd condition the current character needs to be compared to the one 2 positions later. First a bounds check to ensure the lookup does not jump over the end of the string is necessary. Next the character at position i can be compared to position i+2. If they match a repeat case has been found.

if (i < entry.length() - 2) {
    if (entry.charAt(i) == entry.charAt(i+2)) {
        ++repeat;
    }
}

After all the checks, if there are more then 0 pairs and more then 0 repeats, true will be returned.

    }

    if (pair > 0 && repeat > 0) {
        return true;
    }

    return false;
}

As with part 1 the input strings can be streamed over, passed to the filter with the new function to eventually be counted, resulting in the answer to this puzzle.

return String.valueOf(input.stream().filter(s -> isNiceStringNotRidiculous(s)).count());