use std::collections::HashMap; use std::fs; use std::ops::RangeInclusive; use lazy_static::lazy_static; use regex::Regex; type Rules = HashMap<String, (RangeInclusive<u32>, RangeInclusive<u32>)>; type Ticket = Vec<u32>; fn main() -> Result<(), Box<dyn std::error::Error>> { let input = fs::read_to_string("input")?; let (rules, _, tickets) = parse_tickets(&input)?; // Part 1 let invalid = get_invalid_values(&rules, &tickets); println!("{}", invalid.iter().sum::<u32>()); Ok(()) } fn parse_tickets(input: &str) -> Result<(Rules, Ticket, Vec<Ticket>), String> { let blocks: Vec<&str> = input.split("\n\n").collect(); let parse_u32 = |val: &str| { val.parse::<u32>() .map_err(|e| format!("Invalid number: {}", e)) }; if blocks.len() < 3 { Err("Input incomplete")?; } let rules = blocks[0] .lines() .map(|line| { lazy_static! { static ref RULE_RE: Regex = Regex::new(r"([[:alpha:]]+?): (\d+)-(\d+) or (\d+)-(\d+)") .expect("Regex should compile"); } let caps = RULE_RE .captures(line) .ok_or(format!("Invalid rule line: {}", line))?; let name = caps[1].to_string(); let range1 = parse_u32(&caps[2])?..=parse_u32(&caps[3])?; let range2 = parse_u32(&caps[4])?..=parse_u32(&caps[5])?; Ok((name, (range1, range2))) }) .collect::<Result<Rules, String>>()?; let own_ticket = blocks[1] .lines() .skip(1) .next() .ok_or("Input incomplete")? .split(',') .map(|c| parse_u32(c)) .collect::<Result<Ticket, String>>()?; let other_tickets = blocks[2] .lines() .skip(1) .map(|line| line.split(',').map(|c| parse_u32(c)).collect()) .collect::<Result<Vec<Ticket>, String>>()?; Ok((rules, own_ticket, other_tickets)) } fn get_invalid_values(rules: &Rules, tickets: &Vec<Ticket>) -> Vec<u32> { tickets .iter() .flat_map(|ticket| { ticket.iter().filter(|value| { !rules .values() .any(|(rule1, rule2)| rule1.contains(value) || rule2.contains(value)) }) }) .copied() .collect() } #[cfg(test)] mod tests { use super::*; use indoc::indoc; #[test] fn test_simple() -> Result<(), Box<dyn std::error::Error>> { let input = indoc!( " class: 1-3 or 5-7 row: 6-11 or 33-44 seat: 13-40 or 45-50 your ticket: 7,1,14 nearby tickets: 7,3,47 40,4,50 55,2,20 38,6,12 " ); let (rules, _, tickets) = parse_tickets(input)?; let invalid = get_invalid_values(&rules, &tickets); assert_eq!(invalid.iter().sum::<u32>(), 71); Ok(()) } }