advent_of_code/2020/day16_ticket_translation/src/lib.rs
2023-04-11 21:32:32 +02:00

108 lines
4.3 KiB
Rust

use core::fmt::Display;
use std::num::ParseIntError;
#[derive(Debug, PartialEq, Eq)]
pub enum ParseError {
ParseIntError(std::num::ParseIntError),
LineMalformed(String),
}
impl From<ParseIntError> for ParseError {
fn from(value: ParseIntError) -> Self {
Self::ParseIntError(value)
}
}
impl Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ParseIntError(e) => write!(f, "Unable to parse into integer: {e}"),
Self::LineMalformed(v) => write!(f, "Line is malformed: {v}"),
}
}
}
struct Requirement {
is_departure: bool,
left_range: (usize, usize),
right_range: (usize, usize),
}
impl TryFrom<&str> for Requirement {
type Error = ParseError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
let components: Vec<_> = value.split(&[' ', '-']).collect();
if !(6..=7).contains(&components.len()) {
return Err(Self::Error::LineMalformed(value.to_string()));
}
Ok(Self {
is_departure: components[0] == "departure",
left_range: (components[components.len()-5].parse()?, components[components.len()-4].parse()?),
right_range: (components[components.len()-2].parse()?, components[components.len()-1].parse()?),
})
}
}
impl Requirement {
fn is_valid(&self, value: usize) -> bool {
(self.left_range.0..=self.left_range.1).contains(&value) || (self.right_range.0..=self.right_range.1).contains(&value)
}
}
pub fn run(input: &str) -> Result<(usize, usize), ParseError> {
let parts: Vec<_> = input.split("\n\n").collect();
let mut requirements: Vec<_> = parts[0].lines().map(Requirement::try_from).collect::<Result<Vec<_>, _>>()?;
let mine: Vec<_> = parts[1].lines().nth(1).unwrap().split(',').map(|n| n.parse::<usize>()).collect::<Result<Vec<_>, _>>()?;
let nearby: Vec<_> = parts[2].lines().skip(1).map(|line| line.split(',').map(|n| n.parse::<usize>()).collect::<Result<Vec<_>, _>>()).collect::<Result<Vec<_>, _>>()?;
let first = nearby.iter().map(|ticket| ticket.iter().filter(|value| !requirements.iter().any(|r| r.is_valid(**value))).sum::<usize>()).sum();
let valid: Vec<_> = nearby.iter().filter(|ticket| ticket.iter().all(|value| requirements.iter().any(|r| r.is_valid(*value)))).collect();
let mut undecided_fields: Vec<_> = (0..requirements.len()).collect();
let mut departure_fields = Vec::new();
while requirements.iter().any(|req| req.is_departure) {
let mut possible = Vec::new();
undecided_fields.iter().for_each(|field_idx| {
possible.push((*field_idx, requirements.iter().enumerate().filter(|(_req_idx, req)| req.is_valid(mine[*field_idx]) && valid.iter().all(|ticket| req.is_valid(ticket[*field_idx]))).map(|(req_idx, _req)| req_idx).collect::<Vec<_>>()));
});
let pos_idx = if let Some(pos) = possible.iter().position(|(_field_idx, reqs)| reqs.len() == 1) {
pos
} else if let Some(req_id) = (0..requirements.len()).find(|req_idx| possible.iter().filter(|(_field_idx, reqs)| reqs.contains(&req_idx)).count() == 1) {
possible.iter().position(|(_field_id, reqs)| reqs.contains(&req_id)).unwrap()
} else {
panic!("Unable to discard any possibilities");
};
let field_idx = possible[pos_idx].0;
let req_idx = possible[pos_idx].1[0];
if requirements[req_idx].is_departure {
departure_fields.push(field_idx);
}
undecided_fields.remove(undecided_fields.binary_search(&field_idx).unwrap());
requirements.remove(req_idx);
}
let second = mine.iter().enumerate().filter(|(idx, _val)| departure_fields.contains(&&idx)).map(|(_idx, val)| val).product();
Ok((first, second))
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::read_to_string;
fn read_file(name: &str) -> String {
read_to_string(name).expect(&format!("Unable to read file: {name}")[..]).trim().to_string()
}
#[test]
fn test_sample() {
let sample_input = read_file("tests/sample_input");
assert_eq!(run(&sample_input), Ok((71, 132)));
}
#[test]
fn test_challenge() {
let challenge_input = read_file("tests/challenge_input");
assert_eq!(run(&challenge_input), Ok((23954, 453459307723)));
}
}