use super::util; use std::cmp::{min, max}; pub fn solve() { let lines = util::read_file("input/day16.txt"); let range_sets = lines.iter().take_while(|s| !s.is_empty()).map(|s| ValidRangeSet::parse(s)).collect::>(); let your_ticket = lines.iter().skip_while(|s| s != &"your ticket:").skip(1).next().unwrap() .split(",").map(|s| s.parse::().unwrap()).collect::>(); let nearby_tickets = lines.iter().skip_while(|s| s != &"nearby tickets:").skip(1) .map(|t| t.split(",").map(|s| s.parse::().unwrap()).collect::>()).collect::>(); let combined_ranges = ValidRange::combine(&range_sets.iter().flat_map(|r| &r.ranges).collect::>()); let invalids_per_ticket = nearby_tickets.iter() .map(|t| find_invalid(t, &combined_ranges)) .collect::>(); // println!("{:?}", combined_ranges); let part1: u32 = invalids_per_ticket.iter().map(|t| -> u32 { t.iter().sum() }).sum(); println!("Day X Part 1: {}", part1); let mut valid_tickets = Vec::new(); for i in 0..nearby_tickets.len() { if invalids_per_ticket[i].is_empty() { valid_tickets.push(&nearby_tickets[i]); } } let ordered_range_sets = resolve_column_ordering(&valid_tickets, &range_sets); // println!("{:?}", ordered_range_sets); let fields = ordered_range_sets.iter().map(|r| &r.name).filter(|n| n.starts_with("departure")).collect::>(); let part2 = get_ticket_values(&your_ticket, &fields, &ordered_range_sets) .iter() .fold(1u64, |a, b| (a as u64) * (*b as u64)); println!("Day X Part 2: {}", part2); } fn find_invalid(nums: &Vec, combined_ranges: &Vec) -> Vec { return nums.iter().filter(|n| not_in_ranges(n, combined_ranges)).map(|n| n.clone().clone()).collect::>() } fn not_in_ranges(n: &u32, ranges: &Vec) -> bool { let closest_range_idx_res = ranges.binary_search_by_key(n, |r| r.min); let closest_range_idx = if closest_range_idx_res.is_err() { closest_range_idx_res.unwrap_err() } else { // If we're matching a range, the index will be the same as the range, but if we're not matching, it will be one higher closest_range_idx_res.unwrap() + 1 }; let optimized_res = closest_range_idx == 0 || !ranges[closest_range_idx - 1].includes(n); return optimized_res; } fn resolve_column_ordering(tickets: &Vec<&Vec>, range_sets: &Vec) -> Vec { let mut available_order_per_range_set = Vec::new(); for _ in 0..range_sets.len() { available_order_per_range_set.push(vec!{ true; range_sets.len() }); } let mut solved_range_sets = vec!{ false; range_sets.len() }; for ticket in tickets { for range_set_idx in 0..available_order_per_range_set.len() { if solved_range_sets[range_set_idx] { continue; } let range_set = &range_sets[range_set_idx]; let available_orders = &mut available_order_per_range_set[range_set_idx]; let mut removed_options = false; for j in 0..available_orders.len() { if available_orders[j] && !range_set.matches(&ticket[j]) { available_orders[j] = false; removed_options = true; } } if removed_options { check_options_left(range_set_idx, &mut solved_range_sets, &mut available_order_per_range_set); } } } let order_per_range_set = available_order_per_range_set.iter() .inspect(|a| if a.iter().filter(|b| **b).count() > 1 { panic!(format!("Inconclusive in terms of ordering"))} ) .map(|a| a.iter().position(|b| *b).unwrap()) .collect::>(); // Sort the range sets so that they're in their available order let mut res = vec!{ ValidRangeSet{ name: String::from("temp"), ranges: Vec::new() }; range_sets.len()}; for i in 0..range_sets.len() { res[order_per_range_set[i]] = range_sets[i].clone(); } return res; } fn check_options_left(orig_range_set_idx: usize, solved_range_sets: &mut Vec, available_order_per_range_set: &mut Vec>) { let options_left = available_order_per_range_set[orig_range_set_idx].iter().filter(|o| **o).count(); if options_left == 1 { // We now know for sure where range_set i is supposed to be, so we can cross // that option out for all other range sets: solved_range_sets[orig_range_set_idx] = true; let known_order = available_order_per_range_set[orig_range_set_idx].iter().position(|o|*o).unwrap(); // println!("Original range set position '{}' must be position: {}", orig_range_set_idx, known_order); for j in 0..available_order_per_range_set.len() { if j == orig_range_set_idx { continue; } if available_order_per_range_set[j][known_order] { available_order_per_range_set[j][known_order] = false; check_options_left(j, solved_range_sets, available_order_per_range_set); } } } else if options_left == 0 { panic!(format!("Couldn't find any valid ordering for range set {}", orig_range_set_idx)); } } fn get_ticket_values(ticket: &Vec, fields: &Vec<&String>, ordered_range_sets: &Vec) -> Vec { let mut res = Vec::new(); for field in fields { let idx = ordered_range_sets.iter().position(|r| &&r.name == field).unwrap(); res.push(ticket[idx]); } return res; } #[derive(Debug, Copy, Clone)] struct ValidRange { min: u32, max: u32 } impl ValidRange { fn parse(input: &str) -> ValidRange { let values = input.split("-").map(|s| s.trim()).collect::>(); return ValidRange { min: values[0].parse::().unwrap(), max: values[1].parse::().unwrap(), } } fn includes(&self, n: &u32) -> bool { return &self.min <= n && &self.max >= n; } fn combine(ranges: &Vec<&ValidRange>) -> Vec { let mut res = Vec::new(); for range in ranges { let overlaps = res.iter().enumerate().filter(|(_, r)| range.overlaps(r)) .collect::>(); let indices_to_remove = overlaps.iter().map(|(i, _)| i.clone()).rev().collect::>(); if overlaps.len() == 0 { res.push(ValidRange { min: range.min, max: range.max }); } else { let combined_min = min(range.min, overlaps.iter().map(|(_, r)| r.min).min().unwrap()); let combined_max = max(range.max, overlaps.iter().map(|(_, r)| r.max).max().unwrap()); res.push(ValidRange { min: combined_min, max: combined_max }) } for i in indices_to_remove { res.remove(i); } } res.sort_by_key(|r| r.min); return res; } fn overlaps(&self, other: &ValidRange) -> bool { return self.min <= other.max && self.max >= other.min; } } #[derive(Debug, Clone)] struct ValidRangeSet { name: String, ranges: Vec, } impl ValidRangeSet { fn parse(input: &String) -> ValidRangeSet { let spl = input.split(": ").collect::>(); return ValidRangeSet { name: String::from(spl[0]), ranges: spl[1].split(" or ").map(|r| ValidRange::parse(r)).collect::>() } } fn matches(&self, n: &u32) -> bool { return self.ranges.iter().any(|r| r.includes(n)); } }