119 lines
3 KiB
Rust
119 lines
3 KiB
Rust
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(())
|
|
}
|
|
}
|