summaryrefslogtreecommitdiff
path: root/2020/rust/day02/src/main.rs
blob: 42e26e5d6b34387b114a8959b20dfc584c89d8f2 (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
use anyhow::{Result, anyhow};
use itertools::Itertools;
use std::env::args;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::Path;
use std::str::FromStr;

#[derive(Debug)]
struct PasswordPolicy {
    character: char,
    min: usize,
    max: usize,
}

impl FromStr for PasswordPolicy {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut parts = s.split(' ');
        let range = parts.next().ok_or_else(|| anyhow!("Could not find range: {}", s))?;
        let character = parts.next().ok_or_else(|| anyhow!("Could not find char: {}", s))?
            .chars().next().ok_or_else(|| anyhow!("Coult not parse char: {}", s))?;
        let ( min, max ) = match range.split('-')
            .map(|v| v.parse::<usize>()).collect::<Result<Vec<usize>, _>>()
        {
            Ok(vals) => {
                vals.into_iter().collect_tuple().ok_or_else(|| anyhow!("Invalid range"))?
            },
            _ => return Err(anyhow!("Couldn't parse {}", s)),
        };

        Ok(PasswordPolicy { min, max, character })
    }
}

impl From<&str> for PasswordPolicy {
    fn from(s: &str) -> Self {
        PasswordPolicy::from_str(s).expect(s)
    }
}

fn read_input<T: AsRef<Path>>(filename: T) -> Result<Vec<(PasswordPolicy, String)>> {
    let f = File::open(filename)?;
    let reader = BufReader::new(f);

    reader.lines().map(|l| {
        let line = l?;
        let mut s = line.split(':');
        let policy = From::from(s.next().ok_or_else(|| anyhow!("Could not parse policy"))?.trim());
        let password = From::from(s.next()
            .ok_or_else(|| anyhow!("Could not parse password"))?.trim());
        Ok(( policy, password ))
    }).collect()
}

fn part1(input: &[(PasswordPolicy, String)]) -> usize {
    input.iter().filter(|row| {
        let ( policy, password ) = row;
        let occurances = password.chars().filter(|c| *c == policy.character).count();

        occurances >= policy.min && occurances <= policy.max
    }).count()
}

fn part2(input: &[(PasswordPolicy, String)]) -> usize {
    input.iter().filter(|row| {
        let ( policy, password ) = row;

        let first = password.chars().nth(policy.min - 1);
        let second = password.chars().nth(policy.max - 1);

        first != second && (first == Some(policy.character) || second == Some(policy.character))
    }).count()
}

fn main() {
    let ( do_part_1, do_part_2 ) = aoc::do_parts();

    let filename = match args().nth(1) {
        Some(f) => f,
        None => {
            eprintln!("Missing input filename");
            std::process::exit(1);
        },
    };
    match read_input(filename) {
        Ok(input) => {
            if do_part_1 {
                let solution = part1(&input);
                println!("Part1, product found to be: {}", solution);
            }
            if do_part_2 {
                let solution = part2(&input);
                println!("Part2, product found to be: {}", solution);
            }
        },
        Err(err) => eprintln!("Could not read input: {}", err),
    }
}