use crate::interval::Interval; use std::collections::BTreeSet; use std::fs::File; use std::io; use std::io::BufRead; use std::path::Path; pub struct Cafeteria { part1: u64, part2: u64, } impl Cafeteria { pub fn new() -> Cafeteria { Cafeteria { part1: 0, part2: 0 } } pub fn run(&mut self) { println!("--- Day 5: Cafeteria ---"); // The "../../data" and "../../../data" paths are useful for running the binary from // "target/release/" directory with "data/" directory in package root or parent directory. let paths = vec!["./data", "../data", "../../data", "../../../data"]; match read_data_file("cafeteria.txt", &paths) { Ok(lines) => { self.process_data(lines); self.print_result(); } Err(error) => eprintln!("{}", error), }; } fn process_data(&mut self, mut lines: io::Lines>) { // Builds intervals collection. let mut intervals: BTreeSet = BTreeSet::new(); for line in lines .by_ref() .map_while(|x| x.ok().filter(|s| !s.is_empty())) { let values: Vec = line.split('-').map_while(|s| s.parse().ok()).collect(); let mut interval = Interval::new(values[0], values[1]); let mut to_delete = Vec::new(); for next in intervals.range(&interval..) { if interval.contains(next.start) || interval.end + 1 == next.start { // New interval overlaps with the "next" interval already in the collection. if interval.end < next.end { interval.end = next.end; } to_delete.push(next.clone()); } else { break; } } let mut do_insert = true; if let Some(prev) = intervals.range(..=&interval).next_back() { if interval.contains(prev.end) || prev.end + 1 == interval.start { // Enlarges new interval to replace the "previous" interval already in the collection. interval.start = prev.start; to_delete.push(prev.clone()); } else if interval.end <= prev.end { do_insert = false; } } for del in to_delete { intervals.remove(&del); } if do_insert { intervals.insert(interval); } } // Tests values against intervals. for value in lines.map_while(|x| x.ok()?.parse::().ok()) { if intervals.iter().any(|interval| interval.contains(value)) { self.part1 += 1; } } self.part2 = intervals.iter().map(|x| x.len()).sum(); } fn print_result(&self) { println!("Part 1: {:?}\nPart 2: {:?}\n", self.part1, self.part2); } } fn read_data_file( filename: T, search_paths: &Vec, ) -> Result>, String> where T: AsRef, { for path in search_paths { if let Ok(lines) = read_lines(path.as_ref().join(&filename)) { return Ok(lines); } } let message = search_paths .iter() .map(|path| path.as_ref().join(&filename).display().to_string()) .collect::>() .join("\n"); Err(format!( "Cannot open puzzle input file, searched these paths:\n{}", message )) } // This code is taken from https://doc.rust-lang.org/stable/rust-by-example/std_misc/file/read_lines.html fn read_lines(filename: T) -> io::Result>> where T: AsRef, { let file = File::open(filename)?; Ok(io::BufReader::new(file).lines()) }