1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
/* Copyright 2022 Mario Finelli
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! Advent of Code 2022 Day 3: <https://adventofcode.com/2022/day/3>
//!
//! The day three challenge basically amounts to finding characters in
//! strings.
//!
//! For the first part we need to find a single character that appears in both
//! the first half and the second half of each string. I approached this by
//! looping through each line and in the first half of the string adding each
//! character to a [`std::collections::HashSet`] as it's seen. Then in the
//! second half of the string we check each character as we pass to see if it
//! exists in the Set -- once we find a match we're done for that line.
//!
//! I didn't try, but it _may_ be faster or more efficient to just split the
//! string in two and then loop through the second half only and just run a
//! `contains` check on the entire first half string.
//!
//! This is similar to the approach that I took for the second part of the
//! challenge. We loop through each group, but we actually only loop through
//! the characters in the third string of the group and check if both of the
//! first two strings contain the character in which case we're done.
//!
//! Finally, for determining the priority we just define a string with the
//! priorities in order so that we can do a simple lookup for a given
//! character (plus one because the string is zero-indexed).

use std::collections::HashSet;

const PRIORITY: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

/// The solution for the day three challenge. In reality calls a second
/// function based on the second argument (part `1` or part `2`).
///
/// For part one given the input as a string we loop through the characters in
/// each line. If we're in the first half we add them to the set, if we're in
/// the second half we check if the character is in the set (meaning that
/// we've found the duplicate) and return the priority. Then break because
/// it's possible that the duplicate character can exist more than once which
/// results in a total that is too large.
///
/// For the second part given the input as a string we calculate the number of
/// elf groups (lines divided by three because the elves are in groups of
/// three). Then for each group we loop through the characters in the third
/// group and check if it exists in both the first and second group (this
/// could also be loop through the first and check the second and third or
/// loop through the second and check the first and third with the same
/// outcome) and if so add the priority to the total.
///
/// # Example
/// ```rust
/// # use aoc::y22d03::y22d03;
/// // probably read this from the input file...
/// let input = "jaabbcck\njddeeffl\njgghhiim\n";
/// assert_eq!(y22d03(input, 1), 15);
/// assert_eq!(y22d03(input, 2), 10);
/// ```
pub fn y22d03(input: &str, part: u32) -> u32 {
    if part == 1 {
        y22d03p1(input)
    } else {
        y22d03p2(input)
    }
}

fn y22d03p1(input: &str) -> u32 {
    let lines: Vec<_> = input.lines().collect();
    let mut sum = 0;

    for line in lines {
        let mut set = HashSet::new();
        let half = line.len() / 2;
        let mut i = 1;
        for ch in line.chars() {
            if i <= half {
                set.insert(ch);
            } else if set.contains(&ch) {
                sum += PRIORITY.find(ch).unwrap() as u32 + 1;
                break;
            }
            i += 1;
        }
    }

    sum
}

fn y22d03p2(input: &str) -> u32 {
    let lines: Vec<_> = input.lines().collect();
    let groups = lines.len() / 3;

    let mut sum = 0;

    for gi in 0..groups {
        for c in lines[gi * 3 + 2].chars() {
            if lines[gi * 3].contains(c) && lines[gi * 3 + 1].contains(c) {
                sum += PRIORITY.find(c).unwrap() as u32 + 1;
                break;
            }
        }
    }

    sum
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;

    #[test]
    fn it_works() {
        let mut input = "cDeFeg";
        assert_eq!(y22d03p1(input), 5);

        input = "ABBCaaDEEF\nHabcHdef\n";
        assert_eq!(y22d03p1(input), 35);

        input = "vJrwpWtwJgWrhcsFMMfFFhFp";
        assert_eq!(y22d03p1(input), 16);

        input = "jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL";
        assert_eq!(y22d03p1(input), 38);

        input = "PmmdzqPrVvPwwTWBwg";
        assert_eq!(y22d03p1(input), 42);

        input = "wMqvLMZHhHMvwLHjbvcjnnSBnvTQFn";
        assert_eq!(y22d03p1(input), 22);

        input = "ttgJtRGJQctTZtZT";
        assert_eq!(y22d03p1(input), 20);

        input = "CrZsJsPPZsGzwwsLwLmpwMDw";
        assert_eq!(y22d03p1(input), 19);

        input = concat!(
            "vJrwpWtwJgWrhcsFMMfFFhFp\n",
            "jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL\n",
            "PmmdzqPrVvPwwTWBwg\n"
        );
        assert_eq!(y22d03p2(input), 18);

        input = concat!(
            "wMqvLMZHhHMvwLHjbvcjnnSBnvTQFn\n",
            "ttgJtRGJQctTZtZT\n",
            "CrZsJsPPZsGzwwsLwLmpwMDw"
        );
        assert_eq!(y22d03p2(input), 52);
    }

    #[test]
    fn the_solution() {
        let contents = fs::read_to_string("input/2022/day03.txt").unwrap();

        assert_eq!(y22d03(&contents, 1), 7831);
        assert_eq!(y22d03(&contents, 2), 2683);
    }
}