aoc/year2015/day05.rs
1//! # Doesn't He Have Intern-Elves For This?
2//!
3//! [Regular expressions](https://en.wikipedia.org/wiki/Regular_expression) are a good fit for this
4//! problem. However in the interest of speed we'll take a different approach for both parts.
5//!
6//! ## Part One
7//! Each string consists only of lowercase ASCII characters so the cardinality is 26. We can
8//! test for vowels and invalid characters more quickly by converting each character into a bitmask
9//! that fits into a `i32`. For example `a` becomes `1`, b becomes `10` and so on.
10//!
11//! To check if a character is a vowel we logically `AND` against `100000100000100010001` which is
12//! "aeiou" converted to a bitmask. Similarly to check for the invalid sequence "ab" we `AND`
13//! against a mask that has `b` set and notice the previous character is always one less, so we
14//! can left shift to reuse the same mask.
15//!
16//! ## Part Two
17//! We can check for non-overlapping pairs in `O(n)` complexity by storing the last seen index of
18//! each pair in the string. If the difference is more than one, then we know that the pairs are
19//! non-overlapping.
20//!
21//! Instead of using a `HashMap` we rely on the fact there at most 26² possible combinations
22//! in order to use a fixed size array as an implicit data structure. Using zero as a special
23//! starting value gives 27² or 729 possibilities. To avoid having to clear the array for each
24//! string, we bump the index by 1000 (any value larger than the length of the string would do).
25//! This means that if the difference is greater than the current position in the string we can
26//! sure that we haven't encountered this pair in this particular string before.
27pub fn parse(input: &str) -> Vec<&[u8]> {
28 input.lines().map(str::as_bytes).collect()
29}
30
31pub fn part1(input: &[&[u8]]) -> usize {
32 let nice = |line: &&&[u8]| {
33 let mut vowels = 0;
34 let mut pairs = 0;
35 let mut previous = 0;
36
37 for c in line.iter() {
38 let current = 1 << (c - b'a');
39 if 0x101000a & current & (previous << 1) != 0 {
40 return false;
41 }
42 if 0x0104111 & current != 0 {
43 vowels += 1;
44 }
45 if previous == current {
46 pairs += 1;
47 } else {
48 previous = current;
49 }
50 }
51
52 vowels >= 3 && pairs >= 1
53 };
54
55 input.iter().filter(nice).count()
56}
57
58pub fn part2(input: &[&[u8]]) -> usize {
59 let mut pairs = [0; 729];
60
61 let nice = |(base, line): &(usize, &&[u8])| {
62 let mut first = 0;
63 let mut second = 0;
64
65 let mut two_pair = false;
66 let mut split_pair = false;
67
68 for (offset, c) in line.iter().enumerate() {
69 let third = (c - b'a' + 1) as usize;
70 let index = 27 * second + third;
71
72 let position = base * 1000 + offset;
73 let delta = position - pairs[index];
74
75 if delta > offset {
76 pairs[index] = position;
77 } else if delta > 1 {
78 two_pair = true;
79 }
80 if first == third {
81 split_pair = true;
82 }
83
84 first = second;
85 second = third;
86 }
87
88 two_pair && split_pair
89 };
90
91 input.iter().enumerate().filter(nice).count()
92}