|
5 | 5 | //! as the window then we know that all characters are unique, as sets contain no duplicate elements.
|
6 | 6 | //!
|
7 | 7 | //! We'll use a faster approach that minimizes the work needed. Instead of creating a set for each
|
8 |
| -//! window, we'll maintain a count of each character. As the window advances we add the next |
9 |
| -//! character to the count and remove the character the drops out of the window. |
| 8 | +//! window, we'll maintain the last position seen of each character. As we advance character by |
| 9 | +//! character we lookup the previous position. If this is within the packet size, then we advance |
| 10 | +//! the start of the packet to exclude that character. Once the packet has reached the desired |
| 11 | +//! size then we return the current index. |
10 | 12 | //!
|
11 | 13 | //! [`windows`]: slice::windows
|
12 | 14 | //! [`HashSet`]: std::collections::HashSet
|
13 | 15 |
|
14 |
| -/// Convert the input string into a `vec` of `usize`, where "a" maps to 0 and "z" to 25. |
15 |
| -/// |
16 |
| -/// Notes: |
17 |
| -/// * We need to [`trim`] to remove the trailing newline character |
18 |
| -/// * Advent of Code input is always ASCII characters, so casting to an `u8` slice is acceptable. |
19 |
| -/// |
20 |
| -/// [`trim`]: str::trim |
21 |
| -pub fn parse(input: &str) -> Vec<usize> { |
22 |
| - input.trim().bytes().map(|b| (b - b'a') as usize).collect() |
| 16 | +/// Return the input directly. |
| 17 | +pub fn parse(input: &str) -> &str { |
| 18 | + input |
23 | 19 | }
|
24 | 20 |
|
25 | 21 | /// Find the first unique set of size 4
|
26 |
| -pub fn part1(input: &[usize]) -> usize { |
| 22 | +pub fn part1(input: &str) -> usize { |
27 | 23 | find(input, 4)
|
28 | 24 | }
|
29 | 25 |
|
30 | 26 | /// Find the first unique set of size 14
|
31 |
| -pub fn part2(input: &[usize]) -> usize { |
| 27 | +pub fn part2(input: &str) -> usize { |
32 | 28 | find(input, 14)
|
33 | 29 | }
|
34 | 30 |
|
35 |
| -/// Efficient search algorithm. |
36 |
| -/// |
37 |
| -/// The cardinality of the input is only 26 so a fixed size array can store the count of each |
38 |
| -/// character. We are interested in 2 transitions: |
39 |
| -/// * If the count for a character was 0 and is now 1, then this is the only character of this type |
40 |
| -/// in the window and we should increment the `different` counter by 1. |
41 |
| -/// * If the count for the character was 1 and is now 0, then the character is no longer present |
42 |
| -/// in the window, and we should decrement the `different` counter by 1. |
43 |
| -/// |
44 |
| -/// All other transitions have no effect on the value of `different`. Once the `different` counter |
45 |
| -/// is the same as the window size then we return the 1-based index as our answer. |
46 |
| -fn find(input: &[usize], marker: usize) -> usize { |
47 |
| - let mut letters = [0; 26]; |
48 |
| - let mut different = 0; |
49 |
| - |
50 |
| - for i in 0..input.len() { |
51 |
| - let new = input[i]; |
52 |
| - letters[new] += 1; |
53 |
| - if letters[new] == 1 { |
54 |
| - different += 1; |
55 |
| - } |
56 |
| - |
57 |
| - if i >= marker { |
58 |
| - let old = input[i - marker]; |
59 |
| - letters[old] -= 1; |
60 |
| - if letters[old] == 0 { |
61 |
| - different -= 1; |
62 |
| - } |
| 31 | +/// The cardinality of the input is only 26 so a fixed size array can store the last position |
| 32 | +/// of each character. |
| 33 | +fn find(input: &str, marker: usize) -> usize { |
| 34 | + let mut start = 0; |
| 35 | + let mut seen = [0; 26]; |
| 36 | + |
| 37 | + for (i, b) in input.bytes().enumerate() { |
| 38 | + // Use the character as an index into the array. |
| 39 | + let index = (b - b'a') as usize; |
| 40 | + let previous = seen[index]; |
| 41 | + // Positions are 1-based. |
| 42 | + seen[index] = i + 1; |
| 43 | + |
| 44 | + // There's a duplicate so advance the start of the window one character past it. |
| 45 | + if previous > start { |
| 46 | + start = previous; |
63 | 47 | }
|
64 |
| - |
65 |
| - if different == marker { |
| 48 | + // We've reached the desired packet size with no duplicates so finish. |
| 49 | + if i + 1 - start == marker { |
66 | 50 | return i + 1;
|
67 | 51 | }
|
68 | 52 | }
|
|
0 commit comments