diff options
Diffstat (limited to '2020/rust/day02')
-rw-r--r-- | 2020/rust/day02/Cargo.toml | 10 | ||||
-rw-r--r-- | 2020/rust/day02/src/main.rs | 100 |
2 files changed, 110 insertions, 0 deletions
diff --git a/2020/rust/day02/Cargo.toml b/2020/rust/day02/Cargo.toml new file mode 100644 index 0000000..56dd982 --- /dev/null +++ b/2020/rust/day02/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "day02" +version = "0.1.0" +authors = ["cos <cos>"] +edition = "2018" + +[dependencies] +aoc = { path = "../aoc" } +anyhow = "1.0" +itertools = "0.9" diff --git a/2020/rust/day02/src/main.rs b/2020/rust/day02/src/main.rs new file mode 100644 index 0000000..42e26e5 --- /dev/null +++ b/2020/rust/day02/src/main.rs @@ -0,0 +1,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), + } +} |