summaryrefslogtreecommitdiffstats
path: root/src/day_03.rs
blob: da54daa4e802fa30ea56d52c8c00c49ba1922f1c (plain)
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
use std::{ops::Deref, str::FromStr};

use anyhow::{anyhow, Context, Result};

use crate::{Problem, Solution};

trait Priority {
    fn priority(self) -> Result<u8>;
}

impl Priority for char {
    fn priority(self) -> Result<u8> {
        match self as u8 {
            c @ 65..=90 => Ok(c - 65 + 27),
            c @ 97..=122 => Ok(c - 97 + 1),
            c => Err(anyhow!("failed to get priority of {}", c as char)),
        }
    }
}

#[derive(Debug, Clone)]
struct Backpack(Vec<char>);

impl Backpack {
    fn get_local_union(self) -> Result<char> {
        let (left, right) = self.split_at(self.len() / 2);
        left.iter()
            .cloned()
            .find(|&item| right.contains(&item))
            .context(format!("Failed to find union in {:?}", &self))
    }

    fn get_group_union(group: &mut Vec<Backpack>) -> Result<char> {
        group
            .split_first_mut()
            .and_then(|(first, others)| {
                first
                    .iter()
                    .cloned()
                    .find(|item| others.iter_mut().all(|b| b.contains(item)))
            })
            .context(format!("Failed to find union in {:?}", group))
    }
}
impl Deref for Backpack {
    type Target = Vec<char>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl std::str::FromStr for Backpack {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self> {
        Ok(Backpack(s.as_bytes().iter().map(|c| *c as char).collect()))
    }
}

pub struct Day03;

impl Problem for Day03 {
    const DAY: u8 = 3;

    const INPUT: &'static str = include_str!("../input/day_3.txt");
}

impl Solution for Day03 {
    type Answer1 = usize;

    type Answer2 = usize;

    fn part_1(input: &str) -> Result<Self::Answer1, anyhow::Error> {
        input
            .lines()
            .map(Backpack::from_str)
            .try_collect::<Vec<_>>()?
            .into_iter()
            .map(|b| b.get_local_union())
            .try_collect::<Vec<_>>()?
            .into_iter()
            .try_fold(0, |sum, c| c.priority().map(|p| sum + p as usize))
    }

    fn part_2(input: &str) -> Result<Self::Answer2, anyhow::Error> {
        let lines: Vec<&str> = input.lines().collect();
        lines.as_slice().chunks(3).try_fold(0, |acc, g| {
            let mut group = g
                .iter()
                .cloned()
                .map(Backpack::from_str)
                .try_collect::<Vec<_>>()?;

            Ok(Backpack::get_group_union(&mut group)?.priority()? as usize + acc)
        })
    }
}

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

    const INPUT: &str = indoc::indoc! {r#"
        vJrwpWtwJgWrhcsFMMfFFhFp
        jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL
        PmmdzqPrVvPwwTWBwg
        wMqvLMZHhHMvwLHjbvcjnnSBnvTQFn
        ttgJtRGJQctTZtZT
        CrZsJsPPZsGzwwsLwLmpwMDw
    "#};

    #[test]
    fn test_part_1_example() -> Result<()> {
        Ok(assert_eq!(157, Day03::part_1(INPUT)?))
    }

    #[test]
    fn test_part_2_example() -> Result<()> {
        Ok(assert_eq!(70, Day03::part_2(INPUT)?))
    }
}